engine/rr: native Ray Reconstruction stages with capture/replay and shim integration
- engine/rr: d4r's own shaders for the denoiser frame (exposure, six encoders,
five decoders, hkpn output stage) at NGX layouts, plus PTX->GLSL/HIP packing
scripts, reference implementations and weight extraction
- runtime.h/runtime_rr.inc, game_rr.{h,cpp}: GameDenoiser wired into the engine
build (d4r-rr replay binary) and the D3D12 shim's feature-13 path
- shim: answer GetFeatureRequirements from d4r's own state only (the old
cross-binary CUDA handoff crashed OptiScaler's scan); drop the core's
CUDA GetFeatureRequirements import
- shim/harness: exact guide capture/replay of a frame's RR inputs
- kernels/rr: expanded enc0/engine prototypes; kernels/tools: ptxsim rewrite
plus rrsim and an engine test; vulkan: ptx2glsl FP8/cooperative-matrix
support, kvk inout buffers and KVK_DISABLE_OPTIMIZATION
- bridge: report the surface array in cuSurfObjectCreate traces
- packaging: RR settings and the ICU launch overrides in D4R_README
This commit is contained in:
1 parent
30472ae195
commit
50fa65d87b
67 files changed
+11926
-504
No files matched your search
@@ -0,0 +1,173 @@
|
||||
# Ray Reconstruction stages (DLSS-D, NGX feature 13)
|
||||
|
||||
d4r's own Vulkan compute shaders for the denoiser in `nvngx_dlssd.dll` 310.7 (presets D and E). One shader per
|
||||
NVIDIA kernel of the frame; the engine records them in NVIDIA's order:
|
||||
|
||||
| stage | NVIDIA kernel | shader | grid (640x360 render, 1280x720 output) |
|
||||
|---|---|---|---|
|
||||
| exposure | `cuda_dldn_engine_luma_convert_kernel`, `reduce_sum` x2-3, `auto_exposure_copy_kernel` | `engine/exposure_k.comp` (MODE 1) | |
|
||||
| enc0 | `cuda_dldn_engine_swin_enc0_kernel` (D: `..._enc0_diamond_wallaby_kernel`) | `enc0.comp` | 81x49, 32 threads |
|
||||
| enc1-enc5 | `cuda_dldn_engine_swin_enc1_kernel` ... `enc5` | `enc1.comp` ... `enc5.comp` | |
|
||||
| dec4-dec1 | `cuda_dldn_engine_swin_dec4_kernel` ... `dec1` | `dec4.comp` ... `dec1.comp` | |
|
||||
| dec0 | `cuda_dldn_engine_swin_dec0_kernel` (D: `..._dec0_diamond_wallaby_kernel`) | `dec0.comp` | 81x49, 32 threads |
|
||||
| output | `cuda_dldn_engine_hkpn_output_kernel_transformer` (D: `..._diamond_wallaby`) | `hkpn.comp` | 80x45, 16x16 threads |
|
||||
|
||||
The D variants of enc0, dec0 and the output kernel are different code; the other nine kernels are shared and only
|
||||
their weights differ between presets.
|
||||
|
||||
## Stage contract
|
||||
|
||||
Every stage shader reads the same data NVIDIA's kernel reads, in the same layouts, so each one can be checked on
|
||||
its own against a captured launch (`test_layer.py`) and the stages chain exactly like NGX's kernels:
|
||||
|
||||
- binding 0: the parameter block, `std430` readonly. It is NVIDIA's argument block byte for byte, except that every
|
||||
pointer into the plane arena holds its **byte offset in the arena** (a 64-bit value), the weight pointer is zero,
|
||||
and a texture or surface handle is 1 where NGX bound one and 0 where it left the argument null (the kernels test
|
||||
optional inputs' handles for null). Fields are read at NVIDIA's argument offsets.
|
||||
- binding 1: the layer's weight image exactly as NGX uploads it (argument 40's allocation), `std430` readonly.
|
||||
- binding 2: the plane arena, `std430` read/write.
|
||||
- bindings 3, 4, ...: the textures in ascending argument offset (combined image samplers, clamp-to-edge,
|
||||
normalized coordinates, linear or nearest as NVIDIA's texture object; 8-bit unsigned arrays are sampled as UNORM,
|
||||
`SNORM_INT8X4` as SNORM), then the surfaces in ascending argument offset (storage images, `rgba16f` or
|
||||
`rgba8_snorm`).
|
||||
- The dispatch grid is NVIDIA's launch grid. The workgroup size is the shader's choice.
|
||||
|
||||
## Checking a stage
|
||||
|
||||
Captures (bridge launch and replay dumps of a harness run, see `rr_capture.py`) live outside the repository.
|
||||
|
||||
```sh
|
||||
glslangValidator -V --target-env vulkan1.3 engine/rr/enc1.comp -o build/rr/enc1.spv
|
||||
python3 engine/rr/test_layer.py CAPTURE swin_enc1 2 build/rr/enc1.spv build/rr/work-enc1 [--time 50]
|
||||
```
|
||||
|
||||
It starts from the arena as it was before NVIDIA's launch, with zeroed surfaces, and compares every surface and
|
||||
the whole arena with what NVIDIA's launch left. `build/rr/kvk` is `vulkan/kvk.cpp`
|
||||
(`g++ -O2 -std=c++17 -o build/rr/kvk vulkan/kvk.cpp -lvulkan`).
|
||||
|
||||
## Native stages (preset E)
|
||||
|
||||
`build_native.py CAPTURE MODEL_DIR` turns a `compile_rr.py` model directory into one where every Swin stage is
|
||||
d4r's own cooperative-matrix shader instead of translated PTX. Each was derived phase by phase from ZLUDA replays
|
||||
(`phase_dump.py`: every mma's operands of one window) into an exact numpy reference, then written against it:
|
||||
|
||||
| stages | reference | shader | weights |
|
||||
|---|---|---|---|
|
||||
| enc1-enc5, dec4-dec1 | `swin_ref.py` (one generic model: C, heads, merge / expand) | `swin_m.comp` | `swin_pack.py` |
|
||||
| dec0 | `dec0_ref.py` | `dec0_m.comp` | `dec0_pack.py` |
|
||||
| enc0 | `enc0_ref.py` (after the per-pixel features) | `enc0_m.comp` from `gen_enc0_m.py` | `enc0_pack.py` |
|
||||
|
||||
`enc1_m.comp` / `dec1_m.comp` are the hand-written C = 64 instances `swin_m.comp` generalises; `enc1_ref.py` holds
|
||||
the shared numerics (inter, mm, the cubic softmax, the norm trees). enc0 keeps ptx2rr's translation of NVIDIA's feature
|
||||
gather (waves 0-2 run its pixel loop) and is native from the embedding on.
|
||||
|
||||
Binding 1 differs from the stage contract above: it holds the packed weights (enc0: NGX's image, then the packed
|
||||
blob). Everything else is the contract, so `test_layer.py --weights FILE` checks a native stage the same way. Against
|
||||
capE3 every native stage leaves the arena byte-identical to NVIDIA's; surfaces differ in a few values by one binary16
|
||||
ulp (`exp2` approximation, and decoder texture 88 where NVIDIA's bilinear unit rounds differently), and the whole
|
||||
network's output is ~96 dB from NVIDIA's.
|
||||
|
||||
The shaders run every product transposed (weights x activations) so results feed the next product in registers;
|
||||
`enc1_m.comp`'s header explains the layouts and `rr_exact.glsl` the binary16 rounding RADV needs (it folds
|
||||
`float(float16_t(x))` and does not fuse f16 `fma`).
|
||||
|
||||
## Native models and preset D
|
||||
|
||||
`engine/rr/build_native_model.sh CAPTURE OUT_DIR [NVNGX_DLSSD_DLL]` builds a model whose eleven network stages are
|
||||
the native shaders (`swin_m.comp`, `dec0_m.comp`, the generated `enc0_m.comp`) for the preset of the capture, with the
|
||||
translated output stage.
|
||||
|
||||
The weights are read from the user's DLL itself (`dll_weights.py`), not from the capture. The DLL holds each model's
|
||||
tensors as plain binary16 arrays in natural `[out][in]` order, stage after stage; nothing names them, so they are
|
||||
found by structure: every attention head has a 100 x 100 relative position bias table whose entries depend only on
|
||||
the offset between two window positions, and a model is eleven consecutive stages with 2 2 4 4 8 8 8 4 4 2 2 heads.
|
||||
What NGX uploads is the same data rearranged: FP8 codes in the kernels' fragment order, the norm gains times
|
||||
sqrt(C), the bias times 2 sqrt(2) for the kernels' 64 x 96 query / key order, and (preset E) the 4096-byte noise
|
||||
table encoder 0 dithers with. `dll_weights.py DLL OUT --model N --check DIR` compares a reading with a phase-dump
|
||||
extraction: for 310.7 every tensor of both presets is identical, and so are the packed weight files. The capture
|
||||
only selects which of the DLL's models it ran (and supplies the two argument-block templates); a DLL whose network
|
||||
kernels are not the ones the shaders were written against (`native_kernels.txt`, SHA-256 of the PTX modules) is
|
||||
refused unless `D4R_RR_ANY_KERNELS=1`.
|
||||
|
||||
`compile_rr.py` uses the translated output kernel for both presets: `engine/rr/hkpn.comp` has the reset path only
|
||||
(`--reset-only` selects it for captures that reset every frame).
|
||||
|
||||
Preset D (`..._diamond_wallaby` kernels) differs from E in three places:
|
||||
|
||||
- **enc0**: another feature gather (kept as the translation, like E's) with 30 input features for E's 32, and no
|
||||
noise table (its weight image is 4096 bytes smaller).
|
||||
- **dec0**: the eight bytes per pixel for the output stage are channels 12, 13, 0, 18, 19, 20, 21, 0
|
||||
(`-DRR_PRESET_D`; E: 12, 13, 18-23). Everything else is E's kernel.
|
||||
- **output stage**: its own kernel (closest-depth motion from texture 296, a 4x4 bicubic history fetch). It is the
|
||||
translated stage; it has no subgroup operations and runs with 64-wide waves (`rr.txt`).
|
||||
|
||||
The reset flag (enc0 byte 365, output stage byte 440) follows `Reset`, not the jitter. With `Reset = 0` the output
|
||||
stage reprojects and blends its history: check stages against a temporal capture (the harness with
|
||||
`D4R_HARNESS_JITTER_SCENE=1`, and `D4R_CUDA_REPLAY_DUMP_LIMIT=400` so every launch is saved), not only against
|
||||
captures that reset every frame. `engine/rr/hkpn.comp` implements the reset path only.
|
||||
|
||||
Preset D, RX 9070 XT, RADV 26.2.4, 2026-10-12, 1280x720 -> 3840x2160 (`d4r-rr`, `D4R_RR_TIME_REPEAT=20`): 5.0-5.2 ms
|
||||
per frame (network 4.5, output 0.55-0.65), from 17.5 ms with every stage translated. Every network stage reproduces
|
||||
the capture's arena byte for byte on reset and temporal frames. The presented image against the CUDA path over
|
||||
temporal frames: 96-106 dB on the reset frame, then 41-52 dB (one-ulp differences in the filtered surfaces are
|
||||
amplified by the recurrent feature; twelve frames stay at 41-45 dB).
|
||||
|
||||
RE Requiem (2026-10-12, 1280x720 -> 3840x2160, R9G9B9E5 colour, roughness packed in the normals' w): with
|
||||
`hkpn.comp` the picture was 3-pixel blocks that shook with the jitter (no history); with the translated output stage
|
||||
presets D and E both give a stable, reconstructed image on the engine. Not compared with the CUDA path in that game.
|
||||
|
||||
## Matching the reference bit for bit (2026-10-12)
|
||||
|
||||
The reference is the CUDA path through ZLUDA on the same GPU, so its `rcp.approx`, `ex2.approx` and bilinear texture
|
||||
fetches are the GPU's own instructions and texture unit, not correctly rounded arithmetic. Two places in the native
|
||||
filters had been "corrected" to exact arithmetic and were one ulp off in ~0.01-0.2% of the filtered surfaces:
|
||||
|
||||
- `rcp16` (the filters' softmax normalisation and gate) is now the plain hardware reciprocal;
|
||||
- the decoders' texture 88 goes through the bilinear sampler at NVIDIA's coordinates (`rr_decfilter.glsl`,
|
||||
`dec0_m.comp`) instead of a hand-computed 1/4, 3/4 blend.
|
||||
|
||||
With both, every network stage (arena and surfaces 112 / 120 / 128 / 144 / 304) is identical to the capture on every
|
||||
frame of capDT12, capD4K, capD1080 (preset D) and capE3 (preset E). What remains is the translated output stage:
|
||||
~0.004% of surface 224 / 240 one ulp off (single channels, values just above 0.5; cause not found). The output is
|
||||
107-112 dB on frames where the network's inputs are still exact. At 640x360 -> 1080p that holds for all twelve
|
||||
frames of capDT12 (was 96 dB, then 41-45); at 1280x720 and 1920x1080 -> 4K the history's differing values reach
|
||||
encoder 0 through texture 496 and the output falls to 52 / 45 / 43 dB (capD4K), 53 / 49 dB (capD1080) as before.
|
||||
|
||||
Speed: the compiled stages spend about half their instructions on float conversions. `spv_exact_convert.py` marks
|
||||
the conversions NoContraction so RADV keeps a binary16 rounding without the opaque OR (`RR_EXACT_CONVERT`), which is
|
||||
bit-exact and ~8% faster: preset D 4.8 ms at 1280x720 -> 4K, 9.5 ms at 1920x1080 -> 4K (was 5.1 / 10.3).
|
||||
`D4R_RR_RELAXED=1|2` builds drop NVIDIA's double rounding instead (8.2 / 7.6 ms at 1080p) but are not exact: about 1%
|
||||
of a layer's FP8 codes change and the output is 39-44 dB from the reference on every frame.
|
||||
|
||||
Profile of a full-resolution stage at 1080p (dec0, 3.0 ms): expand 0.31, key norm 0.16, attention 1.37 (per head:
|
||||
projections 0.15, scores + softmax 0.26, P V 0.17, out-projection 0.10), MLP 0.69, output filter 0.50. The 136
|
||||
matrix products themselves are 0.45-0.5 ms; weight and table memory traffic is under 8%. enc0's translated feature
|
||||
gather is 0.28 of its 3.0 ms.
|
||||
|
||||
## Optional inputs: specular hit distance and disocclusion mask (2026-10-12)
|
||||
|
||||
RE Requiem passes `DLSSD.SpecularHitDistance` (R32_FLOAT) and `DLSS.DisocclusionMask` (R16_FLOAT). Harness captures
|
||||
with synthetic ones (`D4R_HARNESS_RR_EXTRA=hit,mask`; capDX, capDXhit, capDXmask) show the wiring:
|
||||
|
||||
| input | encoder 0 texture | encoder 0 flag byte | output stage |
|
||||
|---|---|---|---|
|
||||
| specular hit distance | argument 248 | 536 | flag byte 160 |
|
||||
| disocclusion mask | argument 296 | 539 | - |
|
||||
|
||||
Encoder 0 also tests the handle at argument 248 for null before it takes the hit distance path (it writes a
|
||||
reprojected specular motion into channels 2 and 3 of surface 304), which is why the parameter block now carries the
|
||||
handles' presence. With both bound the network is identical to the capture on every frame (capDX). The output
|
||||
stage then differs from the CUDA path on the image's border pixels only (63-67 dB on temporal frames): NVIDIA's
|
||||
kernel fetches neighbours outside the image there, CUDA clamps such integer fetches to the edge (every denoiser
|
||||
texture's address mode is clamp; the translation and rrsim do the same), and ZLUDA's image load returns zero. With
|
||||
zero outside the image the stage matches the capture like before (99.997%); the engine keeps CUDA's clamp.
|
||||
|
||||
Exact speed-ups since the first native build, all bit-identical per stage: conversions kept by NoContraction instead of
|
||||
an opaque OR, the MLP activation and its FP8 conversion from a 64 KiB table (`enc1_pack.act_table`), FP8 conversions in
|
||||
pairs, no saturation clamp on attention probabilities. Preset D: 4.4 ms at 1280x720 -> 4K, 8.6 ms at 1920x1080 -> 4K.
|
||||
|
||||
Output stage, hypotheses tested for its remaining ~0.004% one-ulp values and rejected (2026-10-12): correctly rounded
|
||||
exp2 / reciprocal (rrsim's exact arithmetic is further from the capture than the GPU), fusing the kernel's 34
|
||||
single-use multiply-subtract pairs into fma, merging the two constant multiplications after the log transform,
|
||||
`precise` on the inlined exp2, NoContraction on its conversions. The differing values come in spatial clusters, one
|
||||
colour channel at a time, always one ulp, mostly just above 0.5 in surface 224.
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Turn a preset-E Ray Reconstruction model directory (engine/compile_rr.py) into the native one: every Swin stage
|
||||
from d4r's own shaders instead of translated PTX.
|
||||
|
||||
usage: build_native.py CAPTURE MODEL_DIR [--frame N] [--work DIR]
|
||||
|
||||
enc0 engine/rr/enc0_m.comp, generated (gen_enc0_m.py) from the model's translated enc0.generated.comp,
|
||||
whose per-pixel feature gather it keeps; weights: NGX's image + enc0_pack.py's blob
|
||||
enc1-enc5, engine/rr/swin_m.comp with swin_pack.py's per-stage header and weights
|
||||
dec4-dec1
|
||||
dec0 engine/rr/dec0_m.comp with dec0_pack.py's weights
|
||||
The weights are extracted from ZLUDA replays of the capture's launches (engine/rr/phase_dump.py: every mma's operands
|
||||
of one window) and the stages' weight images; extraction checks itself against the dump. Like the model directory,
|
||||
everything written is derived from the user's DLL and stays on the machine. Runs one replay or compile at a time
|
||||
(phase_dump.py caps its replay at 24 GB).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
import swin_ref # noqa: E402
|
||||
import dec0_ref # noqa: E402
|
||||
import enc0_ref # noqa: E402
|
||||
|
||||
SWIN = ['enc1', 'enc2', 'enc3', 'enc4', 'enc5', 'dec4', 'dec3', 'dec2', 'dec1']
|
||||
|
||||
|
||||
def run(*cmd, **kw):
|
||||
subprocess.run([str(c) for c in cmd], check=True, **kw)
|
||||
|
||||
|
||||
def glsl(source, output, *includes):
|
||||
run('glslangValidator', '-V', '--target-env', 'vulkan1.3', *[f'-I{i}' for i in includes], source, '-o', output,
|
||||
stdout=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('model')
|
||||
ap.add_argument('--frame', type=int, default=2)
|
||||
ap.add_argument('--work', help='dumps and build files (default MODEL/native-work)')
|
||||
o = ap.parse_args()
|
||||
model = Path(o.model)
|
||||
work = Path(o.work) if o.work else model / 'native-work'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
cap = Capture(os.path.expanduser(o.capture))
|
||||
manifest = (model / 'rr.txt').read_text().split('\n')
|
||||
|
||||
def dump(stage):
|
||||
out = work / f'{stage}_all.npz'
|
||||
launch = cap.find(f'swin_{stage}', o.frame)
|
||||
block = '5,5' if launch.grid[0] > 5 and launch.grid[1] > 5 else '1,1'
|
||||
if not out.exists():
|
||||
run(sys.executable, HERE / 'phase_dump.py', o.capture, stage, o.frame, block, out)
|
||||
return out, block
|
||||
|
||||
def stage_line(stage, shader, weights):
|
||||
for i, line in enumerate(manifest):
|
||||
f = line.split()
|
||||
if len(f) > 4 and f[0] == 'stage' and f[1] == stage:
|
||||
f[2], f[4] = shader, weights
|
||||
manifest[i] = ' '.join(x for x in f if x != 'noopt')
|
||||
|
||||
for name in ('rr_exact.glsl', 'rr_attn.glsl', 'rr_decfilter.glsl', 'enc0_m_decl.glsl', 'enc0_m_tail.glsl'):
|
||||
shutil.copy(HERE / name, work / name)
|
||||
for stage in SWIN:
|
||||
d, block = dump(stage)
|
||||
print(f'{stage}: extracting', flush=True)
|
||||
l, ex, ey, sx, sy, inp, outs, tex = swin_ref.load(o.capture, stage, o.frame)
|
||||
bx, by = (int(v) for v in block.split(','))
|
||||
swin_ref.extract(stage, d, l.weights, work / f'{stage}_w.npz', inp, 8 * bx - sx, 8 * by - sy, ex, ey)
|
||||
sd = work / stage
|
||||
sd.mkdir(exist_ok=True)
|
||||
run(sys.executable, HERE / 'swin_pack.py', stage, work / f'{stage}_w.npz', model / f'weights-{stage}m.bin',
|
||||
sd / 'swin_stage.h')
|
||||
glsl(HERE / 'swin_m.comp', model / f'{stage}_m.spv', sd, work)
|
||||
stage_line(stage, f'{stage}_m.spv', f'weights-{stage}m.bin')
|
||||
# dec0
|
||||
d, _ = dump('dec0')
|
||||
print('dec0: extracting', flush=True)
|
||||
dec0_ref.extract(d, cap.find('swin_dec0', o.frame).weights, work / 'dec0_w.npz')
|
||||
run(sys.executable, HERE / 'dec0_pack.py', work / 'dec0_w.npz', model / 'weights-dec0m.bin', work / 'dec0_m_w.h')
|
||||
glsl(HERE / 'dec0_m.comp', model / 'dec0_m.spv', work)
|
||||
stage_line('dec0', 'dec0_m.spv', 'weights-dec0m.bin')
|
||||
# enc0: NGX's weight image (the translated feature gather reads it) followed by the native blob
|
||||
d, _ = dump('enc0')
|
||||
print('enc0: extracting', flush=True)
|
||||
nv = cap.find('swin_enc0', o.frame).weights
|
||||
enc0_ref.extract(d, nv, work / 'enc0_w.npz')
|
||||
base = (len(nv) + 255) // 256 * 256
|
||||
run(sys.executable, HERE / 'enc0_pack.py', work / 'enc0_w.npz', work / 'enc0_native.bin', work / 'enc0_m_w.h', base)
|
||||
(model / 'weights-enc0m.bin').write_bytes(nv + bytes(base - len(nv)) + (work / 'enc0_native.bin').read_bytes())
|
||||
run(sys.executable, HERE / 'gen_enc0_m.py', model / 'enc0.generated.comp', work / 'enc0_m.comp')
|
||||
glsl(work / 'enc0_m.comp', model / 'enc0_m.spv', work)
|
||||
stage_line('enc0', 'enc0_m.spv', 'weights-enc0m.bin')
|
||||
(model / 'rr.txt').write_text('\n'.join(manifest))
|
||||
print(f'{model / "rr.txt"}: native stages written')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Builds a Ray Reconstruction model directory whose network stages are the native shaders (engine/rr/*_m.comp),
|
||||
# for the preset the capture was taken with (D or E).
|
||||
#
|
||||
# usage: build_native_model.sh CAPTURE_DIR OUT_DIR [NVNGX_DLSSD_DLL]
|
||||
#
|
||||
# CAPTURE_DIR is a bridge capture of a few denoiser frames (engine/rr/rr_capture.py); the DLL is the one it was taken
|
||||
# with (default: CAPTURE_DIR/run/app/nvngx_dlssd.dll, then $D4R_RR_DLL). The steps:
|
||||
# 1. engine/compile_rr.py: the translated model (the argument-block templates, the exposure passes, the translated
|
||||
# output stage and the translated encoder 0 whose feature gather the native one keeps)
|
||||
# 2. engine/rr/dll_weights.py: the network's weights, read from the DLL itself. The capture only selects which of
|
||||
# the DLL's models it ran, and a DLL whose kernels are not the ones the native shaders were written against is
|
||||
# refused (D4R_RR_ANY_KERNELS=1 builds anyway)
|
||||
# 3. per stage: the weights checked against the capture's frame (STAGE_ref.py), packed (STAGE_pack.py) and the
|
||||
# native shader compiled against that packing's header
|
||||
# 4. rr.txt rewritten to the native stages; the weight images NGX uploaded in the capture are removed
|
||||
# Preset D differs from E in three kernels: encoder 0 (its feature gather and 30 input features for E's 32), decoder 0
|
||||
# (the eight bytes it hands the output stage: -DRR_PRESET_D) and the output stage, which is the translated kernel
|
||||
# (it has no subgroup operations, so it runs with 64-wide waves).
|
||||
# Everything written is derived from the user's own nvngx_dlssd.dll and stays on their machine.
|
||||
#
|
||||
# GPU jobs run one at a time, each in a memory-capped scope (a translated stage's pipeline compile can take tens of GB).
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
CAP="$(realpath "${1:?usage: build_native_model.sh CAPTURE_DIR OUT_DIR}")"
|
||||
OUT="$(realpath -m "${2:?usage: build_native_model.sh CAPTURE_DIR OUT_DIR}")"
|
||||
RR="$ROOT/engine/rr"
|
||||
WORK="$OUT/native-work"
|
||||
DLL="${3:-${D4R_RR_DLL:-$CAP/run/app/nvngx_dlssd.dll}}"
|
||||
[[ -f "$DLL" ]] || { echo "no nvngx_dlssd.dll at $DLL: pass the DLL the capture was taken with" >&2; exit 1; }
|
||||
FRAME="${D4R_RR_MODEL_FRAME:-2}"
|
||||
CAPPED=(systemd-run --user --scope -q -p MemoryMax=24G -p MemorySwapMax=0)
|
||||
mkdir -p "$OUT" "$WORK"
|
||||
cd "$ROOT"
|
||||
|
||||
python3 engine/compile_rr.py "$CAP" "$OUT" > "$WORK/compile_rr.log" 2>&1 || { tail -5 "$WORK/compile_rr.log"; exit 1; }
|
||||
python3 "$RR/dll_weights.py" "$DLL" "$WORK/dll" --capture "$CAP" ${D4R_RR_ANY_KERNELS:+--any-kernels}
|
||||
PRESET="$(awk '$1 == "preset" { print $2 }' "$OUT/rr.txt")"
|
||||
# RR_EXACT_CONVERT: the compiled module's float conversions are decorated NoContraction (spv_exact_convert.py), which
|
||||
# keeps the driver from folding a binary16 rounding away without the opaque OR the shaders otherwise need
|
||||
DEFS=(-DRR_EXACT_CONVERT); [[ "$PRESET" == 4 ]] && DEFS+=(-DRR_PRESET_D)
|
||||
# D4R_RR_RELAXED=1: one rounding where NVIDIA has two (engine/rr/rr_exact.glsl): faster, not bit-exact
|
||||
[[ -n "${D4R_RR_RELAXED:-}" ]] && DEFS+=("-DRR_RELAXED=$D4R_RR_RELAXED")
|
||||
glsl() { # glsl OUT.spv SOURCE INCLUDE_DIR...
|
||||
local out="$1" src="$2"; shift 2
|
||||
local dir inc=(); for dir in "$@"; do inc+=("-I$dir"); done
|
||||
"${CAPPED[@]}" glslangValidator --target-env vulkan1.3 -V "${DEFS[@]}" "${inc[@]}" -o "$out" "$src" > "$WORK/glsl.log" 2>&1 ||
|
||||
{ grep -E 'ERROR' "$WORK/glsl.log" | head -5; exit 1; }
|
||||
python3 "$RR/spv_exact_convert.py" "$out" > /dev/null
|
||||
}
|
||||
native() { # native STAGE WEIGHT_FILE: point rr.txt's stage line at STAGE_m.spv
|
||||
sed -i -E "s/^stage $1 $1\.spv 32 weights-$1\.bin (.*)/stage $1 $1_m.spv 32 $2 \1/; /^stage $1 /s/ noopt\$//" "$OUT/rr.txt"
|
||||
grep -q "^stage $1 $1_m.spv " "$OUT/rr.txt" || { echo "rr.txt: no stage line for $1" >&2; exit 1; }
|
||||
}
|
||||
|
||||
# the Swin stages shared by both presets (engine/rr/swin_m.comp)
|
||||
for s in enc1 enc2 enc3 enc4 enc5 dec4 dec3 dec2 dec1; do
|
||||
b=1,1; case $s in enc1|enc2|dec1|dec2) b=5,5 ;; esac
|
||||
d="$WORK/$s"; mkdir -p "$d"
|
||||
cp "$WORK/dll/$s.npz" "$d/weights.npz"
|
||||
python3 "$RR/swin_ref.py" "$CAP" "$s" "$FRAME" "$d/weights.npz" --blocks "$b" 0,0 | tail -2 | sed "s/^/$s: /"
|
||||
python3 "$RR/swin_pack.py" "$s" "$d/weights.npz" "$OUT/weights-${s}m.bin" "$d/swin_stage.h" > /dev/null
|
||||
glsl "$OUT/${s}_m.spv" "$RR/swin_m.comp" "$d" "$RR"
|
||||
native "$s" "weights-${s}m.bin"
|
||||
done
|
||||
|
||||
# decoder 0
|
||||
d="$WORK/dec0"; mkdir -p "$d"
|
||||
cp "$WORK/dll/dec0.npz" "$d/weights.npz"
|
||||
python3 "$RR/dec0_ref.py" "$CAP" "$FRAME" "$d/weights.npz" --blocks 5,5 0,0 | tail -2 | sed 's/^/dec0: /'
|
||||
python3 "$RR/dec0_pack.py" "$d/weights.npz" "$OUT/weights-dec0m.bin" "$d/dec0_m_w.h" > /dev/null
|
||||
glsl "$OUT/dec0_m.spv" "$RR/dec0_m.comp" "$d" "$RR"
|
||||
native dec0 weights-dec0m.bin
|
||||
|
||||
# encoder 0: the translated feature gather + the native rest
|
||||
d="$WORK/enc0"; mkdir -p "$d"
|
||||
cp "$WORK/dll/enc0.npz" "$d/weights.npz"
|
||||
# preset E's gather dithers with the noise table that starts NGX's weight image: the table, then the packing
|
||||
BASE=0; [[ "$PRESET" == 4 ]] || BASE=4096
|
||||
python3 "$RR/enc0_pack.py" "$d/weights.npz" "$d/enc0_m.bin" "$d/enc0_m_w.h" "$BASE" > /dev/null
|
||||
if [[ "$BASE" != 0 ]]; then
|
||||
[[ "$(stat -c %s "$WORK/dll/noise.bin" 2>/dev/null)" == 4096 ]] || { echo "no noise table found in $DLL" >&2; exit 1; }
|
||||
cat "$WORK/dll/noise.bin" "$d/enc0_m.bin" > "$OUT/weights-enc0m.bin"
|
||||
else
|
||||
cp "$d/enc0_m.bin" "$OUT/weights-enc0m.bin"
|
||||
fi
|
||||
python3 "$RR/gen_enc0_m.py" "$OUT/enc0.generated.comp" "$d/enc0_m.comp" > /dev/null
|
||||
glsl "$OUT/enc0_m.spv" "$d/enc0_m.comp" "$d" "$RR"
|
||||
native enc0 weights-enc0m.bin
|
||||
|
||||
# the translated output stage (preset D; preset E's when engine/rr/hkpn.comp is not used) has no subgroup operations
|
||||
if grep -q '"shader": "generated from the DLL"' <(python3 -c "import json,sys; print(json.dumps(json.load(open('$OUT/model.json'))['stages']['hkpn']))"); then
|
||||
sed -i -E 's/^stage hkpn hkpn\.spv 32 /stage hkpn hkpn.spv 64 /' "$OUT/rr.txt"
|
||||
fi
|
||||
for s in enc0 enc1 enc2 enc3 enc4 enc5 dec4 dec3 dec2 dec1 dec0; do rm -f "$OUT/weights-$s.bin"; done
|
||||
printf 'Native Ray Reconstruction model (preset %s) in %s\n' "$([[ "$PRESET" == 4 ]] && echo D || echo E)" "$OUT"
|
||||
grep '^stage' "$OUT/rr.txt" | awk '{ printf " %s %s (%s-wide)\n", $2, $3, $4 }'
|
||||
@@ -0,0 +1,348 @@
|
||||
#version 460
|
||||
// Native decoder 0 of the DLSS-D (preset E) denoiser, register-resident: cuda_dldn_engine_swin_dec0_kernel computed
|
||||
// like engine/rr/enc1_m.comp (whose header explains the transposed products and the slot orders). The arithmetic is
|
||||
// engine/rr/dec0_ref.py's (bit-exact to NVIDIA except surface 112's bilinear taps, which NVIDIA's texture unit rounds
|
||||
// differently in a few percent of the values by one ulp); binding 1 holds engine/rr/dec0_pack.py's packing.
|
||||
//
|
||||
// One workgroup per 8x8-token window at the render resolution, four waves:
|
||||
// expand the 6 x 6 low-resolution tokens under the window and its border (dec1's 64 channels) -> 4 sub-positions
|
||||
// x 32 channels; wave w computes sub-position w, FP8 into EX in slot order
|
||||
// prologue one key token per thread: expanded + skip (enc0's tokens), RMS norm -> XT; window tokens -> XR (f16)
|
||||
// block two 32-channel heads and four MLP branches of 32 -> 32 -> 32, as enc1_m with C = 32
|
||||
// outputs one token per thread: 8 FP8 channels for the output stage (argument 48, [y][x][8]), tanh of four
|
||||
// channels to surface 144, and the kernel-prediction filter of texture 88 (bilinear from half resolution)
|
||||
// blended with textures 96 / 104 to surface 112
|
||||
#extension GL_KHR_cooperative_matrix : require
|
||||
#extension GL_KHR_memory_scope_semantics : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_float_e4m3 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 128) in;
|
||||
#include "dec0_m_w.h"
|
||||
#include "rr_exact.glsl"
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint p[]; } param;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16U { uint16_t w[]; } w16u;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8U { uint8_t w[]; } w8u;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
layout(set = 0, binding = 3) uniform sampler2D T88;
|
||||
layout(set = 0, binding = 4) uniform sampler2D T96;
|
||||
layout(set = 0, binding = 5) uniform sampler2D T104;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S112;
|
||||
layout(set = 0, binding = 7, rgba8_snorm) uniform writeonly image2D S144;
|
||||
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
#define LDA(dst, base, k, mt, kt) coopMatLoad((dst), w8.w, (base) + 256u * ((mt) * ((k) / 16u) + (kt)), 16u, RM)
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
#define RND(m) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (m)[i_] = rnd16((m)[i_]);
|
||||
#define Q8(dst, src) Q8PAIRS(dst, src)
|
||||
#define K32(acc, a0, b0, a1, b1) { (acc) = coopMatMulAdd((a1), (b1), coopMatMulAdd((a0), (b0), (acc))); RND(acc) }
|
||||
const uint HEAD = W_WK1 - W_WK0, BRANCH = W_W1_1 - W_W1_0;
|
||||
|
||||
// shared memory, aliased by phase: expand and prologue [LOW | EX], then the attention [K | V^T]
|
||||
shared uint POOL[48 * 16 + 48 * 32];
|
||||
#define LOW POOL // the low-resolution tokens (6 x 6, padded to 48): 64 FP8 channels, arena order
|
||||
#define EX_AT 768u // their expansion: 4 sub-positions x 32 FP8 channels in slot order (32 words a row)
|
||||
#define RR_KS POOL // the current head's K
|
||||
#define RR_KS0 0u
|
||||
#define RR_VS POOL // the current head's V^T
|
||||
#define RR_VS0 768u
|
||||
shared uint XT[96 * 8]; // the normalised key tokens: slot-major, 32 FP8 channels in slot order
|
||||
shared float16_t XR[64 * 32]; // the window tokens before the norm (the residual), slot-major
|
||||
shared float16_t TAP[100 * 4]; // texture 88 bilinear at the window's 10 x 10 filter tap positions
|
||||
#define OUTF XR // after the block: the final tokens, token-major, natural channels (f16)
|
||||
|
||||
// the window's key tokens: the 8x8 window, then a border row above, below, a column left and right
|
||||
ivec2 keyXY(uint t, int x0, int y0)
|
||||
{
|
||||
if (t < 64u) return ivec2(x0 + int(t & 7u), y0 + int(t >> 3u));
|
||||
const int i = int(t & 7u), side = int((t - 64u) >> 3u);
|
||||
return ivec2(side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8), side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i));
|
||||
}
|
||||
int clampTap(int v, int n) { return v < 0 ? 1 : (v >= n ? n - 2 : v); }
|
||||
float tanhNV(float t)
|
||||
{
|
||||
const float m = abs(t), s = t * t;
|
||||
const float big = m >= 9.0109138 ? 1.0 : fma(1.0 / (exp2(m * 2.885390043258667) + 1.0), -2.0, 1.0);
|
||||
return m >= 0.6 ? (t < 0.0 ? -big : big) :
|
||||
fma(fma(fma(fma(fma(0.01573968306183815, s, -0.05230396240949631), s, 0.13315297663211823), s, -0.33332768082618713), s, 0.0), t, t);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const uint hl = lane >> 4u, col = lane & 15u;
|
||||
RR_ZERO16 = uint16_t(gl_NumWorkGroups.z - 1u);
|
||||
const int ex = int(param.p[0]), ey = int(param.p[1]), hx = ex / 2, hy = ey / 2;
|
||||
const uint loOff = param.p[2], skOff = param.p[6], outOff = param.p[12], shift = param.p[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const int X0 = (x0 >> 1) - 1, Y0 = (y0 >> 1) - 1;
|
||||
const uint lplane = uint(hx * hy) * 32u;
|
||||
|
||||
// ---------------------------------------------------------------- expand
|
||||
for (uint i = tid; i < 48u * 16u; i += 128u) {
|
||||
const uint r = i >> 4u, wd = i & 15u;
|
||||
uint u = 0u;
|
||||
if (r < 36u) {
|
||||
const int X = mirror(X0 + int(r % 6u), hx), Y = mirror(Y0 + int(r / 6u), hy);
|
||||
u = arena.w[(loOff + (wd >> 3u) * lplane + uint(Y * hx + X) * 32u) / 4u + (wd & 7u)];
|
||||
}
|
||||
LOW[i] = u;
|
||||
}
|
||||
barrier();
|
||||
{
|
||||
A8 we[2][4];
|
||||
[[unroll]] for (uint mm = 0u; mm < 2u; ++mm)
|
||||
[[unroll]] for (uint kt = 0u; kt < 4u; ++kt) LDA(we[mm][kt], W_WE, 64u, 2u * wv + mm, kt);
|
||||
[[unroll]] for (uint n = 0u; n < 3u; ++n) {
|
||||
B8 lb[4];
|
||||
[[unroll]] for (uint kt = 0u; kt < 4u; ++kt) coopMatLoad(lb[kt], LOW, n * 256u + 4u * kt, 16u, CM);
|
||||
[[unroll]] for (uint mm = 0u; mm < 2u; ++mm) {
|
||||
const uint m = 2u * wv + mm;
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BE / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
K32(a, we[mm][0], lb[0], we[mm][1], lb[1])
|
||||
K32(a, we[mm][2], lb[2], we[mm][3], lb[3])
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
coopMatStore(q, POOL, EX_AT + n * 512u + 4u * m, 32u, CM);
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- prologue: one key token per thread
|
||||
if (tid < 96u) {
|
||||
const ivec2 k = keyXY(inter(tid), x0, y0);
|
||||
const uint r = uint(((k.y >> 1) - Y0) * 6 + ((k.x >> 1) - X0)), s = uint(2 * (k.y & 1) + (k.x & 1));
|
||||
const int xx = mirror(k.x, ex), yy = mirror(k.y, ey);
|
||||
const uint sb = (skOff + uint(yy * ex + xx) * 32u) / 4u;
|
||||
float16_t v[32];
|
||||
[[unroll]] for (uint wd = 0u; wd < 8u; ++wd) {
|
||||
const uint e = POOL[EX_AT + r * 32u + 8u * s + wd], sk = arena.w[sb + wd];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) {
|
||||
precise float16_t x = float16_t(uintBitsToFloate4m3EXT(uint8_t((e >> (8u * b)) & 0xffu)))
|
||||
+ float16_t(uintBitsToFloate4m3EXT(uint8_t((sk >> (8u * b)) & 0xffu)));
|
||||
v[4u * wd + b] = x;
|
||||
}
|
||||
}
|
||||
// dec0_ref.norm_plane: per word t the pair sums, words t and t + 4, then t ^ 1, t ^ 2, the halves
|
||||
precise f16vec2 h[4];
|
||||
[[unroll]] for (uint t = 0u; t < 4u; ++t) {
|
||||
precise f16vec2 p0 = f16vec2(v[4u * t], v[4u * t + 1u]) * f16vec2(v[4u * t], v[4u * t + 1u])
|
||||
+ f16vec2(v[4u * t + 2u], v[4u * t + 3u]) * f16vec2(v[4u * t + 2u], v[4u * t + 3u]);
|
||||
precise f16vec2 p1 = f16vec2(v[16u + 4u * t], v[17u + 4u * t]) * f16vec2(v[16u + 4u * t], v[17u + 4u * t])
|
||||
+ f16vec2(v[18u + 4u * t], v[19u + 4u * t]) * f16vec2(v[18u + 4u * t], v[19u + 4u * t]);
|
||||
h[t] = p0 + p1;
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t sm = hs.x + hs.y;
|
||||
precise float16_t se = sm + float16_t(0.0001220703125);
|
||||
const float16_t rr = rsq16(se);
|
||||
[[unroll]] for (uint wd = 0u; wd < 8u; ++wd) {
|
||||
floate4m3_t c[4];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) {
|
||||
const uint j = 4u * wd + b;
|
||||
precise float16_t g = rr * w16.w[W_GAIN0 / 2u + inter(j)];
|
||||
precise float16_t n = v[j] * g;
|
||||
c[b] = q8h(n);
|
||||
}
|
||||
XT[tid * 8u + wd] = code(c[0]) | (code(c[1]) << 8u) | (code(c[2]) << 16u) | (code(c[3]) << 24u);
|
||||
}
|
||||
if (tid < 64u) [[unroll]] for (uint j = 0u; j < 32u; ++j) XR[tid * 32u + j] = v[j];
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- the residual: window tokens + table 0
|
||||
ACC R[2];
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
HAC x;
|
||||
coopMatLoad(x, XR, wv * 512u + 16u * m, 32u, CM);
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
precise float16_t c = float16_t(x[i]) + w16.w[W_TABLE0 / 2u + 16u * m + 8u * hl + uint(i)];
|
||||
R[m][i] = float(c);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- attention, two heads
|
||||
[[unroll]] for (uint hd = 0u; hd < 2u; ++hd) {
|
||||
if (hd == 1u) barrier();
|
||||
for (uint j = 0u; j < 3u; ++j) { // 12 jobs (token tile n, channel tile c)
|
||||
const uint job = wv + 4u * j, n = job >> 1u, c = job & 1u;
|
||||
A8 wk[2], xa[2];
|
||||
B8 wvb[2], xb[2];
|
||||
[[unroll]] for (uint kt = 0u; kt < 2u; ++kt) {
|
||||
coopMatLoad(xb[kt], XT, n * 128u + 4u * kt, 8u, CM);
|
||||
coopMatLoad(xa[kt], XT, n * 128u + 4u * kt, 8u, RM);
|
||||
LDA(wk[kt], W_WK0 + HEAD * hd, 32u, c, kt);
|
||||
LDB(wvb[kt], W_WV0 + HEAD * hd, 32u, kt, c);
|
||||
}
|
||||
ACC a = ACC(0.0);
|
||||
K32(a, wk[0], xb[0], wk[1], xb[1])
|
||||
A8 ka;
|
||||
Q8(ka, a)
|
||||
coopMatStore(ka, POOL, n * 128u + 4u * c, 8u, RM);
|
||||
ACC v = ACC(0.0);
|
||||
K32(v, xa[0], wvb[0], xa[1], wvb[1])
|
||||
A8 va;
|
||||
Q8(va, v)
|
||||
coopMatStore(va, POOL, 768u + c * 384u + 4u * n, 24u, RM);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_attn.glsl"
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WO0 + HEAD * hd, 32u, m, 0u); LDA(a1, W_WO0 + HEAD * hd, 32u, m, 1u);
|
||||
K32(R[m], a0, Ob[0], a1, Ob[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- MLP
|
||||
B8 Nb[2];
|
||||
{
|
||||
float16_t rv[2][8];
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) [[unroll]] for (int i = 0; i < 8; ++i) rv[m][i] = float16_t(R[m][i]);
|
||||
// dec0_ref.norm_plane on slot order: lane half hl holds words 2hl, 2hl + 1 (tile 0) and 4 + 2hl, 5 + 2hl (tile 1)
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t p0 = rv[0][i0] * rv[0][i0] + rv[0][i1] * rv[0][i1];
|
||||
precise float16_t p1 = rv[1][i0] * rv[1][i0] + rv[1][i1] * rv[1][i1];
|
||||
precise float16_t hv = p0 + p1;
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const uint k = 16u * m + 8u * hl + uint(i);
|
||||
precise float16_t g = r * w16.w[W_GAIN1 / 2u + k];
|
||||
precise float16_t n = rv[m][i] * g;
|
||||
Nb[m][i] = q8h(n);
|
||||
precise float16_t a = rv[m][i] + w16.w[W_TABLE1 / 2u + k];
|
||||
R[m][i] = float(a);
|
||||
}
|
||||
}
|
||||
for (uint br = 0u; br < 4u; ++br) {
|
||||
const uint w1 = W_W1_0 + BRANCH * br, b1 = W_B1_0 + BRANCH * br, w2 = W_W2_0 + BRANCH * br;
|
||||
B8 Hb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC h;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) h[i] = float(w16.w[b1 / 2u + 16u * c + 8u * hl + uint(i)]);
|
||||
A8 a0, a1;
|
||||
LDA(a0, w1, 32u, c, 0u); LDA(a1, w1, 32u, c, 1u);
|
||||
K32(h, a0, Nb[0], a1, Nb[1])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) Hb[c][i] = ACTQ(float16_t(h[i]));
|
||||
}
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w2, 32u, m, 0u); LDA(a1, w2, 32u, m, 1u);
|
||||
K32(R[m], a0, Hb[0], a1, Hb[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- outputs, one token per thread
|
||||
{
|
||||
const uint t = inter(16u * wv + col);
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) OUTF[t * 32u + inter(16u * m + 8u * hl + uint(i))] = float16_t(R[m][i]);
|
||||
}
|
||||
barrier();
|
||||
// texture 88 (one level down) at the window's 10 x 10 full-resolution tap positions (clamped like the filter's
|
||||
// coordinates), through the bilinear sampler at NVIDIA's coordinates: the reference's texture unit does not round
|
||||
// like f32 arithmetic (computing the 1/4, 3/4 blend by hand left ~0.2% of surface 112 one ulp off, capDT 2026-10-12)
|
||||
if (tid < 100u) {
|
||||
const int xi = clampTap(x0 - 1 + int(tid % 10u), ex), yi = clampTap(y0 - 1 + int(tid / 10u), ey);
|
||||
const float iw = uintBitsToFloat(param.p[34]), ih = uintBitsToFloat(param.p[35]);
|
||||
precise float cu = fma(iw, float(xi), iw * 0.5), cv = fma(ih, float(yi), ih * 0.5);
|
||||
const vec4 bl = textureLod(T88, vec2(cu, cv), 0.0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) TAP[tid * 4u + c] = float16_t(bl[c]);
|
||||
}
|
||||
barrier();
|
||||
// two threads per token: half 0 filters channels 0 and 1, half 1 channel 2 and writes the output stage's codes
|
||||
// and surface 144
|
||||
{
|
||||
const uint t = tid >> 1u, hf = tid & 1u;
|
||||
const int x = x0 + int(t & 7u), y = y0 + int(t >> 3u);
|
||||
if (x < 0 || y < 0 || x >= ex || y >= ey) return;
|
||||
float16_t a[10];
|
||||
[[unroll]] for (uint c = 0u; c < 10u; ++c) a[c] = OUTF[t * 32u + c];
|
||||
if (hf == 1u) {
|
||||
float16_t b[12];
|
||||
[[unroll]] for (uint c = 0u; c < 12u; ++c) b[c] = OUTF[t * 32u + 12u + c];
|
||||
const uint o = (outOff + uint(y * ex + x) * 8u) / 4u;
|
||||
#ifdef RR_PRESET_D
|
||||
// cuda_dldn_engine_swin_dec0_diamond_wallaby_kernel: channels 12, 13, 0, 18, 19, 20, 21, 0 (its only
|
||||
// difference from the preset E kernel; capDT, 2026-10-12)
|
||||
arena.w[o] = code(q8h(b[0])) | (code(q8h(b[1])) << 8u) | (code(q8h(b[6])) << 24u);
|
||||
arena.w[o + 1u] = code(q8h(b[7])) | (code(q8h(b[8])) << 8u) | (code(q8h(b[9])) << 16u);
|
||||
#else
|
||||
arena.w[o] = code(q8h(b[0])) | (code(q8h(b[1])) << 8u) | (code(q8h(b[6])) << 16u) | (code(q8h(b[7])) << 24u);
|
||||
arena.w[o + 1u] = code(q8h(b[8])) | (code(q8h(b[9])) << 8u) | (code(q8h(b[10])) << 16u) | (code(q8h(b[11])) << 24u);
|
||||
#endif
|
||||
imageStore(S144, ivec2(x, y), vec4(tanhNV(float(b[2])), tanhNV(float(b[3])), tanhNV(float(b[4])), tanhNV(float(b[5]))));
|
||||
}
|
||||
// surface 112: softmax of channels 0-8 over the 3x3 taps, one f16 fma chain per channel; the gate (channel 9)
|
||||
// blends it with texture 96; + texture 104
|
||||
const float16_t ma = max(max(max(a[0], a[2]), a[4]), a[6]), mb = max(max(max(a[1], a[3]), a[5]), a[7]);
|
||||
const float16_t mx = max(max(ma, mb), a[8]);
|
||||
float e[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) { precise float16_t d = a[i] - mx; e[i] = exp2(float(d) * 1.4426950216293335); }
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t rr = rcp16(s);
|
||||
float16_t wg[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) wg[i] = h16(r16(e[i]) * float(rr));
|
||||
const uint tx = uint(x - x0), ty = uint(y - y0), c0 = 2u * hf, nc = 2u - hf;
|
||||
float16_t f[2] = float16_t[2](0.0hf, 0.0hf);
|
||||
bool mid = false;
|
||||
[[unroll]] for (uint k = 0u; k < 9u; ++k) {
|
||||
const uint at = ((ty + k / 3u) * 10u + tx + k % 3u) * 4u + c0;
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) f[c] = fma16q(TAP[at + c], wg[k], f[c], mid);
|
||||
}
|
||||
if (mid) { // an f32 result on a binary16 midpoint: redo the chain with the exact fma (rare)
|
||||
f[0] = 0.0hf; f[1] = 0.0hf;
|
||||
for (uint k = 0u; k < 9u; ++k) {
|
||||
const uint at = ((ty + k / 3u) * 10u + tx + k % 3u) * 4u + c0;
|
||||
for (uint c = 0u; c < 2u; ++c) f[c] = fma16(TAP[at + c], wg[k], f[c]);
|
||||
}
|
||||
}
|
||||
const float16_t g = sig(a[9]);
|
||||
precise float16_t om = float16_t(1.0) - g;
|
||||
const vec4 t96 = texelFetch(T96, ivec2(x, y), 0), t104 = texelFetch(T104, ivec2(x, y), 0);
|
||||
float16_t o[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
precise float16_t fg = f[c] * g;
|
||||
float16_t v = fma16(float16_t(t96[c0 + c]), om, fg);
|
||||
const float16_t t4 = float16_t(t104[c0 + c]);
|
||||
if (!isnan(t4)) { precise float16_t vv = v + t4; v = vv; }
|
||||
o[c] = v;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(o[0]), 1u);
|
||||
if (hf == 0u) imageStore(S112, ivec2(x, y), vec4(o[0], o[1], hb(other), 0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack decoder 0's extracted weights (engine/rr/dec0_ref.py --extract) for engine/rr/dec0_m.comp.
|
||||
|
||||
usage: dec0_pack.py WEIGHTS.npz OUT.bin OUT.h
|
||||
|
||||
The layout rules are enc1_pack.py's: transposed products, every weight's columns in the k order of the product that
|
||||
consumes its result (C = 32: slot j holds natural channel inter(32)[j]). The expand's 128 columns are four
|
||||
sub-positions of 32 channels, each permuted the same way, so a key token's expanded half is already in slot order.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from enc1_ref import inter # noqa: E402
|
||||
from enc1_pack import a_tiles, b_tiles, cubic_table, act_table # noqa: E402
|
||||
|
||||
I32, I96 = inter(32), inter(96)
|
||||
I128 = [32 * (j // 32) + I32[j % 32] for j in range(128)]
|
||||
|
||||
|
||||
def main():
|
||||
src, out_bin, out_h = sys.argv[1:4]
|
||||
w = dict(np.load(src))
|
||||
e = {'we': ('A', w['we'][:, I128]), 'be': ('H', w['be'][I128])}
|
||||
for h in range(2):
|
||||
e[f'wk{h}'] = ('A', w[f'wk{h}'][:, I32])
|
||||
e[f'wv{h}'] = ('B', w[f'wv{h}'][:, I32])
|
||||
e[f'wo{h}'] = ('A', w[f'wo{h}'][:, I32])
|
||||
e[f'bias{h}'] = ('H', w[f'bias{h}'][np.ix_(inter(64), I96)].T)
|
||||
for b in range(4):
|
||||
e[f'w1_{b}'] = ('A', w[f'w1_{b}'][:, I32])
|
||||
e[f'b1_{b}'] = ('H', w[f'b1_{b}'][I32])
|
||||
e[f'w2_{b}'] = ('A', w[f'w2_{b}'][:, I32])
|
||||
e['gain0'] = ('H', w['gain0'])
|
||||
for k in ('table0', 'gain1', 'table1'):
|
||||
e[k] = ('H', w[k][I32])
|
||||
e['cubic'] = ('U', cubic_table())
|
||||
e['act'] = ('Y', act_table())
|
||||
blob, defines = bytearray(), []
|
||||
for name, (kind, a) in e.items():
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if kind == 'A':
|
||||
blob += a_tiles(a).tobytes()
|
||||
elif kind == 'B':
|
||||
blob += b_tiles(a).tobytes()
|
||||
elif kind == 'Y':
|
||||
blob += np.ascontiguousarray(a, np.uint8).tobytes()
|
||||
elif kind == 'U':
|
||||
blob += np.ascontiguousarray(a, np.uint16).tobytes()
|
||||
else:
|
||||
blob += np.ascontiguousarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {off}u // {kind} {a.shape}')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write('// generated by engine/rr/dec0_pack.py\n' + '\n'.join(defines) + '\n')
|
||||
print(f'{len(blob)} bytes, {len(e)} entries')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact numpy reference of the denoiser's decoder 0 (cuda_dldn_engine_swin_dec0_kernel), one 8x8 window at a time,
|
||||
from its weights and real inputs only: the specification the native shader implements. Derived like
|
||||
engine/rr/enc1_ref.py, phase by phase against ZLUDA replays (engine/rr/phase_dump.py).
|
||||
|
||||
expand the low-resolution tokens under the window and its border (6 x 6 of dec1's 64-channel output, arena order)
|
||||
-> 128 = 4 sub-positions x 32 channels (+ bias), FP8; sub-position 2 (y & 1) + (x & 1)
|
||||
prologue key token = expanded + skip token (enc0's 32-channel output at argument 24), f16; RMS norm (enc1's per-word
|
||||
tree on one plane) times the gain at W+8448; the residual is the token + the table at W+39232
|
||||
attention two heads of 32 channels (K = 32, one k32 step), as enc1
|
||||
MLP RMS norm, four branches of 32 -> 32 (+ bias, ramp activation) -> 32 onto the residual (+ table)
|
||||
|
||||
usage: dec0_ref.py CAPTURE FRAME WEIGHTS.npz [--blocks X,Y ...] (verifies the phases of a dump, see --dump)
|
||||
dec0_ref.py CAPTURE FRAME OUT.npz --extract DUMP.npz (weights from a phase dump)
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import e4m3, f16, q8 # noqa: E402
|
||||
from enc1_ref import inter, mm, softmax_cubic, act, mirror, add, H, F # noqa: E402
|
||||
|
||||
I32 = inter(32)
|
||||
UN32 = [I32.index(i) for i in range(32)]
|
||||
I96 = inter(96)
|
||||
|
||||
|
||||
def norm_plane(xn, gain):
|
||||
"""RMS norm of one 32-channel token (natural order) in NVIDIA's tree over the arena order; returns codes in k order"""
|
||||
xa = xn[I32]
|
||||
sq = (xa * xa).astype(H)
|
||||
h = []
|
||||
for t in range(4):
|
||||
def pair(w):
|
||||
return add(sq[4 * w:4 * w + 2], sq[4 * w + 2:4 * w + 4])
|
||||
h.append(add(pair(t), pair(t + 4)))
|
||||
h01 = [add(h[t], h[t ^ 1]) for t in range(4)]
|
||||
h2 = add(h01[0], h01[2])
|
||||
s = H(H(h2[0] + h2[1]) + H(2.0 ** -13))
|
||||
r = H(F(1) / np.sqrt(F(s)))
|
||||
return q8((xn * (r * gain).astype(H)).astype(H))[I32]
|
||||
|
||||
|
||||
def keys(x0, y0):
|
||||
border = ([(x, y0 - 1) for x in range(x0, x0 + 8)] + [(x, y0 + 8) for x in range(x0, x0 + 8)]
|
||||
+ [(x0 - 1, y) for y in range(y0, y0 + 8)] + [(x0 + 8, y) for y in range(y0, y0 + 8)])
|
||||
return [(x, y) for y in range(y0, y0 + 8) for x in range(x0, x0 + 8)] + border
|
||||
|
||||
|
||||
def window(w, lo, skip, x0, y0, ex, ey, dbg=None):
|
||||
"""lo: dec1's output [2][ey/2][ex/2][32] codes; skip: enc0's [ey][ex][32] codes. Returns the residual stream after
|
||||
the block (64 x 32 f16, natural channels)"""
|
||||
dbg = {} if dbg is None else dbg
|
||||
hx, hy = ex // 2, ey // 2
|
||||
X0, Y0 = x0 // 2 - 1, y0 // 2 - 1
|
||||
low = np.zeros((48, 64), np.uint8)
|
||||
for r in range(36):
|
||||
X, Y = mirror(X0 + r % 6, hx), mirror(Y0 + r // 6, hy)
|
||||
low[r] = np.concatenate([lo[0, Y, X], lo[1, Y, X]])
|
||||
Ex = q8(mm(low, w['we'], np.repeat(w['be'][None, :], 48, 0)))
|
||||
dbg['p1_A'] = low
|
||||
xs, ns = [], []
|
||||
for (x, y) in keys(x0, y0):
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
r = (yy // 2 - Y0) * 6 + (xx // 2 - X0) if False else (y // 2 - Y0) * 6 + (x // 2 - X0)
|
||||
s = 2 * (y & 1) + (x & 1)
|
||||
xn = (e4m3(Ex[r, 32 * s:32 * s + 32]).astype(H) + e4m3(skip[yy, xx][UN32]).astype(H)).astype(H)
|
||||
xs.append(xn)
|
||||
ns.append(norm_plane(xn, w['gain0']))
|
||||
X = np.array(ns)
|
||||
dbg['p2_A'] = X
|
||||
R = None
|
||||
for h in range(2):
|
||||
qk = mm(X, w[f'wk{h}'], np.zeros((96, 32)))
|
||||
v = mm(X, w[f'wv{h}'], np.zeros((96, 32)))
|
||||
Q = q8(qk)[:, I32]
|
||||
S = mm(Q[:64], Q.T, w[f'bias{h}'])
|
||||
P = softmax_cubic(S)
|
||||
O = mm(q8(P)[:, I96], q8(v)[I96], np.zeros((64, 32)))
|
||||
C = add(np.array(xs[:64]), w['table0'][None, :]) if h == 0 else R
|
||||
R = mm(q8(O)[:, I32], w[f'wo{h}'], C)
|
||||
dbg[f'p{4 + 5 * h}_A'], dbg[f'p{5 + 5 * h}_A'], dbg[f'p{6 + 5 * h}_A'] = Q[:64], q8(P)[:, I96], q8(O)[:, I32]
|
||||
dbg[f'p{6 + 5 * h}_C'], dbg[f'p{6 + 5 * h}_D'] = C, R
|
||||
N = np.array([norm_plane(R[t], w['gain1']) for t in range(64)])
|
||||
acc = add(R, w['table1'][None, :])
|
||||
for b in range(4):
|
||||
hid = act(mm(N, w[f'w1_{b}'], np.repeat(w[f'b1_{b}'][None, :], 64, 0)))
|
||||
dbg[f'p{12 + 2 * b}_A'] = N
|
||||
dbg[f'p{13 + 2 * b}_A'], dbg[f'p{13 + 2 * b}_C'] = q8(hid)[:, I32], acc
|
||||
acc = mm(q8(hid)[:, I32], w[f'w2_{b}'], acc)
|
||||
dbg[f'p{13 + 2 * b}_D'] = acc
|
||||
return acc
|
||||
|
||||
|
||||
OUT_CH = [12, 13, 18, 19, 20, 21, 22, 23] # the 8 FP8 channels of the output stage's input (argument 48)
|
||||
OUT_CH_D = [12, 13, None, 18, 19, 20, 21, None] # preset D (..._dec0_diamond_wallaby_kernel): bytes 2 and 7 are zero
|
||||
TANH_CH = [14, 15, 16, 17] # surface 144 (rgba8 snorm)
|
||||
|
||||
|
||||
def fma16(a, b, c):
|
||||
return H(np.float64(a) * np.float64(b) + np.float64(c))
|
||||
|
||||
|
||||
def tanh_nv(t):
|
||||
"""NVIDIA's tanh of the surface-144 channels: an odd polynomial below 0.6, 1 - 2 / (e^2x + 1) above, f32"""
|
||||
t = F(t)
|
||||
m = abs(t)
|
||||
big = F(1) if m >= F(9.0109138) else F(np.float64(F(1) / F(np.exp2(F(m * F(2.8853900432586670))) + F(1))) * -2 + 1)
|
||||
s = F(t * t)
|
||||
p = F(0.01573968306183815)
|
||||
for c in (-0.05230396240949631, 0.13315297663211823, -0.33332768082618713, 0.0):
|
||||
p = F(np.float64(p) * np.float64(s) + np.float64(F(c)))
|
||||
small = F(np.float64(p) * np.float64(t) + np.float64(t))
|
||||
return np.copysign(big, t) if m >= F(0.6) else small
|
||||
|
||||
|
||||
def bilinear88(t88, xi, yi):
|
||||
"""texture 88 (half resolution, linear) at the full-resolution texel centre (xi, yi): weights 1/4 and 3/4,
|
||||
clamped at the edge; f32 sum rounded to binary16 (NVIDIA's texture unit rounds a few of these differently)"""
|
||||
def axis(i):
|
||||
k = i // 2
|
||||
return ((k - 1, 0.25), (k, 0.75)) if i % 2 == 0 else ((k, 0.75), (k + 1, 0.25))
|
||||
h, w = t88.shape[:2]
|
||||
v = np.zeros(4)
|
||||
for (yy, wy) in axis(yi):
|
||||
for (xx, wx) in axis(xi):
|
||||
v += wx * wy * t88[min(max(yy, 0), h - 1), min(max(xx, 0), w - 1)].astype(np.float64)
|
||||
return v.astype(H)
|
||||
|
||||
|
||||
def outputs(acc, t88, t96, t104, x0, y0, ex, ey):
|
||||
"""per window token: the 8 output codes, surface 144 (4 floats before the snorm store), surface 112 (3 f16)"""
|
||||
codes, s144, s112 = [], [], []
|
||||
for t in range(64):
|
||||
x, y = x0 + t % 8, y0 + t // 8
|
||||
a = acc[t].astype(H)
|
||||
codes.append(q8(a)[OUT_CH])
|
||||
s144.append([tanh_nv(a[c]) for c in TANH_CH])
|
||||
lg = a[:9]
|
||||
m01 = np.maximum(np.maximum(np.maximum(lg[0:2], lg[2:4]), lg[4:6]), lg[6:8])
|
||||
m = max(max(m01[0], m01[1]), lg[8])
|
||||
e = [np.exp2(F(H(lg[i] - m)) * F(1.4426950216293335)) for i in range(9)]
|
||||
pair = [F(e[2 * k] + e[2 * k + 1]) for k in range(4)]
|
||||
sm = F(pair[0] + F(0))
|
||||
for k in (1, 2, 3):
|
||||
sm = F(sm + pair[k])
|
||||
sm = F(sm + e[8])
|
||||
r = H(F(1) / sm)
|
||||
wg = [H(H(e[i]) * r) for i in range(9)]
|
||||
|
||||
def cl(v, n):
|
||||
return 1 if v < 0 else (n - 2 if v >= n else v)
|
||||
xs, ys = [cl(x - 1, ex), cl(x, ex), cl(x + 1, ex)], [cl(y - 1, ey), cl(y, ey), cl(y + 1, ey)]
|
||||
f = np.zeros(3, H)
|
||||
k = 0
|
||||
for yy in ys:
|
||||
for xx in xs:
|
||||
T = bilinear88(t88, xx, yy)
|
||||
for c in range(3):
|
||||
f[c] = fma16(T[c], wg[k], f[c])
|
||||
k += 1
|
||||
g = H(F(1) / (np.exp2(F(a[9]) * F(-1.4426950216293335)) + F(1)))
|
||||
om = H(H(1) - g)
|
||||
o = np.zeros(3, H)
|
||||
for c in range(3):
|
||||
if 0 <= x < ex and 0 <= y < ey:
|
||||
o[c] = fma16(t96[y, x, c], om, H(f[c] * g))
|
||||
if not np.isnan(t104[y, x, c]):
|
||||
o[c] = H(o[c] + t104[y, x, c])
|
||||
s112.append(o)
|
||||
return np.array(codes), np.array(s144), np.array(s112)
|
||||
|
||||
|
||||
def extract(dump, weights, out):
|
||||
z = np.load(dump)
|
||||
W = np.frombuffer(weights, np.uint8)
|
||||
|
||||
def B(p):
|
||||
return z[f'p{p}_w0_B']
|
||||
|
||||
def C(p):
|
||||
return f16(z[f'p{p}_w0_C']).astype(H)
|
||||
w = {'we': B(1), 'be': C(1)[0], 'gain0': W[8448:8448 + 64].view(H), 'table0': W[39232:39232 + 64].view(H),
|
||||
'gain1': W[39296:39296 + 64].view(H), 'table1': W[41472:41472 + 64].view(H)}
|
||||
for h, base in ((0, 2), (1, 7)):
|
||||
w[f'wk{h}'], w[f'wv{h}'] = B(base), B(base + 1)
|
||||
w[f'bias{h}'] = C(base + 2)
|
||||
w[f'wo{h}'] = B(base + 4)
|
||||
for b in range(4):
|
||||
w[f'w1_{b}'], w[f'b1_{b}'] = B(12 + 2 * b), C(12 + 2 * b)[0]
|
||||
w[f'w2_{b}'] = B(13 + 2 * b)
|
||||
np.savez_compressed(out, **w)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('weights')
|
||||
ap.add_argument('--extract', metavar='DUMP')
|
||||
ap.add_argument('--dump', help='phase dump of --blocks[0] to verify phase by phase')
|
||||
ap.add_argument('--blocks', nargs='*', default=['5,5'])
|
||||
o = ap.parse_args()
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(o.capture))
|
||||
l = c.find('swin_dec0', o.frame)
|
||||
if o.extract:
|
||||
extract(o.extract, l.weights, o.weights)
|
||||
return
|
||||
ex, ey = (int(v) for v in np.frombuffer(l.args[:8], np.uint32))
|
||||
sx, sy = (int(v) for v in np.frombuffer(l.args[80:84], np.uint16))
|
||||
ptr = {p.offset: p.delta for p in l.pointers}
|
||||
pre = np.frombuffer(l.pre(l.arena_alloc), np.uint8)
|
||||
lo = pre[ptr[8]:ptr[8] + 2 * (ex // 2) * (ey // 2) * 32].reshape(2, ey // 2, ex // 2, 32)
|
||||
skip = pre[ptr[24]:ptr[24] + ex * ey * 32].reshape(ey, ex, 32)
|
||||
w = dict(np.load(o.weights))
|
||||
img = {i.offset: i for i in l.images}
|
||||
|
||||
def surf(off, dt=np.float16):
|
||||
i = img[off]
|
||||
return np.frombuffer(i.path.read_bytes(), dt).reshape(i.height, i.width, i.channels)
|
||||
t88, t96, t104, r112, r144 = surf(88), surf(96), surf(104), surf(112), surf(144, np.int8)
|
||||
post = np.frombuffer(l.post_buffer(c, l.arena_alloc), np.uint8)
|
||||
rout = post[ptr[48]:ptr[48] + ex * ey * 8].reshape(ey, ex, 8)
|
||||
for blk in o.blocks:
|
||||
bx, by = (int(v) for v in blk.split(','))
|
||||
d = {}
|
||||
x0, y0 = 8 * bx - sx, 8 * by - sy
|
||||
acc = window(w, lo, skip, x0, y0, ex, ey, d)
|
||||
codes, s144, s112 = outputs(acc, t88, t96, t104, x0, y0, ex, ey)
|
||||
if 'diamond' in l.kernel:
|
||||
codes = np.array([[0 if ch is None else q8(acc[t].astype(H))[ch] for ch in OUT_CH_D] for t in range(64)])
|
||||
n = ok_c = ok_s = ok_t = 0
|
||||
for t in range(64):
|
||||
x, y = x0 + t % 8, y0 + t // 8
|
||||
if not (0 <= x < ex and 0 <= y < r144.shape[0]):
|
||||
continue
|
||||
n += 1
|
||||
ok_c += np.array_equal(codes[t], rout[y, x])
|
||||
ok_t += np.array_equal(np.clip(np.round(np.array(s144[t], np.float64) * 127), -127, 127).astype(np.int8), r144[y, x])
|
||||
ok_s += np.array_equal(s112[t], r112[y, x, :3])
|
||||
print(f'block {bx},{by}: output codes {ok_c}/{n}, surface 144 {ok_t}/{n}, surface 112 {ok_s}/{n}')
|
||||
if o.dump:
|
||||
z = np.load(o.dump)
|
||||
for k in sorted(d, key=lambda s: (int(s[1:].split('_')[0]), s)):
|
||||
ref = z[f'{k.split("_")[0]}_w0_{k.split("_")[1]}']
|
||||
ours = np.asarray(d[k])
|
||||
ours = ours.view(np.uint16) if ours.dtype == H else ours
|
||||
n = min(len(ref), len(ours))
|
||||
print(f'{k}: rows equal {int((ref[:n] == ours[:n]).all(1).sum())}/{n}')
|
||||
o.dump = None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,295 @@
|
||||
#version 460
|
||||
// Native decoder 1 of the DLSS-D (preset E) denoiser, register-resident: cuda_dldn_engine_swin_dec1_kernel.
|
||||
// engine/rr/dec1_ref.py's arithmetic (bit-exact to NVIDIA except surface 112's bilinear taps, see dec0_m.comp);
|
||||
// binding 1 holds engine/rr/dec1_pack.py's packing. The expand and prologue are dec0_m.comp's (64 channels from dec2's
|
||||
// 96), the block is enc1_m.comp's (whose header explains the layouts), the outputs are the tokens (argument 48) and
|
||||
// dec0_m.comp's filter on the 64 -> 16 head.
|
||||
#extension GL_KHR_cooperative_matrix : require
|
||||
#extension GL_KHR_memory_scope_semantics : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_float_e4m3 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 128) in;
|
||||
#include "dec1_m_w.h"
|
||||
#include "rr_exact.glsl"
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint p[]; } param;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16U { uint16_t w[]; } w16u;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
layout(set = 0, binding = 3) uniform sampler2D T88;
|
||||
layout(set = 0, binding = 4) uniform sampler2D T96;
|
||||
layout(set = 0, binding = 5) uniform sampler2D T104;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S112;
|
||||
|
||||
#define SKIP 0
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
#define LDA(dst, base, k, mt, kt) coopMatLoad((dst), w8.w, (base) + 256u * ((mt) * ((k) / 16u) + (kt)), 16u, RM)
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
#define RND(m) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (m)[i_] = rnd16((m)[i_]);
|
||||
#define Q8(dst, src) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (dst)[i_] = q8s((src)[i_]);
|
||||
#define K32(acc, a0, b0, a1, b1) { (acc) = coopMatMulAdd((a1), (b1), coopMatMulAdd((a0), (b0), (acc))); RND(acc) }
|
||||
const uint HEAD = W_WK1 - W_WK0, BRANCH = W_W1_1 - W_W1_0;
|
||||
|
||||
// shared memory, aliased by phase: expand [LOW | EX], attention [KS | VS], outputs [TAP | OUTF]
|
||||
shared uint POOL[48 * 24 + 48 * 64];
|
||||
#define LOW POOL // 48 low-resolution tokens (6 x 6, padded): 96 FP8 channels, arena order (24 words)
|
||||
#define EX_AT (48u * 24u) // their expansion: 4 sub-positions x 64 FP8 channels in slot order (64 words)
|
||||
#define KS POOL // the current head's K: token slot x 32 FP8 channels
|
||||
#define VS_AT 768u // the current head's V^T: 32 channels x 96 token slots
|
||||
#define RR_KS POOL
|
||||
#define RR_KS0 0u
|
||||
#define RR_VS POOL
|
||||
#define RR_VS0 VS_AT
|
||||
shared uint XT[96 * 16]; // the normalised key tokens: slot-major, 64 FP8 channels in slot order
|
||||
shared float16_t TAP[100 * 4]; // texture 88 bilinear at the window's 10 x 10 filter tap positions
|
||||
shared float16_t OUTF[64 * 16]; // the head per window token (raster order)
|
||||
|
||||
ivec2 keyXY(uint t, int x0, int y0)
|
||||
{
|
||||
if (t < 64u) return ivec2(x0 + int(t & 7u), y0 + int(t >> 3u));
|
||||
const int i = int(t & 7u), side = int((t - 64u) >> 3u);
|
||||
return ivec2(side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8), side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i));
|
||||
}
|
||||
int clampTap(int v, int n) { return v < 0 ? 1 : (v >= n ? n - 2 : v); }
|
||||
uint pack4(floate4m3_t a, floate4m3_t b, floate4m3_t c, floate4m3_t d)
|
||||
{
|
||||
return code(a) | (code(b) << 8u) | (code(c) << 16u) | (code(d) << 24u);
|
||||
}
|
||||
float16_t e4(uint w, uint b) { return float16_t(uintBitsToFloate4m3EXT(uint8_t((w >> (8u * b)) & 0xffu))); }
|
||||
|
||||
void main()
|
||||
{
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const uint hl = lane >> 4u, col = lane & 15u;
|
||||
RR_ZERO16 = uint16_t(gl_NumWorkGroups.z - 1u);
|
||||
const int ex = int(param.p[0]), ey = int(param.p[1]), hx = ex / 2, hy = ey / 2;
|
||||
const uint loOff = param.p[2], skOff = param.p[6], outOff = param.p[12], shift = param.p[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const int X0 = (x0 >> 1) - 1, Y0 = (y0 >> 1) - 1;
|
||||
const uint lplane = uint(hx * hy) * 32u, plane = uint(ex * ey) * 32u;
|
||||
|
||||
// ---------------------------------------------------------------- expand: wave w computes sub-position w
|
||||
for (uint i = tid; i < 48u * 24u; i += 128u) {
|
||||
const uint r = i / 24u, wd = i % 24u;
|
||||
uint u = 0u;
|
||||
if (r < 36u) {
|
||||
const int X = mirror(X0 + int(r % 6u), hx), Y = mirror(Y0 + int(r / 6u), hy);
|
||||
u = arena.w[(loOff + (wd >> 3u) * lplane + uint(Y * hx + X) * 32u) / 4u + (wd & 7u)];
|
||||
}
|
||||
LOW[i] = u;
|
||||
}
|
||||
barrier();
|
||||
[[unroll]] for (uint n = 0u; n < 3u; ++n) {
|
||||
B8 lb[6];
|
||||
[[unroll]] for (uint kt = 0u; kt < 6u; ++kt) coopMatLoad(lb[kt], POOL, n * 384u + 4u * kt, 24u, CM);
|
||||
[[unroll]] for (uint mm = 0u; mm < 4u; ++mm) {
|
||||
const uint m = 4u * wv + mm;
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BE / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
[[unroll]] for (uint kt = 0u; kt < 6u; kt += 2u) {
|
||||
A8 w0, w1;
|
||||
LDA(w0, W_WE, 96u, m, kt); LDA(w1, W_WE, 96u, m, kt + 1u);
|
||||
K32(a, w0, lb[kt], w1, lb[kt + 1u])
|
||||
}
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
coopMatStore(q, POOL, EX_AT + n * 1024u + 4u * m, 64u, CM);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- prologue: one key token per thread
|
||||
if (tid < 96u) {
|
||||
const ivec2 k = keyXY(inter(tid), x0, y0);
|
||||
const uint r = uint(((k.y >> 1) - Y0) * 6 + ((k.x >> 1) - X0)), s = uint(2 * (k.y & 1) + (k.x & 1));
|
||||
const int xx = mirror(k.x, ex), yy = mirror(k.y, ey);
|
||||
const uint sb = (skOff + uint(yy * ex + xx) * 32u) / 4u;
|
||||
float16_t v[64];
|
||||
[[unroll]] for (uint wd = 0u; wd < 16u; ++wd) {
|
||||
const uint e = POOL[EX_AT + r * 64u + 16u * s + wd], sk = arena.w[sb + (wd >> 3u) * (plane >> 2u) + (wd & 7u)];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) { precise float16_t x = e4(e, b) + e4(sk, b); v[4u * wd + b] = x; }
|
||||
}
|
||||
// enc1's input norm: per word t (and t + 4) the f16x2 pair sums of both planes, then lanes ^1, ^2, the halves
|
||||
precise f16vec2 h[4];
|
||||
[[unroll]] for (uint q = 0u; q < 4u; ++q) {
|
||||
precise f16vec2 p[4];
|
||||
[[unroll]] for (uint kk = 0u; kk < 4u; ++kk) {
|
||||
const uint o = 32u * (kk & 1u) + 4u * (q + 4u * (kk >> 1u));
|
||||
precise f16vec2 a = f16vec2(v[o], v[o + 1u]) * f16vec2(v[o], v[o + 1u]);
|
||||
precise f16vec2 c = f16vec2(v[o + 2u], v[o + 3u]) * f16vec2(v[o + 2u], v[o + 3u]);
|
||||
p[kk] = a + c;
|
||||
}
|
||||
precise f16vec2 s0 = p[0] + p[1], s1 = p[2] + p[3];
|
||||
h[q] = s0 + s1;
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t sm = hs.x + hs.y;
|
||||
precise float16_t se = sm + float16_t(0.0001220703125);
|
||||
const float16_t rr = rsq16(se);
|
||||
[[unroll]] for (uint wd = 0u; wd < 16u; ++wd) {
|
||||
floate4m3_t c[4];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) {
|
||||
const uint j = 4u * wd + b;
|
||||
precise float16_t g = rr * w16.w[W_GAIN0 / 2u + inter(j)];
|
||||
precise float16_t n = v[j] * g;
|
||||
c[b] = q8h(n);
|
||||
}
|
||||
XT[tid * 16u + wd] = pack4(c[0], c[1], c[2], c[3]);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- the residual: expanded + skip + table 7
|
||||
ACC R[4];
|
||||
{
|
||||
const uint tq = inter(16u * wv + col);
|
||||
const int qx = x0 + int(tq & 7u), qy = y0 + int(tq >> 3u);
|
||||
const uint r = uint(((qy >> 1) - Y0) * 6 + ((qx >> 1) - X0)), s = uint(2 * (qy & 1) + (qx & 1));
|
||||
const uint sb = (skOff + uint(mirror(qy, ey) * ex + mirror(qx, ex)) * 32u) / 4u;
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
const uint ea = EX_AT + r * 64u + 16u * s + 4u * m + 2u * hl;
|
||||
const uint sa = sb + (m >> 1u) * (plane >> 2u) + 4u * (m & 1u) + 2u * hl;
|
||||
const uvec2 e = uvec2(POOL[ea], POOL[ea + 1u]), sk = uvec2(arena.w[sa], arena.w[sa + 1u]);
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
precise float16_t x = e4(e[i >> 2], uint(i) & 3u) + e4(sk[i >> 2], uint(i) & 3u);
|
||||
precise float16_t c = x + w16.w[W_TABLE7 / 2u + 16u * m + 8u * hl + uint(i)];
|
||||
R[m][i] = float(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
barrier(); // EX is read; the attention reuses its memory
|
||||
|
||||
// ---------------------------------------------------------------- attention, two heads
|
||||
[[unroll]] for (uint hd = 0u; hd < ((SKIP & 64) != 0 ? 0u : 2u); ++hd) {
|
||||
// this head's Q/K and V, shared: 12 jobs (token tile n, channel tile c), three per wave. K = (QK^T)^T rows
|
||||
// to KS (token slot x 32 channels, also Q^T read column-major), V^T to VS (32 channels x 96 token slots)
|
||||
if (hd == 1u) barrier(); // every wave is done with head 0's KS / VS
|
||||
for (uint j = 0u; j < ((SKIP & 128) != 0 ? 0u : 3u); ++j) {
|
||||
const uint job = wv + 4u * j, n = job >> 1u, c = job & 1u;
|
||||
A8 wk[4];
|
||||
B8 wvb[4], xb[4];
|
||||
A8 xa[4];
|
||||
[[unroll]] for (uint kt = 0u; kt < 4u; ++kt) {
|
||||
coopMatLoad(xb[kt], XT, n * 256u + 4u * kt, 16u, CM);
|
||||
coopMatLoad(xa[kt], XT, n * 256u + 4u * kt, 16u, RM);
|
||||
LDA(wk[kt], W_WK0 + HEAD * hd, 64u, c, kt);
|
||||
LDB(wvb[kt], W_WV0 + HEAD * hd, 32u, kt, c);
|
||||
}
|
||||
ACC a = ACC(0.0);
|
||||
K32(a, wk[0], xb[0], wk[1], xb[1])
|
||||
K32(a, wk[2], xb[2], wk[3], xb[3])
|
||||
A8 ka;
|
||||
Q8(ka, a)
|
||||
coopMatStore(ka, KS, n * 128u + 4u * c, 8u, RM);
|
||||
ACC v = ACC(0.0);
|
||||
K32(v, xa[0], wvb[0], xa[1], wvb[1])
|
||||
K32(v, xa[2], wvb[2], xa[3], wvb[3])
|
||||
A8 va;
|
||||
Q8(va, v)
|
||||
coopMatStore(va, POOL, VS_AT + c * 384u + 4u * n, 24u, RM);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_attn.glsl"
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WO0 + HEAD * hd, 32u, m, 0u); LDA(a1, W_WO0 + HEAD * hd, 32u, m, 1u);
|
||||
K32(R[m], a0, Ob[0], a1, Ob[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- MLP
|
||||
B8 Nb[4];
|
||||
{
|
||||
float16_t rv[4][8];
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) [[unroll]] for (int i = 0; i < 8; ++i) rv[m][i] = float16_t(R[m][i]);
|
||||
// slot 16m + 8hl + i is NVIDIA's channel 8j + 2t + c with j = 4(m >> 1) + (m & 1) + 2((i >> 1) & 1),
|
||||
// t = 2hl + (i >> 2), c = i & 1 (enc1_ref.nv_rowsum_sq's tree)
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t a = rv[0][i0] * rv[0][i0] + rv[0][i1] * rv[0][i1];
|
||||
precise float16_t b = rv[1][i0] * rv[1][i0] + rv[1][i1] * rv[1][i1];
|
||||
precise float16_t cc = rv[2][i0] * rv[2][i0] + rv[2][i1] * rv[2][i1];
|
||||
precise float16_t d = rv[3][i0] * rv[3][i0] + rv[3][i1] * rv[3][i1];
|
||||
precise float16_t hv = (a + cc) + (b + d);
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const uint k = 16u * m + 8u * hl + uint(i);
|
||||
precise float16_t g = r * w16.w[W_GAIN14 / 2u + k];
|
||||
precise float16_t n = rv[m][i] * g;
|
||||
Nb[m][i] = q8h(n);
|
||||
precise float16_t a = rv[m][i] + w16.w[W_TABLE16 / 2u + k];
|
||||
R[m][i] = float(a);
|
||||
}
|
||||
}
|
||||
for (uint br = 0u; br < ((SKIP & 4) != 0 ? 1u : 8u); ++br) {
|
||||
const uint w1 = W_W1_0 + BRANCH * br, b1 = W_B1_0 + BRANCH * br, w2 = W_W2_0 + BRANCH * br;
|
||||
B8 Hb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC h;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) h[i] = float(w16.w[b1 / 2u + 16u * c + 8u * hl + uint(i)]);
|
||||
A8 a0, a1, a2, a3;
|
||||
LDA(a0, w1, 64u, c, 0u); LDA(a1, w1, 64u, c, 1u); LDA(a2, w1, 64u, c, 2u); LDA(a3, w1, 64u, c, 3u);
|
||||
K32(h, a0, Nb[0], a1, Nb[1])
|
||||
K32(h, a2, Nb[2], a3, Nb[3])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) Hb[c][i] = q8h(act(float16_t(h[i])));
|
||||
}
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w2, 32u, m, 0u); LDA(a1, w2, 32u, m, 1u);
|
||||
K32(R[m], a0, Hb[0], a1, Hb[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- outputs
|
||||
{
|
||||
const uint t = inter(16u * wv + col);
|
||||
const int tx = x0 + int(t & 7u), ty = y0 + int(t >> 3u);
|
||||
const bool inside = tx >= 0 && ty >= 0 && tx < ex && ty < ey;
|
||||
B8 Tb[4];
|
||||
const uint o = (outOff + uint(ty * ex + tx) * 32u) >> 2u;
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
Q8(Tb[m], R[m])
|
||||
const uint at = 4u * (m & 1u) + 2u * hl;
|
||||
if (inside) {
|
||||
arena.w[o + (m >> 1u) * (plane >> 2u) + at] = pack4(Tb[m][0], Tb[m][1], Tb[m][2], Tb[m][3]);
|
||||
arena.w[o + (m >> 1u) * (plane >> 2u) + at + 1u] = pack4(Tb[m][4], Tb[m][5], Tb[m][6], Tb[m][7]);
|
||||
}
|
||||
}
|
||||
// the 64 -> 16 head, per token to OUTF
|
||||
ACC hv;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) hv[i] = float(w16.w[W_BH / 2u + 8u * hl + uint(i)]);
|
||||
A8 a0, a1, a2, a3;
|
||||
LDA(a0, W_WH, 64u, 0u, 0u); LDA(a1, W_WH, 64u, 0u, 1u); LDA(a2, W_WH, 64u, 0u, 2u); LDA(a3, W_WH, 64u, 0u, 3u);
|
||||
K32(hv, a0, Tb[0], a1, Tb[1])
|
||||
K32(hv, a2, Tb[2], a3, Tb[3])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) OUTF[t * 16u + 8u * hl + uint(i)] = float16_t(hv[i]);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_decfilter.glsl"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack decoder 1's extracted weights (engine/rr/dec1_ref.py --extract) for engine/rr/dec1_m.comp.
|
||||
|
||||
usage: dec1_pack.py WEIGHTS.npz OUT.bin OUT.h
|
||||
|
||||
enc1_pack.py's layout (transposed products, columns in the consumer's k order) plus the expand (dec0_pack.py): 256
|
||||
columns, four sub-positions of 64 channels each permuted to slot order.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from enc1_ref import inter # noqa: E402
|
||||
from enc1_pack import a_tiles, b_tiles, cubic_table # noqa: E402
|
||||
|
||||
I32, I64, I96 = inter(32), inter(64), inter(96)
|
||||
I256 = [64 * (j // 64) + I64[j % 64] for j in range(256)]
|
||||
|
||||
|
||||
def main():
|
||||
src, out_bin, out_h = sys.argv[1:4]
|
||||
w = dict(np.load(src))
|
||||
e = {'we': ('A', w['we'][:, I256]), 'be': ('H', w['be'][I256])}
|
||||
for h in range(2):
|
||||
e[f'wk{h}'] = ('A', np.vstack([w[f'wk{h}a'], w[f'wk{h}b']])[:, I32])
|
||||
e[f'wv{h}'] = ('B', np.vstack([w[f'wv{h}a'], w[f'wv{h}b']])[:, I32])
|
||||
e[f'wo{h}'] = ('A', w[f'wo{h}'][:, I64])
|
||||
e[f'bias{h}'] = ('H', w[f'bias{h}'][np.ix_(I64, I96)].T)
|
||||
for b in range(8):
|
||||
e[f'w1_{b}'] = ('A', w[f'w1_{b}'][:, I32])
|
||||
e[f'b1_{b}'] = ('H', w[f'b1_{b}'][I32])
|
||||
e[f'w2_{b}'] = ('A', w[f'w2_{b}'][:, I64])
|
||||
e['wh'], e['bh'] = ('A', w['wh']), ('H', w['bh'])
|
||||
e['gain0'] = ('H', w['gain0'])
|
||||
for k in ('table7', 'gain14', 'table16'):
|
||||
e[k] = ('H', w[k][I64])
|
||||
e['cubic'] = ('U', cubic_table())
|
||||
blob, defines = bytearray(), []
|
||||
for name, (kind, a) in e.items():
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if kind == 'A':
|
||||
blob += a_tiles(a).tobytes()
|
||||
elif kind == 'B':
|
||||
blob += b_tiles(a).tobytes()
|
||||
elif kind == 'U':
|
||||
blob += np.ascontiguousarray(a, np.uint16).tobytes()
|
||||
else:
|
||||
blob += np.ascontiguousarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {off}u // {kind} {a.shape}')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write('// generated by engine/rr/dec1_pack.py\n' + '\n'.join(defines) + '\n')
|
||||
print(f'{len(blob)} bytes, {len(e)} entries')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact numpy reference of the denoiser's decoder 1 (cuda_dldn_engine_swin_dec1_kernel), one 8x8 window at a time.
|
||||
Derived like engine/rr/enc1_ref.py, phase by phase against ZLUDA replays (engine/rr/phase_dump.py); NVIDIA runs it with
|
||||
two warps, each one head and half of the expand's columns.
|
||||
|
||||
expand the 6 x 6 level-2 tokens under the window and its border (dec2's 96 channels, arena order) -> 256 =
|
||||
4 sub-positions x 64 channels (+ bias), FP8; sub-position 2 (y & 1) + (x & 1) (dec0_ref)
|
||||
prologue key token = expanded + skip token (enc1's 64-channel output at argument 24), f16; enc1's input norm (two
|
||||
planes) times the gain at W+25088; the residual is the token + the table at W+62080
|
||||
block enc1's: two 32-channel heads, eight MLP branches 64 -> 32 -> 64 (gain W+62208, table W+66496)
|
||||
outputs the tokens (argument 48); the 64 -> 16 head and dec0's filter (bilinear texture 88 from level 2, gate,
|
||||
textures 96 / 104) to surface 112
|
||||
|
||||
usage: dec1_ref.py CAPTURE FRAME WEIGHTS.npz [--blocks X,Y ...] (verifies against the capture)
|
||||
dec1_ref.py CAPTURE FRAME OUT.npz --extract DUMP.npz (weights from a phase dump)
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import e4m3, f16, q8 # noqa: E402
|
||||
from enc1_ref import inter, mm, softmax_cubic, act, mirror, add, input_norm, nv_rowsum_sq, H, F # noqa: E402
|
||||
from dec0_ref import keys, fma16, bilinear88 # noqa: E402
|
||||
|
||||
I32, I64, I96 = inter(32), inter(64), inter(96)
|
||||
UN64 = [I64.index(i) for i in range(64)]
|
||||
GAIN0, TABLE7, GAIN14, TABLE16 = 25088, 62080, 62208, 66496
|
||||
|
||||
|
||||
def window(w, lo, skip, x0, y0, ex, ey):
|
||||
"""lo: dec2's output [3][ey/2][ex/2][32] codes; skip: enc1's [2][ey][ex][32]. Returns (tokens 64 x 64 codes in arena
|
||||
order, head 64 x 16 f16)"""
|
||||
hx, hy = ex // 2, ey // 2
|
||||
X0, Y0 = x0 // 2 - 1, y0 // 2 - 1
|
||||
low = np.zeros((48, 96), np.uint8)
|
||||
for r in range(36):
|
||||
X, Y = mirror(X0 + r % 6, hx), mirror(Y0 + r // 6, hy)
|
||||
low[r] = np.concatenate([lo[p, Y, X] for p in range(3)])
|
||||
Ex = q8(mm(low, w['we'], np.repeat(w['be'][None, :], 48, 0)))
|
||||
xs, ns = [], []
|
||||
for (x, y) in keys(x0, y0):
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
r = (y // 2 - Y0) * 6 + (x // 2 - X0)
|
||||
s = 2 * (y & 1) + (x & 1)
|
||||
sk = np.concatenate([skip[0, yy, xx], skip[1, yy, xx]])[UN64]
|
||||
xn = (e4m3(Ex[r, 64 * s:64 * s + 64]).astype(H) + e4m3(sk).astype(H)).astype(H)
|
||||
xs.append(xn)
|
||||
xa = xn[I64]
|
||||
n, _ = input_norm([xa[:32], xa[32:]], w['gain0'])
|
||||
ns.append(q8(n)[I64])
|
||||
X = np.array(ns)
|
||||
res = np.array(xs[:64])
|
||||
R = None
|
||||
for h in range(2):
|
||||
qk = mm(X[:, 32:], w[f'wk{h}b'], mm(X[:, :32], w[f'wk{h}a'], np.zeros((96, 32))))
|
||||
v = mm(X[:, 32:], w[f'wv{h}b'], mm(X[:, :32], w[f'wv{h}a'], np.zeros((96, 32))))
|
||||
Q = q8(qk)[:, I32]
|
||||
S = mm(Q[:64], Q.T, w[f'bias{h}'])
|
||||
P = softmax_cubic(S)
|
||||
O = mm(q8(P)[:, I96], q8(v)[I96], np.zeros((64, 32)))
|
||||
C = add(res, w['table7'][None, :]) if h == 0 else R
|
||||
R = mm(q8(O)[:, I32], w[f'wo{h}'], C)
|
||||
N = q8((R * (nv_rowsum_sq(R)[:, None] * w['gain14'][None, :]).astype(H)).astype(H))[:, I64]
|
||||
acc = add(R, w['table16'][None, :])
|
||||
for b in range(8):
|
||||
hid = act(mm(N, w[f'w1_{b}'], np.repeat(w[f'b1_{b}'][None, :], 64, 0)))
|
||||
acc = mm(q8(hid)[:, I32], w[f'w2_{b}'], acc)
|
||||
tokens = q8(acc)[:, I64]
|
||||
head = mm(tokens, w['wh'], np.repeat(w['bh'][None, :], 64, 0))
|
||||
return tokens, head
|
||||
|
||||
|
||||
def filter112(head, t88, t96, t104, x0, y0, ex, ey):
|
||||
"""dec0_ref.outputs' surface 112 on the head's channels 0-8 (logits) and 9 (gate)"""
|
||||
out = []
|
||||
for t in range(64):
|
||||
x, y = x0 + t % 8, y0 + t // 8
|
||||
a = head[t].astype(H)
|
||||
lg = a[:9]
|
||||
m01 = np.maximum(np.maximum(np.maximum(lg[0:2], lg[2:4]), lg[4:6]), lg[6:8])
|
||||
m = max(max(m01[0], m01[1]), lg[8])
|
||||
e = [np.exp2(F(H(lg[i] - m)) * F(1.4426950216293335)) for i in range(9)]
|
||||
pair = [F(e[2 * k] + e[2 * k + 1]) for k in range(4)]
|
||||
sm = F(pair[0] + F(0))
|
||||
for k in (1, 2, 3):
|
||||
sm = F(sm + pair[k])
|
||||
sm = F(sm + e[8])
|
||||
r = H(F(1) / sm)
|
||||
wg = [H(H(e[i]) * r) for i in range(9)]
|
||||
|
||||
def cl(v, n):
|
||||
return 1 if v < 0 else (n - 2 if v >= n else v)
|
||||
f = np.zeros(3, H)
|
||||
k = 0
|
||||
for yy in (cl(y - 1, ey), cl(y, ey), cl(y + 1, ey)):
|
||||
for xx in (cl(x - 1, ex), cl(x, ex), cl(x + 1, ex)):
|
||||
T = bilinear88(t88, xx, yy)
|
||||
for c in range(3):
|
||||
f[c] = fma16(T[c], wg[k], f[c])
|
||||
k += 1
|
||||
g = H(F(1) / (np.exp2(F(a[9]) * F(-1.4426950216293335)) + F(1)))
|
||||
om = H(H(1) - g)
|
||||
o = np.zeros(3, H)
|
||||
if 0 <= x < ex and 0 <= y < ey:
|
||||
for c in range(3):
|
||||
o[c] = fma16(t96[y, x, c], om, H(f[c] * g))
|
||||
if not np.isnan(t104[y, x, c]):
|
||||
o[c] = H(o[c] + t104[y, x, c])
|
||||
out.append(o)
|
||||
return np.array(out)
|
||||
|
||||
|
||||
def extract(dump, weights, out):
|
||||
z = np.load(dump)
|
||||
W = np.frombuffer(weights, np.uint8)
|
||||
|
||||
def B(p, wv=0):
|
||||
return z[f'p{p}_w{wv}_B']
|
||||
|
||||
def C(p, wv=0):
|
||||
return f16(z[f'p{p}_w{wv}_C']).astype(H)
|
||||
|
||||
def table(off, n=64):
|
||||
return W[off:off + 2 * n].view(H)
|
||||
w = {'we': np.hstack([B(1, 0), B(1, 1)]), 'be': np.concatenate([C(1, 0)[0], C(1, 1)[0]]),
|
||||
'gain0': table(GAIN0), 'table7': table(TABLE7), 'gain14': table(GAIN14), 'table16': table(TABLE16)}
|
||||
for h in range(2):
|
||||
w[f'wk{h}a'], w[f'wv{h}a'], w[f'wk{h}b'], w[f'wv{h}b'] = B(2, h), B(3, h), B(4, h), B(5, h)
|
||||
w[f'bias{h}'] = C(6, h)
|
||||
w[f'wo{h}'] = B(8 + h)
|
||||
for b in range(8):
|
||||
w[f'w1_{b}'], w[f'b1_{b}'] = B(10 + 2 * b), C(10 + 2 * b)[0]
|
||||
w[f'w2_{b}'] = B(11 + 2 * b)
|
||||
w['wh'], w['bh'] = B(26), C(26)[0]
|
||||
np.savez_compressed(out, **w)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('weights')
|
||||
ap.add_argument('--extract', metavar='DUMP')
|
||||
ap.add_argument('--blocks', nargs='*', default=['5,5'])
|
||||
o = ap.parse_args()
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(o.capture))
|
||||
l = c.find('swin_dec1', o.frame)
|
||||
if o.extract:
|
||||
extract(o.extract, l.weights, o.weights)
|
||||
return
|
||||
ex, ey = (int(v) for v in np.frombuffer(l.args[:8], np.uint32))
|
||||
sx, sy = (int(v) for v in np.frombuffer(l.args[80:84], np.uint16))
|
||||
ptr = {p.offset: p.delta for p in l.pointers}
|
||||
pre = np.frombuffer(l.pre(l.arena_alloc), np.uint8)
|
||||
post = np.frombuffer(l.post_buffer(c, l.arena_alloc), np.uint8)
|
||||
lo = pre[ptr[8]:ptr[8] + 3 * (ex // 2) * (ey // 2) * 32].reshape(3, ey // 2, ex // 2, 32)
|
||||
skip = pre[ptr[24]:ptr[24] + 2 * ex * ey * 32].reshape(2, ey, ex, 32)
|
||||
outp = post[ptr[48]:ptr[48] + 2 * ex * ey * 32].reshape(2, ey, ex, 32)
|
||||
img = {i.offset: i for i in l.images}
|
||||
|
||||
def surf(off):
|
||||
i = img[off]
|
||||
return np.frombuffer(i.path.read_bytes(), np.float16).reshape(i.height, i.width, 4)
|
||||
t88, t96, t104, r112 = surf(88), surf(96), surf(104), surf(112)
|
||||
w = dict(np.load(o.weights))
|
||||
for blk in o.blocks:
|
||||
bx, by = (int(v) for v in blk.split(','))
|
||||
x0, y0 = 8 * bx - sx, 8 * by - sy
|
||||
tokens, head = window(w, lo, skip, x0, y0, ex, ey)
|
||||
s112 = filter112(head, t88, t96, t104, x0, y0, ex, ey)
|
||||
inside = [t for t in range(64) if 0 <= x0 + t % 8 < ex and 0 <= y0 + t // 8 < ey]
|
||||
tk = sum(np.array_equal(tokens[t], np.concatenate([outp[0, y0 + t // 8, x0 + t % 8], outp[1, y0 + t // 8, x0 + t % 8]]))
|
||||
for t in inside)
|
||||
sf = sum(np.array_equal(s112[t], r112[y0 + t // 8, x0 + t % 8, :3]) for t in inside)
|
||||
print(f'block {bx},{by}: tokens {tk}/{len(inside)}, surface 112 {sf}/{len(inside)}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read the Ray Reconstruction network's weights straight from the user's nvngx_dlssd.dll.
|
||||
|
||||
usage: dll_weights.py DLL --list
|
||||
dll_weights.py DLL --kernels
|
||||
dll_weights.py DLL OUT_DIR (--model N | --capture CAPTURE_DIR) [--check EXTRACTED_DIR] [--any-kernels]
|
||||
|
||||
The DLL holds every model's tensors as plain binary16 arrays in natural ([out][in]) order, one stage after the other
|
||||
(encoder 0 ... decoder 0), with nothing naming them. They are found by structure alone: every attention head has a
|
||||
relative position bias table of 100 x 100 values (the 10 x 10 window with its border) whose entries depend only on the
|
||||
offset between the two positions, and a model is eleven consecutive stages whose head counts are 2 2 4 4 8 8 8 4 4 2 2.
|
||||
The other tensors of a stage sit at fixed distances from its tables (LAYOUT below).
|
||||
|
||||
OUT_DIR/STAGE.npz gets the tensors in the form engine/rr/{swin,enc0,dec0}_ref.py --extract produced from a ZLUDA
|
||||
phase dump, which is what the packers (swin_pack.py, enc0_pack.py, dec0_pack.py) take:
|
||||
FP8 matrices e4m3 codes, [k][n] with k in NVIDIA's fragment order (natural input channel inter(K)[k])
|
||||
gains times sqrt(32), as NGX uploads them
|
||||
bias [64 queries][96 keys] in the kernels' key order, times 2 sqrt(2), as NGX uploads it
|
||||
--model picks the N-th model of the DLL (--list); --capture picks the one a bridge capture of the denoiser ran, by
|
||||
looking up each stage's vectors in the weight image NGX uploaded (and fails if the capture is of another DLL).
|
||||
--check compares every tensor with an extracted one (EXTRACTED_DIR/STAGE/weights.npz).
|
||||
|
||||
The native shaders were written against the kernels of one DLL version. Before anything is written the DLL's network
|
||||
kernels (the PTX modules, engine/rr/native_kernels.txt: their SHA-256) are compared with those; another version is
|
||||
refused unless --any-kernels is given. --kernels prints the digests of a DLL.
|
||||
|
||||
Everything read and written is NVIDIA's: it stays on the user's machine and is never packaged.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import e4m3, q8 # noqa: E402
|
||||
from enc1_ref import inter # noqa: E402
|
||||
from dec0_ref import keys # noqa: E402
|
||||
|
||||
H = np.float16
|
||||
# stage: (kind, C, C_LO (decoders: the channels one level down) or C_M (encoders: the merged channels), heads)
|
||||
STAGES = {
|
||||
'enc0': ('enc0', 32, 64, 2), 'enc1': ('enc', 64, 96, 2), 'enc2': ('enc', 96, 128, 4), 'enc3': ('enc', 128, 160, 4),
|
||||
'enc4': ('enc', 160, 160, 8), 'enc5': ('enc', 160, 0, 8), 'dec4': ('dec', 160, 160, 8), 'dec3': ('dec', 128, 160, 4),
|
||||
'dec2': ('dec', 96, 128, 4), 'dec1': ('dec', 64, 96, 2), 'dec0': ('dec0', 32, 64, 2),
|
||||
}
|
||||
ORDER = list(STAGES)
|
||||
HEADS = [STAGES[s][3] for s in ORDER]
|
||||
TABLE = 100 * 100 # one head's relative position bias, in values
|
||||
# the 96 keys of a window (its 64 tokens, then the border without the corners) in the 10 x 10 table
|
||||
POS = [(y + 1) * 10 + x + 1 for (x, y) in keys(0, 0)]
|
||||
BIAS_SCALE = np.float32(2.0 * np.sqrt(2.0))
|
||||
|
||||
|
||||
def bias_tables(u):
|
||||
"""offsets (in values) of every relative position bias table in the binary16 view u of the DLL"""
|
||||
# entry (i, j) equals entry (i + 1, j + 1) and (i + 10, j + 10): one step right / down for both positions
|
||||
e1 = u[:-1111] == u[101:-1010]
|
||||
e10 = u[:-1111] == u[1010:-101]
|
||||
e11 = u[:-1111] == u[1111:]
|
||||
first = e1[:-18] & e1[1:-17] & e1[2:-16] & e10[:-18] & e10[1:-17] & e11[:-18] & e11[1:-17] & (u[:len(e1) - 18] != u[1:len(e1) - 17])
|
||||
found = []
|
||||
dy, dx = np.divmod(np.arange(100), 10)
|
||||
rel = ((dy[None, :] - dy[:, None] + 9) * 19 + dx[None, :] - dx[:, None] + 9).reshape(-1)
|
||||
for p in np.nonzero(first)[0]:
|
||||
if p + TABLE > len(u) or (found and p < found[-1] + TABLE):
|
||||
continue
|
||||
t = u[p:p + TABLE]
|
||||
r = np.zeros(361, np.uint16)
|
||||
r[rel] = t
|
||||
if np.array_equal(r[rel], t) and len(np.unique(t)) > 64:
|
||||
found.append(int(p))
|
||||
return found
|
||||
|
||||
|
||||
def models(dll):
|
||||
"""[[bias offset (bytes) of each of the eleven stages] per model], in file order"""
|
||||
u = np.frombuffer(dll[:len(dll) // 2 * 2], np.uint16)
|
||||
tables = bias_tables(u)
|
||||
runs = [] # (first table, count) of consecutive tables
|
||||
for p in tables:
|
||||
if runs and p == runs[-1][0] + TABLE * runs[-1][1]:
|
||||
runs[-1][1] += 1
|
||||
else:
|
||||
runs.append([p, 1])
|
||||
out, i = [], 0
|
||||
while i + len(HEADS) <= len(runs):
|
||||
if [n for _, n in runs[i:i + len(HEADS)]] == HEADS:
|
||||
out.append([2 * p for p, _ in runs[i:i + len(HEADS)]])
|
||||
i += len(HEADS)
|
||||
else:
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def kernel_digests(dll):
|
||||
"""{(kernel, SHA-256 of its PTX module)} of the DLL's network kernels"""
|
||||
import hashlib
|
||||
import re
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'kernels', 'tools'))
|
||||
from extract_dlss_ptx import fatbins, ptx_entries
|
||||
out = set()
|
||||
for _, fatbin in fatbins(dll):
|
||||
for text in ptx_entries(fatbin):
|
||||
for name in re.findall(rb'\.entry (cuda_dldn_engine_swin_\w+)\(', text):
|
||||
out.add((name.decode(), hashlib.sha256(text).hexdigest()))
|
||||
return out
|
||||
|
||||
|
||||
def check_kernels(dll, path):
|
||||
"""the kernels the native shaders were written against must all be in the DLL, and no other version of them"""
|
||||
known = {tuple(l.split()) for l in open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'native_kernels.txt'))
|
||||
if l.strip() and not l.startswith('#')}
|
||||
have = kernel_digests(dll)
|
||||
missing, other = sorted(known - have), sorted(have - known)
|
||||
if missing or other:
|
||||
raise SystemExit(f'{path}: its network kernels are not the ones the native shaders were written against '
|
||||
f'({len(missing)} missing, {len(other)} unknown; e.g. {(missing + other)[0][0]}). The native '
|
||||
'stages are unverified for this DLL version: use the translated model (engine/compile_rr.py), or '
|
||||
'--any-kernels to build anyway.')
|
||||
|
||||
|
||||
def noise_table(dll):
|
||||
"""preset E's encoder 0 dithers its features with 8 x 16 x 16 16-bit values, the first 4096 bytes of the weight
|
||||
image NGX uploads. In the DLL they are the array of 2048 values whose low five bits are all clear or all set."""
|
||||
u = np.frombuffer(dll[:len(dll) // 2 * 2], np.uint16)
|
||||
low = u & 31
|
||||
ok = np.concatenate([[False], (low == 0) | (low == 31), [False]])
|
||||
edge = np.nonzero(ok[1:] != ok[:-1])[0]
|
||||
found = []
|
||||
for a, b in zip(edge[::2], edge[1::2]):
|
||||
if b - a >= 2048:
|
||||
t = u[b - 2048:b]
|
||||
if len(np.unique(t)) > 1024 and (t & 31 == 31).sum() > 256:
|
||||
found.append(t.tobytes())
|
||||
return found[0] if len(set(found)) == 1 else None # some DLLs hold the same table twice
|
||||
|
||||
|
||||
class Reader:
|
||||
def __init__(self, dll, at):
|
||||
self.dll, self.at = dll, at
|
||||
|
||||
def back(self, *shape):
|
||||
self.at -= 2 * int(np.prod(shape))
|
||||
return np.frombuffer(self.dll, H, int(np.prod(shape)), self.at).reshape(shape)
|
||||
|
||||
def take(self, *shape):
|
||||
a = np.frombuffer(self.dll, H, int(np.prod(shape)), self.at).reshape(shape)
|
||||
self.at += 2 * int(np.prod(shape))
|
||||
return a
|
||||
|
||||
|
||||
def fp8(nat):
|
||||
"""a natural [out][in] binary16 matrix -> NVIDIA's B operand: codes [k][n], k in fragment order"""
|
||||
k = nat.shape[1]
|
||||
return np.ascontiguousarray(q8(nat)[:, inter(k) if k % 32 == 0 else np.arange(k)].T)
|
||||
|
||||
|
||||
def gain(g, c):
|
||||
return (g.astype(np.float32) * np.float32(np.sqrt(np.float32(c)))).astype(H)
|
||||
|
||||
|
||||
def read_stage(dll, stage, bias_at):
|
||||
"""the stage's tensors (see the module text); returns (dict, first byte, one past the last byte)"""
|
||||
kind, C, CX, NH = STAGES[stage]
|
||||
w = {}
|
||||
r = Reader(dll, bias_at)
|
||||
table_a = r.back(C)
|
||||
wo = r.back(C, 32 * NH)
|
||||
wv = r.back(32 * NH, C)
|
||||
wk = r.back(32 * NH, C)
|
||||
gain_a = r.back(C)
|
||||
if kind == 'enc0': # the embedding of the 32 input features
|
||||
w['b2'] = r.back(32).copy()
|
||||
w['w2'] = fp8(r.back(32, 32))
|
||||
w['b1'] = r.back(32).copy()
|
||||
# [32][inputs]: preset E has 32 input features, preset D 30 (NGX pads the image's matrix with zeros). The
|
||||
# matrix is the widest one that starts with a weight and holds FP8 values only
|
||||
end = r.at
|
||||
for inputs in range(32, 15, -1):
|
||||
m = np.frombuffer(dll, H, 32 * inputs, end - 64 * inputs).reshape(32, inputs)
|
||||
if m[0, :4].any() and np.array_equal(e4m3(q8(m)).astype(H).view(np.uint16), m.view(np.uint16)):
|
||||
break
|
||||
else:
|
||||
raise SystemExit('enc0: no input embedding before its bias')
|
||||
r.at = end - 64 * inputs
|
||||
w['w1'] = fp8(np.hstack([m, np.zeros((32, 32 - inputs), H)]))
|
||||
first = r.at
|
||||
r = Reader(dll, bias_at)
|
||||
bias = r.take(NH, 100, 100)
|
||||
gain_b = r.take(C)
|
||||
w1 = r.take(4 * C, C)
|
||||
b1 = r.take(4 * C)
|
||||
w2 = r.take(C, 4 * C)
|
||||
table_b = r.take(C)
|
||||
for h in range(NH):
|
||||
w[f'wk{h}'] = fp8(wk[32 * h:32 * h + 32])
|
||||
w[f'wv{h}'] = fp8(wv[32 * h:32 * h + 32])
|
||||
w[f'wo{h}'] = fp8(wo[:, 32 * h:32 * h + 32])
|
||||
w[f'bias{h}'] = (bias[h][np.ix_(POS[:64], POS)].astype(np.float32) * BIAS_SCALE).astype(H)
|
||||
for b in range(C // 8):
|
||||
w[f'w1_{b}'] = fp8(w1[32 * b:32 * b + 32])
|
||||
w[f'b1_{b}'] = b1[32 * b:32 * b + 32].copy()
|
||||
w[f'w2_{b}'] = fp8(w2[:, 32 * b:32 * b + 32])
|
||||
names = ('gain0', 'table0', 'gain1', 'table1') if kind in ('enc0', 'dec0') else ('gain0', 'table7', 'gain14', 'table16')
|
||||
for name, a in zip(names, (gain(gain_a, C), table_a.copy(), gain(gain_b, C), table_b.copy())):
|
||||
w[name] = a
|
||||
if kind in ('enc', 'enc0') and CX: # the 2x2 merge
|
||||
w['wm'] = fp8(r.take(CX, 4 * C))
|
||||
w['bm'] = r.take(CX).copy()
|
||||
if kind in ('dec', 'dec0'): # the expand of the level below
|
||||
w['we'] = fp8(r.take(4 * C, CX))
|
||||
w['be'] = r.take(4 * C).copy()
|
||||
if kind != 'dec0': # the 16-channel head of the stage's filter
|
||||
w['wh'] = fp8(r.take(16, C))
|
||||
w['bh'] = r.take(16).copy()
|
||||
return w, first, r.at
|
||||
|
||||
|
||||
def read_model(dll, bias_offsets):
|
||||
"""{stage: tensors}; checks that the stages lie back to back in the DLL, which the layout implies"""
|
||||
out, end = {}, None
|
||||
for stage, at in zip(ORDER, bias_offsets):
|
||||
out[stage], first, last = read_stage(dll, stage, at)
|
||||
if end is not None and first != end:
|
||||
raise SystemExit(f'{stage}: starts at {first}, the stage before ends at {end}: not the layout this reader knows')
|
||||
end = last
|
||||
return out
|
||||
|
||||
|
||||
def in_capture(capture, model):
|
||||
"""whether the capture's launches uploaded this model: every stage's vectors must be in its weight image"""
|
||||
from rr_capture import Capture
|
||||
cap = Capture(os.path.expanduser(capture))
|
||||
for stage, w in model.items():
|
||||
image = next(l for l in cap.launches if f'swin_{stage}' in l.kernel).weights
|
||||
for name, a in w.items():
|
||||
if a.dtype == H and a.ndim == 1 and bytes(image).find(a.tobytes()) < 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('dll')
|
||||
ap.add_argument('output', nargs='?')
|
||||
ap.add_argument('--list', action='store_true')
|
||||
ap.add_argument('--model', type=int)
|
||||
ap.add_argument('--capture')
|
||||
ap.add_argument('--check', metavar='EXTRACTED_DIR')
|
||||
ap.add_argument('--kernels', action='store_true')
|
||||
ap.add_argument('--any-kernels', action='store_true')
|
||||
o = ap.parse_args()
|
||||
dll = Path(o.dll).read_bytes()
|
||||
if o.kernels:
|
||||
for name, digest in sorted(kernel_digests(dll)):
|
||||
print(name, digest)
|
||||
return
|
||||
if o.output and not o.any_kernels:
|
||||
check_kernels(dll, o.dll)
|
||||
found = models(dll)
|
||||
if not found:
|
||||
raise SystemExit(f'{o.dll}: no Ray Reconstruction model with the layout this reader knows')
|
||||
if o.list or not o.output:
|
||||
for i, m in enumerate(found):
|
||||
print(f'model {i}: bias tables at ' + ' '.join(map(str, m)))
|
||||
return
|
||||
if o.model is not None:
|
||||
model = read_model(dll, found[o.model])
|
||||
index = o.model
|
||||
else:
|
||||
if not o.capture:
|
||||
raise SystemExit('--model N or --capture CAPTURE_DIR')
|
||||
index, model = next(((i, m) for i, m in ((i, read_model(dll, f)) for i, f in enumerate(found))
|
||||
if in_capture(o.capture, m)), (None, None))
|
||||
if model is None:
|
||||
raise SystemExit(f'none of the {len(found)} models in {o.dll} is the one the capture ran: the capture was taken '
|
||||
'with another nvngx_dlssd.dll')
|
||||
out = Path(o.output)
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
for stage, w in model.items():
|
||||
np.savez_compressed(out / f'{stage}.npz', **w)
|
||||
noise = noise_table(dll)
|
||||
if noise is not None:
|
||||
if o.capture: # preset E's image starts with it; preset D's has none
|
||||
from rr_capture import Capture
|
||||
image = bytes(next(l for l in Capture(os.path.expanduser(o.capture)).launches if 'swin_enc0' in l.kernel).weights)
|
||||
if 'diamond' not in next(l for l in Capture(os.path.expanduser(o.capture)).launches if 'swin_enc0' in l.kernel).kernel \
|
||||
and image[:4096] != noise:
|
||||
raise SystemExit('the noise table found in the DLL is not the one the capture uploaded')
|
||||
(out / 'noise.bin').write_bytes(noise)
|
||||
print(f'{o.dll}: model {index} of {len(found)}, {len(model)} stages in {out}')
|
||||
if o.check:
|
||||
bad = 0
|
||||
for stage, w in model.items():
|
||||
ref = dict(np.load(Path(o.check) / stage / 'weights.npz'))
|
||||
for name in sorted(set(ref) | set(w)):
|
||||
a, b = w.get(name), ref.get(name)
|
||||
if a is None or b is None:
|
||||
print(f'{stage}.{name}: only in the {"extraction" if a is None else "DLL reading"}')
|
||||
bad += 1
|
||||
continue
|
||||
if name in ('we', 'be'): # a multi-warp extraction repeats the columns
|
||||
b = b[..., :a.shape[-1]]
|
||||
same = a.shape == b.shape and np.array_equal(a.view(np.uint8) if a.dtype == np.uint8 else a.astype(H).view(np.uint16),
|
||||
b.view(np.uint8) if b.dtype == np.uint8 else b.astype(H).view(np.uint16))
|
||||
if not same:
|
||||
n = int((a != b).sum()) if a.shape == b.shape else -1
|
||||
print(f'{stage}.{name}: differs ({a.shape} vs {b.shape}, {n} values)')
|
||||
bad += 1
|
||||
print('check: every tensor equals the extraction' if not bad else f'check: {bad} tensors differ')
|
||||
sys.exit(1 if bad else 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,53 @@
|
||||
// Native part of engine/rr/enc0_m.comp (generated by engine/rr/gen_enc0_m.py): declarations, after the translated
|
||||
// header. Binding 1 holds NGX's weight image followed by engine/rr/enc0_pack.py's blob (offsets in enc0_m_w.h already
|
||||
// include its base); binding 2 is the arena.
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_KHR_shader_subgroup_vote : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
#include "enc0_m_w.h"
|
||||
#include "rr_exact.glsl"
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16U { uint16_t w[]; } w16u;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8U { uint8_t w[]; } w8u;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
#define LDA(dst, base, k, mt, kt) coopMatLoad((dst), w8.w, (base) + 256u * ((mt) * ((k) / 16u) + (kt)), 16u, RM)
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
#define RND(m) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (m)[i_] = rnd16((m)[i_]);
|
||||
#define Q8(dst, src) Q8PAIRS(dst, src)
|
||||
#define K32(acc, a0, b0, a1, b1) { (acc) = coopMatMulAdd((a1), (b1), coopMatMulAdd((a0), (b0), (acc))); RND(acc) }
|
||||
const uint HEAD = W_WK1 - W_WK0, BRANCH = W_W1_1 - W_W1_0;
|
||||
shared uint KVS[2 * 768]; // the current head's K, then V^T; before the attention the key pixels' features
|
||||
#define XF KVS // the key pixels' 32 FP8 features (plane 0 | plane 1), slot-major
|
||||
shared uint XT[96 * 8]; // the normalised key tokens
|
||||
#define RR_KS KVS
|
||||
#define RR_KS0 0u
|
||||
#define RR_VS KVS
|
||||
#define RR_VS0 768u
|
||||
#define MG XT // the merge rows (16 x 128 FP8) reuse XT
|
||||
shared float16_t O112[64 * 4]; // surface 112 per token, for surface 128's 2x2 sums
|
||||
ivec2 keyXY(uint t, int x0, int y0)
|
||||
{
|
||||
if (t < 64u) return ivec2(x0 + int(t & 7u), y0 + int(t >> 3u));
|
||||
const int i = int(t & 7u), side = int((t - 64u) >> 3u);
|
||||
return ivec2(side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8), side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i));
|
||||
}
|
||||
uint pack4(floate4m3_t a, floate4m3_t b, floate4m3_t c, floate4m3_t d)
|
||||
{
|
||||
return code(a) | (code(b) << 8u) | (code(c) << 16u) | (code(d) << 24u);
|
||||
}
|
||||
uvec2 mergeXY(uint r) { return r < 8u ? uvec2(r >> 1u, r & 1u) : uvec2((r - 8u) >> 1u, 2u + (r & 1u)); }
|
||||
// the translated prologue's shared buffer sh: input_noise (10 x 10 pixels, 4 f16) at byte 0, input_tensor (two planes
|
||||
// of 10 x 10 pixels x 16 FP8 features) at byte 800
|
||||
float16_t noiseAt(uint gx, uint gy, uint c)
|
||||
{
|
||||
const uint b = 80u * gy + 8u * gx + 2u * c;
|
||||
return hb((sh[b >> 2] >> (8u * (b & 2u))) & 0xffffu);
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
// ======================================================================== native part (engine/rr/enc0_ref.py)
|
||||
memoryBarrierShared(); barrier(); // wave 0's translated prologue filled sh (features, noise) and wrote 304 / 1016
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const uint hl = lane >> 4u, col = lane & 15u;
|
||||
RR_ZERO16 = uint16_t(gl_NumWorkGroups.z - 1u);
|
||||
const int ex = int(param.w[0]), ey = int(param.w[1]);
|
||||
const uint outOff = param.w[12], mrgOff = param.w[14], shift = param.w[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const uint lplane = uint((ex / 2) * (ey / 2)) * 32u;
|
||||
// the key slots' features, from the 10 x 10 grid (origin x0 - 1, y0 - 1)
|
||||
if (tid < 96u) {
|
||||
const ivec2 k = keyXY(inter(tid), x0, y0);
|
||||
const uint g = uint((k.y - y0 + 1) * 10 + (k.x - x0 + 1));
|
||||
[[unroll]] for (uint wd = 0u; wd < 4u; ++wd) {
|
||||
XF[tid * 8u + wd] = sh[(800u + g * 16u) / 4u + wd];
|
||||
XF[tid * 8u + 4u + wd] = sh[(2400u + g * 16u) / 4u + wd];
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
// the embedding x = W2 q8(relu(W1 f + b1)) + b2 for token tiles n = wv (this wave's queries: x is also their
|
||||
// residual) and n = wv + 4 (the border keys, waves 0 and 1), then each key's norm in registers (dec0_ref.norm_plane
|
||||
// on the accumulator layout, as dec0_m.comp's MLP norm) to XT
|
||||
ACC R[2];
|
||||
for (uint n = wv; n < 6u; n += 4u) {
|
||||
B8 fb0, fb1, hb_[2];
|
||||
coopMatLoad(fb0, XF, n * 128u, 8u, CM);
|
||||
coopMatLoad(fb1, XF, n * 128u + 4u, 8u, CM);
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_B1 / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_W1, 32u, m, 0u); LDA(a1, W_W1, 32u, m, 1u);
|
||||
K32(a, a0, fb0, a1, fb1)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) hb_[m][i] = q8s(max(a[i], 0.0));
|
||||
}
|
||||
float16_t xv[2][8];
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_B2 / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_W2, 32u, m, 0u); LDA(a1, W_W2, 32u, m, 1u);
|
||||
K32(a, a0, hb_[0], a1, hb_[1])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) xv[m][i] = float16_t(a[i]);
|
||||
if (n == wv) { // the residual: x + table 0
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
precise float16_t c = xv[m][i] + w16.w[W_TABLE0 / 2u + 16u * m + 8u * hl + uint(i)];
|
||||
R[m][i] = float(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t p0 = xv[0][i0] * xv[0][i0] + xv[0][i1] * xv[0][i1];
|
||||
precise float16_t p1 = xv[1][i0] * xv[1][i0] + xv[1][i1] * xv[1][i1];
|
||||
precise float16_t hv = p0 + p1;
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
B8 xq;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
precise float16_t g = r * w16.w[W_GAIN0 / 2u + inter(16u * m + 8u * hl + uint(i))];
|
||||
precise float16_t nn = xv[m][i] * g;
|
||||
xq[i] = q8h(nn);
|
||||
}
|
||||
coopMatStore(xq, XT, n * 128u + 4u * m, 8u, CM);
|
||||
}
|
||||
}
|
||||
barrier(); // XT complete; XF is read (the attention reuses its memory)
|
||||
// two heads
|
||||
[[unroll]] for (uint hd = 0u; hd < 2u; ++hd) {
|
||||
if (hd == 1u) barrier();
|
||||
for (uint j = 0u; j < 3u; ++j) {
|
||||
const uint job = wv + 4u * j, n = job >> 1u, c = job & 1u;
|
||||
A8 wk[2], xa[2];
|
||||
B8 wvb[2], xb[2];
|
||||
[[unroll]] for (uint kt = 0u; kt < 2u; ++kt) {
|
||||
coopMatLoad(xb[kt], XT, n * 128u + 4u * kt, 8u, CM);
|
||||
coopMatLoad(xa[kt], XT, n * 128u + 4u * kt, 8u, RM);
|
||||
LDA(wk[kt], W_WK0 + HEAD * hd, 32u, c, kt);
|
||||
LDB(wvb[kt], W_WV0 + HEAD * hd, 32u, kt, c);
|
||||
}
|
||||
ACC a = ACC(0.0);
|
||||
K32(a, wk[0], xb[0], wk[1], xb[1])
|
||||
A8 ka;
|
||||
Q8(ka, a)
|
||||
coopMatStore(ka, KVS, n * 128u + 4u * c, 8u, RM);
|
||||
ACC vv = ACC(0.0);
|
||||
K32(vv, xa[0], wvb[0], xa[1], wvb[1])
|
||||
A8 va;
|
||||
Q8(va, vv)
|
||||
coopMatStore(va, KVS, 768u + c * 384u + 4u * n, 24u, RM);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_attn.glsl"
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WO0 + HEAD * hd, 32u, m, 0u); LDA(a1, W_WO0 + HEAD * hd, 32u, m, 1u);
|
||||
K32(R[m], a0, Ob[0], a1, Ob[1])
|
||||
}
|
||||
}
|
||||
// MLP (dec0_m.comp's)
|
||||
B8 Nb[2];
|
||||
{
|
||||
float16_t rv[2][8];
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) [[unroll]] for (int i = 0; i < 8; ++i) rv[m][i] = float16_t(R[m][i]);
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t p0 = rv[0][i0] * rv[0][i0] + rv[0][i1] * rv[0][i1];
|
||||
precise float16_t p1 = rv[1][i0] * rv[1][i0] + rv[1][i1] * rv[1][i1];
|
||||
precise float16_t hv = p0 + p1;
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const uint k = 16u * m + 8u * hl + uint(i);
|
||||
precise float16_t g = r * w16.w[W_GAIN1 / 2u + k];
|
||||
precise float16_t nn = rv[m][i] * g;
|
||||
Nb[m][i] = q8h(nn);
|
||||
precise float16_t a = rv[m][i] + w16.w[W_TABLE1 / 2u + k];
|
||||
R[m][i] = float(a);
|
||||
}
|
||||
}
|
||||
for (uint br = 0u; br < 4u; ++br) {
|
||||
const uint w1 = W_W1_0 + BRANCH * br, b1 = W_B1_0 + BRANCH * br, w2 = W_W2_0 + BRANCH * br;
|
||||
B8 Hb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC h;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) h[i] = float(w16.w[b1 / 2u + 16u * c + 8u * hl + uint(i)]);
|
||||
A8 a0, a1;
|
||||
LDA(a0, w1, 32u, c, 0u); LDA(a1, w1, 32u, c, 1u);
|
||||
K32(h, a0, Nb[0], a1, Nb[1])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) Hb[c][i] = ACTQ(float16_t(h[i]));
|
||||
}
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w2, 32u, m, 0u); LDA(a1, w2, 32u, m, 1u);
|
||||
K32(R[m], a0, Hb[0], a1, Hb[1])
|
||||
}
|
||||
}
|
||||
// outputs: the tokens (one plane, dec0's skip) and the merge rows
|
||||
const uint t = inter(16u * wv + col);
|
||||
const int tx = x0 + int(t & 7u), ty = y0 + int(t >> 3u);
|
||||
const bool inside = tx >= 0 && ty >= 0 && tx < ex && ty < ey;
|
||||
B8 Tb[2];
|
||||
{
|
||||
const uint ty2 = t >> 3u, tx2 = t & 7u;
|
||||
const uint mr = (ty2 >> 1u) < 2u ? 2u * (tx2 >> 1u) + (ty2 >> 1u) : 8u + 2u * (tx2 >> 1u) + (ty2 >> 1u) - 2u;
|
||||
const uint blk = 2u * (ty2 & 1u) + (tx2 & 1u);
|
||||
const uint o = (outOff + uint(ty * ex + tx) * 32u) >> 2u;
|
||||
barrier(); // XT is read; the merge rows reuse it
|
||||
[[unroll]] for (uint m = 0u; m < 2u; ++m) {
|
||||
Q8(Tb[m], R[m])
|
||||
const uint q0 = pack4(Tb[m][0], Tb[m][1], Tb[m][2], Tb[m][3]), q1 = pack4(Tb[m][4], Tb[m][5], Tb[m][6], Tb[m][7]);
|
||||
const uint at = 4u * m + 2u * hl;
|
||||
if (inside) { arena.w[o + at] = q0; arena.w[o + at + 1u] = q1; }
|
||||
const uint gm = mr * 32u + blk * 8u + 4u * m + 2u * hl;
|
||||
MG[gm] = q0; MG[gm + 1u] = q1;
|
||||
}
|
||||
}
|
||||
{ // the 32 -> 16 head and encoder 0's filter of the noise grid (two f16 fma chains, taps even / odd)
|
||||
ACC hv;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) hv[i] = float(w16.w[W_BH / 2u + 8u * hl + uint(i)]);
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WH, 32u, 0u, 0u); LDA(a1, W_WH, 32u, 0u, 1u);
|
||||
K32(hv, a0, Tb[0], a1, Tb[1])
|
||||
float16_t l[12];
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) l[i] = float16_t(hv[i]);
|
||||
const uint u0 = subgroupShuffleXor(bh(l[0]) | (bh(l[1]) << 16u), 16u), u1 = subgroupShuffleXor(bh(l[2]) | (bh(l[3]) << 16u), 16u);
|
||||
l[8] = hb(u0 & 0xffffu); l[9] = hb(u0 >> 16u); l[10] = hb(u1 & 0xffffu); l[11] = hb(u1 >> 16u);
|
||||
const float16_t ma = max(max(max(l[0], l[2]), l[4]), l[6]), mb = max(max(max(l[1], l[3]), l[5]), l[7]);
|
||||
const float16_t mx = max(max(ma, mb), l[8]);
|
||||
float e[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) { precise float16_t d = l[i] - mx; e[i] = exp2(float(d) * 1.4426950216293335); }
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t rr = rcp16(s);
|
||||
float16_t wg[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) wg[i] = h16(r16(e[i]) * float(rr));
|
||||
const uint gx = t & 7u, gy = t >> 3u; // the token's 3 x 3 taps in the grid: (gx .. gx + 2, gy .. gy + 2)
|
||||
float16_t o112[3], o120[3];
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
float16_t lo = 0.0hf, hi = 0.0hf;
|
||||
[[unroll]] for (uint k = 0u; k < 8u; k += 2u) {
|
||||
lo = fma16(noiseAt(gx + k % 3u, gy + k / 3u, c), wg[k], lo);
|
||||
hi = fma16(noiseAt(gx + (k + 1u) % 3u, gy + (k + 1u) / 3u, c), wg[k + 1u], hi);
|
||||
}
|
||||
precise float16_t sum = lo + hi;
|
||||
const float16_t f = fma16(noiseAt(gx + 2u, gy + 2u, c), wg[8], sum);
|
||||
precise float16_t a = sig(l[9u + c]) * f;
|
||||
precise float16_t b = f - a;
|
||||
o120[c] = a; o112[c] = b;
|
||||
}
|
||||
if (hl == 0u) {
|
||||
if (inside) {
|
||||
imageStore(S112, ivec2(tx, ty), vec4(o112[0], o112[1], o112[2], 0.0));
|
||||
imageStore(S120, ivec2(tx, ty), vec4(o120[0], o120[1], o120[2], 0.0));
|
||||
}
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) O112[t * 4u + c] = o112[c];
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
{ // the 2x2 merge (128 -> 64): wave w computes channel tile w
|
||||
const uvec2 mxy = mergeXY(col);
|
||||
const int X = x0 / 2 + int(mxy.x), Y = y0 / 2 + int(mxy.y), hx = ex / 2, hy = ey / 2;
|
||||
const uint m = wv;
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BM / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
[[unroll]] for (uint ks = 0u; ks < 4u; ++ks) {
|
||||
B8 g0, g1; A8 w0, w1;
|
||||
coopMatLoad(g0, MG, 8u * ks, 32u, CM);
|
||||
coopMatLoad(g1, MG, 8u * ks + 4u, 32u, CM);
|
||||
LDA(w0, W_WM, 128u, m, 2u * ks); LDA(w1, W_WM, 128u, m, 2u * ks + 1u);
|
||||
K32(a, w0, g0, w1, g1)
|
||||
}
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
if (X >= 0 && Y >= 0 && X < hx && Y < hy) {
|
||||
const uint at = (mrgOff + (m >> 1u) * lplane + uint(Y * hx + X) * 32u) / 4u + 4u * (m & 1u) + 2u * hl;
|
||||
arena.w[at] = pack4(q[0], q[1], q[2], q[3]);
|
||||
arena.w[at + 1u] = pack4(q[4], q[5], q[6], q[7]);
|
||||
}
|
||||
}
|
||||
if (wv == 3u && lane < 16u) { // surface 128: 2x2 sums of 0.25 * surface 112, rightwards first, then downwards
|
||||
const uint mx = lane & 3u, my = lane >> 2u;
|
||||
const int x = x0 + 2 * int(mx), y = y0 + 2 * int(my);
|
||||
if (x >= 0 && y >= 0 && x < ex && y < ey) {
|
||||
const uint t00 = (2u * my) * 8u + 2u * mx;
|
||||
vec4 out_ = vec4(0.0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float16_t a00 = O112[t00 * 4u + c] * float16_t(0.25), a01 = O112[(t00 + 1u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t a10 = O112[(t00 + 8u) * 4u + c] * float16_t(0.25), a11 = O112[(t00 + 9u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t b0 = a00 + a01, b1 = a10 + a11;
|
||||
precise float16_t cc = b0 + b1;
|
||||
out_[c] = float(cc);
|
||||
}
|
||||
imageStore(S128, ivec2(x >> 1, y >> 1), out_);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack encoder 0's extracted weights (engine/rr/enc0_ref.py --extract) for engine/rr/enc0_m.comp's native part.
|
||||
|
||||
usage: enc0_pack.py WEIGHTS.npz OUT.bin OUT.h [BASE]
|
||||
|
||||
dec0_pack.py's layout (C = 32) plus the embedding (W1, W2: 32 -> 32 -> 32, columns in slot order), the 2x2 merge
|
||||
(128 -> 64, columns in arena order) and the 32 -> 16 head. BASE (default 0) is added to every offset in OUT.h: the
|
||||
shader binds NGX's weight image first (its translated feature prologue reads it) and this blob after it.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from enc1_ref import inter # noqa: E402
|
||||
from enc1_pack import a_tiles, b_tiles, cubic_table, act_table # noqa: E402
|
||||
|
||||
I32, I64, I96 = inter(32), inter(64), inter(96)
|
||||
|
||||
|
||||
def main():
|
||||
src, out_bin, out_h = sys.argv[1:4]
|
||||
base = int(sys.argv[4]) if len(sys.argv) > 4 else 0
|
||||
w = dict(np.load(src))
|
||||
e = {'w1': ('A', w['w1'][:, I32]), 'b1': ('H', w['b1'][I32]), 'w2': ('A', w['w2'][:, I32]), 'b2': ('H', w['b2'][I32])}
|
||||
for h in range(2):
|
||||
e[f'wk{h}'] = ('A', w[f'wk{h}'][:, I32])
|
||||
e[f'wv{h}'] = ('B', w[f'wv{h}'][:, I32])
|
||||
e[f'wo{h}'] = ('A', w[f'wo{h}'][:, I32])
|
||||
e[f'bias{h}'] = ('H', w[f'bias{h}'][np.ix_(inter(64), I96)].T)
|
||||
for b in range(4):
|
||||
e[f'w1_{b}'] = ('A', w[f'w1_{b}'][:, I32])
|
||||
e[f'b1_{b}'] = ('H', w[f'b1_{b}'][I32])
|
||||
e[f'w2_{b}'] = ('A', w[f'w2_{b}'][:, I32])
|
||||
e['wm'], e['bm'] = ('A', w['wm'][:, I64]), ('H', w['bm'][I64])
|
||||
e['wh'], e['bh'] = ('A', w['wh']), ('H', w['bh'])
|
||||
e['gain0'] = ('H', w['gain0'])
|
||||
for k in ('table0', 'gain1', 'table1'):
|
||||
e[k] = ('H', w[k][I32])
|
||||
e['cubic'] = ('U', cubic_table())
|
||||
e['act'] = ('Y', act_table())
|
||||
blob, defines = bytearray(), []
|
||||
for name, (kind, a) in e.items():
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if kind == 'A':
|
||||
blob += a_tiles(a).tobytes()
|
||||
elif kind == 'B':
|
||||
blob += b_tiles(a).tobytes()
|
||||
elif kind == 'Y':
|
||||
blob += np.ascontiguousarray(a, np.uint8).tobytes()
|
||||
elif kind == 'U':
|
||||
blob += np.ascontiguousarray(a, np.uint16).tobytes()
|
||||
else:
|
||||
blob += np.ascontiguousarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {base + off}u // {kind} {a.shape}')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write('// generated by engine/rr/enc0_pack.py\n' + '\n'.join(defines) + '\n')
|
||||
print(f'{len(blob)} bytes, {len(e)} entries, base {base}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact numpy reference of the denoiser's encoder 0 (cuda_dldn_engine_swin_enc0_kernel) after its input features,
|
||||
one 8x8 window at a time. The 32 FP8 features of the 96 key pixels (NVIDIA's per-pixel gather from the game's guides
|
||||
and the temporal state, which engine/rr/enc0_m.comp keeps as ptx2rr.py's translation) are an input here.
|
||||
|
||||
embedding F (96 x 32) -> q8(relu(F W1 + b1)) -> W2 + b2: the key tokens x (32 channels, f16)
|
||||
block dec0_ref's: RMS norm (gain W+6272), residual x + table W+37056, two heads, four MLP branches
|
||||
outputs the tokens (argument 48, dec0's skip), the 2x2 merge 128 -> 64 (argument 56, enc1's input) and the
|
||||
32 -> 16 head with enc1's filter to surfaces 112, 120 and 128
|
||||
|
||||
usage: enc0_ref.py CAPTURE FRAME WEIGHTS.npz --dump DUMP.npz [--block X,Y] (verifies the dump's block)
|
||||
enc0_ref.py CAPTURE FRAME OUT.npz --extract DUMP.npz (weights from a phase dump)
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import f16, q8 # noqa: E402
|
||||
from enc1_ref import inter, mm, softmax_cubic, act, add, filter_head, MERGE_ORDER, H # noqa: E402
|
||||
from dec0_ref import norm_plane # noqa: E402
|
||||
|
||||
I32, I64, I96 = inter(32), inter(64), inter(96)
|
||||
GAIN0, TABLE0, GAIN1, TABLE1 = 6272, 37056, None, None
|
||||
|
||||
|
||||
def block(w, feat):
|
||||
"""feat: the 96 key pixels' features (codes, NVIDIA's k order). Returns (tokens 64 x 32 codes in arena order,
|
||||
merged 16 x 64 codes in arena order, head 64 x 16 f16, dbg)"""
|
||||
dbg = {}
|
||||
e1 = mm(feat, w['w1'], np.repeat(w['b1'][None, :], 96, 0))
|
||||
x = mm(q8(np.maximum(e1, H(0)))[:, I32], w['w2'], np.repeat(w['b2'][None, :], 96, 0))
|
||||
dbg['x'] = x
|
||||
X = np.array([norm_plane(x[k], w['gain0']) for k in range(96)])
|
||||
R = None
|
||||
for h in range(2):
|
||||
qk = mm(X, w[f'wk{h}'], np.zeros((96, 32)))
|
||||
v = mm(X, w[f'wv{h}'], np.zeros((96, 32)))
|
||||
Q = q8(qk)[:, I32]
|
||||
P = softmax_cubic(mm(Q[:64], Q.T, w[f'bias{h}']))
|
||||
O = mm(q8(P)[:, I96], q8(v)[I96], np.zeros((64, 32)))
|
||||
C = add(x[:64], w['table0'][None, :]) if h == 0 else R
|
||||
R = mm(q8(O)[:, I32], w[f'wo{h}'], C)
|
||||
N = np.array([norm_plane(R[t], w['gain1']) for t in range(64)])
|
||||
acc = add(R, w['table1'][None, :])
|
||||
for b in range(4):
|
||||
hid = act(mm(N, w[f'w1_{b}'], np.repeat(w[f'b1_{b}'][None, :], 64, 0)))
|
||||
acc = mm(q8(hid)[:, I32], w[f'w2_{b}'], acc)
|
||||
tokens = q8(acc)[:, I32]
|
||||
Mg = np.zeros((16, 128), np.uint8)
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER):
|
||||
for blk, (dx, dy) in enumerate([(0, 0), (1, 0), (0, 1), (1, 1)]):
|
||||
Mg[r, 32 * blk:32 * blk + 32] = tokens[(2 * my + dy) * 8 + 2 * mx + dx]
|
||||
merged = q8(mm(Mg, w['wm'], np.repeat(w['bm'][None, :], 16, 0)))[:, I64]
|
||||
head = mm(tokens, w['wh'], np.repeat(w['bh'][None, :], 64, 0))
|
||||
return tokens, merged, head, dbg
|
||||
|
||||
|
||||
def extract(dump, weights, out):
|
||||
z = np.load(dump)
|
||||
W = np.frombuffer(weights, np.uint8)
|
||||
|
||||
def B(p):
|
||||
return z[f'p{p}_w0_B']
|
||||
|
||||
def C(p):
|
||||
return f16(z[f'p{p}_w0_C']).astype(H)
|
||||
w = {'w1': B(1), 'b1': C(1)[0], 'w2': B(2), 'b2': C(2)[0], 'gain0': W[GAIN0:GAIN0 + 64].view(H),
|
||||
'table0': W[TABLE0:TABLE0 + 64].view(H)}
|
||||
# the input norm's gain and the residual table sit elsewhere in preset D's (smaller) weight image: when the preset
|
||||
# E offsets do not reproduce the dump, find them by matching it, like the MLP's below
|
||||
x = mm(q8(np.maximum(mm(z['p1_w0_A'], w['w1'], np.repeat(w['b1'][None, :], 96, 0)), H(0)))[:, I32], w['w2'],
|
||||
np.repeat(w['b2'][None, :], 96, 0))
|
||||
cands = [W[o:o + 64].view(H) for o in range(0, len(W) - 63, 2)]
|
||||
if not all(np.array_equal(norm_plane(x[k], w['gain0']), z['p3_w0_A'][k]) for k in (0, 9)):
|
||||
w['gain0'] = next(t for t in cands if all(np.array_equal(norm_plane(x[k], t), z['p3_w0_A'][k]) for k in (0, 9)))
|
||||
if not np.array_equal(add(x[:2], w['table0'][None]).view(np.uint16), C(7)[:2].view(np.uint16)):
|
||||
w['table0'] = next(t for t in cands if np.array_equal(add(x[:2], t[None]).view(np.uint16), C(7)[:2].view(np.uint16)))
|
||||
for h, base in ((0, 3), (1, 8)):
|
||||
w[f'wk{h}'], w[f'wv{h}'] = B(base), B(base + 1)
|
||||
w[f'bias{h}'] = C(base + 2)
|
||||
w[f'wo{h}'] = B(base + 4)
|
||||
for b in range(4):
|
||||
w[f'w1_{b}'], w[f'b1_{b}'] = B(13 + 2 * b), C(13 + 2 * b)[0]
|
||||
w[f'w2_{b}'] = B(14 + 2 * b)
|
||||
w['wm'], w['bm'] = B(21), C(21)[0]
|
||||
w['wh'], w['bh'] = B(22), C(22)[0]
|
||||
# the MLP norm's gain and table, found by matching the dump
|
||||
R = f16(z['p12_w0_D']).astype(H)
|
||||
tables = [W[o:o + 64].view(H) for o in range(0, len(W) - 63, 2)]
|
||||
w['gain1'] = next(t for t in tables if all(np.array_equal(norm_plane(R[k], t), z['p13_w0_A'][k]) for k in (0, 9)))
|
||||
w['table1'] = next(t for t in tables if np.array_equal(add(R[:2], t[None]).view(np.uint16), C(14)[:2].view(np.uint16)))
|
||||
np.savez_compressed(out, **w)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('weights')
|
||||
ap.add_argument('--extract', metavar='DUMP')
|
||||
ap.add_argument('--dump')
|
||||
ap.add_argument('--block', default='5,5')
|
||||
o = ap.parse_args()
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(o.capture))
|
||||
l = c.find('swin_enc0', o.frame)
|
||||
if o.extract:
|
||||
extract(o.extract, l.weights, o.weights)
|
||||
return
|
||||
z = np.load(o.dump)
|
||||
w = dict(np.load(o.weights))
|
||||
ex, ey = (int(v) for v in np.frombuffer(l.args[:8], np.uint32))
|
||||
sx, sy = (int(v) for v in np.frombuffer(l.args[80:84], np.uint16))
|
||||
bx, by = (int(v) for v in o.block.split(','))
|
||||
x0, y0 = 8 * bx - sx, 8 * by - sy
|
||||
tokens, merged, head, dbg = block(w, z['p1_w0_A'])
|
||||
for p, name in ((3, 'X'), ):
|
||||
pass
|
||||
ptr = {p.offset: p.delta for p in l.pointers}
|
||||
post = np.frombuffer(l.post_buffer(c, l.arena_alloc), np.uint8)
|
||||
otok = post[ptr[48]:ptr[48] + ex * ey * 32].reshape(ey, ex, 32)
|
||||
omrg = post[ptr[56]:ptr[56] + 2 * (ex // 2) * (ey // 2) * 32].reshape(2, ey // 2, ex // 2, 32)
|
||||
img = {i.offset: i for i in l.images}
|
||||
tex = {k: np.frombuffer(i.path.read_bytes(), np.float16).reshape(i.height, i.width, 4) for k, i in img.items()
|
||||
if k in (112, 120, 160)}
|
||||
inside = [t for t in range(64) if 0 <= x0 + t % 8 < ex and 0 <= y0 + t // 8 < ey]
|
||||
tk = sum(np.array_equal(tokens[t], otok[y0 + t // 8, x0 + t % 8]) for t in inside)
|
||||
mg = sum(np.array_equal(merged[r], np.concatenate([omrg[p, y0 // 2 + my, x0 // 2 + mx] for p in range(2)]))
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER))
|
||||
print(f'block {bx},{by}: tokens {tk}/{len(inside)}, merged {mg}/16')
|
||||
return head, x0, y0, ex, ey
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,357 @@
|
||||
#version 460
|
||||
// Native encoder 1 of the DLSS-D (preset E) denoiser, register-resident: cuda_dldn_engine_swin_enc1_kernel
|
||||
// computed like engine/layer_m.comp. The arithmetic is engine/rr/enc1_ref.py's (bit-exact to NVIDIA); the stage
|
||||
// contract is the translated stage's, except binding 1, which holds engine/rr/enc1_pack.py's packing.
|
||||
//
|
||||
// Every product runs transposed, D^T = W^T X^T: weights are the A operand, activations the B operand, and a result's
|
||||
// rows are channels, its columns tokens. On RADV gfx1201 an accumulator's element i in lane l is (row 8 * (l >> 4)
|
||||
// + i, column l & 15), a B operand's the same and an A operand's the transpose (layer_m.comp): so a result is the
|
||||
// next product's B operand element for element, and a result tile is also the A operand of its own transpose (the
|
||||
// keys of the scores, the V^T of P V). enc1_pack.py orders every weight's columns so that the rows come out in the
|
||||
// k order NVIDIA's next product contracts them in; keys and queries are token slots (slot s = token inter(s)).
|
||||
// Accumulators stay f32 and are rounded to binary16 values in place (r16) after each logical k32 step.
|
||||
//
|
||||
// One workgroup per 8x8-token window, four waves; wave w owns query slots 16w..16w+15 and computes the window's
|
||||
// keys and values itself (no barrier between the prologue and the outputs). Shared memory holds only the
|
||||
// normalised tokens, the raw tokens and the merge rows.
|
||||
#extension GL_KHR_cooperative_matrix : require
|
||||
#extension GL_KHR_memory_scope_semantics : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_float_e4m3 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
#extension GL_KHR_shader_subgroup_vote : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 128) in;
|
||||
#include "enc1_m_w.h"
|
||||
#include "rr_exact.glsl"
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint p[]; } param;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16U { uint16_t w[]; } w16u;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
layout(set = 0, binding = 3) uniform sampler2D T88;
|
||||
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D S112;
|
||||
layout(set = 0, binding = 5, rgba16f) uniform writeonly image2D S120;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S128;
|
||||
|
||||
#ifndef SKIP
|
||||
#define SKIP 0
|
||||
#endif
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
// W^T tile (mt, kt) of a weight with K rows (enc1_pack.a_tiles); W tile (kt, nt) of one with N columns (b_tiles)
|
||||
#define LDA(dst, base, k, mt, kt) coopMatLoad((dst), w8.w, (base) + 256u * ((mt) * ((k) / 16u) + (kt)), 16u, RM)
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
// binary16 rounding in place: a real conversion, kept by OR-ing in a zero the compiler cannot see (RADV folds a
|
||||
// plain float(float16_t(x)) back to x); about 2 instructions per element against r16's 6
|
||||
#define RND(m) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (m)[i_] = rnd16((m)[i_]);
|
||||
#define Q8(dst, src) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (dst)[i_] = q8s((src)[i_]);
|
||||
#define K32(acc, a0, b0, a1, b1) { (acc) = coopMatMulAdd((a1), (b1), coopMatMulAdd((a0), (b0), (acc))); RND(acc) }
|
||||
const uint HEAD = W_WK1 - W_WK0, BRANCH = W_W1_1 - W_W1_0;
|
||||
|
||||
shared uint XT[96 * 16]; // the normalised key tokens: slot-major, 64 FP8 channels in arena (= k) order
|
||||
// the merge rows (16 x 256 FP8, enc1_ref.MERGE_ORDER, 4 tokens of 64 channels) reuse XT, dead after the projections
|
||||
#define MG XT
|
||||
shared float16_t O112[64 * 4]; // surface 112 per token, for surface 128's 2x2 sums
|
||||
shared uint KS[96 * 8]; // the current head's K, token slot x 32 FP8 channels (k order)
|
||||
shared uint VS[32 * 24]; // the current head's V^T, 32 channels x 96 token slots
|
||||
|
||||
uvec2 mergeXY(uint r) { return r < 8u ? uvec2(r >> 1u, r & 1u) : uvec2((r - 8u) >> 1u, 2u + (r & 1u)); }
|
||||
uint pack4(floate4m3_t a, floate4m3_t b, floate4m3_t c, floate4m3_t d)
|
||||
{
|
||||
return code(a) | (code(b) << 8u) | (code(c) << 16u) | (code(d) << 24u);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const uint hl = lane >> 4u, col = lane & 15u;
|
||||
RR_ZERO16 = uint16_t(gl_NumWorkGroups.z - 1u);
|
||||
const int ex = int(param.p[0]), ey = int(param.p[1]);
|
||||
const uint inOff = param.p[2], outOff = param.p[12], mrgOff = param.p[14], shift = param.p[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const uint plane = uint(ex * ey) * 32u;
|
||||
|
||||
// ---------------------------------------------------------------- prologue: one key token per thread
|
||||
if ((SKIP & 16) == 0 && tid < 96u) {
|
||||
const uint t = inter(tid);
|
||||
int x, y;
|
||||
if (t < 64u) {
|
||||
x = x0 + int(t & 7u); y = y0 + int(t >> 3u);
|
||||
} else {
|
||||
const int i = int(t & 7u), side = int((t - 64u) >> 3u);
|
||||
x = side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8);
|
||||
y = side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i);
|
||||
}
|
||||
x = mirror(x, ex); y = mirror(y, ey);
|
||||
const uint base = (inOff + uint(y * ex + x) * 32u) >> 2u;
|
||||
uint raw[16];
|
||||
float16_t v[64];
|
||||
[[unroll]] for (uint wd = 0u; wd < 16u; ++wd) {
|
||||
raw[wd] = arena.w[base + (wd >> 3u) * (plane >> 2u) + (wd & 7u)];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b)
|
||||
v[4u * wd + b] = float16_t(uintBitsToFloate4m3EXT(uint8_t((raw[wd] >> (8u * b)) & 0xffu)));
|
||||
}
|
||||
// NVIDIA's order: per word t (and t + 4) the f16x2 pair sums of both planes, then lanes ^1, ^2, the halves
|
||||
precise f16vec2 h[4];
|
||||
[[unroll]] for (uint q = 0u; q < 4u; ++q) {
|
||||
precise f16vec2 p[4];
|
||||
[[unroll]] for (uint k = 0u; k < 4u; ++k) {
|
||||
const uint o = 32u * (k & 1u) + 4u * (q + 4u * (k >> 1u));
|
||||
precise f16vec2 a = f16vec2(v[o], v[o + 1u]) * f16vec2(v[o], v[o + 1u]);
|
||||
precise f16vec2 c = f16vec2(v[o + 2u], v[o + 3u]) * f16vec2(v[o + 2u], v[o + 3u]);
|
||||
p[k] = a + c;
|
||||
}
|
||||
precise f16vec2 s0 = p[0] + p[1], s1 = p[2] + p[3];
|
||||
h[q] = s0 + s1;
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t s = hs.x + hs.y;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint wd = 0u; wd < 16u; ++wd) {
|
||||
floate4m3_t c[4];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) {
|
||||
const uint j = 4u * wd + b;
|
||||
precise float16_t g = r * w16.w[W_GAIN0 / 2u + inter(j)];
|
||||
precise float16_t n = v[j] * g;
|
||||
c[b] = q8h(n);
|
||||
}
|
||||
XT[tid * 16u + wd] = pack4(c[0], c[1], c[2], c[3]);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- the residual: raw tokens + table 7
|
||||
// (each lane's token, mirrored like the prologue's; its channels 16m + 8hl .. + 7 are 8 bytes of plane m >> 1)
|
||||
ACC R[4];
|
||||
{
|
||||
const uint tq = inter(16u * wv + col);
|
||||
const int qx = mirror(x0 + int(tq & 7u), ex), qy = mirror(y0 + int(tq >> 3u), ey);
|
||||
const uint qb = (inOff + uint(qy * ex + qx) * 32u) >> 2u;
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
const uint at = qb + (m >> 1u) * (plane >> 2u) + 4u * (m & 1u) + 2u * hl;
|
||||
const uvec2 u = uvec2(arena.w[at], arena.w[at + 1u]);
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const float16_t x = float16_t(uintBitsToFloate4m3EXT(uint8_t((u[i >> 2] >> (8u * (uint(i) & 3u))) & 0xffu)));
|
||||
precise float16_t c = x + w16.w[W_TABLE7 / 2u + 16u * m + 8u * hl + uint(i)];
|
||||
R[m][i] = float(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- attention, two heads
|
||||
[[unroll]] for (uint hd = 0u; hd < ((SKIP & 64) != 0 ? 0u : 2u); ++hd) {
|
||||
// this head's Q/K and V, shared: 12 jobs (token tile n, channel tile c), three per wave. K = (QK^T)^T rows
|
||||
// to KS (token slot x 32 channels, also Q^T read column-major), V^T to VS (32 channels x 96 token slots)
|
||||
if (hd == 1u) barrier(); // every wave is done with head 0's KS / VS
|
||||
for (uint j = 0u; j < ((SKIP & 128) != 0 ? 0u : 3u); ++j) {
|
||||
const uint job = wv + 4u * j, n = job >> 1u, c = job & 1u;
|
||||
A8 wk[4];
|
||||
B8 wvb[4], xb[4];
|
||||
A8 xa[4];
|
||||
[[unroll]] for (uint kt = 0u; kt < 4u; ++kt) {
|
||||
coopMatLoad(xb[kt], XT, n * 256u + 4u * kt, 16u, CM);
|
||||
coopMatLoad(xa[kt], XT, n * 256u + 4u * kt, 16u, RM);
|
||||
LDA(wk[kt], W_WK0 + HEAD * hd, 64u, c, kt);
|
||||
LDB(wvb[kt], W_WV0 + HEAD * hd, 32u, kt, c);
|
||||
}
|
||||
ACC a = ACC(0.0);
|
||||
K32(a, wk[0], xb[0], wk[1], xb[1])
|
||||
K32(a, wk[2], xb[2], wk[3], xb[3])
|
||||
A8 ka;
|
||||
Q8(ka, a)
|
||||
coopMatStore(ka, KS, n * 128u + 4u * c, 8u, RM);
|
||||
ACC v = ACC(0.0);
|
||||
K32(v, xa[0], wvb[0], xa[1], wvb[1])
|
||||
K32(v, xa[2], wvb[2], xa[3], wvb[3])
|
||||
A8 va;
|
||||
Q8(va, v)
|
||||
coopMatStore(va, VS, c * 384u + 4u * n, 24u, RM);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_attn.glsl"
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WO0 + HEAD * hd, 32u, m, 0u); LDA(a1, W_WO0 + HEAD * hd, 32u, m, 1u);
|
||||
K32(R[m], a0, Ob[0], a1, Ob[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- MLP
|
||||
B8 Nb[4];
|
||||
{
|
||||
float16_t rv[4][8];
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) [[unroll]] for (int i = 0; i < 8; ++i) rv[m][i] = float16_t(R[m][i]);
|
||||
// slot 16m + 8hl + i is NVIDIA's channel 8j + 2t + c with j = 4(m >> 1) + (m & 1) + 2((i >> 1) & 1),
|
||||
// t = 2hl + (i >> 2), c = i & 1 (enc1_ref.nv_rowsum_sq's tree)
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t a = rv[0][i0] * rv[0][i0] + rv[0][i1] * rv[0][i1];
|
||||
precise float16_t b = rv[1][i0] * rv[1][i0] + rv[1][i1] * rv[1][i1];
|
||||
precise float16_t cc = rv[2][i0] * rv[2][i0] + rv[2][i1] * rv[2][i1];
|
||||
precise float16_t d = rv[3][i0] * rv[3][i0] + rv[3][i1] * rv[3][i1];
|
||||
precise float16_t hv = (a + cc) + (b + d);
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const uint k = 16u * m + 8u * hl + uint(i);
|
||||
precise float16_t g = r * w16.w[W_GAIN14 / 2u + k];
|
||||
precise float16_t n = rv[m][i] * g;
|
||||
Nb[m][i] = q8h(n);
|
||||
precise float16_t a = rv[m][i] + w16.w[W_TABLE16 / 2u + k];
|
||||
R[m][i] = float(a);
|
||||
}
|
||||
}
|
||||
for (uint br = 0u; br < ((SKIP & 4) != 0 ? 1u : 8u); ++br) {
|
||||
const uint w1 = W_W1_0 + BRANCH * br, b1 = W_B1_0 + BRANCH * br, w2 = W_W2_0 + BRANCH * br;
|
||||
B8 Hb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC h;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) h[i] = float(w16.w[b1 / 2u + 16u * c + 8u * hl + uint(i)]);
|
||||
A8 a0, a1, a2, a3;
|
||||
LDA(a0, w1, 64u, c, 0u); LDA(a1, w1, 64u, c, 1u); LDA(a2, w1, 64u, c, 2u); LDA(a3, w1, 64u, c, 3u);
|
||||
K32(h, a0, Nb[0], a1, Nb[1])
|
||||
K32(h, a2, Nb[2], a3, Nb[3])
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) Hb[c][i] = q8h(act(float16_t(h[i])));
|
||||
}
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w2, 32u, m, 0u); LDA(a1, w2, 32u, m, 1u);
|
||||
K32(R[m], a0, Hb[0], a1, Hb[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- outputs
|
||||
const uint t = inter(16u * wv + col);
|
||||
const int tx = x0 + int(t & 7u), ty = y0 + int(t >> 3u);
|
||||
const bool inside = tx >= 0 && ty >= 0 && tx < ex && ty < ey;
|
||||
B8 Tb[4];
|
||||
{
|
||||
const uint ty2 = t >> 3u, tx2 = t & 7u;
|
||||
const uint r = (ty2 >> 1u) < 2u ? 2u * (tx2 >> 1u) + (ty2 >> 1u) : 8u + 2u * (tx2 >> 1u) + (ty2 >> 1u) - 2u;
|
||||
const uint blk = 2u * (ty2 & 1u) + (tx2 & 1u);
|
||||
const uint o = (outOff + uint(ty * ex + tx) * 32u) >> 2u;
|
||||
[[unroll]] for (uint m = 0u; m < 4u; ++m) {
|
||||
Q8(Tb[m], R[m])
|
||||
const uint w0 = pack4(Tb[m][0], Tb[m][1], Tb[m][2], Tb[m][3]), w1 = pack4(Tb[m][4], Tb[m][5], Tb[m][6], Tb[m][7]);
|
||||
const uint at = 4u * (m & 1u) + 2u * hl;
|
||||
if (inside) { arena.w[o + (m >> 1u) * (plane >> 2u) + at] = w0; arena.w[o + (m >> 1u) * (plane >> 2u) + at + 1u] = w1; }
|
||||
const uint g = r * 64u + 16u * blk + 4u * m + 2u * hl;
|
||||
MG[g] = w0; MG[g + 1u] = w1;
|
||||
}
|
||||
}
|
||||
if ((SKIP & 32) == 0) { // the 64 -> 16 head and its kernel-prediction filter (lanes 0-15; 16-31 hold head channels 8-15)
|
||||
ACC hv;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) hv[i] = float(w16.w[W_BH / 2u + 8u * hl + uint(i)]);
|
||||
A8 a0, a1, a2, a3;
|
||||
LDA(a0, W_WH, 64u, 0u, 0u); LDA(a1, W_WH, 64u, 0u, 1u); LDA(a2, W_WH, 64u, 0u, 2u); LDA(a3, W_WH, 64u, 0u, 3u);
|
||||
K32(hv, a0, Tb[0], a1, Tb[1])
|
||||
K32(hv, a2, Tb[2], a3, Tb[3])
|
||||
float16_t l[12];
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) l[i] = float16_t(hv[i]);
|
||||
const uint u0 = subgroupShuffleXor(bh(l[0]) | (bh(l[1]) << 16u), 16u), u1 = subgroupShuffleXor(bh(l[2]) | (bh(l[3]) << 16u), 16u);
|
||||
l[8] = hb(u0 & 0xffffu); l[9] = hb(u0 >> 16u); l[10] = hb(u1 & 0xffffu); l[11] = hb(u1 >> 16u);
|
||||
const float16_t ma = max(max(max(l[0], l[2]), l[4]), l[6]), mb = max(max(max(l[1], l[3]), l[5]), l[7]);
|
||||
const float16_t mx = max(max(ma, mb), l[8]);
|
||||
float e[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) { precise float16_t d = l[i] - mx; e[i] = exp2(float(d) * 1.4426950216293335); }
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t rr = rcp16(s);
|
||||
float wg[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) wg[i] = r16(r16(e[i]) * float(rr));
|
||||
const int x = tx, y = ty;
|
||||
const int xm = x - 1 < 0 ? 1 : x - 1, xc = x < 0 ? 1 : (x >= ex ? ex - 2 : x), xp = x + 1 >= ex ? ex - 2 : x + 1;
|
||||
const int ym = y - 1 < 0 ? 1 : y - 1, yc = y < 0 ? 1 : (y >= ey ? ey - 2 : y), yp = y + 1 >= ey ? ey - 2 : y + 1;
|
||||
const ivec2 pts[9] = ivec2[9](ivec2(xm, ym), ivec2(xc, ym), ivec2(xp, ym), ivec2(xm, yc), ivec2(xc, yc),
|
||||
ivec2(xp, yc), ivec2(xm, yp), ivec2(xc, yp), ivec2(xp, yp));
|
||||
float acc[3][2] = float[3][2](float[2](0.0, 0.0), float[2](0.0, 0.0), float[2](0.0, 0.0));
|
||||
[[unroll]] for (uint k = 0u; k < 8u; ++k) {
|
||||
const vec4 tv = texelFetch(T88, pts[k], 0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) { precise float f = tv[c] * wg[k] + acc[c][k & 1u]; acc[c][k & 1u] = r16(f); }
|
||||
}
|
||||
const vec4 t8 = texelFetch(T88, pts[8], 0);
|
||||
float o112[3], o120[3];
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float f0 = acc[c][0] + acc[c][1];
|
||||
precise float f1 = t8[c] * wg[8] + r16(f0);
|
||||
const float f = r16(f1);
|
||||
precise float a = float(sig(l[9u + c])) * f;
|
||||
const float ar = r16(a);
|
||||
precise float b = f - ar;
|
||||
o120[c] = ar; o112[c] = r16(b);
|
||||
}
|
||||
if (hl == 0u) {
|
||||
if (inside) {
|
||||
imageStore(S112, ivec2(x, y), vec4(o112[0], o112[1], o112[2], 0.0));
|
||||
imageStore(S120, ivec2(x, y), vec4(o120[0], o120[1], o120[2], 0.0));
|
||||
}
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) O112[t * 4u + c] = float16_t(o112[c]);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
if ((SKIP & 8) == 0 && wv < 3u) { // the 2x2 merge: merged^T (96 x 16 rows), channel tiles 2w and 2w + 1 = arena plane w
|
||||
const uvec2 mxy = mergeXY(col);
|
||||
const int X = x0 / 2 + int(mxy.x), Y = y0 / 2 + int(mxy.y), hx = ex / 2, hy = ey / 2;
|
||||
const uint o = (mrgOff + wv * uint(hx * hy) * 32u + uint(Y * hx + X) * 32u) >> 2u;
|
||||
[[unroll]] for (uint m2 = 0u; m2 < 2u; ++m2) {
|
||||
const uint m = 2u * wv + m2;
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BM / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
for (uint ks = 0u; ks < 8u; ++ks) {
|
||||
B8 g0, g1; A8 w0, w1;
|
||||
coopMatLoad(g0, MG, 8u * ks, 64u, CM);
|
||||
coopMatLoad(g1, MG, 8u * ks + 4u, 64u, CM);
|
||||
LDA(w0, W_WM, 256u, m, 2u * ks); LDA(w1, W_WM, 256u, m, 2u * ks + 1u);
|
||||
K32(a, w0, g0, w1, g1)
|
||||
}
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
if (X >= 0 && Y >= 0 && X < hx && Y < hy) {
|
||||
arena.w[o + 4u * m2 + 2u * hl] = pack4(q[0], q[1], q[2], q[3]);
|
||||
arena.w[o + 4u * m2 + 2u * hl + 1u] = pack4(q[4], q[5], q[6], q[7]);
|
||||
}
|
||||
}
|
||||
} else if (lane < 16u) { // surface 128: 2x2 sums of 0.25 * surface 112, rightwards first, then downwards
|
||||
const uint mx = lane & 3u, my = lane >> 2u;
|
||||
const int x = x0 + 2 * int(mx), y = y0 + 2 * int(my);
|
||||
if (x >= 0 && y >= 0 && x < ex && y < ey) {
|
||||
const uint t00 = (2u * my) * 8u + 2u * mx;
|
||||
vec4 out_ = vec4(0.0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float16_t a00 = O112[t00 * 4u + c] * float16_t(0.25), a01 = O112[(t00 + 1u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t a10 = O112[(t00 + 8u) * 4u + c] * float16_t(0.25), a11 = O112[(t00 + 9u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t b0 = a00 + a01, b1 = a10 + a11;
|
||||
precise float16_t cc = b0 + b1;
|
||||
out_[c] = float(cc);
|
||||
}
|
||||
imageStore(S128, ivec2(x >> 1, y >> 1), out_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
#version 460
|
||||
// Native encoder 1 of the DLSS-D (preset E) denoiser: cuda_dldn_engine_swin_enc1_kernel rewritten for cooperative
|
||||
// matrices, bit-exact to engine/rr/enc1_ref.py (which is bit-exact to NVIDIA's launch). Same stage contract as the
|
||||
// translated stage (engine/rr/README.md) except binding 1, which holds engine/rr/native_pack.py's packing of the
|
||||
// extracted weights (offsets in the generated enc1_w.h).
|
||||
//
|
||||
// One workgroup is one 8x8-token window, four waves. Products run tokens x weights (A = activations from shared
|
||||
// memory, B = weight tiles), one 16x16 output tile at a time, with NVIDIA's arithmetic: per logical k32 step the
|
||||
// running value enters the first of two 16x16x16 FP8 products as C, the sum stays f32 and is rounded to binary16
|
||||
// once, through shared memory (RADV folds a register-only f32 -> f16 -> f32). Every FP8 operand is stored in NVIDIA's
|
||||
// k order: within each 32-block, slot j holds natural channel inter(j). The reductions (norms, softmax row sums) run
|
||||
// in NVIDIA's exact order.
|
||||
//
|
||||
// 1 prologue the 96 key tokens (window, then a border row above, below, a column left and right; mirrored at the
|
||||
// image edge), RMS norm (gain W_GAIN0) -> XQ; the residual + W_TABLE7 -> RES
|
||||
// 2 QK and V per head, 96 x 64 -> 32 each: 24 tile jobs spread over the waves -> KV
|
||||
// 3 attention per wave 16 query rows, both heads: scores + bias, cubic softmax, P V, out-projection onto RES
|
||||
// 4 MLP per wave: RMS norm (W_GAIN14), RES += W_TABLE16, eight branches 64 -> 32 -> 64 onto RES
|
||||
// 5 outputs the tokens (argument 48), the 2x2 merge to 96 channels (argument 56, waves 0-2), the 64 -> 16 head
|
||||
// and its 3x3 kernel-prediction filter of texture 88 to surfaces 112, 120 and (half resolution) 128
|
||||
#extension GL_KHR_cooperative_matrix : require
|
||||
#extension GL_KHR_memory_scope_semantics : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_float_e4m3 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 128) in;
|
||||
#include "enc1_w.h"
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint p[]; } param;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
layout(set = 0, binding = 3) uniform sampler2D T88;
|
||||
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D S112;
|
||||
layout(set = 0, binding = 5, rgba16f) uniform writeonly image2D S120;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S128;
|
||||
|
||||
#ifdef DBG
|
||||
// debugging: intermediates of window (DBX, DBY) as raw words: XQ, KV, RES after each head and after the MLP
|
||||
layout(set = 0, binding = 7, std430) buffer Dbg { uint d[]; } dbg;
|
||||
#define DBGWIN (gl_WorkGroupID.x == DBX && gl_WorkGroupID.y == DBY)
|
||||
#define DUMP8(arr, n, at) if (DBGWIN) { barrier(); for (uint i_ = tid; i_ < (n); i_ += 128u) dbg.d[(at) + i_] = code(arr[i_]); barrier(); }
|
||||
#define DUMP16(arr, n, at) if (DBGWIN) { barrier(); for (uint i_ = tid; i_ < (n); i_ += 128u) dbg.d[(at) + i_] = bh(arr[i_]); barrier(); }
|
||||
#else
|
||||
#define DUMP8(arr, n, at)
|
||||
#define DUMP16(arr, n, at)
|
||||
#endif
|
||||
#ifndef SKIP
|
||||
#define SKIP 0
|
||||
#endif
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
#define SYNC { memoryBarrierShared(); subgroupBarrier(); }
|
||||
|
||||
shared floate4m3_t XQ[96 * 64]; // the normalised key tokens; after the MLP the output tokens (64 rows)
|
||||
shared float16_t RES[64 * 64]; // the residual stream, natural channel order
|
||||
shared floate4m3_t KV[4 * 96 * 32]; // Q/K head 0, V head 0, Q/K head 1, V head 1; later the merge rows (16 x 256)
|
||||
shared float16_t SW[4 * 1536]; // per wave: f16 results (scores 16 x 96), rounding scratch at +1024
|
||||
shared floate4m3_t PW[4 * 1536]; // per wave: FP8 operands (P 16 x 96; O 16 x 32; N 16 x 64 + hidden 16 x 32)
|
||||
|
||||
// a weight tile: matrix W (K x N) packed by native_pack.py, k-tile kt, n-tile nt
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
// one binary16 rounding of an accumulator, through the wave's scratch
|
||||
#define RND(acc) { \
|
||||
coopMatStore(HAC(acc), SW, scr, 16u, RM); SYNC; \
|
||||
HAC r_; coopMatLoad(r_, SW, scr, 16u, RM); acc = ACC(r_); SYNC; }
|
||||
|
||||
// NVIDIA's k order within a 32-block: slot j holds natural channel inter(j); uninter is the inverse (and also the
|
||||
// arena's channel order: arena slot uninter(k) holds natural channel k)
|
||||
uint inter(uint j) { return (j & ~31u) | (((j & 31u) >> 2u) << 1u) | (j & 1u) | (((j >> 1u) & 1u) << 4u); }
|
||||
uint uninter(uint c) { return (c & ~31u) | (((c & 15u) >> 1u) << 2u) | (c & 1u) | (((c >> 4u) & 1u) << 1u); }
|
||||
int mirror(int v, int n) { v = abs(v); return min(v, 2 * n - 2 - v); }
|
||||
|
||||
// cvt.rn.satfinite.e4m3 (NaN -> 0x7f)
|
||||
floate4m3_t q8(float16_t h)
|
||||
{
|
||||
return isnan(h) ? uintBitsToFloate4m3EXT(uint8_t(0x7fu)) : floate4m3_t(clamp(h, float16_t(-448.0), float16_t(448.0)));
|
||||
}
|
||||
uint code(floate4m3_t v) { return uint(floate4m3BitsToUintEXT(v)); }
|
||||
float16_t hb(uint b) { return uint16BitsToFloat16(uint16_t(b)); }
|
||||
uint bh(float16_t h) { return uint(float16BitsToUint16(h)); }
|
||||
|
||||
// x rounded to binary16 precision (nearest even), staying f32. RADV folds float(float16_t(x)) back to x, so every
|
||||
// f32 -> f16 rounding whose result is used as f32 again goes through this: adding and subtracting 1.5 * 2^(E + 13)
|
||||
// (E = x's binary16 exponent) leaves x rounded to a multiple of its binary16 ulp; precise keeps the pair.
|
||||
float r16(float x)
|
||||
{
|
||||
const uint e = max(floatBitsToUint(x) & 0x7f800000u, 0x38800000u);
|
||||
const float c = uintBitsToFloat(e + (13u << 23u)) * 1.5;
|
||||
precise float t = x + c;
|
||||
precise float r = t - c;
|
||||
return r;
|
||||
}
|
||||
float16_t h16(float x) { return float16_t(r16(x)); }
|
||||
// m * x compared with 1, exactly (the fma recovers the product's rounding error): -1, 0 or 1
|
||||
int cmp1(float m, float x)
|
||||
{
|
||||
precise float t = m * x;
|
||||
precise float e = fma(m, x, -t);
|
||||
return t > 1.0 ? 1 : (t < 1.0 ? -1 : (e > 0.0 ? 1 : (e < 0.0 ? -1 : 0)));
|
||||
}
|
||||
// binary16 rounding of 1 / x (x > 0) to nearest even, from the hardware approximation and two midpoint checks
|
||||
float16_t rcp16(float x)
|
||||
{
|
||||
float16_t q = h16(1.0 / x);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const uint b = bh(q);
|
||||
const float lo = (float(q) + float(hb(b - 1u))) * 0.5, hi = (float(q) + float(hb(b + 1u))) * 0.5;
|
||||
const int cl = cmp1(lo, x), ch = cmp1(hi, x);
|
||||
if (cl > 0 || (cl == 0 && (b & 1u) != 0u)) q = hb(b - 1u);
|
||||
else if (ch < 0 || (ch == 0 && (b & 1u) != 0u)) q = hb(b + 1u);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
// binary16 rounding of 1 / sqrt(s) (s > 0)
|
||||
float16_t rsq16(float16_t s16)
|
||||
{
|
||||
const float s = float(s16);
|
||||
float16_t q = h16(inversesqrt(s));
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const uint b = bh(q);
|
||||
const float lo = (float(q) + float(hb(b - 1u))) * 0.5, hi = (float(q) + float(hb(b + 1u))) * 0.5;
|
||||
const int cl = cmp1(lo * lo, s), ch = cmp1(hi * hi, s);
|
||||
if (cl > 0 || (cl == 0 && (b & 1u) != 0u)) q = hb(b - 1u);
|
||||
else if (ch < 0 || (ch == 0 && (b & 1u) != 0u)) q = hb(b + 1u);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
// fma of binary16 operands with one binary16 rounding, exactly: the product is exact in f32, the sum's f32 rounding
|
||||
// error comes from TwoSum and decides the f32 results that land on a binary16 midpoint
|
||||
float16_t fma16(float16_t a, float16_t b, float16_t c)
|
||||
{
|
||||
precise float p = float(a) * float(b);
|
||||
precise float s = p + float(c);
|
||||
precise float bb = s - p;
|
||||
precise float err = (p - (s - bb)) + (float(c) - bb);
|
||||
const uint u = floatBitsToUint(s);
|
||||
if ((u & 0x1fffu) == 0x1000u && err != 0.0 && abs(s) >= 6.103515625e-05)
|
||||
return h16(uintBitsToFloat(err * s > 0.0 ? u + 1u : u - 1u));
|
||||
return h16(s);
|
||||
}
|
||||
float16_t act(float16_t x)
|
||||
{
|
||||
precise float16_t y = clamp(x, float16_t(-2.0), float16_t(2.0));
|
||||
precise float16_t a = float16_t(0.0810546875) * abs(y);
|
||||
precise float16_t b = float16_t(0.412109375) - a;
|
||||
precise float16_t c = y * b;
|
||||
precise float16_t g = float16_t(0.5) + c;
|
||||
precise float16_t r = x * g;
|
||||
return r;
|
||||
}
|
||||
float16_t sig(float16_t v) { return rcp16(exp2(float(v) * -1.4426950216293335) + 1.0); }
|
||||
uvec2 mergeXY(uint r) { return r < 8u ? uvec2(r >> 1u, r & 1u) : uvec2((r - 8u) >> 1u, 2u + (r & 1u)); }
|
||||
|
||||
void main()
|
||||
{
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const int ex = int(param.p[0]), ey = int(param.p[1]);
|
||||
const uint inOff = param.p[2], outOff = param.p[12], mrgOff = param.p[14], shift = param.p[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const uint plane = uint(ex * ey) * 32u;
|
||||
const uint sw = wv * 1536u, scr = sw + 1024u, r0 = wv * 16u;
|
||||
|
||||
// ---------------------------------------------------------------- 1 prologue
|
||||
#if !(SKIP & 1)
|
||||
if (tid < 96u) {
|
||||
int x, y;
|
||||
if (tid < 64u) {
|
||||
x = x0 + int(tid & 7u); y = y0 + int(tid >> 3u);
|
||||
} else {
|
||||
const int i = int(tid & 7u), side = int((tid - 64u) >> 3u);
|
||||
x = side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8);
|
||||
y = side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i);
|
||||
}
|
||||
x = mirror(x, ex); y = mirror(y, ey);
|
||||
const uint base = (inOff + uint(y * ex + x) * 32u) >> 2u;
|
||||
float16_t v[64];
|
||||
for (uint pl = 0u; pl < 2u; ++pl)
|
||||
for (uint wd = 0u; wd < 8u; ++wd) {
|
||||
const uint u = arena.w[base + pl * (plane >> 2u) + wd];
|
||||
for (uint b = 0u; b < 4u; ++b)
|
||||
v[32u * pl + 4u * wd + b] = float16_t(uintBitsToFloate4m3EXT(uint8_t((u >> (8u * b)) & 0xffu)));
|
||||
}
|
||||
// NVIDIA's order: per word t (and t + 4) the f16x2 pair sums of both planes, then lanes ^1, ^2, the halves
|
||||
precise f16vec2 h[4];
|
||||
for (uint t = 0u; t < 4u; ++t) {
|
||||
precise f16vec2 p[4];
|
||||
for (uint k = 0u; k < 4u; ++k) {
|
||||
const uint o = 32u * (k & 1u) + 4u * (t + 4u * (k >> 1u));
|
||||
precise f16vec2 a = f16vec2(v[o], v[o + 1u]) * f16vec2(v[o], v[o + 1u]);
|
||||
precise f16vec2 c = f16vec2(v[o + 2u], v[o + 3u]) * f16vec2(v[o + 2u], v[o + 3u]);
|
||||
p[k] = a + c;
|
||||
}
|
||||
precise f16vec2 s0 = p[0] + p[1], s1 = p[2] + p[3];
|
||||
h[t] = s0 + s1;
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t s = hs.x + hs.y;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
for (uint j = 0u; j < 64u; ++j) {
|
||||
precise float16_t g = r * w16.w[W_GAIN0 / 2u + inter(j)];
|
||||
precise float16_t n = v[j] * g;
|
||||
XQ[tid * 64u + j] = q8(n);
|
||||
}
|
||||
if (tid < 64u)
|
||||
for (uint k = 0u; k < 64u; ++k) {
|
||||
precise float16_t c = v[uninter(k)] + w16.w[W_TABLE7 / 2u + k];
|
||||
RES[tid * 64u + k] = c;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
barrier();
|
||||
DUMP8(XQ, 6144u, 0u)
|
||||
DUMP16(RES, 4096u, 6144u)
|
||||
|
||||
// ---------------------------------------------------------------- 2 QK and V projections
|
||||
#if !(SKIP & 2)
|
||||
for (uint job = wv; job < 24u; job += 4u) {
|
||||
const uint mt = job % 6u, mat = job / 6u, hd = mat >> 1u;
|
||||
const bool isV = (mat & 1u) != 0u;
|
||||
const uint wa = (isV ? W_WV0A : W_WK0A) + 2048u * hd, wb = wa + 1024u;
|
||||
A8 a0, a1, a2, a3;
|
||||
coopMatLoad(a0, XQ, mt * 1024u, 64u, RM);
|
||||
coopMatLoad(a1, XQ, mt * 1024u + 16u, 64u, RM);
|
||||
coopMatLoad(a2, XQ, mt * 1024u + 32u, 64u, RM);
|
||||
coopMatLoad(a3, XQ, mt * 1024u + 48u, 64u, RM);
|
||||
for (uint nt = 0u; nt < 2u; ++nt) {
|
||||
B8 b0, b1;
|
||||
LDB(b0, wa, 32u, 0u, nt); LDB(b1, wa, 32u, 1u, nt);
|
||||
ACC acc = coopMatMulAdd(a1, b1, coopMatMulAdd(a0, b0, ACC(0.0)));
|
||||
RND(acc);
|
||||
LDB(b0, wb, 32u, 0u, nt); LDB(b1, wb, 32u, 1u, nt);
|
||||
acc = coopMatMulAdd(a3, b1, coopMatMulAdd(a2, b0, acc));
|
||||
coopMatStore(HAC(acc), SW, sw + nt * 16u, 32u, RM);
|
||||
}
|
||||
SYNC;
|
||||
const uint row = lane >> 1u, cb = (lane & 1u) * 16u, kv = mat * 3072u;
|
||||
if (!isV) {
|
||||
for (uint j = cb; j < cb + 16u; ++j) KV[kv + (mt * 16u + row) * 32u + j] = q8(SW[sw + row * 32u + inter(j)]);
|
||||
} else {
|
||||
const uint dr = uninter(mt * 16u + row);
|
||||
for (uint c = cb; c < cb + 16u; ++c) KV[kv + dr * 32u + c] = q8(SW[sw + row * 32u + c]);
|
||||
}
|
||||
SYNC;
|
||||
}
|
||||
#endif
|
||||
barrier();
|
||||
DUMP8(KV, 12288u, 10240u)
|
||||
|
||||
// ---------------------------------------------------------------- 3 attention, 16 query rows per wave
|
||||
#if !(SKIP & 4)
|
||||
for (uint hd = 0u; hd < 2u; ++hd) {
|
||||
const uint qk = 6144u * hd, vv = qk + 3072u;
|
||||
{
|
||||
A8 q0, q1;
|
||||
coopMatLoad(q0, KV, qk + r0 * 32u, 32u, RM);
|
||||
coopMatLoad(q1, KV, qk + r0 * 32u + 16u, 32u, RM);
|
||||
for (uint nt = 0u; nt < 6u; ++nt) {
|
||||
HAC c;
|
||||
coopMatLoad(c, w16.w, (W_BIAS0 + 12288u * hd) / 2u + r0 * 96u + nt * 16u, 96u, RM);
|
||||
B8 k0, k1;
|
||||
coopMatLoad(k0, KV, qk + nt * 512u, 32u, CM);
|
||||
coopMatLoad(k1, KV, qk + nt * 512u + 16u, 32u, CM);
|
||||
ACC acc = coopMatMulAdd(q1, k1, coopMatMulAdd(q0, k0, ACC(c)));
|
||||
coopMatStore(HAC(acc), SW, sw + nt * 16u, 96u, RM);
|
||||
}
|
||||
}
|
||||
SYNC;
|
||||
{ // the cubic-exponent softmax: lane = (row, column parity)
|
||||
const uint row = lane >> 1u, par = lane & 1u;
|
||||
float16_t e[48];
|
||||
for (uint j = 0u; j < 12u; ++j)
|
||||
for (uint tt = 0u; tt < 4u; ++tt) {
|
||||
const float16_t x = SW[sw + row * 96u + 8u * j + 2u * tt + par];
|
||||
precise float16_t m = clamp(x * float16_t(0.017181396484375), float16_t(-0.55615234375), float16_t(0.55615234375));
|
||||
const float16_t t = fma16(m, -m, float16_t(0.92724609375));
|
||||
const float16_t u = fma16(m, t, float16_t(1.375));
|
||||
e[4u * j + tt] = hb((bh(u) << 5u) & 0x7FE0u);
|
||||
}
|
||||
precise float16_t sm[4];
|
||||
for (uint tt = 0u; tt < 4u; ++tt) {
|
||||
precise float16_t ev = ((e[tt] + e[8u + tt]) + (e[16u + tt] + e[24u + tt])) + (e[32u + tt] + e[40u + tt]);
|
||||
precise float16_t od = ((e[4u + tt] + e[12u + tt]) + (e[20u + tt] + e[28u + tt])) + (e[36u + tt] + e[44u + tt]);
|
||||
sm[tt] = ev + od;
|
||||
}
|
||||
precise float16_t out_ = (sm[0] + sm[1]) + (sm[2] + sm[3]);
|
||||
const float16_t other = hb(subgroupShuffleXor(bh(out_), 1u));
|
||||
precise float16_t rs = par == 0u ? out_ + other : other + out_;
|
||||
const float16_t r = rcp16(float(rs));
|
||||
for (uint j = 0u; j < 12u; ++j)
|
||||
for (uint tt = 0u; tt < 4u; ++tt) {
|
||||
precise float16_t pv = e[4u * j + tt] * r;
|
||||
PW[sw + row * 96u + uninter(8u * j + 2u * tt + par)] = q8(pv);
|
||||
}
|
||||
}
|
||||
SYNC;
|
||||
if (hd == 0u) { DUMP16(SW, 6144u, 34816u) DUMP8(PW, 6144u, 40960u) }
|
||||
for (uint nt = 0u; nt < 2u; ++nt) { // P V
|
||||
ACC acc = ACC(0.0);
|
||||
for (uint ks = 0u; ks < 3u; ++ks) {
|
||||
A8 p0, p1; B8 v0, v1;
|
||||
coopMatLoad(p0, PW, sw + ks * 32u, 96u, RM);
|
||||
coopMatLoad(p1, PW, sw + ks * 32u + 16u, 96u, RM);
|
||||
coopMatLoad(v0, KV, vv + ks * 1024u + nt * 16u, 32u, RM);
|
||||
coopMatLoad(v1, KV, vv + ks * 1024u + 512u + nt * 16u, 32u, RM);
|
||||
acc = coopMatMulAdd(p1, v1, coopMatMulAdd(p0, v0, acc));
|
||||
if (ks < 2u) RND(acc);
|
||||
}
|
||||
coopMatStore(HAC(acc), SW, sw + nt * 16u, 32u, RM);
|
||||
}
|
||||
SYNC;
|
||||
{
|
||||
const uint row = lane >> 1u, cb = (lane & 1u) * 16u;
|
||||
for (uint j = cb; j < cb + 16u; ++j) PW[sw + row * 32u + j] = q8(SW[sw + row * 32u + inter(j)]);
|
||||
}
|
||||
SYNC;
|
||||
{ // out-projection onto the residual
|
||||
A8 o0, o1;
|
||||
coopMatLoad(o0, PW, sw, 32u, RM);
|
||||
coopMatLoad(o1, PW, sw + 16u, 32u, RM);
|
||||
for (uint nt = 0u; nt < 4u; ++nt) {
|
||||
HAC c;
|
||||
coopMatLoad(c, RES, r0 * 64u + nt * 16u, 64u, RM);
|
||||
B8 b0, b1;
|
||||
LDB(b0, W_WO0 + 2048u * hd, 64u, 0u, nt); LDB(b1, W_WO0 + 2048u * hd, 64u, 1u, nt);
|
||||
ACC acc = coopMatMulAdd(o1, b1, coopMatMulAdd(o0, b0, ACC(c)));
|
||||
coopMatStore(HAC(acc), RES, r0 * 64u + nt * 16u, 64u, RM);
|
||||
}
|
||||
}
|
||||
SYNC;
|
||||
DUMP16(RES, 4096u, 22528u + 4096u * hd)
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------- 4 MLP
|
||||
#if !(SKIP & 8)
|
||||
if (lane < 16u) {
|
||||
const uint base = (r0 + lane) * 64u;
|
||||
float16_t R[64];
|
||||
for (uint k = 0u; k < 64u; ++k) R[k] = RES[base + k];
|
||||
precise f16vec2 h[4];
|
||||
for (uint t = 0u; t < 4u; ++t) {
|
||||
precise f16vec2 q[8];
|
||||
for (uint j = 0u; j < 8u; ++j) {
|
||||
const f16vec2 x = f16vec2(R[8u * j + 2u * t], R[8u * j + 2u * t + 1u]);
|
||||
q[j] = x * x;
|
||||
}
|
||||
precise f16vec2 a = q[0] + q[2], b = q[1] + q[3], c = q[4] + q[6], d = q[5] + q[7];
|
||||
precise f16vec2 ac = a + c, bd = b + d;
|
||||
h[t] = ac + bd;
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t s = hs.x + hs.y;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
for (uint j = 0u; j < 64u; ++j) {
|
||||
const uint k = inter(j);
|
||||
precise float16_t g = r * w16.w[W_GAIN14 / 2u + k];
|
||||
precise float16_t n = R[k] * g;
|
||||
PW[sw + lane * 96u + j] = q8(n);
|
||||
}
|
||||
for (uint k = 0u; k < 64u; ++k) {
|
||||
precise float16_t c = R[k] + w16.w[W_TABLE16 / 2u + k];
|
||||
RES[base + k] = c;
|
||||
}
|
||||
}
|
||||
SYNC;
|
||||
{
|
||||
A8 n0, n1, n2, n3;
|
||||
coopMatLoad(n0, PW, sw, 96u, RM);
|
||||
coopMatLoad(n1, PW, sw + 16u, 96u, RM);
|
||||
coopMatLoad(n2, PW, sw + 32u, 96u, RM);
|
||||
coopMatLoad(n3, PW, sw + 48u, 96u, RM);
|
||||
for (uint br = 0u; br < 8u; ++br) {
|
||||
const uint w1 = W_W1_0 + 2048u * br, w2 = W_W2_0 + 2048u * br;
|
||||
for (uint nt = 0u; nt < 2u; ++nt) {
|
||||
HAC c;
|
||||
coopMatLoad(c, w16.w, (W_B1_0 + 1024u * br) / 2u + nt * 16u, 32u, RM);
|
||||
B8 b0, b1;
|
||||
LDB(b0, w1, 32u, 0u, nt); LDB(b1, w1, 32u, 1u, nt);
|
||||
ACC acc = coopMatMulAdd(n1, b1, coopMatMulAdd(n0, b0, ACC(c)));
|
||||
RND(acc);
|
||||
LDB(b0, w1, 32u, 2u, nt); LDB(b1, w1, 32u, 3u, nt);
|
||||
acc = coopMatMulAdd(n3, b1, coopMatMulAdd(n2, b0, acc));
|
||||
coopMatStore(HAC(acc), SW, sw + nt * 16u, 32u, RM);
|
||||
}
|
||||
SYNC;
|
||||
{
|
||||
const uint row = lane >> 1u, cb = (lane & 1u) * 16u;
|
||||
for (uint j = cb; j < cb + 16u; ++j) PW[sw + row * 96u + 64u + j] = q8(act(SW[sw + row * 32u + inter(j)]));
|
||||
}
|
||||
SYNC;
|
||||
A8 g0, g1;
|
||||
coopMatLoad(g0, PW, sw + 64u, 96u, RM);
|
||||
coopMatLoad(g1, PW, sw + 80u, 96u, RM);
|
||||
for (uint nt = 0u; nt < 4u; ++nt) {
|
||||
HAC c;
|
||||
coopMatLoad(c, RES, r0 * 64u + nt * 16u, 64u, RM);
|
||||
B8 b0, b1;
|
||||
LDB(b0, w2, 64u, 0u, nt); LDB(b1, w2, 64u, 1u, nt);
|
||||
ACC acc = coopMatMulAdd(g1, b1, coopMatMulAdd(g0, b0, ACC(c)));
|
||||
coopMatStore(HAC(acc), RES, r0 * 64u + nt * 16u, 64u, RM);
|
||||
}
|
||||
SYNC;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
DUMP16(RES, 4096u, 30720u)
|
||||
|
||||
// ---------------------------------------------------------------- 5 outputs
|
||||
{ // the tokens, in arena order (= NVIDIA's k order)
|
||||
const uint row = lane >> 1u, pl = lane & 1u, t = r0 + row;
|
||||
const int x = x0 + int(t & 7u), y = y0 + int(t >> 3u);
|
||||
uint words[8];
|
||||
for (uint wd = 0u; wd < 8u; ++wd) {
|
||||
uint u = 0u;
|
||||
for (uint b = 0u; b < 4u; ++b) {
|
||||
const uint j = 32u * pl + 4u * wd + b;
|
||||
const floate4m3_t c = q8(RES[t * 64u + inter(j)]);
|
||||
XQ[t * 64u + j] = c;
|
||||
u |= code(c) << (8u * b);
|
||||
}
|
||||
words[wd] = u;
|
||||
}
|
||||
if (x >= 0 && y >= 0 && x < ex && y < ey) {
|
||||
const uint o = (outOff + pl * plane + uint(y * ex + x) * 32u) >> 2u;
|
||||
for (uint wd = 0u; wd < 8u; ++wd) arena.w[o + wd] = words[wd];
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
#if !(SKIP & 16)
|
||||
for (uint i = 0u; i < 32u; ++i) { // the merge rows: (mx, my) in NVIDIA's order, 4 tokens of 64 channels
|
||||
const uint idx = tid * 32u + i, r = idx >> 8u, k = idx & 255u, blk = k >> 6u;
|
||||
const uvec2 m = mergeXY(r);
|
||||
KV[idx] = XQ[((2u * m.y + (blk >> 1u)) * 8u + 2u * m.x + (blk & 1u)) * 64u + (k & 63u)];
|
||||
}
|
||||
barrier();
|
||||
if (wv < 3u) { // the 2x2 merge, one 32-channel plane per wave
|
||||
for (uint n2 = 0u; n2 < 2u; ++n2) {
|
||||
const uint nt = 2u * wv + n2;
|
||||
HAC c;
|
||||
coopMatLoad(c, w16.w, W_BM / 2u + nt * 16u, 96u, RM);
|
||||
ACC acc = ACC(c);
|
||||
for (uint ks = 0u; ks < 8u; ++ks) {
|
||||
A8 m0, m1; B8 b0, b1;
|
||||
coopMatLoad(m0, KV, ks * 32u, 256u, RM);
|
||||
coopMatLoad(m1, KV, ks * 32u + 16u, 256u, RM);
|
||||
LDB(b0, W_WM, 96u, 2u * ks, nt); LDB(b1, W_WM, 96u, 2u * ks + 1u, nt);
|
||||
acc = coopMatMulAdd(m1, b1, coopMatMulAdd(m0, b0, acc));
|
||||
if (ks < 7u) RND(acc);
|
||||
}
|
||||
coopMatStore(HAC(acc), SW, sw + n2 * 16u, 32u, RM);
|
||||
}
|
||||
SYNC;
|
||||
const uint row = lane >> 1u, hf = lane & 1u;
|
||||
const uvec2 m = mergeXY(row);
|
||||
const int X = x0 / 2 + int(m.x), Y = y0 / 2 + int(m.y), hx = ex / 2, hy = ey / 2;
|
||||
uint words[4];
|
||||
for (uint wd = 0u; wd < 4u; ++wd) {
|
||||
uint u = 0u;
|
||||
for (uint b = 0u; b < 4u; ++b) u |= code(q8(SW[sw + row * 32u + inter(16u * hf + 4u * wd + b)])) << (8u * b);
|
||||
words[wd] = u;
|
||||
}
|
||||
if (X >= 0 && Y >= 0 && X < hx && Y < hy) {
|
||||
const uint o = (mrgOff + wv * uint(hx * hy) * 32u + uint(Y * hx + X) * 32u) >> 2u;
|
||||
for (uint wd = 0u; wd < 4u; ++wd) arena.w[o + 4u * hf + wd] = words[wd];
|
||||
}
|
||||
SYNC;
|
||||
}
|
||||
#endif
|
||||
{ // the 64 -> 16 head
|
||||
HAC c;
|
||||
coopMatLoad(c, w16.w, W_BH / 2u, 16u, RM);
|
||||
A8 t0, t1, t2, t3; B8 b0, b1;
|
||||
coopMatLoad(t0, XQ, r0 * 64u, 64u, RM);
|
||||
coopMatLoad(t1, XQ, r0 * 64u + 16u, 64u, RM);
|
||||
coopMatLoad(t2, XQ, r0 * 64u + 32u, 64u, RM);
|
||||
coopMatLoad(t3, XQ, r0 * 64u + 48u, 64u, RM);
|
||||
LDB(b0, W_WH, 16u, 0u, 0u); LDB(b1, W_WH, 16u, 1u, 0u);
|
||||
ACC acc = coopMatMulAdd(t1, b1, coopMatMulAdd(t0, b0, ACC(c)));
|
||||
RND(acc);
|
||||
LDB(b0, W_WH, 16u, 2u, 0u); LDB(b1, W_WH, 16u, 3u, 0u);
|
||||
acc = coopMatMulAdd(t3, b1, coopMatMulAdd(t2, b0, acc));
|
||||
coopMatStore(HAC(acc), SW, sw, 16u, RM);
|
||||
}
|
||||
SYNC;
|
||||
{ // the kernel-prediction filter; lanes 16-31 repeat 0-15 so that the shuffles below stay uniform
|
||||
const uint tl = lane & 15u, tk = r0 + tl;
|
||||
const int x = x0 + int(tk & 7u), y = y0 + int(tk >> 3u);
|
||||
float16_t l[12];
|
||||
for (uint i = 0u; i < 12u; ++i) l[i] = SW[sw + tl * 16u + i];
|
||||
const float16_t ma = max(max(max(l[0], l[2]), l[4]), l[6]), mb = max(max(max(l[1], l[3]), l[5]), l[7]);
|
||||
const float16_t mx = max(max(ma, mb), l[8]);
|
||||
float e[9];
|
||||
for (uint i = 0u; i < 9u; ++i) {
|
||||
precise float16_t d = l[i] - mx;
|
||||
e[i] = exp2(float(d) * 1.4426950216293335);
|
||||
}
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t r = rcp16(s);
|
||||
float wg[9];
|
||||
for (uint i = 0u; i < 9u; ++i) wg[i] = r16(r16(e[i]) * float(r));
|
||||
const int xm = x - 1 < 0 ? 1 : x - 1, xc = x < 0 ? 1 : (x >= ex ? ex - 2 : x), xp = x + 1 >= ex ? ex - 2 : x + 1;
|
||||
const int ym = y - 1 < 0 ? 1 : y - 1, yc = y < 0 ? 1 : (y >= ey ? ey - 2 : y), yp = y + 1 >= ey ? ey - 2 : y + 1;
|
||||
const ivec2 pts[9] = ivec2[9](ivec2(xm, ym), ivec2(xc, ym), ivec2(xp, ym), ivec2(xm, yc), ivec2(xc, yc),
|
||||
ivec2(xp, yc), ivec2(xm, yp), ivec2(xc, yp), ivec2(xp, yp));
|
||||
// f32 values that are binary16 numbers throughout (r16 at every NVIDIA f16 rounding)
|
||||
float o112[3], o120[3];
|
||||
float acc[3][2] = float[3][2](float[2](0.0, 0.0), float[2](0.0, 0.0), float[2](0.0, 0.0));
|
||||
for (uint k = 0u; k < 8u; ++k) {
|
||||
const vec4 tv = texelFetch(T88, pts[k], 0);
|
||||
for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float f = tv[c] * wg[k] + acc[c][k & 1u];
|
||||
acc[c][k & 1u] = r16(f);
|
||||
}
|
||||
}
|
||||
const vec4 t8 = texelFetch(T88, pts[8], 0);
|
||||
for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float f0 = acc[c][0] + acc[c][1];
|
||||
precise float f1 = t8[c] * wg[8] + r16(f0);
|
||||
const float f = r16(f1);
|
||||
precise float a = float(sig(l[9u + c])) * f;
|
||||
const float ar = r16(a);
|
||||
precise float b = f - ar;
|
||||
o120[c] = ar; o112[c] = r16(b);
|
||||
}
|
||||
// surface 128: the 2x2 sums of 0.25 * surface 112, rightwards first, then downwards
|
||||
float q[3], cs[3];
|
||||
const uint nb = min(lane + 1u, 31u), dn = min(lane + 8u, 31u);
|
||||
for (uint c = 0u; c < 3u; ++c) {
|
||||
q[c] = o112[c] * 0.25;
|
||||
precise float b = q[c] + subgroupShuffle(q[c], nb);
|
||||
const float br = r16(b);
|
||||
precise float d = br + subgroupShuffle(br, dn);
|
||||
cs[c] = r16(d);
|
||||
}
|
||||
if (lane < 16u && x >= 0 && y >= 0 && x < ex && y < ey) {
|
||||
imageStore(S112, ivec2(x, y), vec4(o112[0], o112[1], o112[2], 0.0));
|
||||
imageStore(S120, ivec2(x, y), vec4(o120[0], o120[1], o120[2], 0.0));
|
||||
if (((x | y) & 1) == 0) imageStore(S128, ivec2(x >> 1, y >> 1), vec4(cs[0], cs[1], cs[2], 0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack encoder 1's extracted weights (engine/rr/enc1_ref.py --extract) for engine/rr/enc1_m.comp.
|
||||
|
||||
usage: enc1_pack.py WEIGHTS.npz OUT.bin OUT.h
|
||||
|
||||
The shader runs every product transposed (D^T = W^T X^T: weights are the A operand, activations the B operand), so a
|
||||
result's rows are output channels and its columns tokens, and the result is the next product's B operand in place.
|
||||
The rows come out in the order of W's columns, so every weight's columns are permuted here into the k order NVIDIA's
|
||||
next product contracts them in (engine/rr/enc1_ref.py: q8(x)[:, inter(n)]), and the f16 vectors that act on those
|
||||
rows likewise. Keys and queries are token slots: slot s is token inter(96)[s] (the prologue's key list), so the
|
||||
attention bias is stored transposed in slot order.
|
||||
|
||||
A tiles W (K x N) -> W^T 16x16 tiles [mt][kt], row-major (m, k): 256 * (mt * K/16 + kt) bytes
|
||||
B tiles the V projection runs X Wv (A = tokens): tiles [kt][nt], row-major (k, n)
|
||||
f16 vectors as is; the attention bias as 96 x 64 [key slot][query slot]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from enc1_ref import inter # noqa: E402
|
||||
|
||||
I32, I64, I96 = inter(32), inter(64), inter(96)
|
||||
|
||||
|
||||
def a_tiles(w):
|
||||
k, n = w.shape
|
||||
return np.ascontiguousarray(w.T).reshape(n // 16, 16, k // 16, 16).transpose(0, 2, 1, 3).reshape(-1)
|
||||
|
||||
|
||||
def b_tiles(w):
|
||||
k, n = w.shape
|
||||
return w.reshape(k // 16, 16, n // 16, 16).transpose(0, 2, 1, 3).reshape(-1)
|
||||
|
||||
|
||||
CUBIC_MAX = 0x3873 # binary16 bits of 0.55615234375, the clamp of the scaled score
|
||||
|
||||
|
||||
def act_table():
|
||||
"""the MLP's hidden activation and its FP8 conversion, q8(act(h)), for every binary16 h, indexed by its bits: one
|
||||
byte load in place of the six binary16 operations and the conversion (the shaders' ACTQ)"""
|
||||
from enc1_ref import act
|
||||
from rrmath import q8
|
||||
h = np.arange(65536, dtype=np.uint32).astype(np.uint16).view(np.float16)
|
||||
with np.errstate(all='ignore'):
|
||||
return np.asarray(q8(act(h).astype(np.float64)), np.uint8)
|
||||
|
||||
|
||||
def cubic_table():
|
||||
"""the cubic softmax numerator's bits (enc1_ref.softmax_cubic) for every binary16 score, indexed by its bits (the
|
||||
scaling and the clamp included; NaN scores take NVIDIA's min / max, which return the other operand)"""
|
||||
x = np.arange(65536, dtype=np.uint32).astype(np.uint16).view(np.float16)
|
||||
with np.errstate(invalid='ignore', over='ignore'):
|
||||
m = (x * np.float16(0.017181396484375)).astype(np.float16)
|
||||
lim = np.float16(0.55615234375)
|
||||
m = np.where(np.isnan(m), lim, np.clip(m, -lim, lim)).astype(np.float16)
|
||||
|
||||
def fma16(a, b, c):
|
||||
return (a.astype(np.float64) * b.astype(np.float64) + c.astype(np.float64)).astype(np.float16)
|
||||
t = fma16(m, -m, np.full_like(m, np.float16(0.92724609375)))
|
||||
u = fma16(m, t, np.full_like(m, np.float16(1.375)))
|
||||
return ((u.view(np.uint16).astype(np.uint32) << 5) & 0x7FE0).astype(np.uint16)
|
||||
|
||||
|
||||
def main():
|
||||
src, out_bin, out_h = sys.argv[1:4]
|
||||
w = dict(np.load(src))
|
||||
e = {}
|
||||
for h in range(2):
|
||||
e[f'wk{h}'] = ('A', np.vstack([w[f'wk{h}a'], w[f'wk{h}b']])[:, I32])
|
||||
e[f'wv{h}'] = ('B', np.vstack([w[f'wv{h}a'], w[f'wv{h}b']])[:, I32])
|
||||
e[f'wo{h}'] = ('A', w[f'wo{h}'][:, I64])
|
||||
e[f'bias{h}'] = ('H', w[f'bias{h}'][np.ix_(I64, I96)].T)
|
||||
for b in range(8):
|
||||
e[f'w1_{b}'] = ('A', w[f'w1_{b}'][:, I32])
|
||||
e[f'b1_{b}'] = ('H', w[f'b1_{b}'][I32])
|
||||
e[f'w2_{b}'] = ('A', w[f'w2_{b}'][:, I64])
|
||||
e['wm'], e['bm'] = ('A', w['wm'][:, I96]), ('H', w['bm'][I96])
|
||||
e['wh'], e['bh'] = ('A', w['wh']), ('H', w['bh'])
|
||||
e['gain0'] = ('H', w['gain0'])
|
||||
for k in ('table7', 'gain14', 'table16'):
|
||||
e[k] = ('H', w[k][I64])
|
||||
e['cubic'] = ('U', cubic_table())
|
||||
blob, defines = bytearray(), []
|
||||
for name, (kind, a) in e.items():
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if kind == 'A':
|
||||
blob += a_tiles(a).tobytes()
|
||||
elif kind == 'B':
|
||||
blob += b_tiles(a).tobytes()
|
||||
elif kind == 'U':
|
||||
blob += np.ascontiguousarray(a, np.uint16).tobytes()
|
||||
else:
|
||||
blob += np.ascontiguousarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {off}u // {kind} {a.shape}')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write('// generated by engine/rr/enc1_pack.py\n' + '\n'.join(defines) + '\n')
|
||||
print(f'{len(blob)} bytes, {len(e)} entries')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,330 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact numpy reference of the denoiser's encoder 1 (cuda_dldn_engine_swin_enc1_kernel), one 8x8 window at a time,
|
||||
from its weights and real inputs only: the specification the native shader implements.
|
||||
|
||||
Derived phase by phase against ZLUDA replays (engine/rr/phase_dump.py), every step bit-exact:
|
||||
prologue the 96 key tokens (the window plus a one-token border, corners excluded; mirrored at the image edge)
|
||||
from the arena's two 32-channel planes, f16 RMS norm (NVIDIA's summation order) times the gain at W+0
|
||||
attention two heads: QK and V projections (K = 64 in two k32 steps), scores = q8(QK[:64]) q8(QK)^T + bias,
|
||||
the cubic-exponent softmax, P V, out-projection onto the residual (input tile + table at W+36992)
|
||||
MLP RMS norm times the gain at W+37120, eight branches of 64 -> 32 (bias, ramp activation) -> 64,
|
||||
accumulated onto the residual (+ table at W+41408)
|
||||
outputs q8 tokens to param+48, the 2x2 merge (16 x 256 -> 96 + bias) to param+56, the 64 -> 16 head and its
|
||||
3x3 kernel-prediction filter of texture 88 to surfaces 112, 120 and (half resolution) 128
|
||||
|
||||
usage: enc1_ref.py CAPTURE FRAME WEIGHTS.npz [--blocks X,Y ...] (verifies against the capture)
|
||||
WEIGHTS.npz: engine/rr/enc1_ref.py --extract DUMP.npz CAPTURE FRAME OUT.npz builds it from a phase dump
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import e4m3, f16, q8, mma_k32 # noqa: E402
|
||||
|
||||
H = np.float16
|
||||
F = np.float32
|
||||
|
||||
|
||||
def inter(n):
|
||||
perm = []
|
||||
for b in range(n // 32):
|
||||
for m in range(8):
|
||||
perm += [32 * b + 2 * m, 32 * b + 2 * m + 1, 32 * b + 16 + 2 * m, 32 * b + 17 + 2 * m]
|
||||
return perm
|
||||
|
||||
|
||||
INTER64 = inter(64)
|
||||
# arena channel order (NVIDIA's A-fragment order) -> natural: natural channel k is arena channel ARENA[k]
|
||||
ARENA = [0, 1, 4, 5, 8, 9, 12, 13, 16, 17, 20, 21, 24, 25, 28, 29, 2, 3, 6, 7, 10, 11, 14, 15, 18, 19, 22, 23, 26, 27,
|
||||
30, 31]
|
||||
ARENA = ARENA + [32 + c for c in ARENA]
|
||||
|
||||
|
||||
def add(a, b):
|
||||
return (a + b).astype(H)
|
||||
|
||||
|
||||
def mm(a_codes, b_codes, c):
|
||||
"""a (M x K e4m3 codes, NVIDIA k order) . b (K x N codes) + c (M x N f16 values) with ZLUDA's k32 rounding"""
|
||||
return f16(mma_k32(a_codes, b_codes, np.asarray(c, H).view(np.uint16))).astype(H)
|
||||
|
||||
|
||||
def nv_rowsum_sq(x, eps=2.0 ** -13):
|
||||
"""f16(rsqrt(sum of squares + eps)) per row of a 64-column C/D tile, NVIDIA's order"""
|
||||
sq = (x * x).astype(H)
|
||||
|
||||
def tile(j, t):
|
||||
return sq[:, 8 * j + 2 * t: 8 * j + 2 * t + 2]
|
||||
h = []
|
||||
for t in range(4):
|
||||
a, b = add(tile(0, t), tile(2, t)), add(tile(1, t), tile(3, t))
|
||||
c, d = add(tile(4, t), tile(6, t)), add(tile(5, t), tile(7, t))
|
||||
h.append(add(add(a, c), add(b, d)))
|
||||
h1 = [add(h[t], h[t ^ 1]) for t in range(4)]
|
||||
h2 = add(h1[0], h1[2])
|
||||
s = add(add(h2[:, 0], h2[:, 1]), np.full(x.shape[0], H(eps)))
|
||||
return (F(1) / np.sqrt(s.astype(F))).astype(H)
|
||||
|
||||
|
||||
def input_norm(tok_arena, gain):
|
||||
"""the prologue's per-token norm: tok_arena (2 planes x 32, arena order) -> f16 normalised token (natural)"""
|
||||
sq = [(v * v).astype(H) for v in tok_arena]
|
||||
h = []
|
||||
for t in range(4):
|
||||
def pair(p, w):
|
||||
s = sq[p][4 * w: 4 * w + 4]
|
||||
return add(s[0:2], s[2:4])
|
||||
h.append(add(add(pair(0, t), pair(1, t)), add(pair(0, t + 4), pair(1, t + 4))))
|
||||
h01 = [add(h[t], h[t ^ 1]) for t in range(4)]
|
||||
h2 = add(h01[0], h01[2])
|
||||
s = H(H(h2[0] + h2[1]) + H(2.0 ** -13))
|
||||
r = H(F(1) / np.sqrt(F(s)))
|
||||
x = np.concatenate(tok_arena)[ARENA]
|
||||
return (x * (r * gain).astype(H)).astype(H), x
|
||||
|
||||
|
||||
def softmax_cubic(x):
|
||||
"""E5: the cubic-exponent softmax of a 64 x 96 score block (natural columns)"""
|
||||
def fma16(a, b, c):
|
||||
return (a.astype(np.float64) * b.astype(np.float64) + c.astype(np.float64)).astype(H)
|
||||
m = np.clip((x * H(0.017181396484375)).astype(H), H(-0.55615234375), H(0.55615234375))
|
||||
t = fma16(m, -m, np.full_like(m, H(0.92724609375)))
|
||||
u = fma16(m, t, np.full_like(m, H(1.375)))
|
||||
e = ((((u.view(np.uint16).astype(np.uint32)) << 5) & 0x7FE0).astype(np.uint16)).view(H)
|
||||
|
||||
def tile(j, tt, half):
|
||||
return e[:, 8 * j + 2 * tt + half]
|
||||
s = {}
|
||||
for tt in range(4):
|
||||
for half in range(2):
|
||||
ev = add(add(add(tile(0, tt, half), tile(2, tt, half)), add(tile(4, tt, half), tile(6, tt, half))),
|
||||
add(tile(8, tt, half), tile(10, tt, half)))
|
||||
od = add(add(add(tile(1, tt, half), tile(3, tt, half)), add(tile(5, tt, half), tile(7, tt, half))),
|
||||
add(tile(9, tt, half), tile(11, tt, half)))
|
||||
s[tt, half] = add(ev, od)
|
||||
out = [add(add(s[0, h_], s[1, h_]), add(s[2, h_], s[3, h_])) for h_ in range(2)]
|
||||
rs = add(out[0], out[1])
|
||||
r = (F(1) / rs.astype(F)).astype(H)
|
||||
return (e * r[:, None]).astype(H)
|
||||
|
||||
|
||||
def act(x):
|
||||
y = np.clip(x, H(-2), H(2)).astype(H)
|
||||
g = add(H(0.5), (y * (H(0.412109375) - (H(0.0810546875) * np.abs(y)).astype(H)).astype(H)).astype(H))
|
||||
return (x * g).astype(H)
|
||||
|
||||
|
||||
MERGE_ORDER = [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 2),
|
||||
(2, 3), (3, 2), (3, 3)]
|
||||
|
||||
|
||||
def mirror(v, n):
|
||||
v = abs(v)
|
||||
return min(v, 2 * n - 2 - v)
|
||||
|
||||
|
||||
def window(w, planes, tex, x0, y0, ex, ey, dbg=None):
|
||||
"""one window: returns (tokens out [64 x 64 codes, arena order], merged [16 x 96 codes, A order],
|
||||
surf112 / surf120 [64 x 3 f16], surf128 contributions)"""
|
||||
border = ([(x, y0 - 1) for x in range(x0, x0 + 8)] + [(x, y0 + 8) for x in range(x0, x0 + 8)]
|
||||
+ [(x0 - 1, y) for y in range(y0, y0 + 8)] + [(x0 + 8, y) for y in range(y0, y0 + 8)])
|
||||
coords = [(x, y) for y in range(y0, y0 + 8) for x in range(x0, x0 + 8)] + border
|
||||
norm, raw = [], []
|
||||
for (x, y) in coords:
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
n_, r_ = input_norm([e4m3(planes[p, yy, xx]).astype(H) for p in (0, 1)], w['gain0'])
|
||||
norm.append(n_)
|
||||
raw.append(r_)
|
||||
X = q8(np.array(norm))[:, INTER64] # 96 x 64 codes, NVIDIA k order
|
||||
res = np.array(raw[:64])
|
||||
dbg = {} if dbg is None else dbg
|
||||
dbg['XQ'], dbg['RES0'] = X, add(res, w['table7'][None, :])
|
||||
R = None
|
||||
for h in range(2):
|
||||
qk = mm(X[:, 32:], w[f'wk{h}b'], mm(X[:, :32], w[f'wk{h}a'], np.zeros((96, 32))))
|
||||
v = mm(X[:, 32:], w[f'wv{h}b'], mm(X[:, :32], w[f'wv{h}a'], np.zeros((96, 32))))
|
||||
Q = q8(qk)[:, inter(32)]
|
||||
dbg[f'QK{h}'], dbg[f'V{h}'] = Q, q8(v)[inter(96)]
|
||||
S = mm(Q[:64], Q.T, w[f'bias{h}'])
|
||||
P = softmax_cubic(S)
|
||||
O = mm(q8(P)[:, inter(96)], q8(v)[inter(96)], np.zeros((64, 32)))
|
||||
dbg[f'S{h}'], dbg[f'P{h}'], dbg[f'O{h}'] = S, q8(P)[:, inter(96)], O
|
||||
C = add(res, w['table7'][None, :]) if h == 0 else R
|
||||
R = mm(q8(O)[:, inter(32)], w[f'wo{h}'], C)
|
||||
dbg[f'R{h}'] = R
|
||||
N = q8((R * (nv_rowsum_sq(R)[:, None] * w['gain14'][None, :]).astype(H)).astype(H))[:, INTER64]
|
||||
acc = add(R, w['table16'][None, :])
|
||||
for b in range(8):
|
||||
hid = act(mm(N, w[f'w1_{b}'], np.repeat(w[f'b1_{b}'][None, :], 64, 0)))
|
||||
acc = mm(q8(hid)[:, inter(32)], w[f'w2_{b}'], acc)
|
||||
dbg['ACC'] = acc
|
||||
tokens = q8(acc)[:, INTER64]
|
||||
Mg = np.zeros((16, 256), np.uint8)
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER):
|
||||
for blk, (dx, dy) in enumerate([(0, 0), (1, 0), (0, 1), (1, 1)]):
|
||||
Mg[r, 64 * blk: 64 * blk + 64] = tokens[(2 * my + dy) * 8 + 2 * mx + dx]
|
||||
merged = q8(mm(Mg, w['wm'], np.repeat(w['bm'][None, :], 16, 0)))[:, inter(96)]
|
||||
head = mm(tokens, w['wh'], np.repeat(w['bh'][None, :], 64, 0))
|
||||
s112, s120 = filter_head(head, tex, x0, y0, ex, ey)
|
||||
return tokens, merged, s112, s120
|
||||
|
||||
|
||||
def filter_head(D, tex, x0, y0, ex, ey, ungated=False):
|
||||
"""surfaces 112 and 120 of the encoders (the gate's split of the filtered value); ungated (the bottleneck enc5):
|
||||
the filtered value itself, for surface 112"""
|
||||
def sig(v):
|
||||
return H(F(1) / (np.exp2(F(v) * F(-1.4426950216293335)) + F(1)))
|
||||
|
||||
def ex2(v):
|
||||
return np.exp2(F(v) * F(1.4426950216293335))
|
||||
out112, out120 = np.zeros((64, 3), H), np.zeros((64, 3), H)
|
||||
for tk in range(64):
|
||||
x, y = x0 + tk % 8, y0 + tk // 8
|
||||
l_ = D[tk].astype(H)
|
||||
m01 = np.maximum(np.maximum(np.maximum(l_[0:2], l_[2:4]), l_[4:6]), l_[6:8])
|
||||
m = max(max(m01[0], m01[1]), l_[8])
|
||||
e = [ex2(H(l_[i] - m)) for i in range(9)]
|
||||
pair = [F(e[2 * k] + e[2 * k + 1]) for k in range(4)]
|
||||
s = F(F(pair[0]) + F(0))
|
||||
for k in (1, 2, 3):
|
||||
s = F(s + pair[k])
|
||||
s = F(s + e[8])
|
||||
r = H(F(1) / s)
|
||||
wgt = [H(H(e[i]) * r) for i in range(9)]
|
||||
|
||||
def cl(v, n):
|
||||
return 1 if v < 0 else (n - 2 if v >= n else v)
|
||||
xm, xc, xp = cl(x - 1, ex), cl(x, ex), cl(x + 1, ex)
|
||||
ym, yc, yp = cl(y - 1, ey), cl(y, ey), cl(y + 1, ey)
|
||||
|
||||
def T(xx, yy):
|
||||
return tex[yy, xx].astype(H)
|
||||
pts = [((xm, ym), (xc, ym)), ((xp, ym), (xm, yc)), ((xc, yc), (xp, yc)), ((xm, yp), (xc, yp))]
|
||||
acc = np.zeros((3, 2), H)
|
||||
for k, (pa, pb) in enumerate(pts):
|
||||
for c in range(3):
|
||||
for h_, pt in enumerate((pa, pb)):
|
||||
acc[c, h_] = H(F(T(*pt)[c]) * F(wgt[2 * k + h_]) + F(acc[c, h_]))
|
||||
f = [H(acc[c, 0] + acc[c, 1]) for c in range(3)]
|
||||
f = [H(F(T(xp, yp)[c]) * F(wgt[8]) + F(f[c])) for c in range(3)]
|
||||
if ungated:
|
||||
out112[tk] = f
|
||||
continue
|
||||
g = [sig(l_[9]), sig(l_[10]), sig(l_[11])]
|
||||
a = [H(g[c] * f[c]) for c in range(3)]
|
||||
out120[tk] = a
|
||||
out112[tk] = [H(f[c] - a[c]) for c in range(3)]
|
||||
return out112, out120
|
||||
|
||||
|
||||
def extract(dump, cap, frame, out):
|
||||
"""the weights, from a phase dump of any block (they do not depend on the block) and the weight image"""
|
||||
z = np.load(dump)
|
||||
|
||||
def B(p):
|
||||
return z[f'p{p}_w0_B']
|
||||
|
||||
def C(p):
|
||||
return f16(z[f'p{p}_w0_C']).astype(H)
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(cap))
|
||||
W = np.frombuffer(c.find('swin_enc1', frame).weights, np.uint8)
|
||||
|
||||
def table(off, n=64):
|
||||
return W[off:off + 2 * n].view(np.float16)
|
||||
w = {'gain0': table(0), 'gain14': table(37120), 'table7': table(36992), 'table16': table(41408)}
|
||||
for h, base in ((0, 1), (1, 8)):
|
||||
w[f'wk{h}a'], w[f'wv{h}a'], w[f'wk{h}b'], w[f'wv{h}b'] = B(base), B(base + 1), B(base + 2), B(base + 3)
|
||||
w[f'bias{h}'] = C(base + 4)
|
||||
w[f'wo{h}'] = B(base + 6)
|
||||
for b in range(8):
|
||||
w[f'w1_{b}'], w[f'b1_{b}'] = B(15 + 2 * b), C(15 + 2 * b)[0]
|
||||
w[f'w2_{b}'] = B(16 + 2 * b)
|
||||
w['wm'], w['bm'] = B(31), C(31)[0]
|
||||
w['wh'], w['bh'] = B(32), C(32)[0]
|
||||
np.savez_compressed(out, **w)
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('weights')
|
||||
ap.add_argument('--extract', metavar='DUMP')
|
||||
ap.add_argument('--blocks', nargs='*', default=['5,5'])
|
||||
o = ap.parse_args()
|
||||
if o.extract:
|
||||
extract(o.extract, o.capture, o.frame, o.weights)
|
||||
return
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(o.capture))
|
||||
l = c.find('swin_enc1', o.frame)
|
||||
ex, ey = (int(v) for v in np.frombuffer(l.args[:8], np.uint32))
|
||||
sx, sy = (int(v) for v in np.frombuffer(l.args[80:84], np.uint16))
|
||||
pre = np.frombuffer(l.pre(l.arena_alloc), np.uint8)
|
||||
post = np.frombuffer(l.post_buffer(c, l.arena_alloc), np.uint8)
|
||||
ptr = {p.offset: p.delta for p in l.pointers}
|
||||
planes = pre[ptr[8]:ptr[8] + 2 * ex * ey * 32].reshape(2, ey, ex, 32)
|
||||
outp = post[ptr[48]:ptr[48] + 2 * ex * ey * 32].reshape(2, ey, ex, 32)
|
||||
mrg = post[ptr[56]:ptr[56] + 3 * (ex // 2) * (ey // 2) * 32].reshape(3, ey // 2, ex // 2, 32)
|
||||
img = {i.offset: i for i in l.images}
|
||||
|
||||
def surf(off):
|
||||
i = img[off]
|
||||
return np.frombuffer(i.path.read_bytes(), np.float16).reshape(i.height, i.width, 4)
|
||||
tex, s112, s120 = surf(88), surf(112), surf(120)
|
||||
w = dict(np.load(o.weights))
|
||||
for blk in o.blocks:
|
||||
bx, by = (int(v) for v in blk.split(','))
|
||||
x0, y0 = 8 * bx - sx, 8 * by - sy
|
||||
tokens, merged, o112, o120 = window(w, planes, tex, x0, y0, ex, ey)
|
||||
tk_ok = sum(np.array_equal(tokens[t], np.concatenate([outp[0, y0 + t // 8, x0 + t % 8],
|
||||
outp[1, y0 + t // 8, x0 + t % 8]]))
|
||||
for t in range(64) if 0 <= x0 + t % 8 < ex and 0 <= y0 + t // 8 < ey)
|
||||
mg_ok = sum(np.array_equal(merged[r], np.concatenate([mrg[p, y0 // 2 + my, x0 // 2 + mx] for p in range(3)]))
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER)
|
||||
if 0 <= x0 // 2 + mx < ex // 2 and 0 <= y0 // 2 + my < ey // 2)
|
||||
inside = [t for t in range(64) if 0 <= x0 + t % 8 < ex and 0 <= y0 + t // 8 < ey]
|
||||
s_ok = sum(np.array_equal(o112[t], s112[y0 + t // 8, x0 + t % 8, :3]) and
|
||||
np.array_equal(o120[t], s120[y0 + t // 8, x0 + t % 8, :3]) for t in inside)
|
||||
print(f'block {bx},{by}: tokens {tk_ok}/{len(inside)}, merged {mg_ok}, surfaces {s_ok}/{len(inside)}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
def pairs_tree(v):
|
||||
"""NVIDIA's reduction over a list: (v0 + v1) + v2 for three, (v0 + v1) + (v2 + v3) for four, then the same
|
||||
recursively on halves rounded up ((v0 + v1) + (v2 + v3)) + v4 for five"""
|
||||
if len(v) == 1:
|
||||
return v[0]
|
||||
if len(v) == 2:
|
||||
return add(v[0], v[1])
|
||||
half = 1 << ((len(v) - 1).bit_length() - 1)
|
||||
return add(pairs_tree(v[:half]), pairs_tree(v[half:]))
|
||||
|
||||
|
||||
def rowsum_sq(x, eps=2.0 ** -13):
|
||||
"""nv_rowsum_sq for any C = 8 n8-tiles: per lane word t the tiles of each parity (tile pairs 2 apart, tree),
|
||||
the parities, then t ^ 1, t ^ 2 and the halves"""
|
||||
sq = (x * x).astype(H)
|
||||
nt = x.shape[1] // 8
|
||||
|
||||
def tile(j, t):
|
||||
return sq[:, 8 * j + 2 * t: 8 * j + 2 * t + 2]
|
||||
h = []
|
||||
for t in range(4):
|
||||
ev = pairs_tree([add(tile(j, t), tile(j + 2, t)) for j in range(0, nt, 4)] if nt % 4 == 0 else
|
||||
[tile(j, t) for j in range(0, nt, 2)])
|
||||
od = pairs_tree([add(tile(j, t), tile(j + 2, t)) for j in range(1, nt, 4)] if nt % 4 == 0 else
|
||||
[tile(j, t) for j in range(1, nt, 2)])
|
||||
h.append(add(ev, od))
|
||||
h1 = [add(h[t], h[t ^ 1]) for t in range(4)]
|
||||
h2 = add(h1[0], h1[2])
|
||||
s = add(add(h2[:, 0], h2[:, 1]), np.full(x.shape[0], H(eps)))
|
||||
return (F(1) / np.sqrt(s.astype(F))).astype(H)
|
||||
Binary file not shown.
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate engine/rr/enc0_m.comp's source: encoder 0 with ptx2rr.py's translation of NVIDIA's per-pixel feature gather
|
||||
(run by wave 0, as NVIDIA runs it in one warp) and the native rest (engine/rr/enc0_m_decl.glsl, enc0_m_tail.glsl).
|
||||
|
||||
usage: gen_enc0_m.py ENC0.generated.comp OUT.comp
|
||||
|
||||
The translation's main is cut at its first workgroup barrier: by then the 10 x 10 pixels' features and the noise grid
|
||||
are in its shared buffer sh and the state surfaces 304 / 1016 are written. Model data derived from the user's DLL is in
|
||||
the translated part; do not commit the output.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
src, out = sys.argv[1:3]
|
||||
lines = open(src).read().split('\n')
|
||||
m = next(i for i, l in enumerate(lines) if l.startswith('void main()'))
|
||||
head = '\n'.join(lines[:m]).replace('layout(local_size_x = 32, local_size_y = 1, local_size_z = 1) in;',
|
||||
'layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in;')
|
||||
body = lines[m + 2:]
|
||||
first = next(i for i, l in enumerate(body) if not re.match(r' (precise |bool |uint64_t |float |uint |int )', l))
|
||||
bar = next(i for i, l in enumerate(body) if l.strip() == 'memoryBarrierShared(); barrier();')
|
||||
decls, prologue = body[:first], body[first:bar]
|
||||
# NVIDIA's warp gathers the 96 key pixels in a loop of three 32-pixel steps, then the four corners: the steps go to
|
||||
# waves 0-2 (the setup before the loop runs in every wave, the corners in wave 0)
|
||||
do_ = next(i for i, l in enumerate(prologue) if l.strip() == 'do')
|
||||
end = next(i for i, l in enumerate(prologue) if l.strip().startswith('} while (again_'))
|
||||
loop = '\n'.join(prologue[do_ - 1:end + 1])
|
||||
inc = re.search(r'(r\d+) = uint\((r\d+) \+ 32u\);\n\s+(p\d+) = \(\1 < 96u\);', loop)
|
||||
counter = inc.group(1)
|
||||
loop = loop.replace(inc.group(0), inc.group(0).replace('+ 32u', '+ 96u'))
|
||||
pre = [l.replace(f'{counter} = uint(0u);', f'{counter} = uint(32u * gl_SubgroupID);') for l in prologue[:do_ - 1]]
|
||||
post = prologue[end + 1:]
|
||||
head = head.replace('shared uint mms[384];', 'shared uint mms[384 * 4];')
|
||||
text = (head + '\n#extension GL_GOOGLE_include_directive : require\n#include "enc0_m_decl.glsl"\n\nvoid main()\n{\n' +
|
||||
'\n'.join(decls) + '\n // the translated feature gather (NVIDIA\'s one warp, its pixel loop split over the waves)\n' +
|
||||
'\n'.join(pre) + '\n if (gl_SubgroupID < 3u) {\n' + loop + '\n }\n if (gl_SubgroupID == 0u) {\n' +
|
||||
'\n'.join(post) + '\n }\n#include "enc0_m_tail.glsl"\n}\n')
|
||||
open(out, 'w').write(text)
|
||||
print(f'{out}: {len(decls)} declaration lines, {len(prologue)} prologue lines')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,298 @@
|
||||
#version 460
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types_float16 : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types_int16 : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
|
||||
// Ray Reconstruction hkpn output stage, preset E (no diamond-wallaby branch).
|
||||
// One workgroup per 16x16 output tile. Stage 1 stages the render-space neighbourhood of
|
||||
// the tile into shared memory (colour log, per-texel weights, anisotropy); stage 2 gathers
|
||||
// a 5x5 elliptical kernel per output pixel and writes the two output surfaces.
|
||||
// Parameter block (binding 0) holds NVIDIA's kernel argument block; word index = byte / 4.
|
||||
|
||||
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
|
||||
|
||||
layout(std430, binding = 0) readonly buffer Params { uint P[]; };
|
||||
layout(std430, binding = 2) buffer Arena { uint A[]; };
|
||||
layout(binding = 3) uniform sampler2D colourTex; // arg 176, 640x360 rgba16f
|
||||
layout(binding = 5) uniform sampler2D exposureTex; // arg 200, 1x1 rgba16f
|
||||
layout(binding = 8) uniform sampler2D albedoA; // arg 280, 640x360 rgba8unorm
|
||||
layout(binding = 9) uniform sampler2D albedoB; // arg 288, 640x360 rgba8unorm
|
||||
layout(binding = 11) uniform sampler2D decTex; // arg 448, 640x384 rgba16f
|
||||
layout(rgba16f, binding = 12) uniform writeonly image2D outLog; // arg 224
|
||||
layout(rgba16f, binding = 13) uniform writeonly image2D outLin; // arg 240
|
||||
|
||||
#define F_LOG2E uintBitsToFloat(0x3FB8AA3Bu)
|
||||
#define F_2LOG2E uintBitsToFloat(0x4038AA3Bu)
|
||||
#define F_LOG10_2 uintBitsToFloat(0x3E9A209Bu)
|
||||
#define F_0_36 uintBitsToFloat(0x3EB851ECu)
|
||||
#define F_100 uintBitsToFloat(0x42C80000u)
|
||||
#define F_1E4 uintBitsToFloat(0x38D1B717u)
|
||||
#define F_0_005 uintBitsToFloat(0x3BA3D70Au)
|
||||
#define F_0_1 uintBitsToFloat(0x3DCCCCCDu)
|
||||
#define F_1_7 uintBitsToFloat(0x3FD9999Au)
|
||||
#define F_0_17 uintBitsToFloat(0x3E2E147Bu)
|
||||
#define F_0_95 uintBitsToFloat(0x3F733333u)
|
||||
#define F_0_6 uintBitsToFloat(0x3F19999Au)
|
||||
#define F_9 uintBitsToFloat(0x41102CB4u)
|
||||
|
||||
// shared tile: 21 entries per row, up to 21x21 entries (render scale <= 1)
|
||||
#define TILE_STRIDE 21
|
||||
// binary16 numbers, stored as such: half the shared-memory traffic of the 5x5 gather
|
||||
shared f16vec4 tLog[TILE_STRIDE * TILE_STRIDE]; // (L_R, L_G, L_B, alpha = 0) log-domain colour
|
||||
shared f16vec4 tSig[TILE_STRIDE * TILE_STRIDE]; // (s0, s1, s2, s3) sigmoid of arena bytes 0..3
|
||||
shared vec4 tAn[TILE_STRIDE * TILE_STRIDE]; // (p0, p1, p2, p3) anisotropy parameters
|
||||
|
||||
float pf(uint idx) { return uintBitsToFloat(P[idx]); }
|
||||
|
||||
// e4m3 byte -> float (exact); e4m3fn NaN for 0x7F/0xFF.
|
||||
float e4m3(uint b) {
|
||||
uint e = (b >> 3u) & 15u;
|
||||
uint m = b & 7u;
|
||||
if (e == 15u && m == 7u) return uintBitsToFloat(0x7FC00000u);
|
||||
float v = (e == 0u) ? float(m) * uintBitsToFloat(0x3B000000u)
|
||||
: (1.0 + float(m) * 0.125) * exp2(float(e) - 7.0);
|
||||
return ((b & 128u) != 0u) ? -v : v;
|
||||
}
|
||||
|
||||
// round to nearest even binary16, returned as f32: the hardware conversion (RNE), kept from being folded back to x by
|
||||
// OR-ing an opaque zero into its bits (RADV folds a register-only f32 -> f16 -> f32)
|
||||
// Beyond the binary16 range NVIDIA's arithmetic keeps a finite rounded value: that case takes the explicit rounding.
|
||||
uint16_t zero16;
|
||||
float f16rnSlow(float x) {
|
||||
if (x == 0.0 || isnan(x) || isinf(x)) return x;
|
||||
int e;
|
||||
frexp(x, e);
|
||||
int ee = max(e - 11, -24);
|
||||
float q = uintBitsToFloat(uint(ee + 127) << 23);
|
||||
return roundEven(x / q) * q;
|
||||
}
|
||||
float f16rn(float x) {
|
||||
if (abs(x) >= 65504.0) return f16rnSlow(x);
|
||||
return float(uint16BitsToFloat16(float16BitsToUint16(float16_t(x)) | zero16));
|
||||
}
|
||||
|
||||
// Sigmoid as evaluated in binary16 on this lowering: r = f16(2^(-x*log2e)), den = f16(1+r),
|
||||
// s = f16(1/den), with one Newton step for small s and the observed fix-ups of r.
|
||||
float sig16(float e) {
|
||||
float x = f16rn(e);
|
||||
float r = f16rn(exp2(-x * F_LOG2E));
|
||||
uint hb = uint(float16BitsToUint16(float16_t(r)));
|
||||
if (hb == 0x1F79u || hb == 0x25CFu) {
|
||||
r = f16rn(r - 0.0009765625);
|
||||
} else if (hb == 0xC13Bu) {
|
||||
r = f16rn(r + 0.00006103515625);
|
||||
} else if (hb == 0xC1EFu) {
|
||||
r = f16rn(r + 0.000030517578125);
|
||||
}
|
||||
float d32 = f16rn(1.0 + r);
|
||||
float rc = 1.0 / d32;
|
||||
float s16 = f16rn(rc);
|
||||
uint sb = uint(float16BitsToUint16(float16_t(s16))) & 0x7FFFu;
|
||||
if (sb != 0u && sb < 0x8Fu) {
|
||||
float ee = fma(-d32, s16, 1.0);
|
||||
s16 = f16rn(fma(rc, ee, s16));
|
||||
}
|
||||
return s16;
|
||||
}
|
||||
|
||||
// tanh as evaluated: polynomial for small |x|, exponential form otherwise.
|
||||
float tanhApprox(float x) {
|
||||
float ax = abs(x);
|
||||
float x2 = x * x;
|
||||
float q = fma(uintBitsToFloat(0x3C80F082u), x2, uintBitsToFloat(0xBD563CAEu));
|
||||
q = fma(q, x2, uintBitsToFloat(0x3E085941u));
|
||||
q = fma(q, x2, uintBitsToFloat(0xBEAAA9EDu));
|
||||
q = fma(q, x2, 0.0);
|
||||
float small = fma(q, x, x);
|
||||
float ex = exp2(ax * F_2LOG2E);
|
||||
float big = fma(1.0 / (ex + 1.0), -2.0, 1.0);
|
||||
if (ax >= F_9) big = 1.0;
|
||||
float bigSigned = uintBitsToFloat((floatBitsToUint(x) & 0x80000000u) | floatBitsToUint(big));
|
||||
return (ax >= F_0_6) ? bigSigned : small;
|
||||
}
|
||||
|
||||
// round half away from zero
|
||||
int roundAway(float v) {
|
||||
return int(trunc(v + ((v < 0.0) ? -0.5 : 0.5)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
zero16 = uint16_t(P[0] >> 24u); // the output width is below 2^24
|
||||
// ---- parameters (byte offsets / 4) ----
|
||||
uint outW = P[0];
|
||||
uint outH = P[1];
|
||||
float sx = pf(6u); // 24: render/out scale x
|
||||
float sy = pf(7u); // 28
|
||||
float ox = pf(10u); // 40: jitter offset x
|
||||
float oy = pf(11u); // 44
|
||||
float expScale = pf(37u); // 148
|
||||
uint decMaxX = P[14] - 1u; // 56: dec0 width
|
||||
uint decMaxY = P[15] - 1u; // 60: dec0 height
|
||||
uint colMaxX = P[35]; // 140: colour/albedo max x
|
||||
uint colMaxY = P[36]; // 144: colour/albedo max y
|
||||
uint arenaBase = P[64]; // 256: arena offset
|
||||
uint arenaPitch = P[4]; // 16: render width (640)
|
||||
|
||||
// ---- tile origin / size in render space ----
|
||||
uvec2 wg = gl_WorkGroupID.xy;
|
||||
float tileX = ox + fma(float(wg.x * 16u) + 0.5, sx, -0.5);
|
||||
float tileY = oy + fma(float(wg.y * 16u) + 0.5, sy, -0.5);
|
||||
int c0 = int(floor(tileX)) - 2;
|
||||
int r0 = int(floor(tileY)) - 2;
|
||||
int tw = int(ceil(16.0 * sx)) + 5;
|
||||
int th = int(ceil(16.0 * sy)) + 5;
|
||||
|
||||
// ---- stage 1: tile load ----
|
||||
uint total = uint(tw * th);
|
||||
for (uint idx = gl_LocalInvocationIndex; idx < total; idx += 256u) {
|
||||
int col = int(idx) % tw;
|
||||
int row = int(idx) / tw;
|
||||
int ix = clamp(c0 + col, 0, int(decMaxX));
|
||||
int iy = clamp(r0 + row, 0, int(decMaxY));
|
||||
|
||||
vec3 d = texelFetch(decTex, ivec2(ix, iy), 0).rgb;
|
||||
int ax = min(ix, int(colMaxX));
|
||||
int ay = min(iy, int(colMaxY));
|
||||
vec3 alb = texelFetch(albedoA, ivec2(ax, ay), 0).rgb
|
||||
+ texelFetch(albedoB, ivec2(ax, ay), 0).rgb;
|
||||
float albSum = (alb.r + alb.g) + alb.b;
|
||||
if (albSum != 0.0) {
|
||||
d.r *= max(alb.r, F_0_005);
|
||||
d.g *= max(alb.g, F_0_005);
|
||||
d.b *= max(alb.b, F_0_005);
|
||||
}
|
||||
vec3 lg;
|
||||
lg.r = f16rn(F_0_36 * (log2(max(fma(d.r, F_100, 1.0), F_1E4)) * F_LOG10_2));
|
||||
lg.g = f16rn(F_0_36 * (log2(max(fma(d.g, F_100, 1.0), F_1E4)) * F_LOG10_2));
|
||||
lg.b = f16rn(F_0_36 * (log2(max(fma(d.b, F_100, 1.0), F_1E4)) * F_LOG10_2));
|
||||
|
||||
uint wordIdx = (arenaBase >> 2u) + uint(arenaPitch * uint(iy) + uint(ix)) * 2u;
|
||||
uint lo = A[wordIdx];
|
||||
uint hi = A[wordIdx + 1u];
|
||||
vec4 sg = vec4(sig16(e4m3(lo & 0xFFu)),
|
||||
sig16(e4m3((lo >> 8u) & 0xFFu)),
|
||||
sig16(e4m3((lo >> 16u) & 0xFFu)),
|
||||
sig16(e4m3(lo >> 24u)));
|
||||
vec4 an = vec4(e4m3(hi & 0xFFu),
|
||||
e4m3((hi >> 8u) & 0xFFu),
|
||||
e4m3((hi >> 16u) & 0xFFu),
|
||||
e4m3(hi >> 24u));
|
||||
|
||||
uint se = uint(row * TILE_STRIDE + col);
|
||||
tLog[se] = f16vec4(lg, 0.0);
|
||||
tSig[se] = f16vec4(sg);
|
||||
tAn[se] = an;
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---- stage 2: per output pixel ----
|
||||
uvec2 pix = gl_GlobalInvocationID.xy;
|
||||
if (pix.x >= outW || pix.y >= outH) return;
|
||||
|
||||
float expo = texelFetch(exposureTex, ivec2(0, 0), 0).r;
|
||||
float e41 = ((expo == 0.0) ? 1.0 : expo) * expScale;
|
||||
|
||||
float fx = (float(pix.x) + 0.5) * sx;
|
||||
float fy = (float(pix.y) + 0.5) * sy;
|
||||
float cx = fx + ox;
|
||||
float cy = fy + oy;
|
||||
|
||||
// colour fetch: log of exposure-scaled colour at the nearest render texel
|
||||
int ux = clamp(roundAway((fx - 0.5) + ox), 0, int(colMaxX));
|
||||
int uy = clamp(roundAway((fy - 0.5) + oy), 0, int(colMaxY));
|
||||
vec3 col = texelFetch(colourTex, ivec2(ux, uy), 0).rgb;
|
||||
vec3 gcol;
|
||||
gcol.r = F_0_36 * (log2(max(fma(col.r * e41, F_100, 1.0), F_1E4)) * F_LOG10_2);
|
||||
gcol.g = F_0_36 * (log2(max(fma(col.g * e41, F_100, 1.0), F_1E4)) * F_LOG10_2);
|
||||
gcol.b = F_0_36 * (log2(max(fma(col.b * e41, F_100, 1.0), F_1E4)) * F_LOG10_2);
|
||||
|
||||
// centre entry, anisotropy
|
||||
float flx = floor(cx);
|
||||
float fly = floor(cy);
|
||||
int ix0 = int(flx) - c0;
|
||||
int iy0 = int(fly) - r0;
|
||||
float dx0 = (flx + 0.5) - cx;
|
||||
float dy0 = (fly + 0.5) - cy;
|
||||
float ks = (sy >= 0.5) ? 1.0 : (0.5 / sy);
|
||||
|
||||
vec4 an = tAn[iy0 * TILE_STRIDE + ix0];
|
||||
float sa = fma(1.0 / (1.0 + exp2(-F_LOG2E * an.x)), F_1_7, F_0_17);
|
||||
float sb = fma(1.0 / (1.0 + exp2(-F_LOG2E * an.y)), F_1_7, F_0_17);
|
||||
float rho = F_0_95 * tanhApprox(an.z);
|
||||
|
||||
float sa2 = sa * sa;
|
||||
float sb2 = sb * sb;
|
||||
float denom = (sa2 * sb2) * (1.0 - rho * rho);
|
||||
float invDenom = -(1.0 / denom);
|
||||
float cross2 = rho * (2.0 * sqrt(sa2 * sb2));
|
||||
|
||||
// 5x5 gather
|
||||
float sumw = 0.0;
|
||||
vec3 accL = vec3(0.0);
|
||||
vec4 accS = vec4(0.0);
|
||||
for (int c = -2; c <= 2; c++) {
|
||||
for (int r = -2; r <= 2; r++) {
|
||||
float dx = ks * (dx0 + float(c));
|
||||
float dy = ks * (dy0 + float(r));
|
||||
uint se = uint((iy0 + r) * TILE_STRIDE + (ix0 + c));
|
||||
precise float t_c = (cross2 * dx) * dy;
|
||||
precise float t_b = dx * (sb2 * dx);
|
||||
precise float t_a = dy * (sa2 * dy);
|
||||
precise float num = (t_c - t_b) - t_a;
|
||||
precise float Q = invDenom * num;
|
||||
float w = exp2((Q * -0.5) * F_LOG2E);
|
||||
sumw += w;
|
||||
vec4 lv = vec4(tLog[se]);
|
||||
vec4 sv = vec4(tSig[se]);
|
||||
accL = fma(vec3(w), lv.rgb, accL);
|
||||
accS = fma(vec4(w), sv, accS);
|
||||
}
|
||||
}
|
||||
|
||||
float inv = 1.0 / sumw;
|
||||
vec3 Lm = accL * inv;
|
||||
float S0 = accS.x * inv;
|
||||
float S1 = accS.y * inv;
|
||||
float S2 = accS.z * inv;
|
||||
|
||||
// blend weight
|
||||
float q = F_0_1 / (sx * sy);
|
||||
float t1 = sumw * q;
|
||||
float t2 = S0 * t1;
|
||||
float t3 = (sx * sy) * t2;
|
||||
float t4 = 4.0 * t3;
|
||||
float t5 = (sy < 0.5) ? t4 : t2;
|
||||
float wgt = (t5 < 0.0) ? 0.0 : ((t5 > 1.0) ? 1.0 : t5);
|
||||
|
||||
float fA = fma(1.0 - S1, wgt, S1);
|
||||
float fB = fma(1.0 - S2, wgt, S2);
|
||||
|
||||
// surface 224 (E): fA * D + (1 - fA) * gcol; D = Lm (f16 tile log channels),
|
||||
// gcol = log of the exposure-scaled colour texel (PTX linear texel fetch path); D.a = 0 so aA = 1 - fA
|
||||
vec3 cA = fma(Lm, vec3(fA), gcol * (1.0 - fA));
|
||||
float aA = 1.0 - fA;
|
||||
float sumA = cA.r + cA.g + cA.b + aA;
|
||||
if (isnan(sumA) || isinf(sumA)) {
|
||||
cA = Lm;
|
||||
aA = 0.0;
|
||||
}
|
||||
imageStore(outLog, ivec2(pix), vec4(f16rn(cA.r), f16rn(cA.g), f16rn(cA.b), f16rn(aA)));
|
||||
|
||||
// surface 240 (E, PTX BB0_83): {R,G,B,1} of lin = (10^(clamp(c)/0.36)-1)/100/e41,
|
||||
// c = fma(Lm, fB, gcol*(1-fB)); c = Lm when the sum is not finite; stored RNE f16
|
||||
vec3 cB = fma(Lm, vec3(fB), gcol * (1.0 - fB));
|
||||
float sumB = cB.r + cB.g + cB.b;
|
||||
if (isnan(sumB) || isinf(sumB)) cB = Lm;
|
||||
cB = clamp(cB, vec3(F_1E4), vec3(3.0));
|
||||
// PTX divides by constants and takes lg2(10) with the hardware approximations at run time; keep the
|
||||
// constants opaque so the compiler cannot fold them with different rounding.
|
||||
uint zero = P[0] >> 24u;
|
||||
float c036 = uintBitsToFloat(0x3EB851ECu | zero);
|
||||
float c100 = uintBitsToFloat(0x42C80000u | zero);
|
||||
float lg10 = log2(uintBitsToFloat(0x41200000u | zero));
|
||||
float rc036 = 1.0 / c036;
|
||||
float rc100 = 1.0 / c100;
|
||||
float rce41 = 1.0 / e41;
|
||||
precise vec3 lin = (exp2((cB * rc036) * lg10) - 1.0) * rc100 * rce41;
|
||||
imageStore(outLin, ivec2(pix), vec4(float(float16_t(lin.r)), float(float16_t(lin.g)), float(float16_t(lin.b)), 1.0));
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# SHA-256 of the PTX modules of the network kernels engine/rr's native shaders (swin_m.comp, dec0_m.comp, enc0_m)
|
||||
# were written against and verified with: nvngx_dlssd.dll 310.7. engine/rr/dll_weights.py DLL --kernels prints a DLL's.
|
||||
cuda_dldn_engine_swin_dec0_diamond_wallaby_kernel f9420970b6e29c1cd100057ba9eb56a0465a95a4aaeaa21dd355605b75160e5b
|
||||
cuda_dldn_engine_swin_dec0_kernel 5e533151fd1d469bb344548b113e67b5fc798db807cebaf627864ce877f01242
|
||||
cuda_dldn_engine_swin_dec1_kernel ac293a9826ea7c8be44168698e79f122d0bb2cb23dbf55999a094876e04ce32f
|
||||
cuda_dldn_engine_swin_dec2_kernel 282d8f093bf343c7a9080c7379ff933443943d503c7dea647535444711494528
|
||||
cuda_dldn_engine_swin_dec3_kernel f2325cc78cac7c3f20d4b3b5d6604b8a350774dd83642da10ab525bd3fd1e5b0
|
||||
cuda_dldn_engine_swin_dec4_kernel 9bb7cfeba4380b023f2a1b2db5fa311edd40d4e4fdce62af2fe9227a83c52213
|
||||
cuda_dldn_engine_swin_enc0_diamond_wallaby_kernel 35a2b205de3feb52dacfd54ebdcb2eda4dc75fe78a181a28687876f9a1d2a17d
|
||||
cuda_dldn_engine_swin_enc0_kernel e0927a196eeffc93996a0c53337fe6fecc8a869145d832a02c59d9e12741db9d
|
||||
cuda_dldn_engine_swin_enc1_kernel c9df416b891e4dddbeb6311ce110b8362c1c7f1d9e9efb2595b9b4ccae155fce
|
||||
cuda_dldn_engine_swin_enc2_kernel 58702ea68b478da919a31cb9052fdb82eba3d7d1ec0d94ef2bb2827686a3b36a
|
||||
cuda_dldn_engine_swin_enc3_kernel 7f07b4cf84adcdc6bfb367c00767b1f124bbdb1956f704a5dbc72f9e1d3fb9ac
|
||||
cuda_dldn_engine_swin_enc4_kernel d1466bfa00c702d1a67820d1ad12e351fc4185d90695b21286c1d9523e772d6e
|
||||
cuda_dldn_engine_swin_enc5_kernel 720c531da5552ff9a0304c8628aea46d9eaeae3c60f9ee0cdb4f94bebfe1a6e0
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack a denoiser stage's weights (engine/rr/enc1_ref.py --extract) for the native stage shaders.
|
||||
|
||||
usage: native_pack.py WEIGHTS.npz OUT.bin OUT.h
|
||||
|
||||
Every FP8 matrix W (K x N, rows in NVIDIA's k order) becomes 16x16 tiles, tile (kt, nt) at
|
||||
256 * (kt * N/16 + nt) bytes, row-major inside the tile (stride 16): a B operand of the cooperative matrix loads.
|
||||
Every f16 matrix is stored as is (row-major); an f16 bias vector (a 1-D entry whose name starts with 'b') as 16
|
||||
equal rows, so that it loads as an accumulator tile. OUT.h lists the byte offset of every entry as a #define,
|
||||
for the shader's include.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def tiles(w):
|
||||
k, n = w.shape
|
||||
assert k % 16 == 0 and n % 16 == 0, w.shape
|
||||
return w.reshape(k // 16, 16, n // 16, 16).transpose(0, 2, 1, 3).reshape(-1)
|
||||
|
||||
|
||||
def main():
|
||||
src, out_bin, out_h = sys.argv[1:4]
|
||||
w = dict(np.load(src))
|
||||
blob = bytearray()
|
||||
defines = []
|
||||
for name in sorted(w):
|
||||
a = w[name]
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if a.dtype == np.uint8:
|
||||
blob += tiles(a).tobytes()
|
||||
elif a.ndim == 1 and name.startswith('b'):
|
||||
blob += np.tile(np.asarray(a, np.float16), (16, 1)).tobytes()
|
||||
else:
|
||||
blob += np.asarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {off}u // {a.dtype} {a.shape}')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write('// generated by engine/rr/native_pack.py\n' + '\n'.join(defines) + '\n')
|
||||
print(f'{len(blob)} bytes, {len(w)} entries')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dump every mma of a denoiser layer as matrices, from a ZLUDA replay of a captured launch: the oracle for porting
|
||||
a layer to native shaders.
|
||||
|
||||
usage: phase_dump.py CAPTURE STAGE FRAME BX,BY OUT.npz [--phases A-B]
|
||||
|
||||
For one thread block, debug stores added to the PTX record every mma's A, B, C and D registers (each mma's last
|
||||
execution) into padding behind the plane arena; the replay runs through ZLUDA (engine/rr/zluda_replay.py). The
|
||||
fragments are then put back together with the m16n8k32 layouts (A row-major 16x32 e4m3, B column-major 32x8 e4m3,
|
||||
C/D 16x8 f16) into one matrix per phase: phase P's mma are grouped into m-tiles by A fragment and into n-tiles by
|
||||
B fragment, k-steps follow the C <- D chains. OUT.npz holds, per phase p and wave w: p{p}_w{w}_A (M x K, e4m3 codes),
|
||||
_B (K x N codes), _C (M x N f16 of the first k-step), _D (M x N f16 of the last).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
sys.path.insert(0, str(HERE))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
|
||||
REG = r'%r\d+'
|
||||
|
||||
|
||||
def kernel_module(kernel):
|
||||
for p in sorted(Path('~/.cache/d4r-rr-corpus').expanduser().glob('*.ptx')):
|
||||
if f'.entry {kernel}(' in p.read_text(errors='replace'):
|
||||
return p
|
||||
raise SystemExit(f'no corpus module for {kernel}')
|
||||
|
||||
|
||||
def mma_statements(lines, start, end):
|
||||
"""(first line, last line, D, A, B, C) of every mma in [start, end)"""
|
||||
out, i = [], start
|
||||
while i < end:
|
||||
if 'mma.sync.aligned.m16n8k32' in lines[i]:
|
||||
j, text = i, ''
|
||||
while True:
|
||||
text += lines[j]
|
||||
if lines[j].rstrip().endswith(';'):
|
||||
break
|
||||
j += 1
|
||||
groups = [re.findall(REG, g) for g in re.findall(r'\{([^}]*)\}', text)]
|
||||
out.append((i + 1, j + 1, *groups[:4]))
|
||||
i = j + 1
|
||||
else:
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def phases_of(mmas, lines):
|
||||
"""maximal runs of mma separated only by blank or comment lines"""
|
||||
phases, cur = [], []
|
||||
for m in mmas:
|
||||
if cur:
|
||||
gap = [l.strip() for l in lines[cur[-1][1]:m[0] - 1]]
|
||||
if any(g and not g.startswith('//') for g in gap):
|
||||
phases.append(cur)
|
||||
cur = []
|
||||
cur.append(m)
|
||||
if cur:
|
||||
phases.append(cur)
|
||||
return phases
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('stage')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('block')
|
||||
ap.add_argument('out')
|
||||
ap.add_argument('--phases', help='first-last (1-based), default all')
|
||||
o = ap.parse_args()
|
||||
# the capture decides the kernel: a preset D capture runs cuda_dldn_engine_swin_{enc0,dec0}_diamond_wallaby_kernel
|
||||
cap = Capture(os.path.expanduser(o.capture))
|
||||
launch = [l for l in cap.launches if f'swin_{o.stage}' in l.kernel][o.frame - 1]
|
||||
kernel = launch.kernel
|
||||
module = kernel_module(kernel)
|
||||
lines = module.read_text().replace('\r', '').split('\n')
|
||||
k = next(i for i, l in enumerate(lines) if f'.entry {kernel}(' in l)
|
||||
e = next((i for i in range(k + 1, len(lines)) if '.entry ' in lines[i]), len(lines))
|
||||
phases = phases_of(mma_statements(lines, k, e), lines)
|
||||
first, last = (int(x) for x in o.phases.split('-')) if o.phases else (1, len(phases))
|
||||
T = launch.block[0] * launch.block[1] * launch.block[2]
|
||||
arena_bytes = len(launch.pre(launch.arena_alloc))
|
||||
# any argument pointing into the arena locates its base (enc0 has none at 8)
|
||||
aptr = next(p for p in launch.pointers if p.alloc == launch.arena_alloc)
|
||||
delta8, off8 = aptr.delta, aptr.offset
|
||||
bx, by = (int(v) for v in o.block.split(','))
|
||||
# one probe slot per register per mma: after the mma, all of A, B, C, D (C read after the mma is still C
|
||||
# unless C and D share registers, which the k-step chains never do for the first step)
|
||||
slots, inserts = [], {}
|
||||
pname = f'{kernel}_param_0'
|
||||
for p in range(first, last + 1):
|
||||
for (l0, l1, D, A, B, C) in phases[p - 1]:
|
||||
code = []
|
||||
for role, regs in (('D', D), ('A', A), ('B', B), ('C', C)):
|
||||
for r in regs:
|
||||
s = len(slots)
|
||||
slots.append((p, l0, role, r))
|
||||
code.append(f'mov.b32 %dv, {r}; mad.wide.u32 %da, %dt, 4, %db; '
|
||||
f'add.s64 %da, %da, {4 * T * s}; @%dq2 st.global.u32 [%da], %dv;')
|
||||
inserts[l1] = ('{ .reg .b64 %da, %db; .reg .b32 %dv, %dt, %dx, %dy; .reg .pred %dq, %dq2;\n'
|
||||
f'mov.u32 %dx, %ctaid.x; mov.u32 %dy, %ctaid.y; setp.eq.u32 %dq, %dx, {bx}; '
|
||||
f'setp.eq.and.u32 %dq2, %dy, {by}, %dq;\n'
|
||||
f'mov.u32 %dt, %tid.z; mov.u32 %dx, %tid.y; mov.u32 %dy, %tid.x; '
|
||||
f'mad.lo.u32 %dt, %dt, {launch.block[1]}, %dx; mad.lo.u32 %dt, %dt, {launch.block[0]}, %dy;\n'
|
||||
f'ld.param.u64 %db, [{pname}+{off8}]; cvta.to.global.u64 %db, %db; '
|
||||
f'add.s64 %db, %db, {arena_bytes - delta8};\n' + '\n'.join(code) + '\n}')
|
||||
out = list(lines)
|
||||
for l1 in sorted(inserts, reverse=True):
|
||||
out[l1:l1] = [inserts[l1]]
|
||||
work = ROOT / 'build/rr' / f'phase-{o.stage}'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
(work / 'probe.ptx').write_text('\n'.join(out))
|
||||
pad = 4 * T * len(slots)
|
||||
env = dict(os.environ, D4R_ZLUDA_WMMA='1', D4R_ZLUDA_WMMA_FP8='1', D4R_ZLUDA_WMMA_FP8_NATIVE='1',
|
||||
D4R_ZLUDA_IMPLICIT_MAX_BLOCK='256')
|
||||
cmd = ['systemd-run', '--user', '--scope', '-q', '-p', 'MemoryMax=24G', '-p', 'MemorySwapMax=0']
|
||||
cmd += [f'--setenv={k_}={v_}' for k_, v_ in env.items() if k_.startswith('D4R_ZLUDA')]
|
||||
r = subprocess.run(cmd + [sys.executable, str(HERE / 'zluda_replay.py'), os.path.expanduser(o.capture),
|
||||
f'swin_{o.stage}' + ('_diamond' if 'diamond' in kernel else ''), str(o.frame), str(work),
|
||||
'--ptx', str(work / 'probe.ptx'),
|
||||
'--pad', str(pad)], capture_output=True, text=True)
|
||||
if 'failed' in r.stdout + r.stderr:
|
||||
sys.exit(r.stdout[-2000:] + r.stderr[-3000:])
|
||||
raw = np.frombuffer((work / 'arena.out.bin').read_bytes()[arena_bytes:], np.uint32).reshape(len(slots), T)
|
||||
val = {(p, l0, role, r): raw[s] for s, (p, l0, role, r) in enumerate(slots)}
|
||||
result = {}
|
||||
waves = T // 32
|
||||
for p in range(first, last + 1):
|
||||
mm = phases[p - 1]
|
||||
# k-step chains: C of a later mma = D of an earlier one
|
||||
dset = {tuple(m[2]): n for n, m in enumerate(mm)}
|
||||
prev = {n: dset.get(tuple(m[5])) for n, m in enumerate(mm)}
|
||||
step = {}
|
||||
for n in range(len(mm)):
|
||||
s_, q = 0, n
|
||||
while prev[q] is not None:
|
||||
q = prev[q]
|
||||
s_ += 1
|
||||
step[n] = s_
|
||||
ksteps = max(step.values()) + 1
|
||||
a_keys = list(dict.fromkeys(tuple(m[3]) for m in mm))
|
||||
b_keys = list(dict.fromkeys(tuple(m[4]) for m in mm))
|
||||
# m-tile of an A fragment: its index among the A fragments of the same k-step
|
||||
a_tile = {}
|
||||
for s_ in range(ksteps):
|
||||
for t_, key in enumerate(dict.fromkeys(tuple(m[3]) for n, m in enumerate(mm) if step[n] == s_)):
|
||||
a_tile[(s_, key)] = t_
|
||||
b_tile = {}
|
||||
for s_ in range(ksteps):
|
||||
for t_, key in enumerate(dict.fromkeys(tuple(m[4]) for n, m in enumerate(mm) if step[n] == s_)):
|
||||
b_tile[(s_, key)] = t_
|
||||
mt = max(t for (s_, _), t in a_tile.items()) + 1
|
||||
nt = max(t for (s_, _), t in b_tile.items()) + 1
|
||||
for w in range(waves):
|
||||
lanes = np.arange(32) + 32 * w
|
||||
A = np.zeros((16 * mt, 32 * ksteps), np.uint8)
|
||||
B = np.zeros((32 * ksteps, 8 * nt), np.uint8)
|
||||
C = np.zeros((16 * mt, 8 * nt), np.uint16)
|
||||
D = np.zeros((16 * mt, 8 * nt), np.uint16)
|
||||
for n, (l0, l1, Dr, Ar, Br, Cr) in enumerate(mm):
|
||||
s_ = step[n]
|
||||
i, j = a_tile[(s_, tuple(Ar))], b_tile[(s_, tuple(Br))]
|
||||
for lane in range(32):
|
||||
g, t = lane >> 2, lane & 3
|
||||
av = [int(val[(p, l0, 'A', r)][lanes[lane]]) for r in Ar]
|
||||
bv = [int(val[(p, l0, 'B', r)][lanes[lane]]) for r in Br]
|
||||
for q in range(4): # A regs: (g, k 4t..), (g+8, 4t..), (g, 16+4t..), (g+8, 16+4t..)
|
||||
row = 16 * i + g + 8 * (q & 1)
|
||||
col = 32 * s_ + 4 * t + 16 * (q >> 1)
|
||||
for byte in range(4):
|
||||
A[row, col + byte] = (av[q] >> (8 * byte)) & 0xff
|
||||
for q in range(2): # B regs: column g, k 4t.. and 16+4t..
|
||||
for byte in range(4):
|
||||
B[32 * s_ + 4 * t + 16 * q + byte, 8 * j + g] = (bv[q] >> (8 * byte)) & 0xff
|
||||
for q in range(2): # C/D regs: row g (+8), columns 2t, 2t+1
|
||||
row = 16 * i + g + 8 * q
|
||||
if s_ == 0:
|
||||
cv = int(val[(p, l0, 'C', Cr[q])][lanes[lane]])
|
||||
C[row, 8 * j + 2 * t] = cv & 0xffff
|
||||
C[row, 8 * j + 2 * t + 1] = cv >> 16
|
||||
if s_ == ksteps - 1:
|
||||
dv = int(val[(p, l0, 'D', Dr[q])][lanes[lane]])
|
||||
D[row, 8 * j + 2 * t] = dv & 0xffff
|
||||
D[row, 8 * j + 2 * t + 1] = dv >> 16
|
||||
for name, mat in (('A', A), ('B', B), ('C', C), ('D', D)):
|
||||
result[f'p{p}_w{w}_{name}'] = mat
|
||||
print(f'phase {p}: {len(mm)} mma, {mt} m-tiles x {nt} n-tiles x {ksteps} k-steps -> '
|
||||
f'{16 * mt}x{8 * nt}x{32 * ksteps}, lines {mm[0][0]}-{mm[-1][1]}')
|
||||
np.savez_compressed(o.out, **result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+729
@@ -0,0 +1,729 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate a GLSL compute shader for one Ray Reconstruction (DLSS-D) PTX kernel.
|
||||
|
||||
python3 engine/rr/ptx2rr.py MODULE.ptx KERNEL OUT.comp [--block X,Y,Z]
|
||||
|
||||
The translation core is vulkan/ptx2glsl.py. This module subclasses its translator and
|
||||
implements the stage contract of engine/rr/README.md: binding 0 = parameter block,
|
||||
binding 1 = weight buffer (argument 40), binding 2 = plane arena, then textures and
|
||||
surfaces in ascending argument offset. One PTX block is one workgroup of X*Y*Z invocations.
|
||||
|
||||
The generated shader is derived from the user's own DLL and is model data: write it to the
|
||||
model/build directory only; do not commit or redistribute it.
|
||||
"""
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
_spec = importlib.util.spec_from_file_location('ptx2glsl', ROOT / 'vulkan' / 'ptx2glsl.py')
|
||||
base = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(base)
|
||||
|
||||
# Workgroup shapes of the 15 Swin/HKPN kernels, from the captured launch manifests.
|
||||
BLOCKS = {
|
||||
'enc0': (32, 1, 1), 'enc1': (32, 1, 1), 'enc2': (32, 1, 4), 'enc3': (32, 1, 4), 'enc4': (32, 1, 8),
|
||||
'enc5': (32, 1, 8), 'dec0': (32, 1, 1), 'dec1': (32, 1, 2), 'dec2': (32, 1, 4), 'dec3': (32, 1, 4),
|
||||
'dec4': (32, 1, 8), 'hkpn': (16, 16, 1),
|
||||
}
|
||||
|
||||
|
||||
def default_block(kernel):
|
||||
m = re.search(r'swin_(enc|dec)(\d)', kernel)
|
||||
key = f'{m.group(1)}{m.group(2)}' if m else ('hkpn' if 'hkpn_output' in kernel else None)
|
||||
if key not in BLOCKS:
|
||||
raise SystemExit(f'no default block shape for {kernel}; pass --block X,Y,Z')
|
||||
return BLOCKS[key]
|
||||
|
||||
|
||||
def braces(tok):
|
||||
s = tok.strip()
|
||||
if not (s.startswith('{') and s.endswith('}')):
|
||||
raise base.Err(f'expected a {{...}} vector operand, got {tok}')
|
||||
return [x.strip() for x in s[1:-1].split(',')]
|
||||
|
||||
|
||||
def mem_shape(o):
|
||||
"""(bytes per element, element count) of ld/st.global modifiers o."""
|
||||
count = 2 if 'v2' in o else 4 if 'v4' in o else 1
|
||||
size = int(re.sub(r'\D', '', o[-1])) // 8
|
||||
if size not in (2, 4):
|
||||
raise base.Err(f'unsupported global access width in {".".join(o)}')
|
||||
return size, count
|
||||
|
||||
|
||||
RR_RUNTIME = r'''
|
||||
float16_t H(uint u) { return uint16BitsToFloat16(uint16_t(u)); }
|
||||
uint HU(float16_t h) { return uint(float16BitsToUint16(h)); }
|
||||
f16vec2 H2(uint u) { return uint16BitsToFloat16(u16vec2(uint16_t(u & 0xffffu), uint16_t(u >> 16))); }
|
||||
uint HU2_(u16vec2 u) { return uint(u.x) | (uint(u.y) << 16); }
|
||||
uint H2U(f16vec2 h) { return HU2_(float16BitsToUint16(h)); }
|
||||
int s16(uint u) { return (int(u) << 16) >> 16; }
|
||||
float rcp_a(float x) { return 1.0 / x; }
|
||||
// x + copysign(0.5, x) rounded toward zero and then truncated: round half away from zero, exactly
|
||||
float round_half_away(float x) { float t = trunc(x); return abs(x - t) >= 0.5 ? t + sign(x) : t; }
|
||||
uint PARAM32(uint o) { return param.w[o >> 2]; }
|
||||
uint64_t PARAM64(uint o) { return uint64_t(param.w[o >> 2]) | (uint64_t(param.w[(o >> 2) + 1u]) << 32); }
|
||||
uint PARAM16(uint o) { return (param.w[o >> 2] >> ((o & 2u) * 8u)) & 0xffffu; }
|
||||
uint PARAM8(uint o) { return (param.w[o >> 2] >> ((o & 3u) * 8u)) & 0xffu; }
|
||||
uint SH32(uint a) { return sh[a >> 2]; }
|
||||
uint SH16(uint a) { return (sh[a >> 2] >> ((a & 2u) * 8u)) & 0xffffu; }
|
||||
uint bfi32(uint ins, uint base, uint pos, uint len)
|
||||
{
|
||||
pos &= 0xffu; len &= 0xffu;
|
||||
if (pos >= 32u) return base;
|
||||
uint mask = len >= 32u ? (0xffffffffu << pos) : (((1u << len) - 1u) << pos);
|
||||
return (~mask & base) | (mask & (ins << pos));
|
||||
}
|
||||
|
||||
// Global memory: weight buffer (binding 1, read-only) and plane arena (binding 2). Addresses are byte offsets.
|
||||
#ifdef OOB_REPORT
|
||||
// debugging (ptx2rr.py --oob-report): per buffer (0 weights read, 1 arena read, 2 arena write) a count and the lowest
|
||||
// and highest out-of-range byte address, in dbg.d[3k .. 3k+2]
|
||||
void OOB(uint k, uint a, uint size) { if (a + 2u > size) { atomicAdd(dbg.d[3u * k], 1u); atomicMin(dbg.d[3u * k + 1u], a); atomicMax(dbg.d[3u * k + 2u], a); } }
|
||||
#define CHECKW(a) OOB(0u, a, uint(wbuf.wh.length()) * 2u)
|
||||
#define CHECKR(a) OOB(1u, a, uint(abuf.ah.length()) * 2u)
|
||||
#define CHECKS(a) OOB(2u, a, uint(abuf.ah.length()) * 2u)
|
||||
#else
|
||||
#define CHECKW(a)
|
||||
#define CHECKR(a)
|
||||
#define CHECKS(a)
|
||||
#endif
|
||||
uint GW16(uint a) { CHECKW(a); return uint(wbuf.wh[a >> 1]); }
|
||||
uint GW32(uint a) { CHECKW(a + 2u); return uint(wbuf.wh[a >> 1]) | (uint(wbuf.wh[(a >> 1) + 1u]) << 16); }
|
||||
uint GA16(uint a) { CHECKR(a); return uint(abuf.ah[a >> 1]); }
|
||||
uint GA32(uint a) { CHECKR(a + 2u); return uint(abuf.ah[a >> 1]) | (uint(abuf.ah[(a >> 1) + 1u]) << 16); }
|
||||
void SA16(uint a, uint v) { CHECKS(a); abuf.ah[a >> 1] = uint16_t(v & 0xffffu); }
|
||||
void SA32(uint a, uint v) { CHECKS(a + 2u); abuf.ah[a >> 1] = uint16_t(v & 0xffffu); abuf.ah[(a >> 1) + 1u] = uint16_t(v >> 16); }
|
||||
|
||||
// Surface stores with .zero out-of-bounds semantics.
|
||||
void sust_p4(writeonly image2D s, int x, int y, vec4 v)
|
||||
{
|
||||
ivec2 n = imageSize(s);
|
||||
if (uint(x) < uint(n.x) && uint(y) < uint(n.y)) imageStore(s, ivec2(x, y), v);
|
||||
}
|
||||
void sust_p1(writeonly image2D s, int x, int y, float v) { sust_p4(s, x, y, vec4(v, 0.0, 0.0, 0.0)); }
|
||||
// Binary stores write the halves' bits. RADV folds float(float16_t(x)) back to x when the half came from a
|
||||
// cvt.rn.f16.f32 in a register, and the image store then truncates x instead (the output stage's history surface
|
||||
// was one binary16 ulp low in half its values, which the next frame's encoder 0 amplifies): OR-ing in a zero the
|
||||
// compiler cannot see (the dispatch's z count is 1) keeps the conversion's rounding.
|
||||
#define SUST_Z (gl_NumWorkGroups.z - 1u)
|
||||
void sust_b2(writeonly image2D s, int x, int y, uint a, uint b)
|
||||
{
|
||||
sust_p4(s, x >> 2, y, vec4(float(H(a | SUST_Z)), float(H(b | SUST_Z)), 0.0, 0.0));
|
||||
}
|
||||
void sust_b4(writeonly image2D s, int x, int y, uint a, uint b, uint c, uint d)
|
||||
{
|
||||
sust_p4(s, x >> 3, y, vec4(float(H(a | SUST_Z)), float(H(b | SUST_Z)), float(H(c | SUST_Z)), float(H(d | SUST_Z))));
|
||||
}
|
||||
|
||||
// shfl.sync.up/down within a 32-lane warp (ZLUDA's SHFL_SYNC_IMPL). in_bounds is the predicate output.
|
||||
uint shfl_dir(uint v, uint delta, uint opts, bool down, out bool in_bounds)
|
||||
{
|
||||
int section_mask = int((opts >> 8) & 31u), warp_end = int(opts & 31u);
|
||||
int self = int(gl_SubgroupInvocationID & 31u);
|
||||
int d = int(delta & 31u);
|
||||
int idx = down ? self + d : self - d;
|
||||
int sub_end = (section_mask & self) | (~section_mask & (down ? warp_end : 0));
|
||||
bool oob = down ? idx > sub_end : idx < sub_end;
|
||||
if (oob) idx = self;
|
||||
in_bounds = !oob;
|
||||
return subgroupShuffle(v, uint(idx) & 31u);
|
||||
}
|
||||
|
||||
// shfl.sync.idx (PTX ISA): j = minLane | (b & ~segmask), in bounds while j <= maxLane, otherwise the lane's own value
|
||||
uint shfl_idx(uint v, uint b, uint opts, out bool in_bounds)
|
||||
{
|
||||
uint mask = (opts >> 8) & 31u, lane = gl_SubgroupInvocationID & 31u;
|
||||
uint maxLane = (lane & mask) | (opts & 31u & ~mask), j = (lane & mask) | (b & 31u & ~mask);
|
||||
in_bounds = j <= maxLane;
|
||||
return subgroupShuffle(v, in_bounds ? j : lane);
|
||||
}
|
||||
// movmatrix.sync.trans.aligned.m8n8.b16 on one b32 fragment register per lane: lane (g = lane>>2, t = lane&3)
|
||||
// holds row g, columns 2t and 2t+1. The transpose gathers (row 2t, col g) and (row 2t+1, col g).
|
||||
uint movm_trans(uint src)
|
||||
{
|
||||
// through the subgroup's mma scratch rather than two shuffles: RADV miscompiled the shuffle form in the large
|
||||
// stages (enc1's movmatrix results were wrong unless a store followed them; ZLUDA replay, 2026-10-11)
|
||||
const uint base = gl_SubgroupID * 384u + 256u;
|
||||
uint lane = gl_SubgroupInvocationID & 31u;
|
||||
uint g = lane >> 2u, t = lane & 3u;
|
||||
// order this operation's writes after every lane's reads of the previous one's results (the same scratch)
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
mms[base + lane] = src;
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
uint w1 = mms[base + 8u * t + (g >> 1u)];
|
||||
uint w2 = mms[base + 8u * t + 4u + (g >> 1u)];
|
||||
uint s = 16u * (g & 1u);
|
||||
return ((w1 >> s) & 0xffffu) | (((w2 >> s) & 0xffffu) << 16);
|
||||
}
|
||||
|
||||
// PTX min/max: a NaN operand yields the other one (GLSL leaves NaN undefined, so the result depended on codegen)
|
||||
float16_t pmin(float16_t a, float16_t b) { return isnan(a) ? b : isnan(b) ? a : min(a, b); }
|
||||
float16_t pmax(float16_t a, float16_t b) { return isnan(a) ? b : isnan(b) ? a : max(a, b); }
|
||||
f16vec2 pmin2(f16vec2 a, f16vec2 b) { return f16vec2(pmin(a.x, b.x), pmin(a.y, b.y)); }
|
||||
f16vec2 pmax2(f16vec2 a, f16vec2 b) { return f16vec2(pmax(a.x, b.x), pmax(a.y, b.y)); }
|
||||
float pminf(float a, float b) { return isnan(a) ? b : isnan(b) ? a : min(a, b); }
|
||||
float pmaxf(float a, float b) { return isnan(a) ? b : isnan(b) ? a : max(a, b); }
|
||||
// e4m3 <-> f16 (PTX cvt semantics; RNE, satfinite; NaN -> 0x7f e4m3 / 0x7e00 f16).
|
||||
uint e4m3_to_h(uint b)
|
||||
{
|
||||
uint e = (b >> 3) & 15u, m = b & 7u, s = (b & 0x80u) << 8;
|
||||
if (e == 15u && m == 7u) return s | 0x7e00u;
|
||||
float f = e == 0u ? ldexp(float(m), -9) : ldexp(float(8u + m), int(max(e, 1u)) - 10);
|
||||
return s | HU(float16_t(f));
|
||||
}
|
||||
#ifdef SOFTWARE_E4M3
|
||||
f16vec2 e4m3x2_to_f16x2(uint v) { return H2(e4m3_to_h(v & 0xffu) | (e4m3_to_h((v >> 8) & 0xffu) << 16)); }
|
||||
#else
|
||||
// the hardware conversion: exact for every non-NaN code (2026-10-12); NaN keeps e4m3_to_h's sign | 0x7e00
|
||||
uint e4m3_to_h_hw(uint b)
|
||||
{
|
||||
if ((b & 0x7fu) == 0x7fu) return ((b & 0x80u) << 8) | 0x7e00u;
|
||||
return uint(float16BitsToUint16(float16_t(uintBitsToFloate4m3EXT(uint8_t(b)))));
|
||||
}
|
||||
f16vec2 e4m3x2_to_f16x2(uint v) { return H2(e4m3_to_h_hw(v & 0xffu) | (e4m3_to_h_hw((v >> 8) & 0xffu) << 16)); }
|
||||
#endif
|
||||
uint h2e4m3(float f, bool relu)
|
||||
{
|
||||
if (isnan(f)) return 0x7fu;
|
||||
if (relu && !(f > 0.0)) return 0u;
|
||||
uint s = f < 0.0 ? 0x80u : 0u;
|
||||
float a = abs(f);
|
||||
if (a >= 448.0) return s | 0x7eu;
|
||||
if (a == 0.0) return s;
|
||||
int ex;
|
||||
frexp(a, ex);
|
||||
int qe = max(ex - 4, -9);
|
||||
uint n = uint(roundEven(ldexp(a, -qe)));
|
||||
uint bits;
|
||||
if (n < 8u) bits = n;
|
||||
else { int p = findMSB(n); bits = (uint(qe + p + 7) << 3) | ((n >> uint(p - 3)) & 7u); }
|
||||
return s | min(bits, 0x7eu);
|
||||
}
|
||||
#ifdef SOFTWARE_E4M3
|
||||
uint f16x2_to_e4m3x2(f16vec2 h, bool relu) { return h2e4m3(float(h.x), relu) | (h2e4m3(float(h.y), relu) << 8); }
|
||||
#else
|
||||
// cvt.rn.satfinite(.relu).e4m3x2.f16x2 on the hardware conversion: round to nearest even after clamping to +-448
|
||||
// (satfinite), NaN to 0x7f. Equal to the software h2e4m3 for all 65536 f16 inputs (2026-10-12) and about two thirds
|
||||
// of a stage's time faster (dec0: 2.85 -> 1.03 ms).
|
||||
uint h2e4m3_hw(float16_t h, bool relu)
|
||||
{
|
||||
if (isnan(h)) return 0x7fu;
|
||||
if (relu) h = h > float16_t(0.0) ? h : float16_t(0.0);
|
||||
return uint(floate4m3BitsToUintEXT(floate4m3_t(clamp(h, float16_t(-448.0), float16_t(448.0)))));
|
||||
}
|
||||
uint f16x2_to_e4m3x2(f16vec2 h, bool relu) { return h2e4m3_hw(h.x, relu) | (h2e4m3_hw(h.y, relu) << 8); }
|
||||
#endif
|
||||
|
||||
// mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16 on the FP8 cooperative matrices, with ZLUDA's lowering of
|
||||
// it (engine/layer_m.comp's TEXL): C enters the first of two 16x16x16 products, the sum stays f32 and is rounded to
|
||||
// binary16 once. The operands pass through the subgroup's own scratch (MMS): NVIDIA's fragments are written in
|
||||
// place (A row-major 16x32, B column-major 32x8 in a 16-column tile, C/D row-major f16 16x16), then loaded as
|
||||
// whole matrices. Lane l = 4g + t holds A rows g and g+8 (k 4t.. and 16+4t..), B column g (same k), C/D row g
|
||||
// and g+8, columns 2t and 2t+1.
|
||||
#define MMA coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define MMB coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define MMF coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define MMH coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
void mma_e4m3_f16(uint a0, uint a1, uint a2, uint a3, uint b0, uint b1, uint c0, uint c1, out uint d0, out uint d1)
|
||||
{
|
||||
const uint base = gl_SubgroupID * 384u, A = base, B = base + 128u, C = base + 256u;
|
||||
const uint lane = gl_SubgroupInvocationID & 31u, g = lane >> 2u, t = lane & 3u;
|
||||
// order this operation's writes after every lane's reads of the previous one's results (the same scratch)
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
mms[A + g * 8u + t] = a0; mms[A + (g + 8u) * 8u + t] = a1;
|
||||
mms[A + g * 8u + 4u + t] = a2; mms[A + (g + 8u) * 8u + 4u + t] = a3;
|
||||
mms[B + g * 8u + t] = b0; mms[B + g * 8u + 4u + t] = b1;
|
||||
mms[B + (g + 8u) * 8u + t] = 0u; mms[B + (g + 8u) * 8u + 4u + t] = 0u;
|
||||
mms[C + g * 8u + t] = c0; mms[C + (g + 8u) * 8u + t] = c1;
|
||||
mms[C + g * 8u + 4u + t] = 0u; mms[C + (g + 8u) * 8u + 4u + t] = 0u;
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
MMA x0, x1; MMB y0, y1; MMH c;
|
||||
coopMatLoad(x0, mms, A, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
coopMatLoad(x1, mms, A + 4u, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
coopMatLoad(y0, mms, B, 8u, gl_CooperativeMatrixLayoutColumnMajor);
|
||||
coopMatLoad(y1, mms, B + 4u, 8u, gl_CooperativeMatrixLayoutColumnMajor);
|
||||
coopMatLoad(c, mms, C, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
MMF acc = coopMatMulAdd(x1, y1, coopMatMulAdd(x0, y0, MMF(c)));
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
coopMatStore(MMH(acc), mms, C, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
d0 = mms[C + g * 8u + t]; d1 = mms[C + (g + 8u) * 8u + t];
|
||||
}
|
||||
// Two m16n8k32 products with the same A fragment (NVIDIA issues every one of them in such adjacent pairs): one 16x16
|
||||
// product, the first's B, C and D in columns 0-7, the second's in columns 8-15.
|
||||
void mma_e4m3_f16_pair(uint a0, uint a1, uint a2, uint a3, uint b0, uint b1, uint c0, uint c1,
|
||||
uint e0, uint e1, uint f0, uint f1, out uint d0, out uint d1, out uint h0, out uint h1)
|
||||
{
|
||||
const uint base = gl_SubgroupID * 384u, A = base, B = base + 128u, C = base + 256u;
|
||||
const uint lane = gl_SubgroupInvocationID & 31u, g = lane >> 2u, t = lane & 3u;
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
mms[A + g * 8u + t] = a0; mms[A + (g + 8u) * 8u + t] = a1;
|
||||
mms[A + g * 8u + 4u + t] = a2; mms[A + (g + 8u) * 8u + 4u + t] = a3;
|
||||
mms[B + g * 8u + t] = b0; mms[B + g * 8u + 4u + t] = b1;
|
||||
mms[B + (g + 8u) * 8u + t] = e0; mms[B + (g + 8u) * 8u + 4u + t] = e1;
|
||||
mms[C + g * 8u + t] = c0; mms[C + (g + 8u) * 8u + t] = c1;
|
||||
mms[C + g * 8u + 4u + t] = f0; mms[C + (g + 8u) * 8u + 4u + t] = f1;
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
MMA x0, x1; MMB y0, y1; MMH c;
|
||||
coopMatLoad(x0, mms, A, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
coopMatLoad(x1, mms, A + 4u, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
coopMatLoad(y0, mms, B, 8u, gl_CooperativeMatrixLayoutColumnMajor);
|
||||
coopMatLoad(y1, mms, B + 4u, 8u, gl_CooperativeMatrixLayoutColumnMajor);
|
||||
coopMatLoad(c, mms, C, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
MMF acc = coopMatMulAdd(x1, y1, coopMatMulAdd(x0, y0, MMF(c)));
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
coopMatStore(MMH(acc), mms, C, 8u, gl_CooperativeMatrixLayoutRowMajor);
|
||||
memoryBarrierShared(); subgroupBarrier();
|
||||
d0 = mms[C + g * 8u + t]; d1 = mms[C + (g + 8u) * 8u + t];
|
||||
h0 = mms[C + g * 8u + 4u + t]; h1 = mms[C + (g + 8u) * 8u + 4u + t];
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def statement_lines(ptx, kernel):
|
||||
"""the source line of every statement of KERNEL, in the order vulkan/ptx2glsl.py's tokenizer yields them"""
|
||||
text = re.sub(r'//[^\n]*', '', ptx.replace('\r', ''))
|
||||
m = re.search(r'\.entry\s+' + re.escape(kernel) + r'\s*\((.*?)\)', text, re.S)
|
||||
start = text.index('{', m.end())
|
||||
depth, k = 0, start
|
||||
while True:
|
||||
if text[k] == '{':
|
||||
depth += 1
|
||||
elif text[k] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
break
|
||||
k += 1
|
||||
lines, i, line = [], start + 1, text.count('\n', 0, start + 1) + 1
|
||||
while i < k:
|
||||
c = text[i]
|
||||
if c.isspace():
|
||||
line += c == '\n'
|
||||
i += 1
|
||||
continue
|
||||
if c in '{}':
|
||||
i += 1
|
||||
continue
|
||||
lm = re.match(r'(\$?[A-Za-z_][\w$]*):', text[i:i + 200])
|
||||
if lm:
|
||||
i += lm.end()
|
||||
continue
|
||||
j = text.index(';', i)
|
||||
lines.append(line)
|
||||
line += text.count('\n', i, j)
|
||||
i = j + 1
|
||||
return lines
|
||||
|
||||
|
||||
def base_helper(name):
|
||||
"""one helper function of vulkan/ptx2glsl.py's runtime, with its leading comment"""
|
||||
lines = base.RUNTIME.split('\n')
|
||||
start = next(i for i, l in enumerate(lines) if re.match(r'\w+ ' + name + r'\(', l))
|
||||
while lines[start - 1].startswith('//'):
|
||||
start -= 1
|
||||
end = start
|
||||
if not lines[start].rstrip().endswith('}'): # one-line helpers end on their own line
|
||||
while lines[end] != '}':
|
||||
end += 1
|
||||
return '\n'.join(lines[start:end + 1]) + '\n'
|
||||
|
||||
|
||||
RR_RUNTIME += base_helper('prmt') + base_helper('shfl_bfly').replace(
|
||||
'return subgroupShuffle(v, (gl_SubgroupInvocationID & 32u) | uint(idx));', 'return subgroupShuffle(v, uint(idx) & 31u);')
|
||||
|
||||
|
||||
class RRT(base.T):
|
||||
def __init__(self, ptx, kernel, block):
|
||||
bx, by, bz = block
|
||||
if bx * by * bz % 32:
|
||||
raise base.Err('block size must be a multiple of the 32-lane subgroup')
|
||||
self.bx, self.by, self.bz = bx, by, bz
|
||||
self.sreg = {
|
||||
'%tid.x': f'(gl_LocalInvocationIndex % {bx}u)',
|
||||
'%tid.y': f'((gl_LocalInvocationIndex / {bx}u) % {by}u)',
|
||||
'%tid.z': f'(gl_LocalInvocationIndex / {bx * by}u)',
|
||||
'%ntid.x': f'{bx}u', '%ntid.y': f'{by}u', '%ntid.z': f'{bz}u',
|
||||
'%ctaid.x': 'gl_WorkGroupID.x', '%ctaid.y': 'gl_WorkGroupID.y', '%ctaid.z': 'gl_WorkGroupID.z',
|
||||
'%nctaid.x': 'gl_NumWorkGroups.x', '%nctaid.y': 'gl_NumWorkGroups.y', '%nctaid.z': 'gl_NumWorkGroups.z',
|
||||
'%laneid': 'gl_SubgroupInvocationID',
|
||||
'WARP_SZ': '32u',
|
||||
}
|
||||
self.origin = {}
|
||||
self.f64 = {} # %fd registers: the kernels only load f64 immediates into them, to round them to f16
|
||||
self.probes = {} # debugging: PTX line -> registers stored to the debug buffer after it executes
|
||||
self.probe_block = (0, 0)
|
||||
self.probe_slots = 0
|
||||
self.oob = False
|
||||
# every register precise, the integer ones too: they carry f16x2 and f32 bit patterns between operations, and
|
||||
# without it the compiler may contract a product and a sum across them depending on scheduling (results then
|
||||
# changed with unrelated code and with the device's robustness features)
|
||||
self.precise_all = True
|
||||
self.stmt_lines = statement_lines(ptx, kernel)
|
||||
self.stmt_index = 0
|
||||
super().__init__(ptx, kernel)
|
||||
self.pair_mma()
|
||||
self.origins()
|
||||
|
||||
def stmt(self, s):
|
||||
line = self.stmt_lines[self.stmt_index] if self.stmt_index < len(self.stmt_lines) else None
|
||||
self.stmt_index += 1
|
||||
super().stmt(s)
|
||||
for reg in self.probes.get(line, ()):
|
||||
# the debug buffer: 32-bit words [slot][local invocation], the last execution of the statement
|
||||
n = self.reg(reg)
|
||||
g = self.regs[n]
|
||||
v = (f'floatBitsToUint({n})' if g == 'float' else f'uint({n})' if g in ('uint', 'uint64_t', 'bool') else
|
||||
f'HU({n})' if g == 'float16_t' else f'H2U({n})')
|
||||
bx, by = self.probe_block
|
||||
self.E(f'if (gl_WorkGroupID.xy == uvec2({bx}u, {by}u)) dbg.d[{self.probe_slots}u * (gl_WorkGroupSize.x * gl_WorkGroupSize.y) + '
|
||||
f'gl_LocalInvocationIndex] = {v};')
|
||||
self.probe_slots += 1
|
||||
|
||||
def pair_mma(self):
|
||||
"""merge adjacent mma statements with the same A fragment and independent operands into one paired product;
|
||||
the second statement becomes a no-op so statement numbering (probes) is unchanged"""
|
||||
stmts = [k for k, (kind, _) in enumerate(self.toks) if kind == 'stmt']
|
||||
n = 0
|
||||
while n + 1 < len(stmts):
|
||||
i, j = stmts[n], stmts[n + 1]
|
||||
x, y = self.toks[i][1], self.toks[j][1]
|
||||
if x.startswith('mma.sync.aligned.m16n8k32') and y.startswith('mma.sync.aligned.m16n8k32'):
|
||||
gx = [braces(t) for t in base.split_operands(x.split(None, 1)[1])]
|
||||
gy = [braces(t) for t in base.split_operands(y.split(None, 1)[1])]
|
||||
if gx[1] == gy[1] and not set(gx[0]) & set(gy[2] + gy[3] + gy[1]):
|
||||
ops = ', '.join('{' + ', '.join(g) + '}' for g in (gx[0], gy[0], gx[1], gx[2], gx[3], gy[2], gy[3]))
|
||||
self.toks[i] = ('stmt', 'mma.pair.e4m3 ' + ops)
|
||||
self.toks[j] = ('stmt', 'mma.paired.noop')
|
||||
n += 2
|
||||
continue
|
||||
n += 1
|
||||
|
||||
# ------------------------------------------------ SREG remap onto the flat workgroup
|
||||
def is_reg(self, tok):
|
||||
return tok.strip() not in self.sreg and super().is_reg(tok)
|
||||
|
||||
def val(self, tok, t):
|
||||
tok = tok.strip()
|
||||
if re.fullmatch(r'(0[xX][0-9a-fA-F]+|-?\d+)[uU]', tok):
|
||||
tok = tok[:-1]
|
||||
tok = re.sub(r'^0X', '0x', tok)
|
||||
if tok in self.sreg:
|
||||
e = self.sreg[tok]
|
||||
return f'int({e})' if t == 's32' else f'uint64_t({e})' if t in ('u64', 'b64') else f'uint({e})'
|
||||
return super().val(tok, t)
|
||||
|
||||
# ------------------------------------------------ global memory origin: weight buffer or arena
|
||||
def origins(self):
|
||||
stmts = []
|
||||
for kind, s in self.toks:
|
||||
if kind != 'stmt' or s.startswith('.'):
|
||||
continue
|
||||
s = re.sub(r'^@!?%p\d+\s+', '', s)
|
||||
parts = s.split(None, 1)
|
||||
stmts.append((parts[0], base.split_operands(parts[1]) if len(parts) > 1 else []))
|
||||
org = self.origin
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for op, a in stmts:
|
||||
if not a or not re.fullmatch(r'%rd\d+', a[0]) or op.startswith(('ld.', 'st.', 'bra')):
|
||||
if op != 'ld.param.u64':
|
||||
continue
|
||||
if op == 'ld.param.u64':
|
||||
# the argument's true offset (the load may go through a register based inside the block)
|
||||
new = {'w' if self.param_off(a[1]) == 40 else 'a'}
|
||||
else:
|
||||
new = set()
|
||||
for x in a[1:]:
|
||||
if re.fullmatch(r'%rd\d+', x):
|
||||
new |= org.get(x, set())
|
||||
cur = org.setdefault(a[0], set())
|
||||
if new - cur:
|
||||
cur |= new
|
||||
changed = True
|
||||
|
||||
def gorigin(self, reg):
|
||||
s = self.origin.get(reg, set())
|
||||
if len(s) != 1:
|
||||
raise base.Err(f'global pointer {reg} has origin {sorted(s)} (need exactly one of w/a)')
|
||||
return next(iter(s))
|
||||
|
||||
def gaddr(self, tok):
|
||||
m = re.fullmatch(r'\[\s*(%rd\d+)\s*(?:\+\s*(\d+))?\s*\]', tok.strip())
|
||||
if not m:
|
||||
raise base.Err(f'unsupported global address {tok}')
|
||||
return m.group(1), int(m.group(2) or 0)
|
||||
|
||||
def gbyte(self, reg, off):
|
||||
return f'uint({self.val(reg, "u64")}) + {off}u'
|
||||
|
||||
def ld(self, o, a):
|
||||
if 'global' not in o:
|
||||
return super().ld(o, a)
|
||||
size, count = mem_shape(o)
|
||||
reg, off = self.gaddr(a[1])
|
||||
fn = 'GW' if self.gorigin(reg) == 'w' else 'GA'
|
||||
dsts = braces(a[0]) if count > 1 else [a[0]]
|
||||
for i, d in enumerate(dsts):
|
||||
self.A(d, f'{fn}{size * 8}({self.gbyte(reg, off + i * size)})', 'u16' if size == 2 else 'u32')
|
||||
|
||||
def st(self, o, a):
|
||||
if 'global' not in o:
|
||||
return super().st(o, a)
|
||||
size, count = mem_shape(o)
|
||||
reg, off = self.gaddr(a[0])
|
||||
if self.gorigin(reg) != 'a':
|
||||
raise base.Err('store through the read-only weight pointer')
|
||||
vals = braces(a[1]) if count > 1 else [a[1]]
|
||||
for i, v in enumerate(vals):
|
||||
if size == 2:
|
||||
self.E(f'SA16({self.gbyte(reg, off + i * size)}, {self.val(v, "u16")});')
|
||||
else:
|
||||
self.E(f'SA32({self.gbyte(reg, off + i * size)}, {self.val(v, "u32")});')
|
||||
|
||||
# ------------------------------------------------ textures and surfaces
|
||||
def handle_off(self, h):
|
||||
off = self.handle.get(h)
|
||||
if off is None:
|
||||
raise base.Err(f'{h} is not a texture or surface handle loaded from the parameter block')
|
||||
return off
|
||||
|
||||
def set_surface(self, off, fmt):
|
||||
old = self.surfaces.get(off)
|
||||
if old is not None and old != fmt:
|
||||
raise base.Err(f'surface {off} used with formats {old} and {fmt}')
|
||||
self.surfaces[off] = fmt
|
||||
|
||||
def tex(self, op, a):
|
||||
o = op.split('.')[1:]
|
||||
dst = braces(a[0])
|
||||
h, c = self.texref(a[1])
|
||||
off = self.handle_off(h)
|
||||
self.textures.add(off)
|
||||
if 'base' in o and o[-1] == 's32':
|
||||
# integer coordinates still go through the texture's address mode in CUDA (clamp for every denoiser
|
||||
# texture); texelFetch does not clamp, and what it returns out of range depends on the device's robustness
|
||||
fetch = (f'texelFetch(T{off}, clamp(ivec2({self.val(c[0], "s32")}, {self.val(c[1], "s32")}), ivec2(0), '
|
||||
f'textureSize(T{off}, 0) - 1), 0)')
|
||||
elif 'base' in o and o[-1] == 'f32':
|
||||
fetch = f'textureLod(T{off}, vec2({self.val(c[0], "f32")}, {self.val(c[1], "f32")}), 0.0)'
|
||||
elif 'level' in o and o[-1] == 'f32':
|
||||
fetch = (f'textureLod(T{off}, vec2({self.val(c[0], "f32")}, {self.val(c[1], "f32")}), '
|
||||
f'{self.val(a[2], "f32")})')
|
||||
else:
|
||||
raise base.Err(f'unsupported texture op {op}')
|
||||
f16 = 'f16' in o
|
||||
self.E(f'{{ vec4 t_ = {fetch};')
|
||||
for i, d in enumerate(dst):
|
||||
self.A(d, f'float16_t(t_[{i}])' if f16 else f't_[{i}]', 'f16' if f16 else 'float')
|
||||
self.E('}')
|
||||
|
||||
def sust(self, op, a):
|
||||
h, c = self.texref(a[0])
|
||||
off = self.handle_off(h)
|
||||
v = braces(a[1])
|
||||
x, y = self.val(c[0], 's32'), self.val(c[1], 's32')
|
||||
S = f'S{off}'
|
||||
if op == 'sust.p.2d.v4.b32.zero':
|
||||
self.set_surface(off, 'rgba16f')
|
||||
self.E(f'sust_p4({S}, {x}, {y}, vec4({", ".join(self.val(q, "f32") for q in v)}));')
|
||||
elif op == 'sust.p.2d.b32.zero':
|
||||
self.set_surface(off, 'r16f')
|
||||
self.E(f'sust_p1({S}, {x}, {y}, {self.val(v[0], "f32")});')
|
||||
elif op == 'sust.b.2d.v2.b16.zero':
|
||||
self.set_surface(off, 'rg16f')
|
||||
self.E(f'sust_b2({S}, {x}, {y}, {", ".join(self.val(q, "u16") for q in v)});')
|
||||
elif op == 'sust.b.2d.v4.b16.zero':
|
||||
self.set_surface(off, 'rgba16f')
|
||||
self.E(f'sust_b4({S}, {x}, {y}, {", ".join(self.val(q, "u16") for q in v)});')
|
||||
else:
|
||||
raise base.Err(f'unsupported surface op {op}')
|
||||
|
||||
# ------------------------------------------------ warp-level ops
|
||||
def mma(self, a):
|
||||
d, x, b, c = (braces(t) for t in a)
|
||||
if not (len(d) == 2 and len(x) == 4 and len(b) == 2 and len(c) == 2):
|
||||
raise base.Err('mma operand shape')
|
||||
args = ', '.join(self.val(r, 'u32') for r in x + b + c)
|
||||
self.E('{ uint md0_, md1_;')
|
||||
self.E(f'mma_e4m3_f16({args}, md0_, md1_);')
|
||||
self.A(d[0], 'md0_', 'u32')
|
||||
self.A(d[1], 'md1_', 'u32')
|
||||
self.E('}')
|
||||
|
||||
def shfl_dir(self, op, a):
|
||||
down = op.split('.')[2] == 'down'
|
||||
d = a[0].split('|')
|
||||
self.E('{ bool ib_;')
|
||||
self.A(d[0], f'shfl_dir({self.val(a[1], "u32")}, {self.val(a[2], "u32")}, {self.val(a[3], "u32")}, '
|
||||
f'{"true" if down else "false"}, ib_)', 'u32')
|
||||
if len(d) > 1:
|
||||
self.A(d[1], 'ib_', 'bool')
|
||||
self.E('}')
|
||||
|
||||
def cvt_to_e4m3(self, op, a):
|
||||
relu = 'relu' in op.split('.')
|
||||
self.A(a[0], f'f16x2_to_e4m3x2({self.val(a[1], "f16x2")}, {"true" if relu else "false"})', 'u16')
|
||||
|
||||
def cvt(self, op, o, a):
|
||||
types = [t for t in o if re.match(r'^(f|s|u|b)\d+(x2)?$', t)]
|
||||
if types[:2] in (['u32', 'u64'], ['s32', 's64'], ['u32', 's64'], ['s32', 'u64']):
|
||||
return self.A(a[0], f'uint({self.val(a[1], "u64")})', 'u32')
|
||||
return super().cvt(op, o, a)
|
||||
|
||||
def f64_op(self, op, a):
|
||||
if op == 'mov.f64' and re.fullmatch(r'0[dD][0-9A-Fa-f]{16}', a[1].strip()):
|
||||
self.f64[a[0].strip()] = struct.unpack('<d', int(a[1].strip()[2:], 16).to_bytes(8, 'little'))[0]
|
||||
return True
|
||||
if op == 'cvt.rn.f16.f64':
|
||||
v = self.f64.get(a[1].strip())
|
||||
if v is None:
|
||||
raise base.Err(f'{a[1]} is not a known f64 constant')
|
||||
bits = int(np.float16(v).view(np.uint16))
|
||||
self.A(a[0], f'{bits}u', 'u16')
|
||||
return True
|
||||
return False
|
||||
|
||||
def op(self, op, a):
|
||||
parts = op.split('.')
|
||||
if '%fd' in ','.join(a) and self.f64_op(op, a):
|
||||
return
|
||||
if op.startswith('mma.sync.aligned.m16n8k32.row.col.f16.e4m3.e4m3.f16'):
|
||||
return self.mma(a)
|
||||
if op == 'mma.pair.e4m3':
|
||||
d, h, x, b, c, e, f = (braces(t) for t in a)
|
||||
args = ', '.join(self.val(r, 'u32') for r in x + b + c + e + f)
|
||||
self.E('{ uint md0_, md1_, mh0_, mh1_;')
|
||||
self.E(f'mma_e4m3_f16_pair({args}, md0_, md1_, mh0_, mh1_);')
|
||||
for reg, v in zip(d + h, ('md0_', 'md1_', 'mh0_', 'mh1_')):
|
||||
self.A(reg, v, 'u32')
|
||||
return self.E('}')
|
||||
if op == 'mma.paired.noop':
|
||||
return
|
||||
if op == 'movmatrix.sync.trans.aligned.m8n8.b16':
|
||||
return self.A(a[0], f'movm_trans({self.val(a[1], "u32")})', 'u32')
|
||||
# PTX clamps shift amounts at the register width (shl/shr.u give 0, shr.s the sign); GLSL leaves them undefined
|
||||
if parts[0] in ('shl', 'shr') and len(parts) == 2 and parts[1] in ('b32', 'u32', 's32'):
|
||||
x, n = self.val(a[1], 's32' if parts[1] == 's32' else 'u32'), self.val(a[2], 'u32')
|
||||
if parts[0] == 'shl':
|
||||
return self.A(a[0], f'(({n}) >= 32u ? 0u : ({x}) << ({n}))', 'u32')
|
||||
if parts[1] == 's32':
|
||||
return self.A(a[0], f'uint(({x}) >> min({n}, 31u))', 'u32')
|
||||
return self.A(a[0], f'(({n}) >= 32u ? 0u : ({x}) >> ({n}))', 'u32')
|
||||
if op == 'shfl.sync.idx.b32':
|
||||
d = a[0].split('|')
|
||||
self.E('{ bool ib_;')
|
||||
self.A(d[0], f'shfl_idx({self.val(a[1], "u32")}, {self.val(a[2], "u32")}, {self.val(a[3], "u32")}, ib_)', 'u32')
|
||||
if len(d) > 1:
|
||||
self.A(d[1], 'ib_', 'bool')
|
||||
return self.E('}')
|
||||
if op in ('shfl.sync.down.b32', 'shfl.sync.up.b32'):
|
||||
return self.shfl_dir(op, a)
|
||||
if parts[0] == 'sust':
|
||||
return self.sust(op, a)
|
||||
if parts[0] == 'tex':
|
||||
return self.tex(op, a)
|
||||
if parts[0] == 'cvt' and [t for t in parts[1:] if t in ('f16x2', 'e4m3x2')] == ['e4m3x2', 'f16x2']:
|
||||
return self.cvt_to_e4m3(op, a)
|
||||
if op == 'neg.f16':
|
||||
return self.A(a[0], f'-{self.val(a[1], "f16")}', 'f16')
|
||||
if parts[0] in ('min', 'max') and 'NaN' not in parts and any(t in parts for t in ('f16', 'f16x2', 'f32')):
|
||||
t = 'f16x2' if 'f16x2' in parts else 'f16' if 'f16' in parts else 'f32'
|
||||
fn = {'f16x2': 'p{}2', 'f16': 'p{}', 'f32': 'p{}f'}[t].format(parts[0])
|
||||
x, y = self.val(a[1], t), self.val(a[2], t)
|
||||
return self.A(a[0], f'{fn}({x}, {y})', 'float' if t == 'f32' else t)
|
||||
if op == 'neg.f16x2':
|
||||
return self.A(a[0], f'-{self.val(a[1], "f16x2")}', 'f16x2')
|
||||
if op == 'abs.f16x2':
|
||||
return self.A(a[0], f'abs({self.val(a[1], "f16x2")})', 'f16x2')
|
||||
if parts[0] == 'set' and parts[2] == 'f16':
|
||||
c = ('(isnan({0}) || isnan({1}))' if parts[1] == 'nan' else
|
||||
'!(isnan({0}) || isnan({1}))' if parts[1] == 'num' else self.cmp(set(parts), True))
|
||||
return self.A(a[0], f'(({c.format(self.val(a[1], "f16"), self.val(a[2], "f16"))}) ? 1.0hf : 0.0hf)', 'f16')
|
||||
return super().op(op, a)
|
||||
|
||||
# ------------------------------------------------ stage contract
|
||||
def preamble(self, w, tex_filter):
|
||||
w('#version 460')
|
||||
w('#extension GL_EXT_shader_explicit_arithmetic_types : require')
|
||||
w('#extension GL_EXT_shader_16bit_storage : require')
|
||||
w('#extension GL_KHR_shader_subgroup_basic : require')
|
||||
w('#extension GL_KHR_shader_subgroup_shuffle : require')
|
||||
w('#extension GL_KHR_cooperative_matrix : require')
|
||||
w('#extension GL_KHR_memory_scope_semantics : require')
|
||||
w('#extension GL_EXT_float_e4m3 : require')
|
||||
w(f'// Generated by engine/rr/ptx2rr.py from {self.kernel}. Model data derived from the user\'s DLL; do not commit.')
|
||||
w(f'layout(local_size_x = {self.bx * self.by * self.bz}, local_size_y = 1, local_size_z = 1) in;')
|
||||
w('layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint w[]; } param;')
|
||||
w('layout(set = 0, binding = 1, std430) readonly buffer WeightBuf { uint16_t wh[]; } wbuf;')
|
||||
w('layout(set = 0, binding = 2, std430) buffer ArenaBuf { uint16_t ah[]; } abuf;')
|
||||
n = 3
|
||||
for off in sorted(self.textures):
|
||||
w(f'layout(set = 0, binding = {n}) uniform sampler2D T{off};')
|
||||
n += 1
|
||||
for off in sorted(self.surfaces):
|
||||
w(f'layout(set = 0, binding = {n}, {self.surfaces[off]}) uniform writeonly image2D S{off};')
|
||||
n += 1
|
||||
w(f'shared uint sh[{max(1, (self.shared_bytes + 3) // 4)}];')
|
||||
if self.probe_slots or self.oob:
|
||||
w(f'layout(set = 0, binding = {n}, std430) buffer Debug {{ uint d[]; }} dbg;')
|
||||
if self.oob:
|
||||
w('#define OOB_REPORT 1')
|
||||
w(f'shared uint mms[{384 * (self.bx * self.by * self.bz // 32)}]; // mma_e4m3_f16 scratch, per subgroup')
|
||||
|
||||
def runtime(self):
|
||||
return RR_RUNTIME
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description='PTX kernel -> GLSL compute shader (Ray Reconstruction stage contract)')
|
||||
ap.add_argument('module')
|
||||
ap.add_argument('kernel')
|
||||
ap.add_argument('out')
|
||||
ap.add_argument('--block', help='workgroup shape X,Y,Z (default: from the kernel table)')
|
||||
ap.add_argument('--surface', action='append', default=[], metavar='OFFSET=FORMAT',
|
||||
help="the GLSL image format of a surface whose stores are formatted (sust.p), when it is not the "
|
||||
"store's own guess, e.g. 144=rgba8_snorm (repeatable)")
|
||||
ap.add_argument('--probe', action='append', default=[], metavar='LINE:%REG,...',
|
||||
help='debugging: store the registers after the statement at that module line executes (last '
|
||||
'execution) for workgroup --probe-block, into a debug buffer bound after the surfaces')
|
||||
ap.add_argument('--probe-block', default='0,0')
|
||||
ap.add_argument('--oob-report', action='store_true', help='debugging: count out-of-range buffer accesses')
|
||||
ns = ap.parse_args()
|
||||
block = tuple(int(x) for x in ns.block.split(',')) if ns.block else default_block(ns.kernel)
|
||||
formats = {int(k): v for k, v in (x.split('=') for x in ns.surface)}
|
||||
src = open(ns.module).read()
|
||||
probes = {}
|
||||
for spec in ns.probe:
|
||||
line, regs = spec.split(':', 1)
|
||||
probes[int(line)] = [r.strip() for r in regs.split(',')]
|
||||
|
||||
def make():
|
||||
t = RRT(src, ns.kernel, block)
|
||||
t.probes = probes
|
||||
t.probe_block = tuple(int(x) for x in ns.probe_block.split(','))
|
||||
t.oob = ns.oob_report
|
||||
return t
|
||||
t = base.build(make, False, False, False)
|
||||
for off, fmt in formats.items():
|
||||
if off in t.surfaces:
|
||||
t.surfaces[off] = fmt
|
||||
open(ns.out, 'w').write(t.emit({}))
|
||||
# the binding order (stage contract): textures, then surfaces, in ascending argument offset
|
||||
Path(ns.out).with_suffix('.json').write_text(json.dumps(
|
||||
{'kernel': ns.kernel, 'block': block, 'textures': sorted(t.textures),
|
||||
'surfaces': [[off, t.surfaces[off]] for off in sorted(t.surfaces)], 'shared': t.shared_bytes,
|
||||
'probes': [[line, reg] for line in sorted(probes) for reg in probes[line]], 'oob': ns.oob_report}) + '\n')
|
||||
sys.stderr.write(f'block={block} textures={sorted(t.textures)} surfaces={t.surfaces} shared={t.shared_bytes}\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print the expression tree that defines a PTX register, followed back through arithmetic, pair packs/unpacks,
|
||||
conversions and warp shuffles down to loads, constants and registers named as leaves.
|
||||
|
||||
usage: ptx_expr.py STAGE LINE %REG [--depth N] [--stop %r1,%r2]
|
||||
|
||||
LINE is a line at or after the register's definition (the search goes backwards from it). Brace-scoped temporaries
|
||||
(`{ .reg .b16 hl, hu; ... }`) are resolved within their block. Shuffles print as `shfl.bfly^k(expr)`: the same
|
||||
expression on the lane `laneid ^ k`.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def module_of(kernel):
|
||||
for p in sorted(Path('~/.cache/d4r-rr-corpus').expanduser().glob('*.ptx')):
|
||||
if f'.entry {kernel}(' in p.read_text(errors='replace'):
|
||||
return p
|
||||
|
||||
|
||||
class Tracer:
|
||||
def __init__(self, lines, start):
|
||||
self.lines = lines
|
||||
self.start = start
|
||||
self.consts = {}
|
||||
self.cache = {}
|
||||
|
||||
def find_def(self, reg, before, block_lo):
|
||||
"""the statement (line index) that last writes REG before line BEFORE"""
|
||||
pat = re.compile(r'(^|[\s{,|])' + re.escape(reg) + r'([\s,|}]|$)')
|
||||
lo = block_lo if not reg.startswith('%') else self.start
|
||||
for i in range(before - 1, lo - 1, -1):
|
||||
s = re.sub(r'//.*', '', self.lines[i]).strip().lstrip('{').strip()
|
||||
s = re.sub(r'^@!?%p\d+\s+', '', s)
|
||||
if not s or s.startswith(('.', '$', '}')):
|
||||
continue
|
||||
parts = s.split(None, 1)
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
rest = parts[1]
|
||||
dsts = ([x.strip() for x in rest[1:rest.index('}')].split(',')] if rest.startswith('{')
|
||||
else [x.strip() for x in rest.split(',')[0].split('|')])
|
||||
if reg in dsts and not parts[0].startswith(('st.', 'sust', 'bra')):
|
||||
return i, parts[0], rest.rstrip(';').strip(), dsts
|
||||
return None
|
||||
|
||||
def block_start(self, line):
|
||||
depth = 0
|
||||
for i in range(line, self.start, -1):
|
||||
depth += self.lines[i].count('}') - self.lines[i].count('{')
|
||||
if depth < 0:
|
||||
return i
|
||||
return self.start
|
||||
|
||||
def expr(self, reg, before, depth, stop, half=None):
|
||||
reg = reg.strip()
|
||||
if re.fullmatch(r'-?\d+|0[fFdD][0-9A-Fa-f]+|0x[0-9a-fA-F]+', reg):
|
||||
return reg
|
||||
if reg in stop or depth <= 0:
|
||||
return reg + (f'.{half}' if half is not None else '')
|
||||
d = self.find_def(reg, before, self.block_start(before - 1))
|
||||
if d is None:
|
||||
return reg + '?'
|
||||
i, op, rest, dsts = d
|
||||
args = [a.strip() for a in re.split(r',(?![^{]*\})', rest)]
|
||||
srcs = args[1:] if not rest.startswith('{') else args[1:]
|
||||
e = lambda r, h=None: self.expr(r, i, depth - 1, stop, h)
|
||||
if op.startswith('mov.b32') and rest.startswith('{'): # unpack: {lo, hi} <- r
|
||||
which = 'lo' if dsts.index(reg) == 0 else 'hi'
|
||||
return e(srcs[0], which)
|
||||
if op.startswith('mov.b32') and srcs and srcs[0].startswith('{'): # pack r <- {lo, hi}
|
||||
lo, hi = [x.strip() for x in srcs[0][1:-1].split(',')]
|
||||
if half == 'lo':
|
||||
return e(lo)
|
||||
if half == 'hi':
|
||||
return e(hi)
|
||||
return f'pack({e(lo)}, {e(hi)})'
|
||||
if op.startswith(('mov.', 'cvt.')) and len(srcs) == 1:
|
||||
inner = e(srcs[0], half)
|
||||
return inner if op.startswith('mov.') else f'{op.split(".")[0]}.{op.split(".")[-2]}({inner})'
|
||||
if op.startswith('shfl.sync.bfly'):
|
||||
return f'shfl^{srcs[1]}({e(srcs[0], half)})'
|
||||
if op.startswith('ld.'):
|
||||
return f'load@L{i + 1}[{srcs[0]}]' + (f'.{half}' if half else '')
|
||||
name = op.split('.')[0]
|
||||
if op.endswith('x2') and half:
|
||||
return f'{name}(' + ', '.join(e(s, half) for s in srcs) + ')'
|
||||
return f'{name}(' + ', '.join(e(s, half) for s in srcs) + ')'
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('stage')
|
||||
ap.add_argument('line', type=int)
|
||||
ap.add_argument('reg')
|
||||
ap.add_argument('--depth', type=int, default=30)
|
||||
ap.add_argument('--stop', default='')
|
||||
o = ap.parse_args()
|
||||
kernel = f'cuda_dldn_engine_swin_{o.stage}_kernel'
|
||||
lines = module_of(kernel).read_text().replace('\r', '').split('\n')
|
||||
k = next(i for i, l in enumerate(lines) if f'.entry {kernel}(' in l)
|
||||
t = Tracer(lines, k)
|
||||
print(t.expr(o.reg, o.line, o.depth, set(filter(None, o.stop.split(',')))))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Which earlier result each phase operand of a dumped layer (engine/rr/phase_dump.py) comes from.
|
||||
usage: relations.py DUMP.npz [WAVE]"""
|
||||
import sys
|
||||
import numpy as np
|
||||
sys.path.insert(0, __import__('os').path.dirname(__file__))
|
||||
from rrmath import f16, q8
|
||||
|
||||
z = np.load(sys.argv[1]); w = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||
phases = sorted({int(k.split('_')[0][1:]) for k in z.files})
|
||||
P = lambda p, n: z[f'p{p}_w{w}_{n}']
|
||||
|
||||
|
||||
def inter(n):
|
||||
"""the C/D -> A reinterpretation within each 32 block: column 4m+j <- 2m+j, 4m+2+j <- 16+2m+j"""
|
||||
perm = []
|
||||
for b in range(n // 32):
|
||||
for m in range(8):
|
||||
perm += [32 * b + 2 * m, 32 * b + 2 * m + 1, 32 * b + 16 + 2 * m, 32 * b + 16 + 2 * m + 1]
|
||||
return perm
|
||||
|
||||
|
||||
def rel(op, M, p):
|
||||
found = []
|
||||
if not M.any():
|
||||
return ['zero']
|
||||
for q in range(p - 1, 0, -1):
|
||||
D = P(q, 'D')
|
||||
if op == 'C':
|
||||
for name, X in (('D', D),):
|
||||
if X.shape == M.shape and np.array_equal(X, M):
|
||||
found.append(f'{name}{q}')
|
||||
continue
|
||||
Q = q8(f16(D))
|
||||
cands = []
|
||||
if Q.shape[1] % 32 == 0:
|
||||
cands += [('q8(D%d)[:, inter]' % q, Q[:, inter(Q.shape[1])])]
|
||||
cands += [('q8(D%d)' % q, Q)]
|
||||
for name, X in cands:
|
||||
for tname, Y in ((name, X), (name + '.T', X.T)):
|
||||
if Y.shape == M.shape and np.array_equal(Y, M):
|
||||
found.append(tname)
|
||||
elif Y.shape[1] == M.shape[1] and Y.shape[0] > M.shape[0] and np.array_equal(Y[:M.shape[0]], M):
|
||||
found.append(tname + f'[:{M.shape[0]}]')
|
||||
if op == 'B' and Y.shape[0] == M.shape[0] and Y.shape[1] > M.shape[1] and np.array_equal(Y[:, :M.shape[1]], M):
|
||||
found.append(tname + f'[:, :{M.shape[1]}]')
|
||||
if found:
|
||||
break
|
||||
return found or ['?']
|
||||
|
||||
|
||||
for p in phases:
|
||||
A, B, C = P(p, 'A'), P(p, 'B'), P(p, 'C')
|
||||
print(f'phase {p:2d} {A.shape[0]}x{B.shape[1]}x{A.shape[1]}: A <- {rel("A", A, p)} B <- {rel("B", B, p)} '
|
||||
f'C <- {rel("C", C, p)}')
|
||||
@@ -0,0 +1,71 @@
|
||||
// The attention core shared by the 32-channel-head stages (engine/rr/enc1_m.comp, dec0_m.comp), included inside the
|
||||
// head loop: this wave's 16 query slots against the window's 96 key slots. Expects KS (K, token slot x 32 FP8
|
||||
// channels in k order) and VS (V^T, 32 channels x 96 token slots) filled for head hd, the bias at W_BIAS0 + HEAD * hd
|
||||
// (enc1_pack: 96 x 64, [key slot][query slot]) and the cubic table at W_CUBIC. Leaves Ob[2]: O^T (32 channels x
|
||||
// this wave's queries) as the out-projection's B operand. RR_KS / RR_VS (+ word offsets RR_KS0 / RR_VS0) name
|
||||
// the arrays when they are not KS and VS.
|
||||
#ifndef RR_KS
|
||||
#define RR_KS KS
|
||||
#define RR_KS0 0u
|
||||
#endif
|
||||
#ifndef RR_VS
|
||||
#define RR_VS VS
|
||||
#define RR_VS0 0u
|
||||
#endif
|
||||
B8 Qb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) coopMatLoad(Qb[c], RR_KS, RR_KS0 + wv * 128u + 4u * c, 8u, CM);
|
||||
// scores S^T (keys x this wave's queries) + bias, then the cubic softmax down each column
|
||||
B8 Pb[6];
|
||||
{
|
||||
float16_t e[6][8];
|
||||
[[unroll]] for (uint kt = 0u; kt < 6u; ++kt) {
|
||||
HAC c;
|
||||
coopMatLoad(c, w16.w, (W_BIAS0 + HEAD * hd) / 2u + kt * 1024u + 16u * wv, 64u, RM);
|
||||
ACC s = ACC(c);
|
||||
A8 k0, k1;
|
||||
coopMatLoad(k0, RR_KS, RR_KS0 + kt * 128u, 8u, RM);
|
||||
coopMatLoad(k1, RR_KS, RR_KS0 + kt * 128u + 4u, 8u, RM);
|
||||
s = coopMatMulAdd(k1, Qb[1], coopMatMulAdd(k0, Qb[0], s)); // rounded once, by the conversion below
|
||||
// the cubic numerator from enc1_pack.cubic_table, indexed by the score's binary16 bits
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) e[kt][i] = hb(uint(w16u.w[W_CUBIC / 2u + bh(float16_t(s[i]))]));
|
||||
}
|
||||
// key slot 16kt + 8hl + i is NVIDIA's column 8j + 2tt + half with j = 4(kt >> 1) + (kt & 1) + 2((i >> 1) & 1),
|
||||
// tt = 2hl + (i >> 2), half = i & 1 (enc1_ref.softmax_cubic's tree)
|
||||
// packed: component h of a pair is column parity h (elements i = 4tq + h, i + 2), so the tree runs on f16x2
|
||||
f16vec2 ep[6][4];
|
||||
[[unroll]] for (uint kt = 0u; kt < 6u; ++kt)
|
||||
[[unroll]] for (uint q = 0u; q < 4u; ++q) ep[kt][q] = f16vec2(e[kt][2u * q], e[kt][2u * q + 1u]);
|
||||
precise f16vec2 st[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) { // pairs q = 2tq (i0) and 2tq + 1 (i1 = i0 + 2)
|
||||
const uint q0 = 2u * tq, q1 = q0 + 1u;
|
||||
precise f16vec2 ev = ((ep[0][q0] + ep[0][q1]) + (ep[2][q0] + ep[2][q1])) + (ep[4][q0] + ep[4][q1]);
|
||||
precise f16vec2 od = ((ep[1][q0] + ep[1][q1]) + (ep[3][q0] + ep[3][q1])) + (ep[5][q0] + ep[5][q1]);
|
||||
st[tq] = ev + od;
|
||||
}
|
||||
precise f16vec2 loc = st[0] + st[1];
|
||||
const uint other = subgroupShuffleXor(packFloat2x16(loc), 16u);
|
||||
precise f16vec2 o = loc + unpackFloat2x16(other);
|
||||
precise float16_t rs = o.x + o.y;
|
||||
// f16(1 / rs) for a binary16 rs: x * m = 1 + d with |d| >= 2^-22 for every binary16 midpoint m, so the f32
|
||||
// reciprocal (within an ulp) already rounds correctly
|
||||
const float16_t r = float16_t(1.0 / float(rs));
|
||||
const f16vec2 r2 = f16vec2(r, r);
|
||||
[[unroll]] for (uint kt = 0u; kt < 6u; ++kt)
|
||||
[[unroll]] for (uint q = 0u; q < 4u; ++q) {
|
||||
precise f16vec2 pv = ep[kt][q] * r2;
|
||||
// a probability is in [0, 1]: the plain conversion, no saturation clamp, both in one instruction
|
||||
{ const fe4m3vec2 t_ = fe4m3vec2(pv); Pb[kt][2u * q] = t_.x; Pb[kt][2u * q + 1u] = t_.y; }
|
||||
}
|
||||
}
|
||||
// O^T = V^T P^T, then the out-projection onto the residual
|
||||
B8 Ob[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC o = ACC(0.0);
|
||||
[[unroll]] for (uint ks = 0u; ks < 3u; ++ks) {
|
||||
A8 v0, v1;
|
||||
coopMatLoad(v0, RR_VS, RR_VS0 + c * 384u + 8u * ks, 24u, RM);
|
||||
coopMatLoad(v1, RR_VS, RR_VS0 + c * 384u + 8u * ks + 4u, 24u, RM);
|
||||
K32(o, v0, Pb[2u * ks], v1, Pb[2u * ks + 1u])
|
||||
}
|
||||
Q8(Ob[c], o)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
"""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)
|
||||
|
||||
@property
|
||||
def weights(self):
|
||||
"""the weight image (argument 40) as NGX uploaded it; 16 zero bytes for a stage without one (the output stage)"""
|
||||
pointer = next((p for p in self.pointers if p.offset == 40), None)
|
||||
return self.pre(pointer.alloc) if pointer is not None else bytes(16)
|
||||
|
||||
@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 if p.offset != 40]
|
||||
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]
|
||||
@@ -0,0 +1,65 @@
|
||||
// The decoders' output filter (engine/rr/dec0_ref.outputs' surface 112), shared by dec1_m.comp and swin_m.comp: included
|
||||
// after the head has been written to OUTF (16 f16 per window token, raster order) and a barrier. Texture 88 (one level
|
||||
// down, bilinear) at the window's 10 x 10 tap positions into TAP; then two threads per token: the softmax of channels
|
||||
// 0-8 over the 3x3 taps, one f16 fma chain per colour channel, blended with texture 96 by the gate (channel 9) plus
|
||||
// texture 104, to surface 112.
|
||||
// texture 88 (one level down) at the window's 10 x 10 full-resolution tap positions (clamped like the filter's
|
||||
// coordinates), through the bilinear sampler at NVIDIA's coordinates: the reference's texture unit does not round
|
||||
// like f32 arithmetic (computing the 1/4, 3/4 blend by hand left ~0.2% of surface 112 one ulp off, capDT 2026-10-12)
|
||||
if (tid < 100u) {
|
||||
const int xi = clampTap(x0 - 1 + int(tid % 10u), ex), yi = clampTap(y0 - 1 + int(tid / 10u), ey);
|
||||
const float iw = uintBitsToFloat(param.p[34]), ih = uintBitsToFloat(param.p[35]);
|
||||
precise float cu = fma(iw, float(xi), iw * 0.5), cv = fma(ih, float(yi), ih * 0.5);
|
||||
const vec4 bl = textureLod(T88, vec2(cu, cv), 0.0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) TAP[tid * 4u + c] = float16_t(bl[c]);
|
||||
}
|
||||
barrier();
|
||||
// two threads per token: half 0 filters channels 0 and 1, half 1 channel 2
|
||||
{
|
||||
const uint t = tid >> 1u, hf = tid & 1u;
|
||||
const int x = x0 + int(t & 7u), y = y0 + int(t >> 3u);
|
||||
if (x < 0 || y < 0 || x >= ex || y >= ey) return;
|
||||
float16_t a[10];
|
||||
[[unroll]] for (uint c = 0u; c < 10u; ++c) a[c] = OUTF[t * 16u + c];
|
||||
// surface 112: softmax of channels 0-8 over the 3x3 taps, one f16 fma chain per channel; the gate (channel 9)
|
||||
// blends it with texture 96; + texture 104
|
||||
const float16_t ma = max(max(max(a[0], a[2]), a[4]), a[6]), mb = max(max(max(a[1], a[3]), a[5]), a[7]);
|
||||
const float16_t mx = max(max(ma, mb), a[8]);
|
||||
float e[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) { precise float16_t d = a[i] - mx; e[i] = exp2(float(d) * 1.4426950216293335); }
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t rr = rcp16(s);
|
||||
float16_t wg[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) wg[i] = h16(r16(e[i]) * float(rr));
|
||||
const uint tx = uint(x - x0), ty = uint(y - y0), c0 = 2u * hf, nc = 2u - hf;
|
||||
float16_t f[2] = float16_t[2](0.0hf, 0.0hf);
|
||||
bool mid = false;
|
||||
[[unroll]] for (uint k = 0u; k < 9u; ++k) {
|
||||
const uint at = ((ty + k / 3u) * 10u + tx + k % 3u) * 4u + c0;
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) f[c] = fma16q(TAP[at + c], wg[k], f[c], mid);
|
||||
}
|
||||
if (mid) { // an f32 result on a binary16 midpoint: redo the chain with the exact fma (rare)
|
||||
f[0] = 0.0hf; f[1] = 0.0hf;
|
||||
for (uint k = 0u; k < 9u; ++k) {
|
||||
const uint at = ((ty + k / 3u) * 10u + tx + k % 3u) * 4u + c0;
|
||||
for (uint c = 0u; c < 2u; ++c) f[c] = fma16(TAP[at + c], wg[k], f[c]);
|
||||
}
|
||||
}
|
||||
const float16_t g = sig(a[9]);
|
||||
precise float16_t om = float16_t(1.0) - g;
|
||||
const vec4 t96 = texelFetch(T96, ivec2(x, y), 0), t104 = texelFetch(T104, ivec2(x, y), 0);
|
||||
float16_t o[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
precise float16_t fg = f[c] * g;
|
||||
float16_t v = fma16(float16_t(t96[c0 + c]), om, fg);
|
||||
const float16_t t4 = float16_t(t104[c0 + c]);
|
||||
if (!isnan(t4)) { precise float16_t vv = v + t4; v = vv; }
|
||||
o[c] = v;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(o[0]), 1u);
|
||||
if (hf == 0u) imageStore(S112, ivec2(x, y), vec4(o[0], o[1], hb(other), 0.0));
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Exact NVIDIA arithmetic helpers for the native Ray Reconstruction stages (engine/rr/*_m.comp, enc1_native.comp).
|
||||
// Requires GL_EXT_shader_explicit_arithmetic_types, GL_EXT_float_e4m3.
|
||||
//
|
||||
// RADV folds a register-only float(float16_t(x)) back to x, so every binary16 rounding whose result is used again goes
|
||||
// through a conversion with an opaque zero (RR_ZERO16, set in main from something the compiler cannot evaluate, e.g.
|
||||
// gl_NumWorkGroups.z - 1) OR-ed into its bits: one instruction more than the plain conversion.
|
||||
uint16_t RR_ZERO16;
|
||||
#if defined(RR_RELAXED)
|
||||
// Relaxed rounding (build_native_model.sh with D4R_RR_RELAXED=1): the accumulators stay f32 between 32-deep steps and
|
||||
// are converted to FP8 in one rounding instead of NVIDIA's two (binary16, then FP8). About 1% of the FP8 codes of a
|
||||
// layer then differ by one step, so a stage no longer reproduces the reference bit for bit; it saves the conversions
|
||||
// that are a quarter of a stage's instructions.
|
||||
float rnd16(float x) { return x; }
|
||||
#elif defined(RR_EXACT_CONVERT)
|
||||
// the module's conversions are decorated NoContraction (engine/rr/spv_exact_convert.py): the driver keeps them
|
||||
float rnd16(float x) { return float(float16_t(x)); }
|
||||
#else
|
||||
float rnd16(float x) { return float(uint16BitsToFloat16(float16BitsToUint16(float16_t(x)) | RR_ZERO16)); }
|
||||
#endif
|
||||
// x rounded to binary16 (nearest even), as f32 / as f16, through a conversion the compiler keeps (see rnd16)
|
||||
float r16(float x) { return rnd16(x); }
|
||||
#if (defined(RR_RELAXED) && RR_RELAXED >= 2) || defined(RR_EXACT_CONVERT)
|
||||
float16_t h16(float x) { return float16_t(x); }
|
||||
#else
|
||||
float16_t h16(float x) { return uint16BitsToFloat16(float16BitsToUint16(float16_t(x)) | RR_ZERO16); }
|
||||
#endif
|
||||
// NVIDIA's k order within a 32-block: slot j holds natural channel inter(j); uninter is the inverse (and also the
|
||||
// arena's channel order: arena slot uninter(k) holds natural channel k)
|
||||
uint inter(uint j) { return (j & ~31u) | (((j & 31u) >> 2u) << 1u) | (j & 1u) | (((j >> 1u) & 1u) << 4u); }
|
||||
uint uninter(uint c) { return (c & ~31u) | (((c & 15u) >> 1u) << 2u) | (c & 1u) | (((c >> 4u) & 1u) << 1u); }
|
||||
int mirror(int v, int n) { v = abs(v); return min(v, 2 * n - 2 - v); }
|
||||
|
||||
// cvt.rn.satfinite.e4m3 (NaN -> 0x7f)
|
||||
floate4m3_t q8(float16_t h)
|
||||
{
|
||||
return isnan(h) ? uintBitsToFloate4m3EXT(uint8_t(0x7fu)) : floate4m3_t(clamp(h, float16_t(-448.0), float16_t(448.0)));
|
||||
}
|
||||
// the same from f32 / f16 values in one saturating hardware conversion (satfinite; for an f32 value that is a binary16
|
||||
// number the result equals the f16 conversion's)
|
||||
floate4m3_t q8s(float x) { floate4m3_t r; saturatedConvertEXT(r, x); return r; }
|
||||
floate4m3_t q8h(float16_t x) { floate4m3_t r; saturatedConvertEXT(r, x); return r; }
|
||||
|
||||
uint code(floate4m3_t v) { return uint(floate4m3BitsToUintEXT(v)); }
|
||||
float16_t hb(uint b) { return uint16BitsToFloat16(uint16_t(b)); }
|
||||
uint bh(float16_t h) { return uint(float16BitsToUint16(h)); }
|
||||
|
||||
// m * x compared with 1, exactly (the fma recovers the product's rounding error): -1, 0 or 1
|
||||
int cmp1(float m, float x)
|
||||
{
|
||||
precise float t = m * x;
|
||||
precise float e = fma(m, x, -t);
|
||||
return t > 1.0 ? 1 : (t < 1.0 ? -1 : (e > 0.0 ? 1 : (e < 0.0 ? -1 : 0)));
|
||||
}
|
||||
// 1 / x of the filters' softmax and gate, rounded to binary16. The reference is the GPU's own reciprocal instruction
|
||||
// (rcp.approx; ZLUDA lowers it to llvm.amdgcn.rcp.f32, which is what 1.0 / x compiles to here), not the correctly
|
||||
// rounded quotient: correcting it left ~0.01% of the filtered surfaces one ulp off (capDT, 2026-10-12).
|
||||
float16_t rcp16(float x) { return h16(1.0 / x); }
|
||||
// binary16 rounding of 1 / sqrt(s) (s > 0)
|
||||
float16_t rsq16(float16_t s16)
|
||||
{
|
||||
const float s = float(s16);
|
||||
float16_t q = h16(inversesqrt(s));
|
||||
#if defined(RR_RELAXED) && RR_RELAXED >= 2
|
||||
return q;
|
||||
#endif
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
const uint b = bh(q);
|
||||
const float lo = (float(q) + float(hb(b - 1u))) * 0.5, hi = (float(q) + float(hb(b + 1u))) * 0.5;
|
||||
const int cl = cmp1(lo * lo, s), ch = cmp1(hi * hi, s);
|
||||
if (cl > 0 || (cl == 0 && (b & 1u) != 0u)) q = hb(b - 1u);
|
||||
else if (ch < 0 || (ch == 0 && (b & 1u) != 0u)) q = hb(b + 1u);
|
||||
}
|
||||
return q;
|
||||
}
|
||||
// fma of binary16 operands with one binary16 rounding, exactly. The product is exact in f32, so an f32 fma rounds the
|
||||
// exact result once; converting that to binary16 rounds twice, which differs only when the f32 value lands exactly
|
||||
// on a binary16 midpoint: then the sum's rounding error (TwoSum, the rare branch) decides the direction.
|
||||
float16_t fma16(float16_t a, float16_t b, float16_t c)
|
||||
{
|
||||
const float fa = float(a), fb = float(b), fc = float(c);
|
||||
precise float s = fma(fa, fb, fc);
|
||||
#if defined(RR_RELAXED) && RR_RELAXED >= 2
|
||||
return float16_t(s);
|
||||
#endif
|
||||
const uint u = floatBitsToUint(s);
|
||||
if ((u & 0x1fffu) == 0x1000u && abs(s) >= 6.103515625e-05) {
|
||||
precise float p = fa * fb;
|
||||
precise float bb = s - p;
|
||||
precise float err = (p - (s - bb)) + (fc - bb);
|
||||
if (err != 0.0) return h16(uintBitsToFloat(err * s > 0.0 ? u + 1u : u - 1u));
|
||||
}
|
||||
return h16(s);
|
||||
}
|
||||
// the same in one f32 fma, rounded once more to binary16: mid is set when the f32 result lies on a binary16 midpoint,
|
||||
// where that double rounding can differ (the caller then redoes the computation with fma16)
|
||||
float16_t fma16q(float16_t a, float16_t b, float16_t c, inout bool mid)
|
||||
{
|
||||
precise float s = fma(float(a), float(b), float(c));
|
||||
#if defined(RR_RELAXED) && RR_RELAXED >= 2
|
||||
return float16_t(s);
|
||||
#endif
|
||||
mid = mid || (floatBitsToUint(s) & 0x1fffu) == 0x1000u;
|
||||
#ifdef RR_EXACT_CONVERT
|
||||
return float16_t(s);
|
||||
#else
|
||||
return hb(bh(float16_t(s)) | uint(RR_ZERO16));
|
||||
#endif
|
||||
}
|
||||
// The saturating FP8 conversion of a whole accumulator, two elements per conversion instruction
|
||||
#define Q8PAIRS(dst, src) [[unroll]] for (int i_ = 0; i_ < 8; i_ += 2) { fe4m3vec2 t_; saturatedConvertEXT(t_, vec2((src)[i_], (src)[i_ + 1])); (dst)[i_] = t_.x; (dst)[i_ + 1] = t_.y; }
|
||||
// q8(act(h)) from the packed table (enc1_pack.act_table; the weight buffer's W8U view at W_ACT)
|
||||
#define ACTQ(h) uintBitsToFloate4m3EXT(w8u.w[W_ACT + bh(h)])
|
||||
float16_t act(float16_t x)
|
||||
{
|
||||
precise float16_t y = clamp(x, float16_t(-2.0), float16_t(2.0));
|
||||
precise float16_t a = float16_t(0.0810546875) * abs(y);
|
||||
precise float16_t b = float16_t(0.412109375) - a;
|
||||
precise float16_t c = y * b;
|
||||
precise float16_t g = float16_t(0.5) + c;
|
||||
precise float16_t r = x * g;
|
||||
return r;
|
||||
}
|
||||
float16_t sig(float16_t v) { return rcp16(exp2(float(v) * -1.4426950216293335) + 1.0); }
|
||||
// the cubic-exponent softmax numerator (enc1_ref.softmax_cubic): a cubic in the clamped scaled score whose binary16
|
||||
// bits, shifted into the exponent field, are the exponential
|
||||
// the same in plain f32 fmas (double rounding); bad is set when an f32 result lies exactly on a binary16 midpoint,
|
||||
// the only case where that differs: the caller then recomputes with cubic_e (subgroupAny, rare)
|
||||
float16_t cubic_e_fast(float16_t x, inout bool bad)
|
||||
{
|
||||
precise float16_t m = clamp(x * float16_t(0.017181396484375), float16_t(-0.55615234375), float16_t(0.55615234375));
|
||||
const float fm = float(m);
|
||||
precise float t = fma(fm, -fm, 0.92724609375);
|
||||
const float tr = rnd16(t);
|
||||
precise float u = fma(fm, tr, 1.375);
|
||||
// t - c and u - 1.375 are exact (Sterbenz), so one more fma gives the rounding error's sign: only an inexact
|
||||
// midpoint is a double rounding
|
||||
precise float et = fma(fm, fm, t - 0.92724609375), eu = fma(fm, -tr, u - 1.375);
|
||||
bad = bad || ((floatBitsToUint(t) & 0x1fffu) == 0x1000u && et != 0.0) || ((floatBitsToUint(u) & 0x1fffu) == 0x1000u && eu != 0.0);
|
||||
return hb((bh(float16_t(u)) << 5u) & 0x7FE0u);
|
||||
}
|
||||
float16_t cubic_e(float16_t x)
|
||||
{
|
||||
precise float16_t m = clamp(x * float16_t(0.017181396484375), float16_t(-0.55615234375), float16_t(0.55615234375));
|
||||
const float16_t t = fma16(m, -m, float16_t(0.92724609375));
|
||||
const float16_t u = fma16(m, t, float16_t(1.375));
|
||||
return hb((bh(u) << 5u) & 0x7FE0u);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write the game inputs of a capture's denoiser frames for engine/replay_rr.cpp (d4r-rr).
|
||||
|
||||
usage: rr_inputs.py CAPTURE OUT_DIR [--frames N] [--no-reset]
|
||||
|
||||
The inputs are enc0's textures (colour 160, motion 168, normals 184, roughness 192, the albedos 232 and 240, depth
|
||||
512); the frame settings come from enc0's argument block: jitter (320), motion scale (344, in output pixels per
|
||||
render pixel), depth inversion (528) and the camera, whose rotation-times-projection (596) and projection (660) give
|
||||
back a world-to-view rotation and the view-to-clip matrix that reproduce the block. The harness passes reset on every
|
||||
frame (scripts/run_rr_engine_compare.sh); --no-reset clears it after the first frame.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
|
||||
OPTIONAL = {'hitdistance': 248, 'disocclusion': 296}
|
||||
INPUTS = {'color': 160, 'motion': 168, 'normals': 184, 'roughness': 192, 'diffuse': 232, 'specular': 240, 'depth': 512}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('out', type=Path)
|
||||
ap.add_argument('--frames', type=int, default=3)
|
||||
ap.add_argument('--no-reset', action='store_true')
|
||||
o = ap.parse_args()
|
||||
cap = Capture(o.capture)
|
||||
o.out.mkdir(parents=True, exist_ok=True)
|
||||
lines = []
|
||||
for f in range(1, o.frames + 1):
|
||||
enc0 = cap.find('swin_enc0', f)
|
||||
a = enc0.args
|
||||
u32 = lambda off: struct.unpack_from('<I', a, off)[0]
|
||||
f32 = lambda off: struct.unpack_from('<f', a, off)[0]
|
||||
mat = lambda off: np.array(struct.unpack_from('<16f', a, off), np.float64).reshape(4, 4)
|
||||
rw, rh, ow, oh = u32(144), u32(148), u32(472), u32(476)
|
||||
if f == 1:
|
||||
lines.append(f'size {rw} {rh} {ow} {oh}')
|
||||
images = {i.offset: i for i in enc0.images}
|
||||
for name, off in INPUTS.items():
|
||||
(o.out / f'{f}-{name}.bin').write_bytes(images[off].path.read_bytes())
|
||||
for name, off in OPTIONAL.items(): # DLSSD.SpecularHitDistance, DLSS.DisocclusionMask when the game passed them
|
||||
if off in images:
|
||||
(o.out / f'{f}-{name}.bin').write_bytes(images[off].path.read_bytes())
|
||||
clip = mat(660)
|
||||
view = mat(596) @ np.linalg.inv(clip)
|
||||
view[3, :3] = 0.0
|
||||
jx, jy = f32(320), f32(324)
|
||||
mvx, mvy = f32(344) * rw / ow, f32(348) * rh / oh
|
||||
reset = 1 if f == 1 or not o.no_reset else 0
|
||||
values = ' '.join(f'{v:.9g}' for v in list(view.flatten()) + list(clip.flatten()))
|
||||
lines.append(f'frame {f} {jx:.9g} {jy:.9g} {mvx:.9g} {mvy:.9g} 1 {reset} {u32(528)} {values}')
|
||||
(o.out / 'frames.txt').write_text('\n'.join(lines) + '\n')
|
||||
print(f'{o.frames} frames of {o.capture} in {o.out}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Shared numeric helpers for the Ray Reconstruction port: e4m3 codes, f16, and ZLUDA's m16n8k32 accumulation."""
|
||||
import numpy as np
|
||||
|
||||
_E4M3 = np.zeros(256)
|
||||
for _c in range(256):
|
||||
_s, _e, _m = _c >> 7, (_c >> 3) & 15, _c & 7
|
||||
_v = np.nan if (_e == 15 and _m == 7) else (_m * 2.0 ** -9 if _e == 0 else (1 + _m / 8) * 2.0 ** (_e - 7))
|
||||
_E4M3[_c] = -_v if _s else _v
|
||||
|
||||
|
||||
def e4m3(codes):
|
||||
return _E4M3[np.asarray(codes, np.uint8)]
|
||||
|
||||
|
||||
def f16(bits):
|
||||
return np.asarray(bits, np.uint16).view(np.float16).astype(np.float64)
|
||||
|
||||
|
||||
def f16bits(x):
|
||||
return np.asarray(x, np.float64).astype(np.float16).view(np.uint16)
|
||||
|
||||
|
||||
def mma_k32(a_codes, b_codes, c_f16bits, ksteps=None):
|
||||
"""D = A.B + C with ZLUDA's lowering: per 32-deep k-step, f32 sum of the exact products and C, one f16 rounding;
|
||||
the next k-step takes that f16 value as C. (f64 stands in for f32: the products and short sums are exact.)"""
|
||||
a, b = e4m3(a_codes), e4m3(b_codes)
|
||||
acc = f16(c_f16bits)
|
||||
for k in range(0, a.shape[1], 32):
|
||||
acc = (a[:, k:k + 32] @ b[k:k + 32, :] + acc).astype(np.float32).astype(np.float16).astype(np.float64)
|
||||
return f16bits(acc)
|
||||
|
||||
# cvt.rn.satfinite.e4m3 of every f16 bit pattern (the hardware conversion, exhaustively equal to PTX's semantics;
|
||||
# NaN -> 0x7f), see engine/rr/ptx2rr.py h2e4m3_hw
|
||||
import os as _os
|
||||
_Q8 = np.load(_os.path.join(_os.path.dirname(__file__), 'f16_to_e4m3.npy'))
|
||||
|
||||
|
||||
def q8(x_f16):
|
||||
"""f16 values (float array) -> e4m3 codes"""
|
||||
return _Q8[np.asarray(x_f16, np.float64).astype(np.float16).view(np.uint16)]
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Decorate every float conversion of a SPIR-V module NoContraction, so the driver keeps it.
|
||||
|
||||
usage: spv_exact_convert.py IN.spv [OUT.spv]
|
||||
|
||||
RADV folds float(float16_t(x)) back to x unless the conversions are marked exact; glslang's `precise` does not reach
|
||||
conversions. With the decoration a binary16 rounding is two instructions (engine/rr/rr_exact.glsl, RR_EXACT_CONVERT)
|
||||
instead of three with an opaque zero OR-ed into the bits.
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
OP_DECORATE, OP_FCONVERT, NO_CONTRACTION = 71, 115, 42
|
||||
|
||||
|
||||
def main():
|
||||
src = sys.argv[1]
|
||||
dst = sys.argv[2] if len(sys.argv) > 2 else src
|
||||
data = open(src, 'rb').read()
|
||||
words = list(struct.unpack(f'<{len(data) // 4}I', data))
|
||||
if words[0] != 0x07230203:
|
||||
sys.exit(f'{src}: not SPIR-V')
|
||||
insts, i = [], 5
|
||||
while i < len(words):
|
||||
count = words[i] >> 16
|
||||
insts.append((words[i] & 0xffff, words[i:i + count]))
|
||||
i += count
|
||||
ids = [w[2] for op, w in insts if op == OP_FCONVERT]
|
||||
first_type = next(k for k, (op, _) in enumerate(insts) if 19 <= op <= 39) # annotations end where types begin
|
||||
out = list(words[:5])
|
||||
for k, (op, w) in enumerate(insts):
|
||||
if k == first_type:
|
||||
for result in ids:
|
||||
out += [3 << 16 | OP_DECORATE, result, NO_CONTRACTION]
|
||||
out += w
|
||||
open(dst, 'wb').write(struct.pack(f'<{len(out)}I', *out))
|
||||
print(f'{dst}: {len(ids)} conversions marked exact')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Give a compute SPIR-V module PTX's floating-point environment: RoundingModeRTE for 16- and 32-bit results and
|
||||
DenormPreserve for 16-bit values (SPV_KHR_float_controls, core in SPIR-V 1.4).
|
||||
|
||||
usage: spv_float_controls.py IN.spv [OUT.spv]
|
||||
|
||||
Without these execution modes the rounding of float conversions is the driver's choice, where every cvt.rn of
|
||||
NVIDIA's kernels rounds to nearest even. glslang keeps only one execution mode per mode id, so the modes are inserted
|
||||
into the compiled module instead.
|
||||
"""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
OP_EXTENSION, OP_CAPABILITY, OP_ENTRY_POINT, OP_EXECUTION_MODE = 10, 17, 15, 16
|
||||
CAP_DENORM_PRESERVE, CAP_ROUNDING_RTE = 4464, 4467
|
||||
MODES = ((4462, 16), (4462, 32), (4459, 16)) # RoundingModeRTE 16/32, DenormPreserve 16
|
||||
EXTENSION = 'SPV_KHR_float_controls'
|
||||
|
||||
|
||||
def instructions(words):
|
||||
i = 5
|
||||
while i < len(words):
|
||||
count = words[i] >> 16
|
||||
yield words[i] & 0xffff, words[i:i + count]
|
||||
i += count
|
||||
|
||||
|
||||
def string_words(text):
|
||||
data = text.encode() + b'\0'
|
||||
data += b'\0' * (-len(data) % 4)
|
||||
return list(struct.unpack(f'<{len(data) // 4}I', data))
|
||||
|
||||
|
||||
def patch(words):
|
||||
insts = list(instructions(words))
|
||||
caps = {w[1] for op, w in insts if op == OP_CAPABILITY}
|
||||
has_ext = any(op == OP_EXTENSION and EXTENSION.encode() in struct.pack(f'<{len(w) - 1}I', *w[1:]) for op, w in insts)
|
||||
entry = next(w[2] for op, w in insts if op == OP_ENTRY_POINT)
|
||||
modes = {(w[2], w[3]) for op, w in insts if op == OP_EXECUTION_MODE and w[1] == entry and len(w) > 3}
|
||||
last_cap = max(i for i, (op, _) in enumerate(insts) if op == OP_CAPABILITY)
|
||||
first_mode = min(i for i, (op, _) in enumerate(insts) if op == OP_EXECUTION_MODE)
|
||||
out = list(words[:5])
|
||||
for i, (op, w) in enumerate(insts):
|
||||
if i == first_mode:
|
||||
for mode, width in MODES:
|
||||
if (mode, width) not in modes:
|
||||
out += [4 << 16 | OP_EXECUTION_MODE, entry, mode, width]
|
||||
out += w
|
||||
if i == last_cap:
|
||||
for cap in (CAP_DENORM_PRESERVE, CAP_ROUNDING_RTE):
|
||||
if cap not in caps:
|
||||
out += [2 << 16 | OP_CAPABILITY, cap]
|
||||
if not has_ext:
|
||||
name = string_words(EXTENSION)
|
||||
out += [(1 + len(name)) << 16 | OP_EXTENSION, *name]
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
src = sys.argv[1]
|
||||
dst = sys.argv[2] if len(sys.argv) > 2 else src
|
||||
data = open(src, 'rb').read()
|
||||
words = list(struct.unpack(f'<{len(data) // 4}I', data))
|
||||
if words[0] != 0x07230203:
|
||||
sys.exit(f'{src}: not SPIR-V')
|
||||
out = patch(words)
|
||||
open(dst, 'wb').write(struct.pack(f'<{len(out)}I', *out))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,471 @@
|
||||
#version 460
|
||||
// Native Swin stage of the DLSS-D (preset E) denoiser, register-resident, for any stage of engine/rr/swin_ref.STAGES
|
||||
// (enc2-enc5, dec2-dec4; enc1_m.comp / dec1_m.comp are the hand-written C = 64 instances it generalises). The stage's
|
||||
// shape and weight offsets come from the include generated by engine/rr/swin_pack.py:
|
||||
// KIND 0 encoder (tokens from the arena, 2x2 merge to CX channels, enc1's gated filter; CX = 0: the bottleneck,
|
||||
// ungated filter, no merge), 1 decoder (expand of the CX channels one level down + skip, dec0's filter)
|
||||
// C channels (KC = C / 32 planes, MT = C / 16 tiles), NH heads of 32 channels, C / 8 MLP branches
|
||||
// The arithmetic is engine/rr/swin_ref.py's; enc1_m.comp's header explains the transposed products and slot orders.
|
||||
// One workgroup per 8x8-token window, four waves, wave w owning query slots 16w..16w+15.
|
||||
#extension GL_KHR_cooperative_matrix : require
|
||||
#extension GL_KHR_memory_scope_semantics : require
|
||||
#extension GL_EXT_shader_explicit_arithmetic_types : require
|
||||
#extension GL_EXT_shader_16bit_storage : require
|
||||
#extension GL_EXT_shader_8bit_storage : require
|
||||
#extension GL_EXT_float_e4m3 : require
|
||||
#extension GL_KHR_shader_subgroup_basic : require
|
||||
#extension GL_KHR_shader_subgroup_shuffle : require
|
||||
#extension GL_EXT_control_flow_attributes : require
|
||||
#extension GL_GOOGLE_include_directive : require
|
||||
layout(local_size_x = 128) in;
|
||||
#include "swin_stage.h"
|
||||
#include "rr_exact.glsl"
|
||||
|
||||
#define KC (C / 32)
|
||||
#define MT (C / 16)
|
||||
#define NB (C / 8)
|
||||
|
||||
layout(set = 0, binding = 0, std430) readonly buffer ParamBlock { uint p[]; } param;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8 { floate4m3_t w[]; } w8;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16 { float16_t w[]; } w16;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W16U { uint16_t w[]; } w16u;
|
||||
layout(set = 0, binding = 1, std430) readonly buffer W8U { uint8_t w[]; } w8u;
|
||||
layout(set = 0, binding = 2, std430) buffer Arena { uint w[]; } arena;
|
||||
layout(set = 0, binding = 3) uniform sampler2D T88;
|
||||
#if KIND == 1
|
||||
layout(set = 0, binding = 4) uniform sampler2D T96;
|
||||
layout(set = 0, binding = 5) uniform sampler2D T104;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S112;
|
||||
#else
|
||||
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D S112;
|
||||
#if CX
|
||||
layout(set = 0, binding = 5, rgba16f) uniform writeonly image2D S120;
|
||||
layout(set = 0, binding = 6, rgba16f) uniform writeonly image2D S128;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define ACC coopmat<float, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define HAC coopmat<float16_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseAccumulator>
|
||||
#define A8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseA>
|
||||
#define B8 coopmat<floate4m3_t, gl_ScopeSubgroup, 16, 16, gl_MatrixUseB>
|
||||
#define RM gl_CooperativeMatrixLayoutRowMajor
|
||||
#define CM gl_CooperativeMatrixLayoutColumnMajor
|
||||
#define LDA(dst, base, k, mt, kt) coopMatLoad((dst), w8.w, (base) + 256u * ((mt) * ((k) / 16u) + (kt)), 16u, RM)
|
||||
#define LDB(dst, base, n, kt, nt) coopMatLoad((dst), w8.w, (base) + 256u * ((kt) * ((n) / 16u) + (nt)), 16u, RM)
|
||||
#define RND(m) [[unroll]] for (int i_ = 0; i_ < 8; ++i_) (m)[i_] = rnd16((m)[i_]);
|
||||
#define Q8(dst, src) Q8PAIRS(dst, src)
|
||||
#define K32(acc, a0, b0, a1, b1) { (acc) = coopMatMulAdd((a1), (b1), coopMatMulAdd((a0), (b0), (acc))); RND(acc) }
|
||||
const uint HEAD = W_WK1 - W_WK0, BRANCH = W_W1_1 - W_W1_0;
|
||||
|
||||
// NVIDIA's reduction over a short list (swin_ref.pairs_tree)
|
||||
#if KC == 1
|
||||
#define TREE(v) (v[0])
|
||||
#elif KC == 2
|
||||
#define TREE(v) (v[0] + v[1])
|
||||
#elif KC == 3
|
||||
#define TREE(v) ((v[0] + v[1]) + v[2])
|
||||
#elif KC == 4
|
||||
#define TREE(v) ((v[0] + v[1]) + (v[2] + v[3]))
|
||||
#else
|
||||
#define TREE(v) (((v[0] + v[1]) + (v[2] + v[3])) + v[4])
|
||||
#endif
|
||||
|
||||
#if KIND == 1
|
||||
// one pool, by phase: [EX | LOW] for the expand; the prologue reads EX and writes XT over LOW (dead) and beyond;
|
||||
// the attention's K and V^T reuse EX (dead after the residual)
|
||||
#define EX_W (48u * C)
|
||||
#define POOL_W (EX_W + (12u * CX > 24u * C ? 12u * CX : 24u * C))
|
||||
shared uint POOL[POOL_W];
|
||||
#define EX POOL // the expansion: 48 rows x 4 sub-positions x C FP8 channels in slot order
|
||||
#define LOW_AT EX_W // the low-resolution tokens (6 x 6, padded): CX FP8 channels, arena order
|
||||
#define XT_AT EX_W // the normalised key tokens: slot-major, C FP8 channels in slot order
|
||||
#define XTS POOL
|
||||
#define RR_KS POOL
|
||||
#define RR_KS0 0u
|
||||
#define RR_VS POOL
|
||||
#define RR_VS0 768u
|
||||
shared float16_t TAP[100 * 4]; // texture 88 bilinear at the window's 10 x 10 filter tap positions
|
||||
shared float16_t OUTF[64 * 16]; // the head per window token (raster order)
|
||||
#else
|
||||
shared uint XT[96 * C / 4]; // the normalised key tokens: slot-major, C FP8 channels in slot order
|
||||
#define XT_AT 0u
|
||||
#define XTS XT
|
||||
shared uint KVS[2 * 768]; // the current head's K, then V^T
|
||||
#define RR_KS KVS
|
||||
#define RR_KS0 0u
|
||||
#define RR_VS KVS
|
||||
#define RR_VS0 768u
|
||||
#define MG XT // the merge rows (16 x 4C FP8) reuse XT, dead after the projections
|
||||
#if CX
|
||||
shared float16_t O112[64 * 4]; // surface 112 per token, for surface 128's 2x2 sums
|
||||
#endif
|
||||
#endif
|
||||
|
||||
ivec2 keyXY(uint t, int x0, int y0)
|
||||
{
|
||||
if (t < 64u) return ivec2(x0 + int(t & 7u), y0 + int(t >> 3u));
|
||||
const int i = int(t & 7u), side = int((t - 64u) >> 3u);
|
||||
return ivec2(side < 2 ? x0 + i : (side == 2 ? x0 - 1 : x0 + 8), side == 0 ? y0 - 1 : (side == 1 ? y0 + 8 : y0 + i));
|
||||
}
|
||||
int clampTap(int v, int n) { return v < 0 ? 1 : (v >= n ? n - 2 : v); }
|
||||
uint pack4(floate4m3_t a, floate4m3_t b, floate4m3_t c, floate4m3_t d)
|
||||
{
|
||||
return code(a) | (code(b) << 8u) | (code(c) << 16u) | (code(d) << 24u);
|
||||
}
|
||||
float16_t e4(uint w, uint b) { return float16_t(uintBitsToFloate4m3EXT(uint8_t((w >> (8u * b)) & 0xffu))); }
|
||||
uvec2 mergeXY(uint r) { return r < 8u ? uvec2(r >> 1u, r & 1u) : uvec2((r - 8u) >> 1u, 2u + (r & 1u)); }
|
||||
|
||||
void main()
|
||||
{
|
||||
const uint tid = gl_LocalInvocationIndex, wv = gl_SubgroupID, lane = gl_SubgroupInvocationID;
|
||||
const uint hl = lane >> 4u, col = lane & 15u;
|
||||
RR_ZERO16 = uint16_t(gl_NumWorkGroups.z - 1u);
|
||||
const int ex = int(param.p[0]), ey = int(param.p[1]), hx = ex / 2, hy = ey / 2;
|
||||
const uint inOff = param.p[2], skOff = param.p[6], outOff = param.p[12], mrgOff = param.p[14], shift = param.p[20];
|
||||
const int x0 = 8 * int(gl_WorkGroupID.x) - int(shift & 0xffffu), y0 = 8 * int(gl_WorkGroupID.y) - int(shift >> 16u);
|
||||
const uint plane = uint(ex * ey) * 32u, lplane = uint(hx * hy) * 32u;
|
||||
|
||||
#if KIND == 1
|
||||
// ---------------------------------------------------------------- expand: wave w computes sub-position w
|
||||
const int X0 = (x0 >> 1) - 1, Y0 = (y0 >> 1) - 1;
|
||||
for (uint i = tid; i < 48u * (CX / 4u); i += 128u) {
|
||||
const uint r = i / (CX / 4u), wd = i % (CX / 4u);
|
||||
uint u = 0u;
|
||||
if (r < 36u) {
|
||||
const int X = mirror(X0 + int(r % 6u), hx), Y = mirror(Y0 + int(r / 6u), hy);
|
||||
u = arena.w[(inOff + (wd >> 3u) * lplane + uint(Y * hx + X) * 32u) / 4u + (wd & 7u)];
|
||||
}
|
||||
POOL[LOW_AT + i] = u;
|
||||
}
|
||||
barrier();
|
||||
[[unroll]] for (uint n = 0u; n < 3u; ++n) {
|
||||
B8 lb[CX / 16];
|
||||
[[unroll]] for (uint kt = 0u; kt < CX / 16; ++kt) coopMatLoad(lb[kt], POOL, LOW_AT + n * 4u * CX + 4u * kt, CX / 4u, CM);
|
||||
for (uint mm = 0u; mm < MT; ++mm) {
|
||||
const uint m = MT * wv + mm;
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BE / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
[[unroll]] for (uint kt = 0u; kt < CX / 16; kt += 2u) {
|
||||
A8 w0, w1;
|
||||
LDA(w0, W_WE, CX, m, kt); LDA(w1, W_WE, CX, m, kt + 1u);
|
||||
K32(a, w0, lb[kt], w1, lb[kt + 1u])
|
||||
}
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
coopMatStore(q, EX, n * 16u * C + 4u * m, C, CM);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------- prologue: one key token per thread
|
||||
if (tid < 96u) {
|
||||
const ivec2 k = keyXY(inter(tid), x0, y0);
|
||||
const int xx = mirror(k.x, ex), yy = mirror(k.y, ey);
|
||||
const uint tb = ((KIND == 1 ? skOff : inOff) + uint(yy * ex + xx) * 32u) / 4u;
|
||||
#if KIND == 1
|
||||
const uint r = uint(((k.y >> 1) - Y0) * 6 + ((k.x >> 1) - X0)), s = uint(2 * (k.y & 1) + (k.x & 1));
|
||||
#endif
|
||||
float16_t v[C];
|
||||
[[unroll]] for (uint wd = 0u; wd < C / 4; ++wd) {
|
||||
const uint t = arena.w[tb + (wd >> 3u) * (plane >> 2u) + (wd & 7u)];
|
||||
#if KIND == 1
|
||||
const uint e = EX[r * C + s * (C / 4u) + wd];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) { precise float16_t x = e4(e, b) + e4(t, b); v[4u * wd + b] = x; }
|
||||
#else
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) v[4u * wd + b] = e4(t, b);
|
||||
#endif
|
||||
}
|
||||
// swin_ref.input_norm: per lane word q (and q + 4) the pair sums of the planes (TREE), the halves, then
|
||||
// q ^ 1, q ^ 2, the components
|
||||
precise f16vec2 h[4];
|
||||
[[unroll]] for (uint q = 0u; q < 4u; ++q) {
|
||||
precise f16vec2 hh[2];
|
||||
[[unroll]] for (uint half_ = 0u; half_ < 2u; ++half_) {
|
||||
precise f16vec2 pr[KC];
|
||||
[[unroll]] for (uint p = 0u; p < KC; ++p) {
|
||||
const uint o = 32u * p + 4u * (q + 4u * half_);
|
||||
precise f16vec2 a = f16vec2(v[o], v[o + 1u]) * f16vec2(v[o], v[o + 1u]);
|
||||
precise f16vec2 c = f16vec2(v[o + 2u], v[o + 3u]) * f16vec2(v[o + 2u], v[o + 3u]);
|
||||
pr[p] = a + c;
|
||||
}
|
||||
precise f16vec2 tr = TREE(pr);
|
||||
hh[half_] = tr;
|
||||
}
|
||||
h[q] = hh[0] + hh[1];
|
||||
}
|
||||
precise f16vec2 h0 = h[0] + h[1], h2_ = h[2] + h[3];
|
||||
precise f16vec2 hs = h0 + h2_;
|
||||
precise float16_t sm = hs.x + hs.y;
|
||||
precise float16_t se = sm + float16_t(0.0001220703125);
|
||||
const float16_t rr = rsq16(se);
|
||||
[[unroll]] for (uint wd = 0u; wd < C / 4; ++wd) {
|
||||
floate4m3_t c[4];
|
||||
[[unroll]] for (uint b = 0u; b < 4u; ++b) {
|
||||
const uint j = 4u * wd + b;
|
||||
precise float16_t g = rr * w16.w[W_GAIN0 / 2u + inter(j)];
|
||||
precise float16_t n = v[j] * g;
|
||||
c[b] = q8h(n);
|
||||
}
|
||||
XTS[XT_AT + tid * (C / 4u) + wd] = pack4(c[0], c[1], c[2], c[3]);
|
||||
}
|
||||
}
|
||||
barrier();
|
||||
|
||||
// ---------------------------------------------------------------- the residual: the window token + table 7
|
||||
ACC R[MT];
|
||||
{
|
||||
const uint tq = inter(16u * wv + col);
|
||||
const int qx = x0 + int(tq & 7u), qy = y0 + int(tq >> 3u);
|
||||
const uint tb = ((KIND == 1 ? skOff : inOff) + uint(mirror(qy, ey) * ex + mirror(qx, ex)) * 32u) / 4u;
|
||||
#if KIND == 1
|
||||
const uint r = uint(((qy >> 1) - Y0) * 6 + ((qx >> 1) - X0)), s = uint(2 * (qy & 1) + (qx & 1));
|
||||
#endif
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m) {
|
||||
const uint ta = tb + (m >> 1u) * (plane >> 2u) + 4u * (m & 1u) + 2u * hl;
|
||||
const uvec2 t = uvec2(arena.w[ta], arena.w[ta + 1u]);
|
||||
#if KIND == 1
|
||||
const uint ea = r * C + s * (C / 4u) + 4u * m + 2u * hl;
|
||||
const uvec2 e = uvec2(EX[ea], EX[ea + 1u]);
|
||||
#endif
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
#if KIND == 1
|
||||
precise float16_t x = e4(e[i >> 2], uint(i) & 3u) + e4(t[i >> 2], uint(i) & 3u);
|
||||
#else
|
||||
const float16_t x = e4(t[i >> 2], uint(i) & 3u);
|
||||
#endif
|
||||
precise float16_t c = x + w16.w[W_TABLE7 / 2u + 16u * m + 8u * hl + uint(i)];
|
||||
R[m][i] = float(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- attention, NH heads
|
||||
[[dont_unroll]] for (uint hd = 0u; hd < NH; ++hd) {
|
||||
barrier(); // every wave is done with the previous head's K / V (and, for the first, the expand's LOW)
|
||||
for (uint j = 0u; j < 3u; ++j) { // 12 jobs (token tile n, channel tile c)
|
||||
const uint job = wv + 4u * j, n = job >> 1u, c = job & 1u;
|
||||
ACC a = ACC(0.0), v = ACC(0.0);
|
||||
[[unroll]] for (uint kt = 0u; kt < 2u * KC; kt += 2u) {
|
||||
B8 xb0, xb1, wv0, wv1;
|
||||
A8 xa0, xa1, wk0, wk1;
|
||||
coopMatLoad(xb0, XTS, XT_AT + n * 4u * C + 4u * kt, C / 4u, CM);
|
||||
coopMatLoad(xb1, XTS, XT_AT + n * 4u * C + 4u * kt + 4u, C / 4u, CM);
|
||||
coopMatLoad(xa0, XTS, XT_AT + n * 4u * C + 4u * kt, C / 4u, RM);
|
||||
coopMatLoad(xa1, XTS, XT_AT + n * 4u * C + 4u * kt + 4u, C / 4u, RM);
|
||||
LDA(wk0, W_WK0 + HEAD * hd, C, c, kt); LDA(wk1, W_WK0 + HEAD * hd, C, c, kt + 1u);
|
||||
LDB(wv0, W_WV0 + HEAD * hd, 32u, kt, c); LDB(wv1, W_WV0 + HEAD * hd, 32u, kt + 1u, c);
|
||||
K32(a, wk0, xb0, wk1, xb1)
|
||||
K32(v, xa0, wv0, xa1, wv1)
|
||||
}
|
||||
A8 ka, va;
|
||||
Q8(ka, a)
|
||||
Q8(va, v)
|
||||
coopMatStore(ka, RR_KS, RR_KS0 + n * 128u + 4u * c, 8u, RM);
|
||||
coopMatStore(va, RR_VS, RR_VS0 + c * 384u + 4u * n, 24u, RM);
|
||||
}
|
||||
barrier();
|
||||
#include "rr_attn.glsl"
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WO0 + HEAD * hd, 32u, m, 0u); LDA(a1, W_WO0 + HEAD * hd, 32u, m, 1u);
|
||||
K32(R[m], a0, Ob[0], a1, Ob[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- MLP
|
||||
B8 Nb[MT];
|
||||
{
|
||||
float16_t rv[MT][8];
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m) [[unroll]] for (int i = 0; i < 8; ++i) rv[m][i] = float16_t(R[m][i]);
|
||||
// swin_ref.rowsum_sq: slot 16m + 8hl + i is NVIDIA's channel 8j + 2t + c with j = 4(m >> 1) + (m & 1) +
|
||||
// 2((i >> 1) & 1), t = 2hl + (i >> 2), c = i & 1: per parity (m & 1) the tile pairs (i, i + 2) of every
|
||||
// 32-channel block, TREE over the blocks; the parities; t ^ 1 (local), t ^ 2 (the other lane half)
|
||||
float16_t h1[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
float16_t ht[2];
|
||||
[[unroll]] for (uint tq = 0u; tq < 2u; ++tq) {
|
||||
const uint i0 = 4u * tq + c, i1 = i0 + 2u;
|
||||
precise float16_t par[2];
|
||||
[[unroll]] for (uint pa = 0u; pa < 2u; ++pa) {
|
||||
precise float16_t pr[KC];
|
||||
[[unroll]] for (uint b = 0u; b < KC; ++b)
|
||||
pr[b] = rv[2u * b + pa][i0] * rv[2u * b + pa][i0] + rv[2u * b + pa][i1] * rv[2u * b + pa][i1];
|
||||
precise float16_t tr = TREE(pr);
|
||||
par[pa] = tr;
|
||||
}
|
||||
precise float16_t hv = par[0] + par[1];
|
||||
ht[tq] = hv;
|
||||
}
|
||||
precise float16_t hh = ht[0] + ht[1];
|
||||
h1[c] = hh;
|
||||
}
|
||||
const uint other = subgroupShuffleXor(bh(h1[0]) | (bh(h1[1]) << 16u), 16u);
|
||||
precise float16_t g0 = h1[0] + hb(other & 0xffffu), g1 = h1[1] + hb(other >> 16u);
|
||||
precise float16_t s = g0 + g1;
|
||||
precise float16_t se = s + float16_t(0.0001220703125);
|
||||
const float16_t r = rsq16(se);
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m)
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) {
|
||||
const uint k = 16u * m + 8u * hl + uint(i);
|
||||
precise float16_t g = r * w16.w[W_GAIN14 / 2u + k];
|
||||
precise float16_t n = rv[m][i] * g;
|
||||
Nb[m][i] = q8h(n);
|
||||
precise float16_t a = rv[m][i] + w16.w[W_TABLE16 / 2u + k];
|
||||
R[m][i] = float(a);
|
||||
}
|
||||
}
|
||||
[[dont_unroll]] for (uint br = 0u; br < NB; ++br) {
|
||||
const uint w1 = W_W1_0 + BRANCH * br, b1 = W_B1_0 + BRANCH * br, w2 = W_W2_0 + BRANCH * br;
|
||||
B8 Hb[2];
|
||||
[[unroll]] for (uint c = 0u; c < 2u; ++c) {
|
||||
ACC h;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) h[i] = float(w16.w[b1 / 2u + 16u * c + 8u * hl + uint(i)]);
|
||||
[[unroll]] for (uint kt = 0u; kt < MT; kt += 2u) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w1, C, c, kt); LDA(a1, w1, C, c, kt + 1u);
|
||||
K32(h, a0, Nb[kt], a1, Nb[kt + 1u])
|
||||
}
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) Hb[c][i] = ACTQ(float16_t(h[i]));
|
||||
}
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, w2, 32u, m, 0u); LDA(a1, w2, 32u, m, 1u);
|
||||
K32(R[m], a0, Hb[0], a1, Hb[1])
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- outputs
|
||||
const uint t = inter(16u * wv + col);
|
||||
const int tx = x0 + int(t & 7u), ty = y0 + int(t >> 3u);
|
||||
const bool inside = tx >= 0 && ty >= 0 && tx < ex && ty < ey;
|
||||
B8 Tb[MT];
|
||||
{
|
||||
const uint o = (outOff + uint(ty * ex + tx) * 32u) >> 2u;
|
||||
#if KIND == 0 && CX
|
||||
const uint ty2 = t >> 3u, tx2 = t & 7u;
|
||||
const uint mr = (ty2 >> 1u) < 2u ? 2u * (tx2 >> 1u) + (ty2 >> 1u) : 8u + 2u * (tx2 >> 1u) + (ty2 >> 1u) - 2u;
|
||||
const uint blk = 2u * (ty2 & 1u) + (tx2 & 1u);
|
||||
barrier(); // every wave is done with XT (the merge rows reuse it)
|
||||
#endif
|
||||
[[unroll]] for (uint m = 0u; m < MT; ++m) {
|
||||
Q8(Tb[m], R[m])
|
||||
const uint w0 = pack4(Tb[m][0], Tb[m][1], Tb[m][2], Tb[m][3]), w1 = pack4(Tb[m][4], Tb[m][5], Tb[m][6], Tb[m][7]);
|
||||
const uint at = 4u * (m & 1u) + 2u * hl;
|
||||
if (inside) { arena.w[o + (m >> 1u) * (plane >> 2u) + at] = w0; arena.w[o + (m >> 1u) * (plane >> 2u) + at + 1u] = w1; }
|
||||
#if KIND == 0 && CX
|
||||
const uint g = mr * C + blk * (C / 4u) + 4u * m + 2u * hl;
|
||||
MG[g] = w0; MG[g + 1u] = w1;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// the C -> 16 head
|
||||
ACC hv;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) hv[i] = float(w16.w[W_BH / 2u + 8u * hl + uint(i)]);
|
||||
[[unroll]] for (uint kt = 0u; kt < MT; kt += 2u) {
|
||||
A8 a0, a1;
|
||||
LDA(a0, W_WH, C, 0u, kt); LDA(a1, W_WH, C, 0u, kt + 1u);
|
||||
K32(hv, a0, Tb[kt], a1, Tb[kt + 1u])
|
||||
}
|
||||
#if KIND == 0
|
||||
{ // enc1's kernel-prediction filter (lanes 0-15; 16-31 hold head channels 8-15)
|
||||
float16_t l[12];
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) l[i] = float16_t(hv[i]);
|
||||
const uint u0 = subgroupShuffleXor(bh(l[0]) | (bh(l[1]) << 16u), 16u), u1 = subgroupShuffleXor(bh(l[2]) | (bh(l[3]) << 16u), 16u);
|
||||
l[8] = hb(u0 & 0xffffu); l[9] = hb(u0 >> 16u); l[10] = hb(u1 & 0xffffu); l[11] = hb(u1 >> 16u);
|
||||
const float16_t ma = max(max(max(l[0], l[2]), l[4]), l[6]), mb = max(max(max(l[1], l[3]), l[5]), l[7]);
|
||||
const float16_t mx = max(max(ma, mb), l[8]);
|
||||
float e[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) { precise float16_t d = l[i] - mx; e[i] = exp2(float(d) * 1.4426950216293335); }
|
||||
precise float s = (e[0] + e[1]) + 0.0;
|
||||
s = s + (e[2] + e[3]);
|
||||
s = s + (e[4] + e[5]);
|
||||
s = s + (e[6] + e[7]);
|
||||
s = s + e[8];
|
||||
const float16_t rr = rcp16(s);
|
||||
float wg[9];
|
||||
[[unroll]] for (uint i = 0u; i < 9u; ++i) wg[i] = r16(r16(e[i]) * float(rr));
|
||||
const int x = tx, y = ty;
|
||||
const int xm = x - 1 < 0 ? 1 : x - 1, xc = x < 0 ? 1 : (x >= ex ? ex - 2 : x), xp = x + 1 >= ex ? ex - 2 : x + 1;
|
||||
const int ym = y - 1 < 0 ? 1 : y - 1, yc = y < 0 ? 1 : (y >= ey ? ey - 2 : y), yp = y + 1 >= ey ? ey - 2 : y + 1;
|
||||
const ivec2 pts[9] = ivec2[9](ivec2(xm, ym), ivec2(xc, ym), ivec2(xp, ym), ivec2(xm, yc), ivec2(xc, yc),
|
||||
ivec2(xp, yc), ivec2(xm, yp), ivec2(xc, yp), ivec2(xp, yp));
|
||||
float acc[3][2] = float[3][2](float[2](0.0, 0.0), float[2](0.0, 0.0), float[2](0.0, 0.0));
|
||||
[[unroll]] for (uint k = 0u; k < 8u; ++k) {
|
||||
const vec4 tv = texelFetch(T88, pts[k], 0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) { precise float f = tv[c] * wg[k] + acc[c][k & 1u]; acc[c][k & 1u] = r16(f); }
|
||||
}
|
||||
const vec4 t8 = texelFetch(T88, pts[8], 0);
|
||||
float o112[3], o120[3];
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float f0 = acc[c][0] + acc[c][1];
|
||||
precise float f1 = t8[c] * wg[8] + r16(f0);
|
||||
const float f = r16(f1);
|
||||
#if CX
|
||||
precise float a = float(sig(l[9u + c])) * f;
|
||||
const float ar = r16(a);
|
||||
precise float b = f - ar;
|
||||
o120[c] = ar; o112[c] = r16(b);
|
||||
#else
|
||||
o112[c] = f; // the bottleneck: the filtered value itself
|
||||
#endif
|
||||
}
|
||||
if (hl == 0u && inside) {
|
||||
imageStore(S112, ivec2(x, y), vec4(o112[0], o112[1], o112[2], 0.0));
|
||||
#if CX
|
||||
imageStore(S120, ivec2(x, y), vec4(o120[0], o120[1], o120[2], 0.0));
|
||||
#endif
|
||||
}
|
||||
#if CX
|
||||
if (hl == 0u) [[unroll]] for (uint c = 0u; c < 3u; ++c) O112[t * 4u + c] = float16_t(o112[c]);
|
||||
#endif
|
||||
}
|
||||
#if CX
|
||||
barrier();
|
||||
{ // the 2x2 merge: merged^T (CX x 16 rows), channel tiles m = wv, wv + 4, ...
|
||||
const uvec2 mxy = mergeXY(col);
|
||||
const int X = x0 / 2 + int(mxy.x), Y = y0 / 2 + int(mxy.y);
|
||||
const uint o = (mrgOff + uint(Y * hx + X) * 32u) >> 2u;
|
||||
for (uint m = wv; m < CX / 16u; m += 4u) {
|
||||
ACC a;
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) a[i] = float(w16.w[W_BM / 2u + 16u * m + 8u * hl + uint(i)]);
|
||||
for (uint ks = 0u; ks < C / 8u; ++ks) {
|
||||
B8 g0, g1; A8 w0, w1;
|
||||
coopMatLoad(g0, MG, 8u * ks, C, CM);
|
||||
coopMatLoad(g1, MG, 8u * ks + 4u, C, CM);
|
||||
LDA(w0, W_WM, 4u * C, m, 2u * ks); LDA(w1, W_WM, 4u * C, m, 2u * ks + 1u);
|
||||
K32(a, w0, g0, w1, g1)
|
||||
}
|
||||
B8 q;
|
||||
Q8(q, a)
|
||||
if (X >= 0 && Y >= 0 && X < hx && Y < hy) {
|
||||
const uint at = o + (m >> 1u) * (lplane >> 2u) + 4u * (m & 1u) + 2u * hl;
|
||||
arena.w[at] = pack4(q[0], q[1], q[2], q[3]);
|
||||
arena.w[at + 1u] = pack4(q[4], q[5], q[6], q[7]);
|
||||
}
|
||||
}
|
||||
if (wv == 3u && lane < 16u) { // surface 128: 2x2 sums of 0.25 * surface 112, rightwards first, then downwards
|
||||
const uint mx = lane & 3u, my = lane >> 2u;
|
||||
const int x = x0 + 2 * int(mx), y = y0 + 2 * int(my);
|
||||
if (x >= 0 && y >= 0 && x < ex && y < ey) {
|
||||
const uint t00 = (2u * my) * 8u + 2u * mx;
|
||||
vec4 out_ = vec4(0.0);
|
||||
[[unroll]] for (uint c = 0u; c < 3u; ++c) {
|
||||
precise float16_t a00 = O112[t00 * 4u + c] * float16_t(0.25), a01 = O112[(t00 + 1u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t a10 = O112[(t00 + 8u) * 4u + c] * float16_t(0.25), a11 = O112[(t00 + 9u) * 4u + c] * float16_t(0.25);
|
||||
precise float16_t b0 = a00 + a01, b1 = a10 + a11;
|
||||
precise float16_t cc = b0 + b1;
|
||||
out_[c] = float(cc);
|
||||
}
|
||||
imageStore(S128, ivec2(x >> 1, y >> 1), out_);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
[[unroll]] for (int i = 0; i < 8; ++i) OUTF[t * 16u + 8u * hl + uint(i)] = float16_t(hv[i]);
|
||||
barrier();
|
||||
#define DEC_FILTER
|
||||
#include "rr_decfilter.glsl"
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pack a Swin stage's extracted weights (engine/rr/swin_ref.py --extract) for engine/rr/swin_m.comp.
|
||||
|
||||
usage: swin_pack.py STAGE WEIGHTS.npz OUT.bin OUT.h
|
||||
|
||||
enc1_pack.py's layout for any stage of engine/rr/swin_ref.STAGES: transposed products, every weight's columns in the
|
||||
k order of the product that consumes its result (slot j holds natural channel inter(n)[j]); the expand's 4C columns
|
||||
are four sub-positions of C channels, each permuted the same way. OUT.h also defines the stage's shape for the shader
|
||||
(KIND, C, CX, NH).
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from enc1_ref import inter # noqa: E402
|
||||
from enc1_pack import a_tiles, b_tiles, cubic_table, act_table # noqa: E402
|
||||
from swin_ref import STAGES # noqa: E402
|
||||
|
||||
|
||||
def main():
|
||||
stage, src, out_bin, out_h = sys.argv[1:5]
|
||||
kind, C, CX, NH, _ = STAGES[stage]
|
||||
w = dict(np.load(src))
|
||||
I32, I96, IC = inter(32), inter(96), inter(C)
|
||||
e = {}
|
||||
if kind == 'dec':
|
||||
I4C = [C * (j // C) + IC[j % C] for j in range(4 * C)]
|
||||
e['we'], e['be'] = ('A', w['we'][:, I4C]), ('H', w['be'][I4C])
|
||||
for h in range(NH):
|
||||
e[f'wk{h}'] = ('A', w[f'wk{h}'][:, I32])
|
||||
e[f'wv{h}'] = ('B', w[f'wv{h}'][:, I32])
|
||||
e[f'wo{h}'] = ('A', w[f'wo{h}'][:, IC])
|
||||
e[f'bias{h}'] = ('H', w[f'bias{h}'][np.ix_(inter(64), I96)].T)
|
||||
for b in range(C // 8):
|
||||
e[f'w1_{b}'] = ('A', w[f'w1_{b}'][:, I32])
|
||||
e[f'b1_{b}'] = ('H', w[f'b1_{b}'][I32])
|
||||
e[f'w2_{b}'] = ('A', w[f'w2_{b}'][:, IC])
|
||||
if kind == 'enc' and CX:
|
||||
e['wm'], e['bm'] = ('A', w['wm'][:, inter(CX)]), ('H', w['bm'][inter(CX)])
|
||||
e['wh'], e['bh'] = ('A', w['wh']), ('H', w['bh'])
|
||||
e['gain0'] = ('H', w['gain0'])
|
||||
for k in ('table7', 'gain14', 'table16'):
|
||||
e[k] = ('H', w[k][IC])
|
||||
e['cubic'] = ('U', cubic_table())
|
||||
e['act'] = ('Y', act_table())
|
||||
blob, defines = bytearray(), []
|
||||
for name, (kd, a) in e.items():
|
||||
while len(blob) % 256:
|
||||
blob.append(0)
|
||||
off = len(blob)
|
||||
if kd == 'A':
|
||||
blob += a_tiles(a).tobytes()
|
||||
elif kd == 'B':
|
||||
blob += b_tiles(a).tobytes()
|
||||
elif kd == 'Y':
|
||||
blob += np.ascontiguousarray(a, np.uint8).tobytes()
|
||||
elif kd == 'U':
|
||||
blob += np.ascontiguousarray(a, np.uint16).tobytes()
|
||||
else:
|
||||
blob += np.ascontiguousarray(a, np.float16).tobytes()
|
||||
defines.append(f'#define W_{name.upper()} {off}u // {kd} {a.shape}')
|
||||
shape = [f'#define KIND {0 if kind == "enc" else 1}', f'#define C {C}', f'#define CX {CX}', f'#define NH {NH}']
|
||||
if NH < 2:
|
||||
defines.append('#define W_WK1 (W_WK0 + 1u)')
|
||||
if C // 8 < 2:
|
||||
defines.append('#define W_W1_1 (W_W1_0 + 1u)')
|
||||
open(out_bin, 'wb').write(bytes(blob))
|
||||
open(out_h, 'w').write(f'// generated by engine/rr/swin_pack.py for {stage}\n' + '\n'.join(shape + defines) + '\n')
|
||||
print(f'{stage}: {len(blob)} bytes, {len(e)} entries')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exact numpy reference of the denoiser's Swin stages enc1-enc5 and dec1-dec4 (cuda_dldn_engine_swin_*_kernel), one
|
||||
8x8 window at a time, from weights and real inputs only. Generalises engine/rr/enc1_ref.py (encoders) and
|
||||
engine/rr/dec1_ref.py (decoders); every stage was checked against its capture (main).
|
||||
|
||||
A stage has C channels (32 * KC), NH heads of 32 channels, NB = C / 8 MLP branches of C -> 32 -> C:
|
||||
prologue encoders: the 96 key tokens from the arena (C / 32 planes); decoders: the expand of the 6 x 6 tokens
|
||||
one level down (C_LO channels -> 4 sub-positions x C) + the skip token. RMS norm (input_norm: per lane word
|
||||
the planes summed pairwise (pairs_tree), the two word halves, then lanes ^1, ^2 and the halves) times gain0; the
|
||||
residual is the token + table7
|
||||
heads Q/K and V projections over KC k32 steps, scores + bias, the cubic softmax, P V, out-projection onto the
|
||||
residual, head after head
|
||||
MLP RMS norm (rowsum_sq) times gain14, residual + table16, NB branches
|
||||
outputs the tokens; encoders: the 2x2 merge (4C -> C_M) and the 64 -> 16 head with enc1's filter (surfaces 112,
|
||||
120, 128); decoders: the head with dec0's filter (surface 112)
|
||||
|
||||
usage: swin_ref.py CAPTURE STAGE FRAME WEIGHTS.npz [--blocks X,Y ...] (verifies against the capture)
|
||||
swin_ref.py CAPTURE STAGE FRAME OUT.npz --extract DUMP.npz (weights from a phase dump)
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rrmath import e4m3, f16, q8 # noqa: E402
|
||||
from enc1_ref import (inter, mm, softmax_cubic, act, mirror, add, rowsum_sq, pairs_tree, filter_head, # noqa: E402
|
||||
MERGE_ORDER, H, F)
|
||||
from dec0_ref import keys # noqa: E402
|
||||
from dec1_ref import filter112 # noqa: E402
|
||||
|
||||
# kind, C, C_LO (decoders: channels one level down) or C_M (encoders: merged channels), heads, warps
|
||||
STAGES = {
|
||||
'enc1': ('enc', 64, 96, 2, 1), 'enc2': ('enc', 96, 128, 4, 4), 'enc3': ('enc', 128, 160, 4, 4),
|
||||
'enc4': ('enc', 160, 160, 8, 8), 'enc5': ('enc', 160, 0, 8, 8),
|
||||
'dec4': ('dec', 160, 160, 8, 8), 'dec3': ('dec', 128, 160, 4, 4), 'dec2': ('dec', 96, 128, 4, 4),
|
||||
'dec1': ('dec', 64, 96, 2, 2),
|
||||
}
|
||||
|
||||
|
||||
def input_norm(xn, gain):
|
||||
"""xn: one token, natural channels (f16). NVIDIA's tree over the arena order; returns the normalised token
|
||||
(natural, f16)"""
|
||||
c = len(xn)
|
||||
xa = xn[inter(c)]
|
||||
sq = (xa * xa).astype(H)
|
||||
|
||||
def pair(p, w):
|
||||
return add(sq[32 * p + 4 * w:32 * p + 4 * w + 2], sq[32 * p + 4 * w + 2:32 * p + 4 * w + 4])
|
||||
|
||||
fold = pairs_tree # (p0 + p1) + p2, (p0 + p1) + (p2 + p3), ((p0 + p1) + (p2 + p3)) + p4
|
||||
h = [add(fold([pair(p, t) for p in range(c // 32)]), fold([pair(p, t + 4) for p in range(c // 32)]))
|
||||
for t in range(4)]
|
||||
h01 = [add(h[t], h[t ^ 1]) for t in range(4)]
|
||||
h2 = add(h01[0], h01[2])
|
||||
s = H(H(h2[0] + h2[1]) + H(2.0 ** -13))
|
||||
r = H(F(1) / np.sqrt(F(s)))
|
||||
return (xn * (r * gain).astype(H)).astype(H)
|
||||
|
||||
|
||||
def block(stage, w, x0, y0, ex, ey, inp):
|
||||
"""the window: returns (tokens 64 x C codes in arena order, merged 16 x C_M codes or None, head 64 x 16 f16).
|
||||
inp: 'tok' [C/32][ey][ex][32] (encoders) or 'lo' [C_LO/32][ey/2][ex/2][32] and 'skip' [C/32][ey][ex][32]"""
|
||||
kind, C, CX, NH, _ = STAGES[stage]
|
||||
IC = inter(C)
|
||||
UN = [IC.index(i) for i in range(C)]
|
||||
xs = []
|
||||
if kind == 'dec':
|
||||
hx, hy = ex // 2, ey // 2
|
||||
X0, Y0 = x0 // 2 - 1, y0 // 2 - 1
|
||||
low = np.zeros((48, CX), np.uint8)
|
||||
for r in range(36):
|
||||
X, Y = mirror(X0 + r % 6, hx), mirror(Y0 + r // 6, hy)
|
||||
low[r] = np.concatenate([inp['lo'][p, Y, X] for p in range(CX // 32)])
|
||||
Ex = q8(mm(low, w['we'], np.repeat(w['be'][None, :], 48, 0)))
|
||||
for (x, y) in keys(x0, y0):
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
r, s = (y // 2 - Y0) * 6 + (x // 2 - X0), 2 * (y & 1) + (x & 1)
|
||||
sk = np.concatenate([inp['skip'][p, yy, xx] for p in range(C // 32)])[UN]
|
||||
xs.append((e4m3(Ex[r, C * s:C * s + C]).astype(H) + e4m3(sk).astype(H)).astype(H))
|
||||
else:
|
||||
for (x, y) in keys(x0, y0):
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
xs.append(e4m3(np.concatenate([inp['tok'][p, yy, xx] for p in range(C // 32)])[UN]).astype(H))
|
||||
X = np.array([q8(input_norm(x, w['gain0']))[IC] for x in xs])
|
||||
R = add(np.array(xs[:64]), w['table7'][None, :])
|
||||
I32, I96 = inter(32), inter(96)
|
||||
for h in range(NH):
|
||||
qk, v = np.zeros((96, 32)), np.zeros((96, 32))
|
||||
for c in range(C // 32):
|
||||
qk = mm(X[:, 32 * c:32 * c + 32], w[f'wk{h}'][32 * c:32 * c + 32], qk)
|
||||
v = mm(X[:, 32 * c:32 * c + 32], w[f'wv{h}'][32 * c:32 * c + 32], v)
|
||||
Q = q8(qk)[:, I32]
|
||||
P = softmax_cubic(mm(Q[:64], Q.T, w[f'bias{h}']))
|
||||
O = mm(q8(P)[:, I96], q8(v)[I96], np.zeros((64, 32)))
|
||||
R = mm(q8(O)[:, I32], w[f'wo{h}'], R)
|
||||
N = q8((R * (rowsum_sq(R)[:, None] * w['gain14'][None, :]).astype(H)).astype(H))[:, IC]
|
||||
acc = add(R, w['table16'][None, :])
|
||||
for b in range(C // 8):
|
||||
hid = act(mm(N, w[f'w1_{b}'], np.repeat(w[f'b1_{b}'][None, :], 64, 0)))
|
||||
acc = mm(q8(hid)[:, I32], w[f'w2_{b}'], acc)
|
||||
tokens = q8(acc)[:, IC]
|
||||
merged = None
|
||||
if kind == 'enc' and CX:
|
||||
Mg = np.zeros((16, 4 * C), np.uint8)
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER):
|
||||
for blk, (dx, dy) in enumerate([(0, 0), (1, 0), (0, 1), (1, 1)]):
|
||||
Mg[r, C * blk:C * blk + C] = tokens[(2 * my + dy) * 8 + 2 * mx + dx]
|
||||
merged = q8(mm(Mg, w['wm'], np.repeat(w['bm'][None, :], 16, 0)))[:, inter(CX)]
|
||||
head = mm(tokens, w['wh'], np.repeat(w['bh'][None, :], 64, 0))
|
||||
return tokens, merged, head
|
||||
|
||||
|
||||
def phase_map(stage):
|
||||
"""dump phase numbers: projections [h][c] = (QK, V), bias[h] (scores phase, wave), out[h], mlp up / down, merge,
|
||||
head, expand"""
|
||||
kind, C, CX, NH, waves = STAGES[stage]
|
||||
KC = C // 32
|
||||
m = {'proj': {}, 'bias': {}, 'out': {}}
|
||||
if waves == 1: # heads in sequence: per head 2 KC projections, scores, P V, out-projection
|
||||
p = 1
|
||||
for h in range(NH):
|
||||
for c in range(KC):
|
||||
m['proj'][h, c] = (p, p + 1, 0)
|
||||
p += 2
|
||||
m['bias'][h] = (p, 0)
|
||||
m['out'][h] = p + 2
|
||||
p += 3
|
||||
else: # one head per warp
|
||||
p = 2 if kind == 'dec' else 1
|
||||
for h in range(NH):
|
||||
for c in range(KC):
|
||||
m['proj'][h, c] = (p + 2 * c, p + 2 * c + 1, h)
|
||||
p += 2 * KC
|
||||
for h in range(NH):
|
||||
m['bias'][h] = (p, h)
|
||||
m['out'][h] = p + 2 + h
|
||||
p += 2 + NH
|
||||
m['mlp'] = [(p + 2 * b, p + 2 * b + 1) for b in range(C // 8)]
|
||||
p += 2 * (C // 8)
|
||||
if kind == 'enc' and CX:
|
||||
m['merge'] = p
|
||||
p += 1
|
||||
m['head'] = p
|
||||
return m
|
||||
|
||||
|
||||
def read_b(W, base, K):
|
||||
"""an FP8 B matrix (K x 32) as NVIDIA stores it in the weight image: 4-byte k groups, offset 512 (kg >> 3) +
|
||||
16 (kg & 3) + 4 ((kg >> 2) & 1) + 64 (n & 7) + 8 ((n >> 3) & 1) + 16 K (n >> 4)"""
|
||||
b = np.zeros((K, 32), np.uint8)
|
||||
for kg in range(K // 4):
|
||||
for n in range(32):
|
||||
o = base + 512 * (kg >> 3) + 16 * (kg & 3) + 4 * ((kg >> 2) & 1) + 64 * (n & 7) + 8 * ((n >> 3) & 1) + 16 * K * (n >> 4)
|
||||
b[4 * kg:4 * kg + 4, n] = W[o:o + 4]
|
||||
return b
|
||||
|
||||
|
||||
def merge_weights(z, W, p, waves, C, CX):
|
||||
"""the merge (4C -> CX) of a multi-warp encoder: warp w computes the 32-column chunks w, w + warps, ...; the dump
|
||||
holds each warp's last chunk, so the chunks come from the weight image (located through warp 1's, chunk 1) and the
|
||||
dumped ones check the layout"""
|
||||
K, size = 4 * C, 4 * C * 32
|
||||
W32 = W[:len(W) // 4 * 4].view(np.uint32)
|
||||
first = z[f'p{p}_w1_B'][0:4, 0].copy().view(np.uint32)[0]
|
||||
pos1 = int(next(i for i in np.nonzero(W32 == first)[0] * 4 if np.array_equal(read_b(W, int(i), K), z[f'p{p}_w1_B'])))
|
||||
base0 = pos1 - size
|
||||
chunks = [read_b(W, base0 + size * j, K) for j in range(CX // 32)]
|
||||
for v in range(waves):
|
||||
last = max(j for j in range(CX // 32) if j % waves == v) if any(j % waves == v for j in range(CX // 32)) else None
|
||||
if last is not None:
|
||||
assert np.array_equal(chunks[last], z[f'p{p}_w{v}_B']), (v, last)
|
||||
b1 = f16(z[f'p{p}_w1_C']).astype(H)[0].tobytes()
|
||||
bpos1 = W.tobytes().find(b1)
|
||||
bias = np.concatenate([W[bpos1 + 64 * (j - 1):bpos1 + 64 * j].view(H) for j in range(CX // 32)])
|
||||
for v in range(waves):
|
||||
last = max((j for j in range(CX // 32) if j % waves == v), default=None)
|
||||
if last is not None:
|
||||
assert np.array_equal(bias[32 * last:32 * last + 32].view(np.uint16), z[f'p{p}_w{v}_C'][0]), ('bias', v)
|
||||
return np.hstack(chunks), bias
|
||||
|
||||
|
||||
def extract(stage, dump, weights, out, inp, x0, y0, ex, ey):
|
||||
"""weights from the phase dump; gains and tables are found in the weight image by matching the dump"""
|
||||
kind, C, CX, NH, waves = STAGES[stage]
|
||||
z = np.load(dump)
|
||||
W = np.frombuffer(weights, np.uint8)
|
||||
|
||||
def B(p, wv=0):
|
||||
return z[f'p{p}_w{wv}_B']
|
||||
|
||||
def Cm(p, wv=0):
|
||||
return f16(z[f'p{p}_w{wv}_C']).astype(H)
|
||||
pm = phase_map(stage)
|
||||
w = {}
|
||||
if kind == 'dec':
|
||||
w['we'] = np.hstack([B(1, v) for v in range(waves)])
|
||||
w['be'] = np.concatenate([Cm(1, v)[0] for v in range(waves)])
|
||||
for h in range(NH):
|
||||
w[f'wk{h}'] = np.vstack([B(pm['proj'][h, c][0], pm['proj'][h, c][2]) for c in range(C // 32)])
|
||||
w[f'wv{h}'] = np.vstack([B(pm['proj'][h, c][1], pm['proj'][h, c][2]) for c in range(C // 32)])
|
||||
w[f'bias{h}'] = Cm(*pm['bias'][h])
|
||||
w[f'wo{h}'] = B(pm['out'][h])
|
||||
for b, (up, dn) in enumerate(pm['mlp']):
|
||||
w[f'w1_{b}'], w[f'b1_{b}'], w[f'w2_{b}'] = B(up), Cm(up)[0], B(dn)
|
||||
if 'merge' in pm:
|
||||
mw = min(waves, 4) # the merge runs on (at most) four warps
|
||||
if waves > 1 and CX // 32 > mw: # a warp computes several chunks: read them from the weight image
|
||||
w['wm'], w['bm'] = merge_weights(z, W, pm['merge'], mw, C, CX)
|
||||
elif waves > 1:
|
||||
w['wm'] = np.hstack([B(pm['merge'], v) for v in range(CX // 32)])
|
||||
w['bm'] = np.concatenate([Cm(pm['merge'], v)[0] for v in range(CX // 32)])
|
||||
else:
|
||||
w['wm'], w['bm'] = B(pm['merge']), Cm(pm['merge'])[0]
|
||||
w['wh'], w['bh'] = B(pm['head']), Cm(pm['head'])[0]
|
||||
# the four f16 vectors: candidates at every even offset, checked against the first token rows of the dump
|
||||
tables = [W[o:o + 2 * C].view(H) for o in range(0, len(W) - 2 * C + 1, 2)]
|
||||
w0 = {k: v for k, v in w.items()}
|
||||
# gain0: the key tokens' norm against the first projection's A (window row 0..)
|
||||
xs = key_tokens(stage, w0, x0, y0, ex, ey, inp)
|
||||
IC = inter(C)
|
||||
A1 = np.hstack([z[f'p{pm["proj"][0, c][0]}_w{pm["proj"][0, c][2]}_A'] for c in range(C // 32)])
|
||||
w['gain0'] = next(t for t in tables if np.array_equal(q8(input_norm(xs[0], t))[IC], A1[0]) and
|
||||
np.array_equal(q8(input_norm(xs[5], t))[IC], A1[5]))
|
||||
p_out = pm['out'][0]
|
||||
C0 = Cm(p_out, 0)
|
||||
rows = np.array(xs[:64])
|
||||
w['table7'] = next(t for t in tables if np.array_equal(add(rows[:len(C0)][:2], t[None]).view(np.uint16), C0[:2].view(np.uint16)))
|
||||
up, dn = pm['mlp'][0]
|
||||
Rt = f16(z[f'p{pm["out"][NH - 1]}_w0_D']).astype(H)
|
||||
Aup, Cdn = z[f'p{up}_w0_A'], Cm(dn)
|
||||
w['gain14'] = next(t for t in tables if np.array_equal(
|
||||
q8((Rt[:2] * (rowsum_sq(Rt[:2])[:, None] * t[None]).astype(H)).astype(H))[:, IC], Aup[:2]))
|
||||
w['table16'] = next(t for t in tables if np.array_equal(add(Rt[:2], t[None]).view(np.uint16), Cdn[:2].view(np.uint16)))
|
||||
np.savez_compressed(out, **w)
|
||||
|
||||
|
||||
def key_tokens(stage, w, x0, y0, ex, ey, inp):
|
||||
kind, C, CX, NH, _ = STAGES[stage]
|
||||
IC = inter(C)
|
||||
UN = [IC.index(i) for i in range(C)]
|
||||
xs = []
|
||||
if kind == 'dec':
|
||||
hx, hy = ex // 2, ey // 2
|
||||
X0, Y0 = x0 // 2 - 1, y0 // 2 - 1
|
||||
low = np.zeros((48, CX), np.uint8)
|
||||
for r in range(36):
|
||||
X, Y = mirror(X0 + r % 6, hx), mirror(Y0 + r // 6, hy)
|
||||
low[r] = np.concatenate([inp['lo'][p, Y, X] for p in range(CX // 32)])
|
||||
Ex = q8(mm(low, w['we'], np.repeat(w['be'][None, :], 48, 0)))
|
||||
for (x, y) in keys(x0, y0):
|
||||
xx, yy = mirror(x, ex), mirror(y, ey)
|
||||
if kind == 'dec':
|
||||
r, s = (y // 2 - Y0) * 6 + (x // 2 - X0), 2 * (y & 1) + (x & 1)
|
||||
sk = np.concatenate([inp['skip'][p, yy, xx] for p in range(C // 32)])[UN]
|
||||
xs.append((e4m3(Ex[r, C * s:C * s + C]).astype(H) + e4m3(sk).astype(H)).astype(H))
|
||||
else:
|
||||
xs.append(e4m3(np.concatenate([inp['tok'][p, yy, xx] for p in range(C // 32)])[UN]).astype(H))
|
||||
return xs
|
||||
|
||||
|
||||
def load(cap, stage, frame):
|
||||
from rr_capture import Capture
|
||||
c = Capture(os.path.expanduser(cap))
|
||||
l = c.find(f'swin_{stage}', frame)
|
||||
kind, C, CX, NH, _ = STAGES[stage]
|
||||
ex, ey = (int(v) for v in np.frombuffer(l.args[:8], np.uint32))
|
||||
sx, sy = (int(v) for v in np.frombuffer(l.args[80:84], np.uint16))
|
||||
ptr = {p.offset: p.delta for p in l.pointers}
|
||||
pre = np.frombuffer(l.pre(l.arena_alloc), np.uint8)
|
||||
post = np.frombuffer(l.post_buffer(c, l.arena_alloc), np.uint8)
|
||||
inp = {}
|
||||
if kind == 'dec':
|
||||
inp['lo'] = pre[ptr[8]:ptr[8] + CX * (ex // 2) * (ey // 2)].reshape(CX // 32, ey // 2, ex // 2, 32)
|
||||
inp['skip'] = pre[ptr[24]:ptr[24] + C * ex * ey].reshape(C // 32, ey, ex, 32)
|
||||
else:
|
||||
inp['tok'] = pre[ptr[8]:ptr[8] + C * ex * ey].reshape(C // 32, ey, ex, 32)
|
||||
outs = {'tok': post[ptr[48]:ptr[48] + C * ex * ey].reshape(C // 32, ey, ex, 32)}
|
||||
if kind == 'enc' and CX:
|
||||
outs['mrg'] = post[ptr[56]:ptr[56] + CX * (ex // 2) * (ey // 2)].reshape(CX // 32, ey // 2, ex // 2, 32)
|
||||
img = {i.offset: i for i in l.images}
|
||||
|
||||
def surf(off):
|
||||
i = img[off]
|
||||
return np.frombuffer(i.path.read_bytes(), np.float16).reshape(i.height, i.width, 4)
|
||||
tex = {k: surf(k) for k in img}
|
||||
return l, ex, ey, sx, sy, inp, outs, tex
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('stage')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('weights')
|
||||
ap.add_argument('--extract', metavar='DUMP')
|
||||
ap.add_argument('--dump-block', default='1,1', help='the block the dump was taken of (for --extract)')
|
||||
ap.add_argument('--blocks', nargs='*', default=['1,1'])
|
||||
o = ap.parse_args()
|
||||
l, ex, ey, sx, sy, inp, outs, tex = load(o.capture, o.stage, o.frame)
|
||||
kind, C, CX, NH, _ = STAGES[o.stage]
|
||||
if o.extract:
|
||||
bx, by = (int(v) for v in o.dump_block.split(','))
|
||||
extract(o.stage, o.extract, l.weights, o.weights, inp, 8 * bx - sx, 8 * by - sy, ex, ey)
|
||||
return
|
||||
w = dict(np.load(o.weights))
|
||||
for blk in o.blocks:
|
||||
bx, by = (int(v) for v in blk.split(','))
|
||||
x0, y0 = 8 * bx - sx, 8 * by - sy
|
||||
tokens, merged, head = block(o.stage, w, x0, y0, ex, ey, inp)
|
||||
inside = [t for t in range(64) if 0 <= x0 + t % 8 < ex and 0 <= y0 + t // 8 < ey]
|
||||
tk = sum(np.array_equal(tokens[t], np.concatenate([outs['tok'][p, y0 + t // 8, x0 + t % 8] for p in range(C // 32)]))
|
||||
for t in inside)
|
||||
msg = f'block {bx},{by}: tokens {tk}/{len(inside)}'
|
||||
if merged is not None:
|
||||
mg = sum(np.array_equal(merged[r], np.concatenate([outs['mrg'][p, y0 // 2 + my, x0 // 2 + mx] for p in range(CX // 32)]))
|
||||
for r, (mx, my) in enumerate(MERGE_ORDER) if 0 <= x0 // 2 + mx < ex // 2 and 0 <= y0 // 2 + my < ey // 2)
|
||||
msg += f', merged {mg}'
|
||||
if kind == 'enc' and not CX: # the bottleneck: the ungated filter to surface 112 only
|
||||
o112, _ = filter_head(head, tex[88], x0, y0, ex, ey, ungated=True)
|
||||
ok = sum(np.array_equal(o112[t], tex[112][y0 + t // 8, x0 + t % 8, :3]) for t in inside)
|
||||
elif kind == 'enc':
|
||||
o112, o120 = filter_head(head, tex[88], x0, y0, ex, ey)
|
||||
ok = sum(np.array_equal(o112[t], tex[112][y0 + t // 8, x0 + t % 8, :3]) and
|
||||
np.array_equal(o120[t], tex[120][y0 + t // 8, x0 + t % 8, :3]) for t in inside)
|
||||
else:
|
||||
s112 = filter112(head, tex[88], tex[96], tex[104], x0, y0, ex, ey)
|
||||
ok = sum(np.array_equal(s112[t], tex[112][y0 + t // 8, x0 + t % 8, :3]) for t in inside)
|
||||
print(msg + f', surfaces {ok}/{len(inside)}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run one Ray Reconstruction stage shader on a captured launch and compare it with NVIDIA's result.
|
||||
|
||||
usage: test_layer.py CAPTURE KERNEL FRAME SHADER.spv WORK [--kvk build/rr/kvk] [--time N] [--subgroup 32|64]
|
||||
|
||||
KERNEL is a substring of the denoiser kernel's name (e.g. swin_enc1, hkpn_output_kernel_transformer), FRAME the
|
||||
1-based occurrence in the capture. The shader follows the RR stage contract (engine/rr/README.md):
|
||||
|
||||
binding 0 the parameter block: NVIDIA's argument block with every pointer into the plane arena
|
||||
replaced by its byte offset in the arena, the weight pointer and every texture or surface
|
||||
handle by zero (stage_params below; the runtime builds the same block)
|
||||
binding 1 the weight image exactly as NGX uploads it (argument 40), read-only
|
||||
binding 2 the plane arena, read/write
|
||||
bindings 3, 4, ... the textures in ascending argument offset, then the surfaces in ascending argument offset
|
||||
|
||||
Every surface starts zeroed and the arena starts as it was before NVIDIA's launch; afterwards each surface and the
|
||||
whole arena are compared with what NVIDIA's launch left. Exit status 0 only when everything is byte-identical.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
|
||||
|
||||
# GLSL image formats of the stage shaders' surfaces -> (channels, kvk bits)
|
||||
SURFACE_FORMATS = {'rgba16f': (4, 16), 'rg16f': (2, 16), 'r16f': (1, 16), 'rgba8_snorm': (4, 9), 'rgba8': (4, 8)}
|
||||
|
||||
|
||||
def stage_params(launch):
|
||||
args = bytearray(launch.args)
|
||||
arena = launch.arena_alloc
|
||||
for p in launch.pointers:
|
||||
value = p.delta if p.alloc == arena and p.offset != 40 else 0
|
||||
struct.pack_into('<Q', args, p.offset, value)
|
||||
# texture and surface handles: 1 where NGX bound one (the kernels test optional inputs' handles for null, e.g.
|
||||
# enc0's specular hit distance at argument 248), 0 elsewhere
|
||||
for image in launch.images:
|
||||
struct.pack_into('<Q', args, image.offset, 1)
|
||||
return bytes(args)
|
||||
|
||||
|
||||
def e4m3_table():
|
||||
t = np.zeros(256)
|
||||
for c in range(256):
|
||||
s, e, m = c >> 7, (c >> 3) & 15, c & 7
|
||||
v = np.nan if (e == 15 and m == 7) else (m * 2.0 ** -9 if e == 0 else (1 + m / 8) * 2.0 ** (e - 7))
|
||||
t[c] = -v if s else v
|
||||
return t
|
||||
|
||||
|
||||
def psnr(a, b):
|
||||
finite = np.isfinite(a) & np.isfinite(b)
|
||||
if not finite.any():
|
||||
return float('nan')
|
||||
err = np.mean((a[finite] - b[finite]) ** 2)
|
||||
peak = max(np.max(np.abs(b[finite])), 1e-6)
|
||||
return float('inf') if err == 0 else 10 * np.log10(peak * peak / err)
|
||||
|
||||
|
||||
def compare_image(name, image, got, ref, ignore_channels=()):
|
||||
"""IGNORE_CHANNELS: channels NVIDIA's kernel fills from an undefined register (left out of the comparison)"""
|
||||
if image.bits == 16:
|
||||
a, b = np.frombuffer(got, np.float16).astype(np.float64), np.frombuffer(ref, np.float16).astype(np.float64)
|
||||
elif image.bits == 32:
|
||||
a, b = np.frombuffer(got, np.float32).astype(np.float64), np.frombuffer(ref, np.float32).astype(np.float64)
|
||||
else:
|
||||
dt = np.int8 if image.bits == 9 else np.uint8
|
||||
a, b = np.frombuffer(got, dt).astype(np.float64), np.frombuffer(ref, dt).astype(np.float64)
|
||||
keep = np.ones(len(a), bool)
|
||||
for c in ignore_channels:
|
||||
keep[c::image.channels] = False
|
||||
a, b = a[keep], b[keep]
|
||||
element_bytes = len(got) // len(keep)
|
||||
same = (np.frombuffer(got, np.uint8).reshape(-1, element_bytes) == np.frombuffer(ref, np.uint8).reshape(-1, element_bytes))[keep]
|
||||
elements = len(a)
|
||||
equal = np.sum(a == b) + np.sum(np.isnan(a) & np.isnan(b))
|
||||
worst = np.nanmax(np.abs(a - b)) if elements else 0
|
||||
print(f' {name}: {image.width}x{image.height}x{image.channels} fmt{image.format}'
|
||||
f'{" (channels " + ",".join(map(str, ignore_channels)) + " ignored)" if ignore_channels else ""}: '
|
||||
f'{100.0 * equal / elements:.4f}% identical values, max |diff| {worst:.6g}, PSNR {psnr(a, b):.2f} dB, '
|
||||
f'nonfinite ours/ref {int(np.sum(~np.isfinite(a)))}/{int(np.sum(~np.isfinite(b)))}')
|
||||
return bool(same.all())
|
||||
|
||||
|
||||
def compare_arena(got, before, ref):
|
||||
g, p, r = (np.frombuffer(x, np.uint8) for x in (got, before, ref))
|
||||
written = (r != p) | (g != p)
|
||||
differ = g != r
|
||||
print(f' arena: {int(written.sum())} bytes written by either side, {int(differ.sum())} differ', end='')
|
||||
if differ.any():
|
||||
idx = np.nonzero(differ)[0]
|
||||
t = e4m3_table()
|
||||
dv = np.abs(t[g[idx]] - t[r[idx]])
|
||||
print(f' (first at byte {idx[0]:#x}, last at {idx[-1]:#x}; as e4m3 values: max |diff| {np.nanmax(dv):.6g}, '
|
||||
f'{int(np.sum(dv <= 0.0625 * np.abs(t[r[idx]]) + 1e-12))} within one e4m3 step)')
|
||||
else:
|
||||
print()
|
||||
return not differ.any()
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('kernel')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('shader')
|
||||
ap.add_argument('work')
|
||||
ap.add_argument('--kvk', default=str(Path(__file__).resolve().parents[2] / 'build/rr/kvk'))
|
||||
ap.add_argument('--time', type=int, default=1)
|
||||
ap.add_argument('--subgroup', type=int, default=32)
|
||||
ap.add_argument('--weights', help='bind this file at binding 1 instead of NGX\'s weight image (native stages)')
|
||||
ap.add_argument('--grid', help='X,Y workgroups (default: the launch grid)')
|
||||
ap.add_argument('--ignore', action='append', default=[], metavar='SURFACE:CHANNEL',
|
||||
help='leave a channel out of the comparison, e.g. 112:3 for a value NVIDIA stores from an '
|
||||
'undefined register (repeatable)')
|
||||
o = ap.parse_args()
|
||||
|
||||
cap = Capture(o.capture)
|
||||
launch = [l for l in cap.launches if o.kernel in l.kernel and 'diamond' not in l.kernel or
|
||||
('diamond' in o.kernel and o.kernel in l.kernel)][o.frame - 1]
|
||||
work = Path(o.work)
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
print(f'{launch.kernel} (launch {launch.sequence}, grid {launch.grid}, block {launch.block})')
|
||||
|
||||
(work / 'params.bin').write_bytes(stage_params(launch))
|
||||
(work / 'weights.bin').write_bytes(Path(o.weights).read_bytes() if o.weights else launch.weights)
|
||||
arena = launch.arena_alloc
|
||||
arena_before = launch.pre(arena)
|
||||
(work / 'arena.bin').write_bytes(arena_before)
|
||||
gx, gy = (int(v) for v in o.grid.split(',')) if o.grid else launch.grid[:2]
|
||||
lines = [f'param {work}/params.bin', f'grid {gx} {gy}']
|
||||
# Bind in the shader's order (its ptx2rr.py sidecar, SHADER.json: textures then surfaces by argument offset).
|
||||
# A handle the shader reads but NGX left null gets a zeroed 1x1 image.
|
||||
sidecar = Path(o.shader).with_suffix('.json')
|
||||
if sidecar.exists():
|
||||
info = json.loads(sidecar.read_text())
|
||||
order = [('texture', off, None) for off in info['textures']] + [('surface', off, f) for off, f in info['surfaces']]
|
||||
else:
|
||||
order = [(i.kind, i.offset, None) for i in launch.images]
|
||||
by_key = {(i.kind, i.offset): i for i in launch.images}
|
||||
(work / 'zero.bin').write_bytes(bytes(64))
|
||||
for kind, offset, fmt in order:
|
||||
image = by_key.get((kind, offset))
|
||||
if image is None:
|
||||
ch, bits = SURFACE_FORMATS.get(fmt, (4, 16))
|
||||
lines.append(f'tex {offset} {work}/zero.bin 1 1 4 16 nearest' if kind == 'texture' else
|
||||
f'surf {offset} 1 1 {ch} {bits} - {work}/dummy{offset}.bin')
|
||||
elif kind == 'texture':
|
||||
lines.append(f'tex {image.offset} {image.path} {image.width} {image.height} {image.channels} {image.bits} '
|
||||
f'{"linear" if image.linear else "nearest"}')
|
||||
else:
|
||||
lines.append(f'surf {image.offset} {image.width} {image.height} {image.channels} {image.bits} - '
|
||||
f'{work}/surf{image.offset}.bin')
|
||||
n_images = len(order)
|
||||
lines.append(f'lut {work}/weights.bin')
|
||||
lines.append(f'inout {work}/arena.bin {work}/arena.out.bin')
|
||||
probes = info.get('probes', []) if sidecar.exists() else []
|
||||
oob = info.get('oob', False) if sidecar.exists() else False
|
||||
if probes: # ptx2rr.py --probe: the debug buffer after the surfaces
|
||||
threads = launch.block[0] * launch.block[1] * launch.block[2]
|
||||
lines.append(f'outbuf {work}/debug.bin {4 * threads * len(probes)}')
|
||||
elif oob: # ptx2rr.py --oob-report: count, lowest and highest address per buffer
|
||||
(work / 'oob.init').write_bytes(struct.pack('<9I', *([0, 0xffffffff, 0] * 3)))
|
||||
lines.append(f'inout {work}/oob.init {work}/oob.bin')
|
||||
lines.append('bindings 0 ' + ' '.join(str(3 + i) for i in range(n_images)) + ' 1 2' +
|
||||
(f' {3 + n_images}' if probes or oob else ''))
|
||||
(work / 'manifest').write_text('\n'.join(lines) + '\n')
|
||||
r = subprocess.run([o.kvk, o.shader, str(work / 'manifest'), str(o.time), str(o.subgroup)],
|
||||
capture_output=True, text=True)
|
||||
sys.stdout.write(r.stdout)
|
||||
if r.returncode != 0:
|
||||
sys.stderr.write(r.stderr)
|
||||
sys.exit(f'kvk failed ({r.returncode})')
|
||||
|
||||
identical = True
|
||||
bound = {(kind, offset) for kind, offset, _ in order}
|
||||
for image in launch.images:
|
||||
if image.kind != 'surface':
|
||||
continue
|
||||
if ('surface', image.offset) not in bound:
|
||||
print(f' surface {image.offset}: not written by this kernel')
|
||||
continue
|
||||
got = (work / f'surf{image.offset}.bin').read_bytes()
|
||||
waived = [int(s.split(':')[1]) for s in o.ignore if int(s.split(':')[0]) == image.offset]
|
||||
identical &= compare_image(f'surface {image.offset}', image, got, image.path.read_bytes(), waived)
|
||||
if arena is not None:
|
||||
identical &= compare_arena((work / 'arena.out.bin').read_bytes(), arena_before,
|
||||
launch.post_buffer(cap, arena))
|
||||
if oob:
|
||||
r = struct.unpack('<9I', (work / 'oob.bin').read_bytes()[:36])
|
||||
for k, name in enumerate(('weight reads', 'arena reads', 'arena writes')):
|
||||
print(f' out of range {name}: {r[3 * k]}' + (f' (bytes {r[3 * k + 1]:#x}..{r[3 * k + 2]:#x}, size '
|
||||
f'{len(launch.weights) if k == 0 else len(arena_before):#x})' if r[3 * k] else ''))
|
||||
print('IDENTICAL' if identical else 'DIFFERENT')
|
||||
sys.exit(0 if identical else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Replay one captured denoiser launch through ZLUDA on Linux (tools/replay_rr_launch.cpp) and compare it with the
|
||||
capture, like test_layer.py does for a stage shader.
|
||||
|
||||
usage: zluda_replay.py CAPTURE KERNEL FRAME WORK [--ptx MODULE] [--zluda DIR]
|
||||
|
||||
KERNEL and FRAME select the launch as in test_layer.py. MODULE defaults to the corpus module holding the kernel
|
||||
(~/.cache/d4r-rr-corpus); pass an edited copy to add debug stores. ZLUDA's own settings (D4R_ZLUDA_WMMA,
|
||||
D4R_ZLUDA_WMMA_FP8, D4R_ZLUDA_WMMA_FP8_NATIVE, ...) are taken from the environment. Outputs land in WORK
|
||||
(surfOFFSET.bin, arena.out.bin) so the two replays can be compared with each other too.
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from rr_capture import Capture # noqa: E402
|
||||
from test_layer import compare_arena, compare_image # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('capture')
|
||||
ap.add_argument('kernel')
|
||||
ap.add_argument('frame', type=int)
|
||||
ap.add_argument('work')
|
||||
ap.add_argument('--ptx')
|
||||
ap.add_argument('--zluda', default=os.path.expanduser('~/.cache/d4r-zluda-rr'))
|
||||
ap.add_argument('--rocm', default='/opt/rocm/lib')
|
||||
ap.add_argument('--pad', type=int, default=0, help='bytes of zeros appended to the arena (debug stores)')
|
||||
o = ap.parse_args()
|
||||
cap = Capture(o.capture)
|
||||
launch = [l for l in cap.launches if o.kernel in l.kernel and 'diamond' not in l.kernel or
|
||||
('diamond' in o.kernel and o.kernel in l.kernel)][o.frame - 1]
|
||||
work = Path(o.work)
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
module = o.ptx or next(str(p) for p in sorted(Path('~/.cache/d4r-rr-corpus').expanduser().glob('*.ptx'))
|
||||
if f'.entry {launch.kernel}(' in p.read_text(errors='replace'))
|
||||
(work / 'args.bin').write_bytes(launch.args)
|
||||
arena = launch.arena_alloc
|
||||
lines = [f'args {work}/args.bin', 'grid ' + ' '.join(map(str, launch.grid)),
|
||||
'block ' + ' '.join(map(str, launch.block))]
|
||||
if o.pad:
|
||||
(work / 'arena-pad.bin').write_bytes(launch.pre(arena) + bytes(o.pad))
|
||||
for p in launch.pointers:
|
||||
out = f' {work}/arena.out.bin' if p.alloc == arena else ''
|
||||
source = f'{work}/arena-pad.bin' if o.pad and p.alloc == arena else f'{launch.directory}/alloc-{p.alloc}.bin'
|
||||
lines.append(f'buf {p.offset} {source} {p.delta}{out}')
|
||||
for image in launch.images:
|
||||
if image.kind == 'texture':
|
||||
# SNORM_INT8X4 (200): a SIGNED_INT8 array read as normalized floats gives the same values
|
||||
fmt = 8 if image.format == 200 else image.format
|
||||
lines.append(f'tex {image.offset} {image.path} {image.width} {image.height} {image.channels} '
|
||||
f'{fmt} {"linear" if image.linear else "nearest"}')
|
||||
else:
|
||||
# ZLUDA has no SNORM_INT8X4 arrays (format 200); surface stores write the same bytes to a SIGNED_INT8 one
|
||||
fmt = 8 if image.format == 200 else image.format
|
||||
lines.append(f'surf {image.offset} {image.width} {image.height} {image.channels} {fmt} '
|
||||
f'{work}/surf{image.offset}.bin')
|
||||
(work / 'manifest').write_text('\n'.join(lines) + '\n')
|
||||
env = dict(os.environ, LD_LIBRARY_PATH=f'{o.zluda}:{o.rocm}:' + os.environ.get('LD_LIBRARY_PATH', ''))
|
||||
print(f'{launch.kernel} (launch {launch.sequence}) through ZLUDA {o.zluda}')
|
||||
r = subprocess.run([str(ROOT / 'build/replay_rr_launch'), module, launch.kernel, str(work / 'manifest')],
|
||||
env=env, capture_output=True, text=True)
|
||||
sys.stdout.write(r.stdout)
|
||||
if r.returncode:
|
||||
sys.stderr.write(r.stderr[-4000:])
|
||||
sys.exit('replay failed')
|
||||
same = True
|
||||
for image in launch.images:
|
||||
if image.kind == 'surface':
|
||||
same &= compare_image(f'surface {image.offset}', image, (work / f'surf{image.offset}.bin').read_bytes(),
|
||||
image.path.read_bytes())
|
||||
if arena is not None and not o.pad:
|
||||
same &= compare_arena((work / 'arena.out.bin').read_bytes(), launch.pre(arena), launch.post_buffer(cap, arena))
|
||||
print('IDENTICAL' if same else 'DIFFERENT')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user