engine: this GPU's own arithmetic and shared weight tiles as the builders' defaults
- layer_m.comp builds relaxed by default (M_RELAXED: f32 accumulators, paired FP8 conversions, no activation table) with two windows per wave sharing weight tiles (WPW=2); M_EXACT=1 keeps NVIDIA's roundings, bit-identical to the CUDA path - layer.comp reuses weight tiles across its waves (TPW=2 in the 32-channel layers) - engine/rr/rr2_w.comp: one wave per window for preset F's dec0_0 and enc0, used by compile_rr2.py at the new default D4R_RR_RELAXED=3 (build_native_model.sh too); D4R_RR_FUSE=1 matches the reference's fused multiply-adds, ptx2rr.py --snorm16 - [Engine] Network defaults to vulkan; the HIP network is opt-in
This commit is contained in:
1 parent
8eb7a94533
commit
f0b55a850a
24 files changed
+1108
-254
No files matched your search
@@ -353,3 +353,148 @@ scripts/run_rr_engine_compare.sh`, frames aligned by one):
|
||||
rounds, a last-bit difference in a few elements. With a non-identity camera, hit distance and mask bound and a
|
||||
moving scene that showed as 40 - 50 dB on later frames in a synthetic scene whose affected feature is rounding
|
||||
noise around zero in both paths; no other configuration was affected.
|
||||
|
||||
## Against a real NVIDIA card: `D4R_RR_FUSE` (2026-10-13)
|
||||
|
||||
Every reference above is the CUDA path through ZLUDA. NVIDIA's own kernels (310.9.1's `rr2_*` PTX, replayed through
|
||||
plain CUDA on an RTX PRO 4500 Blackwell, driver 580; tools and logs in `build/rr/f/nv/`) give an image 39-42 dB from
|
||||
that reference on every frame of capF, capF4K and capFQ. Register bisection between the two (`probe_pack.py`,
|
||||
`probe_cmp.py`: a debug store after every instruction, run on both) found why:
|
||||
|
||||
- **Fused multiply-adds.** NVIDIA's compiler contracts a `mul.f16` / `mul.f16x2` whose product only feeds an
|
||||
`add` / `sub` into one fused multiply-add; the kernels give those instructions no rounding modifier. ZLUDA runs them
|
||||
as written. `ptx_contract.py` is the rule (single-use product, same block, the first operand when both are
|
||||
products): with it ZLUDA's result of every network kernel matches the card's (0 of ~5900 4 KiB chunks for most
|
||||
launches, a handful where an `ex2.approx` / `div.approx` differs by one f32 step). The FP8 products, the e4m3
|
||||
conversions and every unfiltered texture fetch were already identical. The sites are the residual steps
|
||||
`out = in + branch * s`, the MLP activation `x (0.5 + y (0.412 - 0.081 |y|))` and the guide's bilinear blend.
|
||||
Probing a product stops the card from fusing it: compare unprobed results, or probe everything.
|
||||
- **Filtered fetches of 8-bit textures.** A filtered fetch of the SNORM8 normals returns this card's value (8-bit
|
||||
weights) rounded to a 16-bit step: `f32(roundEven(v * 32767) / 32767)`, exact on 256 of 256 probed values. The
|
||||
output filter's UNORM8 fetches differ too; no model for those yet.
|
||||
- **Approximate instructions.** `lg2.approx` near 1 differs by ~8e-8 absolute, `ex2.approx` / `rcp.approx` /
|
||||
`div.approx` by one f32 step. Not emulated.
|
||||
|
||||
`D4R_RR_FUSE=1 compile_rr2.py CAPTURE OUT --ptx-dir <build/rr/f/nv/mkptx.sh output> --native DLL` builds the model
|
||||
with the first two: `rr2_m.comp -DRR_FUSE` (`MADD` / `MADD4`), the activation table from `enc1_ref.act` with
|
||||
`FUSE`, the translated parts from the rewritten modules, encoder 0's normals through `ptx2rr.py --snorm16 528`. All
|
||||
14 native stages are byte-identical to ZLUDA running the rewritten modules (`build/rr/f/nv/nfuse.sh`), and so is
|
||||
the whole model on capF's three frames. Presented image against the NVIDIA card, 640x360 -> 1080p:
|
||||
|
||||
| frame | default build | `D4R_RR_FUSE` |
|
||||
|---|---|---|
|
||||
| 1 (reset) | 40.9 dB, 27.3% of values equal | 85.6 dB, 99.5% equal |
|
||||
| 2 | 39.2 dB | 43.0 dB |
|
||||
| 3 | 39.3 dB | 41.8 dB |
|
||||
|
||||
Temporal frames stay near 42 dB: the remaining one-step differences (approximate instructions, the UNORM8 fetches)
|
||||
enter encoder 0's reprojection and the recurrent history amplifies them, as the one-step values of the output stage
|
||||
did before the fused-convert fix. 1280x720 -> 4K: 5.68 / 6.24 ms (default 5.78 / 6.35). The default build is
|
||||
unchanged and still byte-identical to the captures.
|
||||
|
||||
### This GPU's own arithmetic for preset F: `D4R_RR_RELAXED` (2026-10-13)
|
||||
|
||||
Two NVIDIA setups differ from each other as much as a relaxed build differs from the reference: NVIDIA's kernels on
|
||||
an RTX 4000 Ada (driver 595) and on the Blackwell cards (driver 580) give images 41.0 / 39.0 / 38.9 dB apart on
|
||||
capF's three frames, although their approximate instructions and filtered texture fetches are bit-identical
|
||||
(`build/rr/f/nv/math/`); the `D4R_RR_FUSE` build matches the Blackwell result only. So there is no single NVIDIA
|
||||
image to reproduce, and `compile_rr2.py` now takes `D4R_RR_RELAXED=1|2` like `build_native_model.sh` does for D / E
|
||||
(`rr_exact.glsl`: f32 accumulators across the 32-deep steps, one rounding into FP8; 2 also leaves the conversions to
|
||||
the driver). Preset F, 1280x720 -> 4K: 5.40 / 5.99 ms against 5.78 / 6.35 (network 3.45 / 3.77 against 3.83 / 4.14);
|
||||
40.8 / 39.1 / 39.2 dB from the exact build and 39-41 dB from either NVIDIA card. The default stays exact.
|
||||
|
||||
Why only 6%: with the rounding gone the products are about a sixth of a full-resolution stage. dec0's first kernel
|
||||
at 720p, relaxed (`build/rr/f/nv/exp/t.sh`, cutting parts out): 626 us, of which the two attention heads ~370, the
|
||||
four MLP branches ~100-160, expansion / residual / store ~120-160, an empty dispatch 10; inside the attention the
|
||||
softmax table ~30, the bias tiles ~40, weight loads ~12. Its 88 products alone would take ~108 us at the measured
|
||||
198 T MAC/s. The compiled stage still has 338 binary16 <-> f32 conversions, 176 FP8 conversions and 128 clamps for
|
||||
44 product instructions (biases and tables are binary16, the activation and softmax tables are indexed by binary16
|
||||
bits, FP8 conversions saturate): what is left is format traffic and the four waves' shared-memory hand-offs, not
|
||||
arithmetic. NVIDIA's kernel runs the same stage in 203 us with one 32-thread warp per window.
|
||||
|
||||
`D4R_RR_RELAXED=3` adds `RR_NATIVE` (rr2_m.comp): the residual steps and the MLP activation in f32, straight into
|
||||
FP8, with no binary16 in between and no activation table. 1280x720 -> 4K: 5.17 / 5.72 ms (network 3.24 / 3.48), still
|
||||
39-41 dB from the exact build and from both NVIDIA cards. dec0's first kernel at that level: 588 us; the barriers
|
||||
cost nothing; reading the constants (weight tiles, bias tiles, the softmax table) costs 170 us; the products about
|
||||
108; the rest is the tokens' way through shared memory and the arena, conversions and the expansion
|
||||
(`build/rr/f/nv/exp/run1.sh`).
|
||||
|
||||
What bounds a stage (2026-10-13; `build/rr/f/nv/exp/`). dec0's first kernel at level 3 takes 581 us at 720p and a
|
||||
wave executes about 2500 instructions (`dyn.py`): 96 products, 356 binary16 <-> f32 conversions, 296 FP8
|
||||
conversions, 224 clamps of the saturating FP8 conversions, 142 buffer loads, 75 shared-memory operations, about 400
|
||||
wait / delay markers. What was tried on top of level 3, none of it a gain:
|
||||
|
||||
- the clamps removed: -30 us (2.2 ps per instruction and wave), but not usable: without the clamp the hardware
|
||||
conversion returns NaN above 464 where the clamp gives 448 (`satprobe.comp`);
|
||||
- f32 copies of the biases: 176 fewer conversions and 78 more loads per wave, slightly slower (587-595 us): a load
|
||||
costs about three arithmetic instructions;
|
||||
- the softmax cubic computed, not read from its table: the same time;
|
||||
- no barriers: the same time;
|
||||
- one wave per window walking the four token slices, every weight, key and value tile read once
|
||||
(`rr2_w.comp`, byte-identical to the four-wave build): 27% fewer instructions per window, 30% fewer buffer loads,
|
||||
60% fewer shared-memory loads, and the same time (600 us); it needs all 256 vector registers (four waves: 192).
|
||||
|
||||
So time here is not a function of the instruction count alone, and there is no model yet that predicts both the
|
||||
clamp result and the one-wave result. The output stage is compute-bound too: 100 / 300 extra integer steps per
|
||||
thread in the filter add 0.45 / 1.33 ms (`dz.sh`). NVIDIA's kernel runs dec0's first kernel in 203 us.
|
||||
|
||||
Shared memory throttles this GPU (2026-10-13, `build/rr/f/nv/cost/occ.py`, `occ2.py`). A dependent chain of f32
|
||||
multiply-adds runs at full rate while the workgroup declares up to about 4 KiB of `shared` storage per wave; above
|
||||
that the rate falls in proportion (one wave per workgroup: 6 KiB 1.24x slower, 8 KiB 1.55x, 13 KiB 3.1x, 62 KiB 12x;
|
||||
four waves: 13 KiB full rate, 26 KiB 1.55x, 52 KiB 3.1x). The register count does not matter (0 to 224 live values:
|
||||
the same rate). The decoders' first kernels held 21 to 46 KiB (LOW, EX, XE): `rr2_m.comp` now keeps the 4 x 4 low
|
||||
tokens in KVS and the block's unquantised input in each lane's registers (`xr`), byte-identical on every stage
|
||||
checked, and the network is 0.10 - 0.13 ms faster at 1280x720 -> 4K: exact build 5.68 / 6.25 ms (was 5.78 / 6.35),
|
||||
`D4R_RR_RELAXED=3` 5.07 / 5.56 ms (was 5.17 / 5.72). It is also why the first one-wave prototype (13 KiB in one
|
||||
wave) showed no gain: with 4 KiB (`exp/rr2_w.comp`: the low tokens borrow XT, the expansion borrows KVS, V^T is
|
||||
stored, read into registers and overwritten by QK) it takes 534 us against the four-wave shader's 581, still
|
||||
byte-identical to it. Extra arithmetic injected into either shader costs one instruction per cycle on about 128
|
||||
wave slots (3.8e11 wave-instructions per second); NVIDIA's card measures 7.5e11 (`build/rr/f/nv/sass/rate.cpp`), and
|
||||
its compiled kernel for this stage is 3,729 instructions per window (`sass/nv/dec0_0.sass`) against about 10,000
|
||||
executed here. What an instruction costs here (`cost/cost.py`, `cost2.py`): an f32 multiply-add is the unit; a
|
||||
32-bit integer multiply about seven; converting a pair to FP8 with the clamp about eight; a buffer read about 0.1 ps
|
||||
per byte and lane (16 bytes: some forty multiply-adds), nearly twice that from a 1 MiB table.
|
||||
|
||||
`engine/rr/rr2_w.comp` (2026-10-13) is decoder 0's first kernel for `D4R_RR_RELAXED=3` with one wave per window:
|
||||
every weight, key and value tile read once per window, the expansion plus skip computed once and kept in registers,
|
||||
the scores' cross quadrant computed once and reused transposed (368 products for 384), FP8 values converted in
|
||||
pairs, 4 KiB of shared memory. `compile_rr2.py` uses it for dec0_0 at level 3 (`ONE_WAVE`). The stage alone:
|
||||
453 us against 581 us at 1280x720, arena byte-identical to `rr2_m.comp` at that level
|
||||
(`build/rr/f/nv/invest-20261013/REPORT.md`: 20 variants, each change's instruction count and time). In the engine
|
||||
every image of capF's three frames is identical to the four-wave level 3 model's, and 1280x720 -> 4K is about
|
||||
5.00 / 5.55 ms (four-wave level 3: 5.10 / 5.61; network 3.05 / 3.31 against 3.15 / 3.39). The shader covers C = 32
|
||||
only; the other stages still use `rr2_m.comp`.
|
||||
|
||||
`rr2_w.comp` also covers encoder 0's block now (KIND 4, embedded by `gen_rr2_enc0.py --one-wave` in NVIDIA's own
|
||||
32-thread group, the translated guide tail on the same wave; `compile_rr2.py` `ONE_WAVE = {'dec0_0', 'enc0'}` at
|
||||
`D4R_RR_RELAXED=3`). All 45 images of capF's three frames are identical to the four-wave level 3 model's.
|
||||
1280x720 -> 4K, four interleaved runs each: four-wave 5.10 / 5.68 ms, dec0_0 on one wave 5.04 / 5.62, encoder 0 as
|
||||
well 4.98 / 5.59. Decoder 0's second kernel is not converted: it hands r and the MLP sum (8 KiB of binary16) to its
|
||||
translated tail through shared memory, which a single wave cannot hold without losing the 4 KiB rate.
|
||||
|
||||
### Presets D and E against a real NVIDIA card, and level 3 for them (2026-10-14)
|
||||
|
||||
NVIDIA's own kernels for presets D and E were replayed on an RTX PRO 4500 Blackwell (drivers 595.91 and 580.178: the
|
||||
two give identical images; `build/rr/f/nv/pack.py --corpus ~/.cache/d4r-rr-corpus --first luma_convert`, captures
|
||||
capD / capE3 at 640x360 -> 1280x720, every frame a reset frame). `D4R_RR_RELAXED=3` now also exists for
|
||||
`build_native_model.sh`: level 2 plus `RR_NATIVE` (`rr_exact.glsl` `ACTQ8`: the MLP activation in f32, two elements
|
||||
per saturating conversion, no activation table), and from level 2 on the modules are no longer passed through
|
||||
`spv_exact_convert.py`. Presented image against the NVIDIA card, and the time at 1280x720 -> 4K (reset / next frame):
|
||||
|
||||
| build | D, dB | D, ms | E, dB | E, ms |
|
||||
|---|---|---|---|---|
|
||||
| exact (identical to the CUDA path through ZLUDA) | 38.5 / 39.0 / 38.5 | 4.4 / 4.7 | 40.0 / 39.9 / 40.0 | 4.34 / 4.54 |
|
||||
| `D4R_RR_RELAXED=1` | 36.8 / 37.4 / 36.8 | 3.86 / 4.08 | 38.5 / 38.3 / 38.3 | 3.74 / 3.98 |
|
||||
| `D4R_RR_RELAXED=2` | 36.8 / 37.4 / 36.8 | 3.76 / 3.99 | 38.5 / 38.3 / 38.3 | 3.65 / 3.82 |
|
||||
| `D4R_RR_RELAXED=3` | 36.7 / 37.4 / 36.7 | 3.65 / 3.95 | 38.5 / 38.3 / 38.3 | 3.51 / 3.70 |
|
||||
|
||||
The NVIDIA card runs D at 1280x720 -> 4K in 2.19 / 2.39 ms. Unlike preset F, relaxing costs about 1.6 dB here. What
|
||||
was measured and not built for D / E (`build/rr/f/nv/dstage.sh`, `dnl.sh`, level 3, 720p):
|
||||
|
||||
- shared memory: encoder 1 with 4 / 8 / 16 KiB of extra shared storage takes 287 -> ~305 / ~320 / ~343 us, so the
|
||||
stages above the limit (decoder 1 at 21 KiB, decoder 0 at 17 KiB) would gain a few per cent each, under 1% of a
|
||||
frame;
|
||||
- one wave per window: with every weight tile replaced by one tile loaded once (wrong values, the ceiling of sharing
|
||||
loads between a window's waves) decoder 0 takes the same time (803-818 us), encoder 1 the same, decoder 1 10% less.
|
||||
These windows also have 96 key tokens (3 KiB each for the tokens, the keys and the values at C = 32), which does
|
||||
not fit the 4 KiB a single wave can hold at full rate.
|
||||
Reference in new issue
Block a user