Repository navigation
fix(strata): keep every lane in the latent D kernel's warp reductions (prefill hang on DGX Spark) - #199
Merged
shuk777 merged 2 commits intoNov 13, 2026
Conversation
In latent_dense_kernel<true> (moe_fused.cu and its q3k copy), lanes whose
tile row lies past the end of a partly filled last tile left the epilogue
with `continue` before the full-warp __shfl_xor_sync reductions. A
full-mask shuffle that some lanes never reach is undefined behaviour; on
sm_121 (DGX Spark, GB10) the warp deadlocks, the layer never finishes and
the serve watchdog restarts the engine ("no progress for 60 s ... layer
0/1 of the prompt chunk"). Whether a prompt hits it depends on its length
mod 64, so prompts fail intermittently.
Every lane now runs the reductions and only lanes with a real row store.
The shuffles (xor 4, 8, 16) only combine lanes of the same tig, i.e. the
same token row, so a row past the end only ever feeds itself, and the
tile's spare rows repeat its last row, so their values are finite. Rows
of full tiles are unchanged bit for bit.
Assisted-by: Claude (Anthropic)
lat_dense_d_tail_test runs fused::latent_dense_d and fused_q3k::latent_dense_d at the Edge0 145B shapes (K 2560, 8 groups, ell 1792) with Q8_0 and Q4_0 weights and with the q3k column scale, for every M in 1..127. Each row must equal the same row of a run over two full tiles bit for bit, and nothing past row M may be written. A launch that does not finish within 10 s fails as a hang. Registered with CTest (timeout 300 s); it skips on devices below sm_80. Assisted-by: Claude (Anthropic)
shuk777
approved these changes
Nov 13, 2026
shuk777
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for helping to debug! LGTM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Motivation. On a DGX Spark (GB10, sm_121), Edge0-145B NVFP4 prompts hang in prefill: the serve watchdog fires with
no progress for 60 s during a request (reading the prompt (batched): layer 0 of the prompt chunk from token 0)and restarts the engine. It happens on most prompts but not all, so it looks random. The only workaround isSTRATA_LATENT_FUSED_DU=0, which drops prefill from ~900 to ~210 tok/s.Cause. In
latent_dense_kernel<true>(the latent D projection, inmoe_fused.cuand its copy inmoe_fused_q3k.cu), the epilogue doesWhen the last 64-row tile is only partly filled, lanes whose row is past the end skip the full-mask
__shfl_xor_synccalls that the other lanes of the warp wait in. That is undefined behaviour; on sm_121 the warp deadlocks. Whether a prompt hits it depends on its token count mod 64, hence the intermittency.Change. Every lane runs the reductions; only lanes with a real row store (
const bool valid = r_t < nrows;andif (!valid) continue;after the shuffles, before the stores). The shuffles (xor 4, 8, 16) only combine lanes with the sametig, so the same token row: a row past the end only ever feeds itself, and the tile's spare rows repeat its last row (srow), so their values are finite. Rows of full tiles are unchanged bit for bit (checked below). Two commits:fix(strata): the fix in both kernels (4 lines each).test(strata):lat_dense_d_tail_test, a regression test registered with CTest.No existing issue; I can open one if you prefer to track it there.
Area
edge0-infer/)edge0-infer/tools/assets/)edge0-infer/examples/)docs/)edge0-strata/(prefill kernels)How was it tested?
Environment: NVIDIA DGX Spark (GB10, sm_121, 128 GB unified memory), DGX OS 7.2.3 / Ubuntu 24.04, kernel 6.17 (nvidia), driver 595.91.07, CUDA 13.0 toolkit. Base commit
b7900cb. Engine built peredge0-strata/EDGE0.mdwith-DCMAKE_CUDA_ARCHITECTURES=121.lat_dense_d_tail_test(both kernels, Q8_0 / Q4_0 / q3k colscale, M = 1..127)compute-sanitizer --tool memcheckon the testcompute-sanitizer --tool synccheckon the testSTRATA_LATENT_FUSED_DU=0Not covered: I only have sm_121, so this was not run on RTX cards. There it is the same undefined behaviour that happens to complete; full-tile output is unchanged and the partial-tile rows are the same rows, so no change in results is expected. The q3k kernel is covered by the unit test only (no Q3_K model run end to end).
cuda-gdb on the hung test, unpatched (built with -lineinfo)
M = 1: only
tig == 0lanes (0x11111111) have a row; they wait in the shuffle atmoe_fused.cu:1170while the other lanes (0xeeeeeeee) have left. Warps 4-15 (nb0 >= 16,on0false) wait at the next item's__syncthreads(). The same pattern was captured from the engine itself during a real prompt (block 16, lanes0x88888888split from0x77777777).Test output
Before:
After (plain, under memcheck and under synccheck alike):
Engine, Edge0-145B NVFP4 (serve.log)
Before:
After:
Checklist
ctest --preset cpu -L "L0|L1"passes locally inedge0-infer/(engine changes): not run, noedge0-infer/changeslat_dense_d_tail_test)AI disclosure
The diagnosis (the cuda-gdb captures), the patch, the regression test and this description were prepared with an AI coding agent (Claude, by Anthropic) at my direction, on my hardware. Every result above comes from runs on that machine; logs are trimmed to the relevant lines. The commits carry an
Assisted-by: Claude (Anthropic)trailer.