A standalone Vulkan inference engine (engine/) for DLSS presets K and M that runs from weights extracted from the user's own nvngx_dlss.dll, with d4r's own input, network and reconstruction shaders or the native HIP network layers, recorded into the game's command list by the shim. Opt-in ([Engine] Enabled / D4R_ENGINE=1); anything uncovered falls back to the CUDA/ZLUDA path with the reason logged. Byte-identical to NGX's CUDA backend under the phase-capable reference (1706x960 -> 2560x1440, native HIP network, eleven frames, RX 9070 XT): - K render- and display-resolution motion vectors, HDR and LDR - K game-supplied exposure and automatic exposure with any pre-exposure and exposure scale (one residual: effective exposure exactly 2 or 4) - K and M with the DLSS indicator's inverted-axis parameters - M with any exposure settings (NGX ignores them for M) K packages need the current compile_k.py (marker D4RO0003). Also: HIP shared buffers are pooled across feature recreation (ROCm leaks destroyed imports), hipnet.bin must match the preset, M-only games do not load the K model, install_d4r_runtime.sh stages k, k-ldr, m, m-ldr with hip/, the activation table has a generator (gen_activation_m.py), and package_release.sh can bundle the engine's shaders (never its weights). Signed-off-by: woutermans <[email protected]>
118 lines
7.3 KiB
Python
118 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Build the engine's preset M model directory from a capture of one frame.
|
|
|
|
usage: compile_m.py CAPTURE_DIR OUT_DIR [--frame N]
|
|
CAPTURE_DIR/replay must hold the frame's rrlite_enc0 and rrlite_dec0 launches (the `_folded` kernels) and its
|
|
ten Swin layer launches, with their weight allocations (D4R_CUDA_REPLAY_DUMP_DIR, filter `rrlite_`).
|
|
Writes weights8.bin (the layers' FP8 matrices in the blocked fixed-16 tile layout of test_m.pack8, with the front-end
|
|
enc0/dec0 matrices plain and NGX-ordered as their own stages read them), weights16.bin (f16 vectors and the dither
|
|
table), offsets.bin and the compiled shaders. The weights are values read from NVIDIA's DLSS and are not redistributable
|
|
with d4r; the shaders are d4r's own.
|
|
"""
|
|
# D4RM0003 couples the blocked FP8 operands, RGBA32F half-history backing and 8x64 downsample dispatch;
|
|
# the previous package's shader workgroup dimensions are not interchangeable with this runtime.
|
|
import argparse, glob, hashlib, json, os, struct, subprocess, sys
|
|
from pathlib import Path
|
|
import numpy as np
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE))
|
|
import m_model as mm
|
|
import model_enc3 as me
|
|
import swin_model as sm
|
|
from test_m import pack_layer, defines
|
|
|
|
SHADERS = {'enc1': 'm_enc1', 'enc2': 'm_enc2', 'enc3_tube': 'm_tube', 'dec2': 'm_dec2', 'dec1': 'm_dec1'}
|
|
|
|
|
|
def blob_of(directory, ptr_offset=0):
|
|
args = open(f'{directory}/args.bin', 'rb').read()
|
|
ptr = struct.unpack_from('<Q', args, ptr_offset)[0]
|
|
for line in open(f'{directory}/manifest.txt'):
|
|
q = line.split()
|
|
if q and q[0] == 'alloc':
|
|
base = int(q[2], 16); size = os.path.getsize(f'{directory}/alloc-{q[1]}.bin')
|
|
if base <= ptr < base + size:
|
|
return np.fromfile(f'{directory}/alloc-{q[1]}.bin', np.uint8)[ptr - base:]
|
|
raise ValueError(f'{directory}: the weights allocation is missing')
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument('capture'); ap.add_argument('output', type=Path); ap.add_argument('--frame', type=int, default=0)
|
|
ap.add_argument('--ldr', action='store_true', help="NGX's *_ldr_* stages, for games without the HDR flag (capture such a frame)")
|
|
ap.add_argument('--hip', type=Path, metavar='KERNEL_DIR', help='also package the network for the native HIP layers: the layer '
|
|
'weights as NGX uploads them (hipnet.bin) and the rrlite_*_4x4.hsaco kernels of KERNEL_DIR (kernels/build.sh output)')
|
|
a = ap.parse_args()
|
|
a.output.mkdir(parents=True, exist_ok=True)
|
|
def one(kernel):
|
|
d = sorted(glob.glob(f'{a.capture}/replay/replay-*-{kernel}'))
|
|
if len(d) <= a.frame: raise ValueError(f'no launch of {kernel} for frame {a.frame}')
|
|
return d[a.frame]
|
|
R = 'ldr' if a.ldr else 'hdr'
|
|
w8, w16 = [], []
|
|
n8 = n16 = 0
|
|
def add8(x):
|
|
nonlocal n8
|
|
x = np.ascontiguousarray(x, np.uint8).ravel(); pad = (-x.size) % 64
|
|
off = n8; w8.append(x); w8.append(np.zeros(pad + 64, np.uint8)); n8 += x.size + pad + 64
|
|
return off
|
|
def add16(x):
|
|
nonlocal n16
|
|
x = np.ascontiguousarray(x).view(np.uint8).ravel(); pad = (-x.size) % 64
|
|
off = n16; w16.append(x); w16.append(np.zeros(pad + 64, np.uint8)); n16 += x.size + pad + 64
|
|
return off
|
|
table = [] # (w8 offset, w16 offset) per stage, then the layers' offset blocks
|
|
# input stage: embedding [128][64] in natural output order, bias tile, dither table
|
|
wb = blob_of(one(f'rrlite_enc0_4x4_mvlo_{R}_folded'))
|
|
g = sm.gp(np.arange(64))
|
|
enc0 = (add8(wb[sm.woff_table(4096, 512, 2048, 128, 64)][:, g]), add16(np.tile(wb[12288:12288 + 128].view(np.float16)[g][None, :], (16, 1))), add16(wb[:4096]))
|
|
# expansion: the 20 columns per sub-position the stage uses
|
|
wb = blob_of(one('rrlite_dec0_4x4_folded'))
|
|
cols = (np.arange(4)[:, None] * 32 + np.arange(20)[None, :]).ravel()
|
|
dec0 = (add8(wb[sm.woff_table(0, 512, 1024, 64, 128)][:, cols]), add16(np.tile(wb[8192:8192 + 256].view(np.float16)[cols][None, :], (16, 1))))
|
|
Ps, dirs = mm.launches(a.capture, a.frame)
|
|
layers = []
|
|
for P, name in zip(Ps, mm.ORDER):
|
|
o, b8, b16 = pack_layer(mm.plain_layer(P, name), name)
|
|
layers.append((add8(b8), add16(b16), o))
|
|
blob8, blob16 = np.concatenate(w8), np.concatenate(w16)
|
|
blob8.tofile(a.output / 'weights8.bin'); blob16.tofile(a.output / 'weights16.bin')
|
|
head = struct.pack('<8sII', b'D4RM0003', blob8.size, blob16.size) + struct.pack('<5I', *enc0, *dec0)
|
|
for o8, o16, o in layers: head += struct.pack('<2I72I', o8, o16, *o)
|
|
(a.output / 'offsets.bin').write_bytes(head)
|
|
def glsl(src, out, flags=()):
|
|
subprocess.run(['glslangValidator', '-V', '--target-env', 'vulkan1.3', *flags, str(HERE / src), '-o', str(a.output / out)], check=True, stdout=subprocess.DEVNULL)
|
|
for name, out in SHADERS.items(): glsl('layer_m.comp', out + '.spv', defines(name))
|
|
for s in ('enc0_m', 'dec0_m', 'post_m', 'down_m'): glsl(s + '.comp', s + '.spv', ['-DLDR=1'] if a.ldr and s != 'dec0_m' else [])
|
|
glsl('down_m.comp', 'down_m_rgb10.spv', ['-DOUTPUT_RGB10=1'] + (['-DLDR=1'] if a.ldr else []))
|
|
glsl('post_m.comp', 'post_m_q.spv', ['-DTILE_W=15', '-DTILE_H=15'] + (['-DLDR=1'] if a.ldr else []))
|
|
glsl('post_m.comp', 'post_m_c.spv', ['-DTILE_W=11', '-DTILE_H=11'] + (['-DLDR=1'] if a.ldr else []))
|
|
# the same stages with the token buffers in NGX's plane layout, for a network that runs outside the engine
|
|
for s in ('enc0_m', 'dec0_m'): glsl(s + '.comp', s + '_planes.spv', ['-DPLANES=1'] + (['-DLDR=1'] if a.ldr and s != 'dec0_m' else []))
|
|
if a.hip:
|
|
# hipnet.bin: "D4RMHIP1", layer count, then per layer {kernel name[32], shift x, y, token grid divisor, weight
|
|
# offset, weight bytes} and the weight allocations as NGX uploads them (the native kernels read that layout)
|
|
scale = {'enc1': 1, 'enc2': 2, 'enc3_tube': 4, 'dec2': 2, 'dec1': 1}
|
|
blobs, head, at = [], struct.pack('<8sI4x', b'D4RMHIP1', len(Ps)), 16 + 64 * len(Ps)
|
|
for P, name in zip(Ps, mm.ORDER):
|
|
w = np.ascontiguousarray(P.wbuf); pad = (-w.size) % 64
|
|
head += struct.pack('<32siiIIQQ', f'rrlite_{name}_4x4'.encode(), P.sx, P.sy, scale[name], 0, at, w.size)
|
|
blobs += [w, np.zeros(pad, np.uint8)]; at += w.size + pad
|
|
(a.output / 'hipnet.bin').write_bytes(head + np.concatenate(blobs).tobytes())
|
|
(a.output / 'hip').mkdir(exist_ok=True)
|
|
for name in sorted(set(mm.ORDER)):
|
|
src = a.hip / f'rrlite_{name}_4x4.hsaco'
|
|
if not src.exists(): raise ValueError(f'{src} is missing')
|
|
(a.output / 'hip' / src.name).write_bytes(src.read_bytes())
|
|
marker = a.output / 'ldr' # tells the runtime to skip exposure
|
|
if a.ldr: marker.write_text('1\n')
|
|
elif marker.exists(): marker.unlink()
|
|
for p in '01': glsl('exposure_k.comp', f'exposure_m{p}.spv', ['-DMODE=1', '-DPASS=' + p])
|
|
files = sorted(a.output.glob('*.bin')) + sorted(a.output.glob('*.spv')) + sorted(a.output.glob('hip/*.hsaco'))
|
|
(a.output / 'model.json').write_text(json.dumps({'format': 'D4RM0003', 'preset': 'M', 'files': {str(f.relative_to(a.output)): hashlib.sha256(f.read_bytes()).hexdigest() for f in files}}, indent=1))
|
|
print(f'Compiled native preset M model in {a.output} ({blob8.size} FP8 bytes, {blob16.size} bytes of f16 data)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|