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]>
73 lines
4.3 KiB
Python
73 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Run the engine's own K input kernel on a captured launch of NVIDIA's and compare the network input it writes.
|
|
|
|
usage: test_in.py CAPTURE_DIR FRAME WORK_DIR KVK [repeats]
|
|
"""
|
|
import glob, os, re, struct, subprocess, sys
|
|
import numpy as np
|
|
LDR = os.environ.get('LDR') == '1' # the *_ldr_* kernel variant and the shader's LDR path
|
|
K = 'hiluma_engine_input_depthinv_mvlo_ldr_v2_rel' if LDR else 'hiluma_engine_input_depthinv_mvlo_hdr_v2_rel'
|
|
cap, frame, work, kvk = sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4]
|
|
reps = sys.argv[5] if len(sys.argv) > 5 else '1'
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
os.makedirs(work, exist_ok=True)
|
|
seqs = sorted({int(re.match(r'launch-(\d+)-', os.path.basename(f)).group(1)) for f in glob.glob(f'{cap}/dump/launch-*-{K}-arg*')})
|
|
seq = seqs[frame]
|
|
res = {}
|
|
for f in glob.glob(f'{cap}/dump/launch-{seq:03d}-{K}-arg*'):
|
|
m = re.search(r'-arg(\d+)-(texture|surface|buffer)-(.*)-fmt(-?\d+)\.bin$', f)
|
|
res[int(m.group(1))] = (m.group(2), m.group(3), int(m.group(4)), f)
|
|
P = open(f'{cap}/replay/replay-{seq:06d}-{K}/args.bin', 'rb').read()
|
|
f2 = lambda o: struct.unpack_from('<2f', P, o)
|
|
i2 = lambda o: struct.unpack_from('<2i', P, o)
|
|
f1 = lambda o: struct.unpack_from('<f', P, o)[0]
|
|
def expo_tex(off):
|
|
if off not in res: return 1.0 # LDR: no exposure textures
|
|
v = float(np.fromfile(res[off][3], np.float16)[0])
|
|
return 1.0 if v == 0 else v
|
|
F = np.float32
|
|
a, b, c = expo_tex(208), expo_tex(200), expo_tex(216)
|
|
f152, f158 = F(f1(144)) * F(a), F(f1(144)) * F(b)
|
|
expo = F(f1(156)) * f158; expo = F(1) if expo == 0 else expo
|
|
expo_in = F(f1(148)) * F(c) / f158
|
|
ratio = f158 / f152
|
|
blob = struct.pack('<14f', *f2(40), *f2(24), *f2(8), *f2(16), *f2(120), *f2(136), *f2(128))
|
|
blob += struct.pack('<12i', *i2(72), *i2(80), *i2(88), *i2(96), *i2(104), *i2(112))
|
|
blob += struct.pack('<iI4f', struct.unpack_from('<i', P, 64)[0], P[32], float(expo), float(expo_in), float(ratio), f1(160))
|
|
open(f'{work}/params.bin', 'wb').write(blob)
|
|
tokW = struct.unpack_from('<i', P, 64)[0]
|
|
kind, shape, fmt, fref = res[240]
|
|
nbytes = os.path.getsize(fref)
|
|
tokH = 256 if tokW == 320 else None
|
|
# IN_TEX_OVERRIDE="184=FILE,192=FILE" replaces captured textures; IN_REF compares against that file (the translated
|
|
# reference kernel's output on the same inputs) instead of the captured buffer.
|
|
for kv in filter(None, os.environ.get('IN_TEX_OVERRIDE', '').split(',')):
|
|
o, fn = kv.split('='); res[int(o)] = res[int(o)][:3] + (fn,)
|
|
fref = os.environ.get('IN_REF', fref)
|
|
lines = [f'param {work}/params.bin']
|
|
dims = {}
|
|
for i, (off, filt) in enumerate(((168, 'nearest'), (176, 'linear'), (184, 'nearest'), (192, 'nearest'), (224, 'linear'))):
|
|
kind, shape, fmt, f = res[off]; w, h, ch = (int(x) for x in shape.split('x'))
|
|
dims[off] = (w, h)
|
|
lines.append(f'tex {i + 1} {f} {w} {h} {ch} {32 if fmt == 32 else 16} {filt}')
|
|
tokH = dims[224][1]
|
|
lines.insert(1, f'grid {tokW // 8} {tokH // 8}')
|
|
lines.append(f'outbuf {work}/out.bin {tokW * tokH * 32}')
|
|
open(f'{work}/manifest', 'w').write('\n'.join(lines) + '\n')
|
|
spv = f'{work}/input_k.spv'
|
|
subprocess.run(['glslangValidator', '-V', '--target-env', 'vulkan1.3', *(['-DLDR=1'] if LDR else []), f'{HERE}/input_k.comp', '-o', spv], check=True, stdout=subprocess.DEVNULL)
|
|
# K requires a 32-lane subgroup (the reconstruction's 2x2 "bright" quad is a subgroup shuffle and the
|
|
# network layers use 16x16x16 cooperative matrices); wave64 changes both.
|
|
r = subprocess.run([kvk, spv, f'{work}/manifest', reps, '32'], capture_output=True, text=True)
|
|
if r.returncode:
|
|
print('FAILED', r.stderr[-400:]); sys.exit(1)
|
|
for l in r.stdout.split('\n'):
|
|
if 'us per' in l: print(l)
|
|
ref = np.fromfile(fref, np.float16)[:tokW * tokH * 16].astype(np.float64).reshape(tokH, tokW, 4, 4)
|
|
got = np.fromfile(f'{work}/out.bin', np.float16)[:tokW * tokH * 16].astype(np.float64).reshape(tokH, tokW, 4, 4)
|
|
print(f'frame {frame} (launch {seq}), tokens {tokW}x{tokH}, reset {P[32]}, exposure {float(expo):.4g} / {float(expo_in):.4g}, ratio {float(ratio):.6g}')
|
|
for ch, name in enumerate(('current luma', 'history luma', 'token feature', 'mismatch')):
|
|
a, b = ref[..., ch], got[..., ch]
|
|
d = np.abs(a - b); peak = max(np.abs(a).max(), 1e-9)
|
|
print(f' {name:14s} psnr {10 * np.log10(peak ** 2 / max((d ** 2).mean(), 1e-30)):6.1f} dB max |diff| {d.max():.4g} mean |ref| {np.abs(a).mean():.4g} non-finite {int((~np.isfinite(b)).sum())}')
|