cuda: warp-per-row E8 kernels, lattice table in shared memory - #935
cuda: warp-per-row E8 kernels, lattice table in shared memory#935Nanetnounou wants to merge 1 commit into
Conversation
grouped_hidden_e8_dual and grouped_down_e8 used one thread block per
output element: width/32 useful threads out of 256 launched, an
8-stage __syncthreads tree to reduce a single float, and per-element
lattice lookups through __constant__ memory. __constant__ is built
for broadcast; here every lane reads a different index, so the
constant cache serializes up to 32 accesses per warp, 8 times per
sub-block. Net effect measured on GB10: ~21 GB/s effective weight
bandwidth out of 273 available, in the kernel family the decode path
lives in. grouped_down_e8 was the worst case: 3.7M blocks per token,
784 bytes read per block, 64 useful threads out of 256, 3 useful
bytes per launched thread.
Replacement kernels (grouped_hidden_e8_dual_warp, grouped_down_e8_warp):
- one warp per output row, __shfl_down_sync reduction: no
__syncthreads, no shared-memory round trip for the reduction;
- E8 lattice table staged in shared memory, pre-multiplied by 0.5;
- activation tile in shared memory with a stride of 33, which
removes the 32-way bank conflicts of a power-of-two stride;
- sign parity via __popc;
- 4 independent accumulators per lane: a single accumulator
regresses precision by 45% vs a float64 oracle; 4 restore it
(2.796e-07 vs 2.719e-07 for the old block kernel) at identical
throughput.
Isolating the contributions (routed GPU critical path over 23
forwards, GB10, GLM-5.2 E8-IQ3, decode): original block kernels
10.01 s; +warp/shuffle 8.40 s (-16%); +lattice table in shared
memory 1.58 s (-81%). The dominant cost was the serialized
__constant__ cache, not the reduction.
Measured end to end on the same setup (nsys cuda_gpu_kern_sum):
expert matmul 435 -> 69 ms/token (~120 GB/s, 44% of peak)
60-token prompt, e2e 25.72 s -> 4.00 s (6.4x)
test_backend_cuda.cu: T6_I/T6_O/T6_S make the fmt=6 test widths
parametrable and T6_VERBOSE=1 prints the RMS, so both kernel families
can be checked against the float64 oracle at real dimensions.
Developed with AI assistance (Anthropic Claude), human-reviewed; all
figures above were measured on the hardware described.
|
Authored by Fable 5 in Claude Code, analysis in partnership with @monotophic. Hi! We're running a review pass on this branch now (great work btw), Results land on this thread when the pass completes β no action needed from Also, in case it saves a round of CI frustration: #if defined(__HIP_PLATFORM_AMD__) || defined(__HIP__)
#define f8_shfl_down(v,o) __shfl_down((v),(o),32)
#else
#define f8_shfl_down(v,o) __shfl_down_sync(0xffffffffu,(v),(o),32)
#endifWorth pinning the width to 32 explicitly (as above) rather than taking the |
|
Authored by Fable 5 in Claude Code, analysis in partnership with @monotophic We ran this branch on my GB10 (spark, sm_121) and reviewed the kernels against the CPU reference. Short version: the kernels are correct, the speedup is real and slightly better than advertised on our box, and we found one test-harness bug worth fixing plus one piece of good news about your failing Reproduction (merged with current dev, GLM-5.2 decode shape D=6144/I=2048, 8 experts x S rows, kernel-window timing via
The %-of-peak column is against a measured 266.2 GB/s read ceiling (float4 grid-stride sum, 512 MB) rather than the 273 GB/s spec β on GB10 Good news about your failing cuda-test: it is not an sm_121 regression. At this branch's base the failure is the stale "fmt=7 must be rejected" negative case at test_backend_cuda.cu:295 β fmt=7 became real and #981 fixed that expectation on dev after you branched. We verified Must-fix β Should-fix:
Nits: Nice work β the constant-cache diagnosis, the stride-33 pad derivation, and the "never test the two kernels against each other" discipline are all solid, and |
|
Authored by Fable 5 in Claude Code, analysis in partnership with @monotophic. A correction to our review above, from a belated verification pass: in the Small heads-up while we're here: on the branch as it sits (pre-rebase), the |
What
grouped_hidden_e8_dualandgrouped_down_e8used one thread block per output element:width/32useful threads out of 256 launched, an 8-stage__syncthreadstree to reduce a single float, and per-element lattice lookups through__constant__memory.__constant__is built for broadcast; here every lane reads a different index, so the constant cache serializes up to 32 accesses per warp. Measured effect on GB10: ~21 GB/s effective weight bandwidth out of 273 available.This PR replaces them with warp-per-output-row kernels (
__shfl_down_syncreduction, E8 lattice table in shared memory pre-multiplied by 0.5, activation tile with stride 33 against bank conflicts, sign parity via__popc, 4 independent accumulators per lane).Measured (GB10, GLM-5.2 E8-IQ3, decode,
nsys cuda_gpu_kern_sumover 23 forwards)The dominant cost was the serialized
__constant__cache, not the reduction. End to end: expert matmul 435 β 69 ms/token (~120 GB/s, 44% of peak); a 60-token prompt went 25.72 s β 4.00 s (6.4x).Precision: a single accumulator regresses RMS vs a float64 oracle by 45%; 4 accumulators restore it (2.796e-07 vs 2.719e-07 for the old block kernel) at identical throughput.
Tests
test_backend_cuda.cugains parametrable fmt=6 widths (T6_I/T6_O/T6_S) andT6_VERBOSE=1RMS output, so both kernel families can be checked against the float64 oracle at real dimensions.make check: 415 tests OK. Note:make -C c cuda-test CUDA_ARCH=nativecurrently exits 1 on GB10 on a pristine dev checkout as well β a v1.5.0-based build of the same test passes on the same machine and driver, so this looks like a dev-branch regression on sm_121 unrelated to this PR; we can file a separate issue with details.Disclosure
Developed with AI assistance (Anthropic Claude), human-reviewed; all figures were measured on the hardware described.