- 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
161 lines
6.4 KiB
Python
161 lines
6.4 KiB
Python
"""Ray Reconstruction captures: the launches of one denoiser frame as the CUDA bridge recorded them.
|
|
|
|
A capture directory holds what the bridge writes with
|
|
D4R_CUDA_VERBOSE=1 D4R_CUDA_REPLAY_DUMP_DIR=CAP/replay D4R_CUDA_REPLAY_DUMP_FILTER=cuda_dldn
|
|
D4R_CUDA_LAUNCH_STATS=1 D4R_CUDA_LAUNCH_STATS_FILTER=cuda_dldn D4R_CUDA_LAUNCH_DUMP_DIR=CAP/dump
|
|
plus the run's log as CAP/run/run.log (texture sampler settings and array shapes come from the verbose
|
|
cuTexObjectCreate / cuSurfObjectCreate / cuArrayCreate lines).
|
|
|
|
replay/replay-NNNNNN-KERNEL/ args.bin, manifest.txt and every buffer the launch points into, *before* it ran
|
|
dump/launch-NNN-KERNEL-argOOO-{buffer,texture,surface}-... every resource *after* the launch ran
|
|
|
|
Captures are reference data only: nothing at runtime reads them.
|
|
"""
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
import re
|
|
import struct
|
|
|
|
# CUarray_format codes the denoiser uses, and how a Vulkan image of the same texels is described
|
|
# (kvk's BITS column: 8 = UNORM bytes, 9 = SNORM bytes, 16 = binary16, 32 = binary32)
|
|
ARRAY_FORMATS = {16: 16, 32: 32, 1: 8, 200: 9, 194: 8}
|
|
|
|
|
|
@dataclass
|
|
class Image:
|
|
offset: int
|
|
kind: str # 'texture' or 'surface'
|
|
width: int
|
|
height: int
|
|
channels: int
|
|
format: int # CUarray_format
|
|
path: Path # post-launch contents
|
|
linear: bool = False # textures: bilinear filtering
|
|
|
|
@property
|
|
def bits(self):
|
|
return ARRAY_FORMATS[self.format]
|
|
|
|
@property
|
|
def texel_bytes(self):
|
|
return self.channels * (1 if self.bits in (8, 9) else self.bits // 8)
|
|
|
|
|
|
@dataclass
|
|
class Pointer:
|
|
offset: int
|
|
alloc: int
|
|
delta: int
|
|
|
|
|
|
@dataclass
|
|
class Launch:
|
|
sequence: int
|
|
kernel: str
|
|
directory: Path
|
|
grid: tuple
|
|
block: tuple
|
|
args: bytes
|
|
allocs: dict = field(default_factory=dict) # index -> (base, size)
|
|
pointers: list = field(default_factory=list)
|
|
images: list = field(default_factory=list)
|
|
|
|
def pre(self, alloc):
|
|
"""an allocation's contents before the launch"""
|
|
return (self.directory / f'alloc-{alloc}.bin').read_bytes()
|
|
|
|
def pointer(self, offset):
|
|
return next(p for p in self.pointers if p.offset == offset)
|
|
|
|
def image(self, offset):
|
|
return next(i for i in self.images if i.offset == offset)
|
|
|
|
def weights_at(self, offset):
|
|
"""the weight image (the pointer at argument OFFSET) as NGX uploaded it; 16 zero bytes for a stage without one"""
|
|
pointer = next((p for p in self.pointers if p.offset == offset), None)
|
|
return self.pre(pointer.alloc) if pointer is not None else bytes(16)
|
|
|
|
@property
|
|
def weights(self):
|
|
"""the DLSS-D kernels' weight image (argument 40)"""
|
|
return self.weights_at(40)
|
|
|
|
@property
|
|
def arena_alloc(self):
|
|
"""the plane arena: the allocation every arena pointer of the layer points into"""
|
|
candidates = [p.alloc for p in self.pointers]
|
|
return max(set(candidates), key=lambda a: self.allocs[a][1]) if candidates else None
|
|
|
|
def post_buffer(self, capture, alloc):
|
|
base = self.allocs[alloc][0]
|
|
for path in (capture.root / 'dump').glob(f'launch-{self.sequence:03d}-{self.kernel}-arg*-buffer-*'):
|
|
m = re.search(r'-base0x([0-9a-f]+)\+', path.name)
|
|
if m and int(m.group(1), 16) == base:
|
|
return path.read_bytes()
|
|
raise FileNotFoundError(f'no post-launch dump of allocation {alloc} for launch {self.sequence}')
|
|
|
|
def u32(self, offset):
|
|
return struct.unpack_from('<I', self.args, offset)[0]
|
|
|
|
def f32(self, offset):
|
|
return struct.unpack_from('<f', self.args, offset)[0]
|
|
|
|
|
|
class Capture:
|
|
def __init__(self, root):
|
|
self.root = Path(root)
|
|
log_path = self.root / 'run' / 'run.log'
|
|
self.log = log_path.read_text(errors='replace') if log_path.exists() else ''
|
|
# (log position, texture handle, filter mode) of every texture object creation, in order
|
|
self._tex_events = [(m.start(), int(m.group(2), 16), int(m.group(1))) for m in re.finditer(
|
|
r'cuTexObjectCreate .*? filter=(\d+) .*? object=(0x[0-9a-f]+)', self.log)]
|
|
self.launches = []
|
|
for d in sorted((self.root / 'replay').iterdir()):
|
|
self.launches.append(self._launch(d))
|
|
|
|
def filter_at(self, handle, position):
|
|
value = None
|
|
for pos, h, f in self._tex_events:
|
|
if pos > position:
|
|
break
|
|
if h == handle:
|
|
value = f
|
|
return value
|
|
|
|
def _launch(self, directory):
|
|
manifest = (directory / 'manifest.txt').read_text().split('\n')
|
|
kernel = manifest[0].split()[1]
|
|
launch = [int(x) for x in manifest[1].split()[1:]]
|
|
sequence = int(re.search(r'replay-(\d+)-', directory.name).group(1))
|
|
args = (directory / 'args.bin').read_bytes()
|
|
result = Launch(sequence, kernel, directory, tuple(launch[0:3]), tuple(launch[3:6]), args)
|
|
for line in manifest[2:]:
|
|
f = line.split()
|
|
if not f:
|
|
continue
|
|
if f[0] == 'alloc':
|
|
result.allocs[int(f[1])] = (int(f[2], 16), int(f[3]))
|
|
elif f[0] == 'pointer':
|
|
result.pointers.append(Pointer(int(f[1]), int(f[2]), int(f[3])))
|
|
# the log position of this launch, so a reused texture handle resolves to its settings at the time
|
|
position = self.log.find(f'launch[{sequence}] {kernel}')
|
|
if position < 0:
|
|
position = len(self.log)
|
|
for path in sorted((self.root / 'dump').glob(f'launch-{sequence:03d}-{kernel}-arg*')):
|
|
m = re.search(r'-arg(\d+)-(texture|surface)-(\d+)x(\d+)x(\d+)-fmt(-?\d+)\.bin$', path.name)
|
|
if not m:
|
|
continue
|
|
offset = int(m.group(1))
|
|
image = Image(offset, m.group(2), int(m.group(3)), int(m.group(4)), int(m.group(5)), int(m.group(6)), path)
|
|
if image.kind == 'texture':
|
|
handle = struct.unpack_from('<Q', args, offset)[0]
|
|
image.linear = self.filter_at(handle, position) == 1
|
|
result.images.append(image)
|
|
result.images.sort(key=lambda i: (i.kind != 'texture', i.offset))
|
|
return result
|
|
|
|
def find(self, kernel_part, frame=None):
|
|
"""the launches whose kernel name contains KERNEL_PART, in order; FRAME (1-based) selects one"""
|
|
found = [l for l in self.launches if kernel_part in l.kernel]
|
|
return found if frame is None else found[frame - 1]
|