#!/usr/bin/env python3 """Package extracted K weights and compile d4r's shaders. No PTX translator is used. compile_k.py MODEL_PREFIX LUT.bin OUTPUT_DIRECTORY LUT.bin holds NGX's sixteen anisotropic reconstruction tables (2048 sixteen-byte entries each): the whole allocation behind the output launch's argument 280, as prepare_k.py writes it. NGX picks one by upscaling ratio. A single 32768-byte table (older captures) is accepted and then serves every ratio. --hip KERNEL_DIR --capture CAPTURE_DIR also packages the network for the native HIP layer kernels (engine/hip_net.h): hipnet.bin ("D4RKHIP1", the eleven layers in network order with the weight allocations exactly as NGX uploads them, which the native kernels read) and hip/dltss_pwin_*_layer.hsaco copied from KERNEL_DIR. CAPTURE_DIR/replay must hold one captured launch of each dltss_pwin_*_layer with its weight allocation (D4R_CUDA_REPLAY_DUMP_DIR, filter dltss_pwin). HDR and LDR share the weights, so one hipnet.bin serves both variants. """ # D4RK0002 couples the package to the exposure stage's sample grid: the engine passes automatic exposure the # native output/2 source grid (mandatory dispatch size and uniform sampleSize), and the K1 package's exposure # shader and dispatch assume the render grid, so a K1 offsets blob is not interchangeable with this runtime. # Nothing else in the package changed meaning: the tensor offsets and the layer/input/output stages are the same. import argparse import glob import hashlib import json import os from pathlib import Path import shutil import struct import subprocess import sys from extract_k import LAYERS from test_k import defines, params HERE = Path(__file__).resolve().parent MAGIC = b'D4RK0002' MANIFEST_VERSION = 2 # Native HIP packaging (hipnet.bin). The layer order is the network order of extract_k.LAYERS; NGX uploads every # layer's weights back to back into one allocation in the layout the native kernels read # (kernels/tools/pwin_model.Layout), so each blob is the launch's PwinParams.w up to the next layer's weights # pointer. `divisor` is the token-grid divisor of makeKShape (2**shift); the sx/sy written per entry are the # frame-0 window shifts (the backend recomputes the whole eight-frame cycle itself). HIP_ORDER = ['enc0', 'enc1', 'enc2', 'enc3', 'enc4', 'dec5', 'dec4', 'dec3', 'dec2', 'dec1', 'dec0'] HIP_KERNEL = 'dltss_pwin_{}_layer' HIP_DIVISOR = {'enc0': 1, 'enc1': 2, 'enc2': 4, 'enc3': 8, 'enc4': 16, 'dec5': 32, 'dec4': 16, 'dec3': 8, 'dec2': 4, 'dec1': 2, 'dec0': 1} # makeKWindows (engine/runtime.cpp): an eight-frame cycle of per-axis shifts; the five deepest layers move and # every shallower one follows from the one below as (2 * shift + 4) mod 8. depth 0 = dec5, 1 = enc4/dec4, # 2 = enc3/dec3; the rest keep shift 4. HIP_CYCLE = ((0, 2), (5, 6), (4, 0), (1, 4), (7, 5), (2, 1), (3, 7), (6, 3)) HIP_ROLE = {'dec5': 0, 'enc4': 1, 'dec4': 1, 'enc3': 2, 'dec3': 2} def hip_shift(role, frame): x, y = HIP_CYCLE[frame % 8] for _ in range(role): x, y = (2 * x + 4) % 8, (2 * y + 4) % 8 return x, y def hip_weight_place(launch, offset=64): """The weight allocation a launch's PwinParams.w (argument at `offset`) points into: (base, size, path, ptr).""" ptr = struct.unpack_from(' ptr] end = min(following) if following else base + size with open(path, 'rb') as f: f.seek(ptr - base) w = bytearray(f.read(end - ptr)) pad = (-len(w)) % 64 role = HIP_ROLE.get(name) sx, sy = hip_shift(role, 0) if role is not None else (4, 4) head += struct.pack('<32siiIIQQ', HIP_KERNEL.format(name).encode(), sx, sy, HIP_DIVISOR[name], 0, at, len(w)) blobs += [w, bytes(pad)]; at += len(w) + pad (output / 'hipnet.bin').write_bytes(head + b''.join(blobs)) (output / 'hip').mkdir(exist_ok=True) for name in HIP_ORDER: src = kernels / f'{HIP_KERNEL.format(name)}.hsaco' if not src.exists(): raise ValueError(f'{src} is missing') (output / 'hip' / src.name).write_bytes(src.read_bytes()) def tensor_spans(e): c, h, x = e['C'], e['H'], e['X'] for key in ('g1', 'bo', 'g2', 'b2'): yield e[key], 16 * c for key, size in (('qkv', c * 32), ('table', 64 * 64), ('wo', 32 * c), ('w1', c * 32), ('b1', 16 * 32), ('w2', 32 * c)): expected = {'qkv': h * 3, 'table': h, 'wo': h, 'w1': c // 8, 'b1': c // 8, 'w2': c // 8}[key] if len(e[key]) != expected: raise ValueError(f'{key}: expected {expected} tensors') for offset in e[key]: yield offset, size for key, size in {'embed_w': 16 * c, 'embed_b': 16 * c, 'merge_w': 4 * c * e.get('merge_n', 0), 'merge_b': 16 * e.get('merge_n', 0), 'expand_w': x * 4 * c, 'expand_b': 16 * 4 * c, 'head_w': c * 48, 'head_b': 16 * 48}.items(): if key in e: yield e[key], size def validate(index, weight_bytes): if list(index) != list(LAYERS): raise ValueError('expected the eleven preset K layers in network order') if weight_bytes <= 0 or weight_bytes % 2: raise ValueError('weights must be nonempty f16 data') for name, (kind, heads, c, x, pos) in LAYERS.items(): e = index[name] if (e['kind'], e['H'], e['C'], e['X'], e['posattn']) != (kind, heads, c, x, pos): raise ValueError(f'{name}: incompatible layer shape') required = ('embed_w', 'embed_b') if kind == 'enc0' else () required += ('merge_w', 'merge_b', 'merge_n') if kind in ('enc', 'enc0') else () required += ('expand_w', 'expand_b') if kind in ('dec', 'dec0') else () required += ('head_w', 'head_b') if kind == 'dec0' else () for key in required: if key not in e: raise ValueError(f'{name}: missing {key}') if kind in ('enc', 'enc0') and e['merge_n'] != (x + 15) // 16 * 16: raise ValueError(f'{name}: invalid merge stride') for offset, size in tensor_spans(e): if not isinstance(offset, int) or offset < 0 or offset % 16 or (offset + size) * 2 > weight_bytes: raise ValueError(f'{name}: tensor at {offset} with {size} elements is outside/alignment-invalid') def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('model_prefix', type=Path) p.add_argument('lut', type=Path) p.add_argument('output', type=Path) p.add_argument('--ldr', action='store_true', help='display-referred NGX LDR input and reconstruction') p.add_argument('--hip', type=Path, metavar='KERNEL_DIR', help='also package the network for the native HIP layers: ' 'hipnet.bin (the eleven layers and their weight allocations as NGX uploads them) and ' 'hip/dltss_pwin_*_layer.hsaco from KERNEL_DIR (kernels/build.sh output)') p.add_argument('--capture', type=Path, metavar='CAPTURE_DIR', help='with --hip: the dump directory holding one ' 'captured frame of the eleven dltss_pwin_*_layer launches (D4R_CUDA_REPLAY_DUMP_DIR)') a = p.parse_args() if a.hip and a.capture is None: p.error('--hip needs --capture (the weights come from a captured frame)') weights = Path(str(a.model_prefix) + '.bin') index = json.loads(Path(str(a.model_prefix) + '.json').read_text()) validate(index, weights.stat().st_size) # All sixteen tables (prepare_k.py's lut.bin; NGX picks one by upscaling ratio) or, from older captures, # the single table of the captured ratio, which the engine then uses for every ratio. if a.lut.stat().st_size not in (32768, 16 * 32768): raise ValueError('the K reconstruction tables must be one or sixteen tables of 2048 sixteen-byte entries') if a.lut.stat().st_size == 32768: print('warning: one reconstruction table only; output at other upscaling ratios will not match NGX', file=sys.stderr) a.output.mkdir(parents=True, exist_ok=True) shutil.copyfile(weights, a.output / 'weights.bin') shutil.copyfile(a.lut, a.output / 'lut.bin') # Only offsets; dimensions and window shifts belong to the Engine, not the Model. class ZeroShape: def i32x2(self, offset): return (0, 0) offsets = b''.join(params(ZeroShape(), e)[0][16:] for e in index.values()) (a.output / 'offsets.bin').write_bytes(MAGIC + struct.pack('