engine/rr: native preset F (DLSS 4.5 Ray Reconstruction) and the reference's fused roundings
- engine/compile_rr2.py, runtime_rr2.inc, engine/rr/rr2_m.comp, exposure_f.comp, gen_rr2_enc0.py, rr2_pack.py, rr2_ref.py: the rr2_* model (nvngx_dlssd.dll 310.9's third network) as d4r's own shaders - exposure, enc0 gather/block split, enc1-5 and dec4-0 through one matrix shader, translated output passes; weights packed from the DLL, argument blocks from the capture - ptx2rr: match the driver's fused roundings - opaque-zero OR around cvt.rn.f16.f32 against v_fma_mix, half-away-from-zero formatted stores, UNORM 10:10:10:2 as RGBA32F of q/1023, OCML tanhF with the AMDGPU exp lowering, binary16 fma and fetch roundings; --probe for register bisection against ZLUDA - dec0_m/swin_m/enc0_*: RR_MIX build option (D4R_RR_MIX=1), one v_fma_mix rounding per 32-deep step, ~5% faster, not bit-exact; default build stays exact - runtime_rr.inc: RModel/REngine dispatch to the D4RR0002 model; replay_rr: RGBA32F and A2B10G10R1010 texel sizes, roughnessPacked bit, larger debug staging - shim: engine\rr-f model slot, 310.9 version probe so preset F is only claimed when the CUDA path has it; d4r.ini / d4r_config.py Model = F; run_rr_engine_compare.sh D4R_RR_DLL - kernels/rr/swin_native.h + rr_swin_native_test.cpp: HIP port of encoder 1 for the FP8-product and rounding cost measurements - engine/rr/README: preset F's block, options, capture-by-capture results, timings and the open camera-matrix difference
This commit is contained in:
1 parent
065fe2c2a1
commit
8eb7a94533
31 files changed
+3547
-128
No files matched your search
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the engine's Ray Reconstruction model directory for preset F (DLSS 4.5, the rr2_* kernels of
|
||||
nvngx_dlssd.dll 310.9) from a capture of the denoiser.
|
||||
|
||||
usage: compile_rr2.py CAPTURE_DIR OUT_DIR [--ptx-dir DIR] [--frame N] [--native NVNGX_DLSSD_DLL]
|
||||
|
||||
CAPTURE_DIR is a bridge capture of a few preset F frames (engine/rr/rr_capture.py, taken with
|
||||
D4R_CUDA_REPLAY_DUMP_FILTER=rr2 D4R_CUDA_LAUNCH_STATS_FILTER=rr2 and preset hint 6). Writes:
|
||||
<stage>.spv one shader per kernel, generated from its PTX by engine/rr/ptx2rr.py
|
||||
weights-<stage>.bin the stage's weight image as NGX uploads it (argument 24); the output filter's is the constant
|
||||
table of its PTX module; enc0-aux.bin is encoder 0's second table (argument 104)
|
||||
<stage>.args the argument block of the capture's frame, pointers and handles cleared: the runtime keeps its
|
||||
constants and fills what depends on the frame or the geometry (engine/runtime_rr2.inc)
|
||||
exposure_f0.spv, exposure_f1.spv engine/rr/exposure_f.comp
|
||||
rr.txt the manifest RModel reads ("D4RR0002")
|
||||
With --native, the stages engine/rr/rr2_m.comp implements (enc1 ... enc5, dec4 ... dec1, dec0's first kernel) are that
|
||||
shader with weights read from the DLL itself (engine/rr/rr2_pack.py) instead of the translation: bit-identical to
|
||||
NVIDIA's kernels on the reference captures and about three times faster. D4R_RR_MIX=1 builds the one-rounding variant.
|
||||
Everything but the exposure shader is derived from NVIDIA's DLL: it stays on the user's machine and is never packaged.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE / 'rr'))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
from test_layer import stage_params # noqa: E402
|
||||
|
||||
NETWORK = ['enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'enc5'] + [f'dec{n}_{k}' for n in (4, 3, 2, 1, 0) for k in (0, 1)]
|
||||
STAGES = NETWORK + ['post', 'down', 'pooled']
|
||||
KERNEL = {s: f'rr2_{s}_kernel' for s in NETWORK[:6]}
|
||||
KERNEL.update({f'dec{n}_{k}': f'rr2_dec{n}_kernel_{k}' for n in range(5) for k in (0, 1)})
|
||||
KERNEL.update(post='rr2_post_kernel', down='rr2_downsample_kernel_static_hdr_no_transparency',
|
||||
pooled='rr2_pooled_history2_kernel')
|
||||
BLOCK = {'enc0': '32,1,1', 'enc1': '32,1,1', 'enc2': '32,1,4', 'enc3': '32,1,4', 'enc4': '32,1,8', 'enc5': '32,1,8',
|
||||
'post': '16,16,1', 'down': '16,16,1', 'pooled': '16,16,1'}
|
||||
for n, b in ((4, '32,1,8'), (3, '32,1,4'), (2, '32,1,4'), (1, '32,1,2'), (0, '32,1,1')):
|
||||
BLOCK[f'dec{n}_0'] = BLOCK[f'dec{n}_1'] = b
|
||||
# pointer arguments that are neither the weights (24) nor the plane arena: the exposure values (16 bytes), encoder
|
||||
# 0's second table, and the output passes' progress counters (the runtime leaves those pointers null)
|
||||
BUFFERS = {'enc0': [104, 256], 'post': [16, 256, 264], 'down': [72, 168, 176]}
|
||||
# surfaces whose image format is not the store's own guess. The two pooled histories are UNORM 10:10:10:2 arrays for
|
||||
# NVIDIA; the reference this engine is checked against (ZLUDA) backs them with RGBA32F, and so does the engine.
|
||||
NATIVE = ['enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4_0', 'dec4_1', 'dec3_0', 'dec3_1', 'dec2_0', 'dec2_1', 'dec1_0',
|
||||
'dec1_1', 'dec0_0']
|
||||
# --particles: the downsample kernel with the particle layer
|
||||
KERNEL['downp'] = 'rr2_downsample_kernel_static_hdr'
|
||||
BLOCK['downp'] = '16,16,1'
|
||||
BUFFERS['downp'] = [72, 168, 176]
|
||||
# with --native: the translation of the per-pixel code around the native block (engine/rr/gen_rr2_enc0.py)
|
||||
HYBRID = {'enc0': [], 'dec0_1': ['--dec0_1']}
|
||||
# windows per workgroup of the hybrid stages (gen_rr2_enc0.py --group; D4R_RR2_GROUP to experiment)
|
||||
GROUP = int(os.environ.get('D4R_RR2_GROUP', '1'))
|
||||
# encoder 0's gather as a pass of its own (D4R_RR2_SPLIT=0: one shader, the gather on one wave of the block's four)
|
||||
SPLIT = os.environ.get('D4R_RR2_SPLIT', '1') != '0'
|
||||
# The pooled histories as RGBA32F holding q / 1023, as ZLUDA backs them (16 bytes a pixel). D4R_RR2_POOLED=rgb10_a2
|
||||
# makes them real UNORM 10:10:10:2 images (0.1 ms faster at 4K), but the hardware's bilinear filter then rounds
|
||||
# differently: identical at 3x and 2x scaling, 44 - 51 dB on temporal frames at 1.5x (capFQ, 2026-10-13)
|
||||
POOLED = os.environ.get('D4R_RR2_POOLED', 'rgba32f')
|
||||
SURFACES = {'dec0_1': {176: 'rgba8_snorm', 184: 'rgba8', 192: 'rgba8'}, 'post': {120: POOLED}, 'pooled': {8: POOLED}}
|
||||
|
||||
|
||||
def find_module(ptx_dir, kernel):
|
||||
for path in sorted(Path(ptx_dir).glob('*.ptx')):
|
||||
if f'.entry {kernel}(' in path.read_text(errors='replace'):
|
||||
return path
|
||||
raise FileNotFoundError(f'no PTX module with the entry {kernel} in {ptx_dir}')
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('output', type=Path)
|
||||
ap.add_argument('--ptx-dir', default=os.path.expanduser('~/.cache/d4r-rr-corpus-f'))
|
||||
ap.add_argument('--frame', type=int, default=2, help='which captured frame supplies the templates (1-based)')
|
||||
ap.add_argument('--particles', metavar='CAPTURE_DIR',
|
||||
help='a capture taken with DLSSD.ColorBeforeParticles: adds the downsample kernel NGX runs then (downp)')
|
||||
ap.add_argument('--native', metavar='DLL', help='nvngx_dlssd.dll 310.9: build the native stages from its weights')
|
||||
a = ap.parse_args()
|
||||
cap = Capture(a.capture)
|
||||
out = a.output
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
capped = ['systemd-run', '--user', '--scope', '-q', '-p', 'MemoryMax=24G', '-p', 'MemorySwapMax=0']
|
||||
lines = ['D4RR0002', 'preset 6']
|
||||
pcap = Capture(a.particles) if a.particles else None
|
||||
for stage in STAGES + (['downp'] if pcap else []):
|
||||
kernel = KERNEL[stage]
|
||||
if stage == 'downp':
|
||||
cap = pcap
|
||||
launches = [l for l in cap.launches if l.kernel == kernel]
|
||||
per_frame = len(launches) // len([l for l in cap.launches if l.kernel == KERNEL['enc0']])
|
||||
launch = launches[(a.frame - 1) * per_frame]
|
||||
network = stage in NETWORK
|
||||
if a.native and stage in NATIVE:
|
||||
work = out / 'native-work' / stage
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
weights = f'weights-{stage}m.bin'
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'rr2_pack.py'), stage, a.native, str(out / weights),
|
||||
str(work / 'rr2_stage.h')], check=True, stdout=subprocess.DEVNULL)
|
||||
defs = ['-DRR_EXACT_CONVERT'] + (['-DRR_MIX'] if os.environ.get('D4R_RR_MIX') == '1' else [])
|
||||
subprocess.run(capped + ['glslangValidator', '-V', '--target-env', 'vulkan1.3', *defs, f'-I{work}',
|
||||
f'-I{HERE / "rr"}', str(HERE / 'rr' / 'rr2_m.comp'), '-o', str(out / f'{stage}_m.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'spv_exact_convert.py'), str(out / f'{stage}_m.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
(out / f'{stage}.args').write_bytes(stage_params(launch, 24)[:len(launch.args)])
|
||||
arena = launch.arena_alloc
|
||||
unit = launch.allocs[arena][1] / 3152
|
||||
ptrs = [f'{p.offset}:{p.delta / unit:g}' for p in launch.pointers if p.alloc == arena and p.offset != 24]
|
||||
lines.append(f'stage {stage} {stage}_m.spv 32 {weights} {stage}.args - - - {",".join(ptrs)}')
|
||||
print(f'{stage}: native (engine/rr/rr2_m.comp)')
|
||||
continue
|
||||
generated = out / f'{stage}.generated.comp'
|
||||
cmd = [sys.executable, str(HERE / 'rr' / 'ptx2rr.py'), str(find_module(a.ptx_dir, kernel)), kernel, str(generated),
|
||||
'--block', BLOCK[stage], '--weights-arg', '24' if network else '9999']
|
||||
cmd += [f'--buffer={off}' for off in BUFFERS.get(stage, [])]
|
||||
cmd += [f'--surface={off}={fmt}' for off, fmt in SURFACES.get(stage, {}).items()]
|
||||
subprocess.run(cmd, check=True, stderr=subprocess.DEVNULL)
|
||||
info = json.loads(generated.with_suffix('.json').read_text())
|
||||
weights = '-'
|
||||
hybrid = a.native and stage in HYBRID
|
||||
if hybrid:
|
||||
work = out / 'native-work' / stage
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
weights = f'weights-{stage}m.bin'
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'rr2_pack.py'), stage, a.native, str(out / weights),
|
||||
str(work / 'rr2_stage.h')], check=True, stdout=subprocess.DEVNULL)
|
||||
defs = ['-DRR_EXACT_CONVERT'] + (['-DRR_MIX'] if os.environ.get('D4R_RR_MIX') == '1' else [])
|
||||
# encoder 0 in two passes (gen_rr2_enc0.py --split): its gather as the stage enc0g, whose record's address
|
||||
# is a word appended to the argument block
|
||||
split = stage == 'enc0' and SPLIT and GROUP == 1
|
||||
how = ['--split', 'block', '--xf-word', str((len(launch.args) + 7) // 8 * 2)] if split else ['--group', str(GROUP)]
|
||||
if split:
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'gen_rr2_enc0.py'), str(find_module(a.ptx_dir, kernel)),
|
||||
str(generated), str(work / 'enc0_g.comp'), '--split', 'gather', *how[2:]],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(capped + ['glslangValidator', '-V', '--target-env', 'vulkan1.3', *defs, f'-I{work}',
|
||||
f'-I{HERE / "rr"}', str(work / 'enc0_g.comp'), '-o', str(out / 'enc0g.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'spv_exact_convert.py'), str(out / 'enc0g.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
record = int((work / 'enc0_g.record').read_text())
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'gen_rr2_enc0.py'), str(find_module(a.ptx_dir, kernel)),
|
||||
str(generated), str(work / f'{stage}_m.comp'), *HYBRID[stage], *how],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run(capped + ['glslangValidator', '-V', '--target-env', 'vulkan1.3', *defs, f'-I{work}',
|
||||
f'-I{HERE / "rr"}', str(work / f'{stage}_m.comp'), '-o', str(out / f'{stage}.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'spv_exact_convert.py'), str(out / f'{stage}.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
else:
|
||||
subprocess.run(capped + ['glslangValidator', '-V', '--target-env', 'vulkan1.3', str(generated), '-o',
|
||||
str(out / f'{stage}.spv')], check=True, stdout=subprocess.DEVNULL)
|
||||
if hybrid:
|
||||
pass
|
||||
elif network:
|
||||
weights = f'weights-{stage}.bin'
|
||||
(out / weights).write_bytes(launch.weights_at(24))
|
||||
elif generated.with_suffix('.globals.bin').exists():
|
||||
weights = f'weights-{stage}.bin'
|
||||
(out / weights).write_bytes(generated.with_suffix('.globals.bin').read_bytes())
|
||||
if stage == 'enc0':
|
||||
(out / 'enc0-aux.bin').write_bytes(launch.pre(launch.pointer(104).alloc))
|
||||
args = stage_params(launch, 24 if network else 9999)[:len(launch.args)]
|
||||
if hybrid and split:
|
||||
args = args + bytes(-len(args) % 8) + bytes(8) # the gather record's address (the runtime's)
|
||||
(out / f'{stage}.args').write_bytes(args)
|
||||
# the stage's arena regions, in units of 1/3152 of NGX's arena (W0 * H0 / 32 bytes: the regions scale with it)
|
||||
arena = launch.arena_alloc
|
||||
unit = launch.allocs[arena][1] / 3152 if arena is not None else 1
|
||||
ptrs = [f'{p.offset}:{p.delta / unit:g}' for p in launch.pointers
|
||||
if p.alloc == arena and p.offset not in BUFFERS.get(stage, []) and not (network and p.offset == 24)]
|
||||
if not network:
|
||||
ptrs = [] # the output passes' regions are the runtime's own
|
||||
tex = ','.join(map(str, info['textures'])) or '-'
|
||||
surf = ','.join(f'{off}:{fmt}' for off, fmt in info['surfaces']) or '-'
|
||||
buf = ','.join(map(str, info['buffers'])) or '-'
|
||||
# the output passes have no 32-lane matrix products: 64-wide waves (their warp-level shuffles address the
|
||||
# lane's own half, engine/rr/ptx2rr.py)
|
||||
wave = 32 if network else 64
|
||||
lines.append(f'stage {stage} {stage}.spv {wave} {weights} {stage}.args {tex} {surf} {buf} {",".join(ptrs) or "-"}' +
|
||||
(f' x{GROUP}' if hybrid and GROUP > 1 else '')) # windows per workgroup (engine/rr/gen_rr2_enc0.py)
|
||||
if hybrid and split: # the same block, bindings and regions; "rN": N words per window record
|
||||
lines.append(f'stage enc0g enc0g.spv 32 - {stage}.args {tex} {surf} {buf} {",".join(ptrs) or "-"} r{record}')
|
||||
print(f'{stage}: {kernel}, {len(launch.args)} argument bytes' + (' (native block)' if hybrid else ''))
|
||||
for p in (0, 1):
|
||||
subprocess.run(['glslangValidator', '-V', '--target-env', 'vulkan1.3', f'-DPASS={p}',
|
||||
str(HERE / 'rr' / 'exposure_f.comp'), '-o', str(out / f'exposure_f{p}.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
# the driver folds float(float16_t(x)) away unless the conversions are marked (engine/rr/spv_exact_convert.py):
|
||||
# the kernel truncates the exposure to binary16
|
||||
subprocess.run([sys.executable, str(HERE / 'rr' / 'spv_exact_convert.py'), str(out / f'exposure_f{p}.spv')],
|
||||
check=True, stdout=subprocess.DEVNULL)
|
||||
(out / 'rr.txt').write_text('\n'.join(lines) + '\n')
|
||||
print(f'Ray Reconstruction preset F model in {out}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user