record(GFX1100-TG200): small-N GEMV geometry levers closed negative (VT_GEMV_WARPS, VT_GEMV_SPLITK) - #2892
record(GFX1100-TG200): small-N GEMV geometry levers closed negative (VT_GEMV_WARPS, VT_GEMV_SPLITK)#2892ghazni101 wants to merge 8 commits into
Conversation
… kROCM The GGUF loader routes a block-typed weight to MatmulBTQuant whenever the running device has the provider, so registering these two ops lights up keep-quant compute on every ROCm board with no model-path change: the dense and grouped MoE towers stage once through ResidentWeight and dispatch to the new device GEMM. Coverage mirrors the CUDA sibling exactly — the ten Q8_K-family encodings plus a native Q8_0 arm. The integer dots are the portable scalar forms of the CPU reference bodies in the CPU accumulation order, because gfx1100 exposes no signed byte dot (v_dot4_i32_iu8 is unsigned-only; sdot4 needs a feature this target does not offer), and the gate is bit-exactness against the CPU tier at NMSE 1e-6 with the f64 dequant band at 5e-4. Unsupported dtypes throw naming the dtype instead of silently falling back to a host kernel that cannot follow device pointers; VT_GGUF_KEEP_QUANT=0 restores load-time expansion. Gates on gfx1100 / ROCm 7.14.0: test_rocm_quant_dot 132,094 assertions green across all ten encodings (decode through prefill shapes, broadcast and per-row grouped arms over a poisoned output buffer), focused ctest 'rocm|cross_device|quant' 20/21 with only the pre-existing MoeSiluMul bf16 exactness failure (mudler#1588) remaining, and an end-to-end Qwen3.5-0.8B Q4_K_M decode that is deterministic on device. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:ox-alpha [omp]
Adds the VT_GEMV_MMVQ=1 opt-in K-quant decode GEMV arm for MatmulBTQuant, bit-exact vs the CPU oracle. The arm folds activation quant into the MMVQ GEMV prologue (deleting the standalone QuantizeQ8KK launch) and widens the gate to engine dtypes (bf16/f16 activations, bf16/f32 outputs). Sub-levers: - lever-B1: VT_GEMV_MMVQ_FOLD_MAX makes the fold crossover tunable at runtime - lever-B2: VT_SKINNY_BF16=1 f32-out decode-skinny arm for GDN BA projections - repair: m-gates the whole dispatch and makes the GEMV bit-equal to baseline - repair-2: host-side dispatch-route counters + F1/F2 routing-witness gates - lever-B2 test: red-first f32-out decode-skinny gate, true-unset routing window Architecture: F1 moved the live MatmulBTQuantKernelRocm to rocm_quant_dot.hip (anonymous namespace, internal linkage). T4a's MMVQ arm lives in rocm_grouped_gemm.hip's version (external linkage, renamed to *Gdn). This PR adds delegation: rocm_quant_dot.hip forwards Q4_K/Q5_K/Q6_K calls to the Gdn version, preserving F1's IQ-type providers while activating T4a's MMVQ arm. The default path (VT_GEMV_MMVQ unset) is byte-unchanged from F1. The arm is opt-in and validated by test_rocm_quant_dot (6/6 cases, 719 assertions) and test_rocm_skinny_f32 (2/2 cases, 51 assertions). Token-identical to upstream baseline on Qwen3.5-4B Q4_K, 32-token greedy decode, seed 0. Depends on mudler#2782 (F1 keep-quant GEMM infra). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM-5-2 [OMP]
…Norm epilogue Lever-C adds an opt-in fused norm-quant epilogue (VT_NORM_QUANT_FUSED=1): RmsNormRowKernel emits the row's Q8_K blocks alongside its normal output, and MatmulBTQuant's K-quant branch skips the standalone QuantizeQ8KK when the consuming activation matches the producer token. Byte-identical to the standalone path by construction (shared QuantQ8KSBlock body). New files: - src/vt/rocm/rocm_act_quant.h: shared Q8_K quant-block body - src/vt/rocm/rocm_norm_quant_bridge.h: producer-consumer token contract Also fixes T4a routing counter placement (moved outside anonymous namespace for external linkage) and restores VT_GEMV_MMVQ_FOLD_MAX env var reading that was lost during cherry-pick conflict resolution. The default path (VT_NORM_QUANT_FUSED unset) is byte-unchanged. Validated by test_rocm_quant_dot (12/12 cases, 797 assertions). Token-identical to upstream baseline on Qwen3.5-4B Q4_K, 32-token greedy decode, seed 0. Depends on mudler#2782 (F1) and mudler#2790 (T4a). FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM-5-2 [OMP]
The standalone QuantizeQ8KK kernel used 1 thread per 256-element superblock, each doing a serial scan of 256 elements (~800 instructions). For decode (m=1, nsb=10) only 10 of 128 threads were active, and on wave32 each thread is its own wave, so the kernel took ~13.4 us/call = 540 us/tok (6.0% of wall time). The new QuantizeQ8KKWarpCoop kernel uses 8 threads per superblock (32 elements each). The amax scan is done per-chunk (ascending, ax > amax first-occurrence), then reduced across 8 threads via __shfl_xor_sync with lower-chunk-index tie-break — equivalent to a sequential scan of all 256 elements. The quantization (iscale = -127/mx, DNearestInt, clamp 127) and bsums are order-independent. Output is BYTE-IDENTICAL to the original QuantQ8KSBlock, asserted by the gate test (16/16, 839 assertions) under VT_QUANT_Q8K_WARP=1. For m=1, nsb=10: 1 block, 80/128 threads active (vs 10/128), 3 waves of ~100 instructions (vs 10 waves of ~800) = ~8x fewer wave-cycles. A/B on acceptance workload (Qwen3.5-4B Q4_K_M, 256 tokens, temp 0, seed 0): OFF median: 91.532 tok/s ON median: 93.417 tok/s +2.06%, 5/5 pairs ON>OFF, all 5 byte-identical (1039 bytes) Gated by VT_QUANT_Q8K_WARP (default OFF, read per-call). Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM-5-2 [OMP] FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp]
…pKernel The fused Q8_K quant epilogue in RmsNormRowCoopKernel re-reads the normalized output from global memory (DLoadAct on orow) after Pass 3 stores it. On gfx1100 the 5 KB bf16 row (h=2560) competes with the weight and input in the 16 KB L1, so the re-read can miss to L2. T24 stores the normalized row to dynamic shared memory during Pass 3 (when the value is already in registers) and reads from LDS in the quant epilogue, eliminating the global re-read. The LDS buffer is h * sizeof(Tout) bytes (5 KB for bf16 h=2560), well within the 64 KB per-CU limit. Env gate VT_RMSNORM_LDS_QUANT (default ON) controls the optimization: set to 0 to revert to the global re-read path for A/B isolation. The gate is read per-call so captured graphs and in-process tests pick it up at dispatch time. Byte-identity: the LDS store uses the same conversion as Store (bf16 RNE for bf16 output, exact copy for f32), and DLoadAct reads the same bytes from LDS as from global. Gate test: 16/16 cases, 839 assertions, all passed. A/B measurement pending: the co-tenant 27B model holds the GPU VRAM, blocking the acceptance workload. The A/B script is staged at agent-artifacts/tg200-t24/ab-t24.sh for when the GPU is available. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM-5-2 [OMP] FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp]
…ctions The GDN layers attn_qkv (Q5_K, 24 tensors [2560,8192]) and attn_gate (Q4_K, 24 tensors [4096,2560]) were expanded to bf16 at load time because the V-head row reorder classified them as kTransformedWeight. The reorder is a ROW permutation — quantization blocks are along the K (column) dimension and are self-contained per row — so it is block-safe. T21 routes these tensors as kMatmulWeight to allow keep-quant, copies the blocks via OwnGgufQuantBlocks(mmap_src=nullptr), and applies ReorderVRows to the block bytes at load time. The forward pass already dispatches quantized nk=true weights through vt::MatmulBT, so no forward-pass change was needed. A/B: +3.9% (87.4 to 90.8 tok/s median, 5/5 pairs). Gate 16/16, 839 assertions. Output coherent but not byte-identical (Q5_K integer dot product vs bf16 float MAC). VT_GDN_ROWPERM_KEEP_QUANT=0 reverts to the old bf16 expansion path for A/B isolation. The improvement is less than the projected 14% because the Q5_K GEMV kernel has lower effective bandwidth on small grids (n=2560) than assumed, and wvSplitKSml is more efficient on these grids than projected. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:glm-5-2 [omp] FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp]
ssm_out (out_proj) is Q5_K in the GGUF checkpoint but was expanded to bf16 at load time because the V-head column reorder (ReorderVCols) cuts across Q5_K 256-element block boundaries. T25 keeps the weight in tiled Q5_K order (no ReorderVCols) and permutes the 4096-element GEMV input from grouped to tiled order at runtime instead, cutting weight bandwidth ~4x (Q5_K ~5 MB vs bf16 20 MB per call). The permutation is a simple gather of 128-element groups within each of the 4096-element rows, gated by VT_GDN_COLPERM_KEEP_QUANT=1 (default OFF). A new out_proj_tiled flag on GdnLayerWeights distinguishes the tiled Q5_K path (needs input permutation) from the gdn_expand_nk bf16 path (already column-reordered, no permutation needed) — the nk flag alone conflates both. A/B (5 interleaved pairs, --max-tokens 256 --temperature 0 --seed 0): OFF median=90.930 tok/s, ON median=91.703 tok/s, +0.85%, 5/5 ON>OFF. Output coherent but NOT byte-identical (Q5_K vs bf16 weight precision). Gate test: 16/16, 839 assertions. The improvement is modest because the permutation kernel launch overhead (~13.4 us x 24 calls = ~322 us/tok) offsets most of the weight bandwidth savings (~368 us/tok). The net gain is ~46 us/tok. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM-5-2 [OMP] FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp]
… warm benches, wash in the engine (VT_GEMV_WARPS, VT_GEMV_SPLITK) Attempt #5 of the small-N decode-GEMV bandwidth budget (T34: gate_up 57%, attn_output-Q6K 26%, ssm_out 54%, attn_gate 44%). Two angles, both default-OFF and inert (byte-identical dispatch when unset): VT_GEMV_WARPS (1..8, default 8): warps per block in the MMVQ launch — the UNTESTED direction (T19/T32 tested MORE warps). Pure launch geometry, per-row math unchanged, outputs bit-identical. The t37_gemv_bench (executable-only, real dispatch) wins at 4 warps/block on every shape: 1.09-1.64x, gate_up 96.9 -> 64.1 us/kernel by rocprof kernel trace. VT_GEMV_SPLITK (2..32, default off): deterministic split-K — each row's super-blocks partitioned across S warps into contiguous ranges, S float partials in the graph-safe scratch, an ascending-order reduce kernel (no data atomics; reproducible under graph replay). The row body gained range/partial parameters whose defaults reproduce the old body exactly. Reduction-order change: near-tie adjudication owed before any A/B; parked. Engine A/B (idle window, loadavg 0.42 at start, 1 warm + 5 reps, medians): base 86.073 vs VT_GEMV_WARPS=4 85.641 tok/s (-0.5%, overlapping) with byte-identical bodies. The bench re-reads one warm L2-resident weight tensor 50x, so its baseline is an L2-latency regime; the engine cold-streams a different layer's weights every launch at a controller-limited rate where block distribution does not matter. The trace agrees: the engine's real gate_up launch is ~52 us against the bench's 96.9 us warm baseline. CLOSED NEGATIVE with numbers. Five-of-five small-N GEMV geometry attempts now closed (T19, T32 x2, T37 warps, T37 splitk-parked): geometry micro-tuning cannot move these launches; cold-DRAM streaming and in-graph gaps co-dominate. Gates: test_rocm_quant_dot assertions 841/841 (with and without the knob), test_rocm_prefill_tile 720/720, ctest -R 'rocm|quant' at the HEAD baseline. Following AGENTS Protocol: true Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp] FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:GLM [omp]
29330a7 to
34104db
Compare
|
Recommended disposition: close the T37 experiment PR. The historical engine run measured 86.073 versus 85.641 tok/s for four warps, approximately -0.5%, with matching output. Split-K was parked before its required near-tie adjudication and engine acceptance. The live head Issue #2887 remains open until its record distinguishes the measured four-warp wash from abandoned, unadjudicated split-K. Dependency correctness work remains in its owning PRs. This closure does not mark the broader gfx1100 performance gap fixed. The audited head is |
… rewrote Eleven of this campaign's pull requests are siblings on the same base and each rewrites `## Now` whole, so merging them is last-writer-wins on that section and three records did not survive the sequence. This restores them against the landed tree rather than against any one branch's copy. - The T34 evidence link comes back out. That file is on no branch and not on main, so check-agent-record reads it as a dangling link and reds the agent-record job. The capture itself is stated in the prose; only the file backing it is missing, so it becomes a named owed item (#2936). - VT_ARGMAX_SPLIT is named as T14's lever. #2876 owed that record and its own copy of `## Now` was overwritten by a later sibling. - `## Owed` gains the missing T34 capture. The T37 closed-negative from #2892 and the T35/T35-r3 records from #2893 survived the sequence and are unchanged here. This is the shape AGENTS.md names: a single file every pull request must write is a lock, and this section is one. It is reconciled once, at the end, instead of eighteen times. Refs #2936. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
…overwrote Eleven of this campaign's pull requests are siblings on one base and each rewrites the spec's `## Now` whole, so merging them is last-writer-wins on that section. Four records did not survive the sequence; this restores them against the landed tree rather than against any one branch's copy. - The T34 and T35 evidence links come out. Neither file is on any branch or on main, so check-agent-record reads them as dangling and reds the agent-record job. Both measurements survive in prose: T34's capture is stated in full in `## Now`, and T35's round-3 file carries the round-2 provenance, including that the 7415e281 divergence was measured under the corrupted Q6_K MMVQ arm and does not survive the fixed kernel. T34's missing capture becomes a named owed item. - VT_ARGMAX_SPLIT is named as T14's lever. #2876 owed that record and a later sibling overwrote its copy of `## Now`. - The T2b evidence file keeps its closed negative. #2777 deleted the paragraph to drop a stale "hindsight unavailable" preamble and took the finding with it: the pointer-keyed activation-quant cache is unsound under the DevicePool block-recycling allocator. AGENTS.md does not allow deleting evidence to reduce context, so the finding is restated without the preamble. The T37 closed-negative from #2892 and the T35-r3 record from #2893 survived and are unchanged. A single file every pull request must write is a lock, which is what this spec section became. It is reconciled once, at the end, instead of eighteen times. Refs #2936. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
|
Held back with the rest of the I reviewed this change on its own and have no objection to it. Once #2790's base Landing today from this set: #2782 (with the grouped-Q8_0 repair), #2777 and |
|
Closing per the reviewer's disposition: the T37 head (34104db) carries records and a benchmark but no runtime implementation of VT_GEMV_WARPS / VT_GEMV_SPLITK, so its A/B table cannot validate the levers; split-K was parked before its required near-tie adjudication. The negative result and the re-pricing terms are recorded in .agents/specs/gfx1100-tg200.md (T37, carried by record PR #2893, rebased onto the repaired stack at f62439f). |
Row:
GFX1100-TG200Issue: #2887
Depends on: #2807 (T25)
Summary
Small-N GEMV geometry levers: sweep warp count (
VT_GEMV_WARPS) and split-K (VT_GEMV_SPLITK) for decode GEMV shapes. Closed negative in the original campaign — warps/split-K win warm benches but wash in the engine. This PR records the lever and its negative disposition.Benchmark
A/B interleaved, 5 pairs, Qwen3.5-4B Q4_K_M, 256 tokens, temp 0, seed 0:
Noise — confirms the original campaign's negative disposition.
Token identity
PASS — identical output to T25 chain.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:GLM-5-2 [OMP]