fix(deepseek-v4): gate the DSA top-k window clamps and non-positive topk, attribute the launch error, and stop a guard comment overclaiming (#552) - #557
Merged
Conversation
…opk, attribute the launch error, and stop a guard comment overclaiming (#552) A fresh review of #505 returned PASS with 6 non-blocking findings. It could not find a single input where the two-pass threshold selection diverges from `DsaTopkSelect` -- 3,000,081 fuzzed shapes across three independent implementations, zero divergence -- and reproduced both the defect and the fix on real sm_121a. This lands the six findings. CODE 1. `DsaTopkLaunch` had no post-launch `cudaGetLastError()` while every sibling launcher here has one. That is exactly why the #505 stack overflow surfaced as `cudaStreamDestroy: an illegal memory access` -- an error raised by whatever ran next, and the text the whole #505 evidence chain ended up quoting. Now `Check(cudaGetLastError(), "dsa_topk launch")`, so the next fault in this kernel is attributable to it. 2. `topk <= 0` diverged between the arms: the host reference asserts (`deepseek_v4_dsa.cpp:76`) while the device launcher silently returned an empty vector. The launcher now throws to match. The "two independent implementations agree" gate could not see this because no case passed a non-positive topk. TESTS 3. No case exercised `win_start < 0` or `win_end > num_keys`, so mutating either clamp away left all four #505 cases green while an off-device fuzz caught both at once; on device they are out-of-bounds `logits` reads. New case drives under-run, over-run, both-at-once, and an in-range control at topk=512, and asserts no emitted key escapes the clamped window. 4. New case asserts BOTH arms refuse topk=0 and topk=-1, not merely that they agree on accepted inputs. PROSE -- these are corrections to claims that were wrong, not polish 5. The `w < topk` comment named a NaN write-past-row as the failure class it defends. That cannot occur: pass 2's predicate is satisfied by exactly `rank(th)` elements with `rank(th) <= topk`, and a NaN never satisfies it either since `better(x, NaN)` is false, so `w` cannot exceed topk for ANY input. The reviewer proved it by removing the bound and by weakening it to `w <= topk` -- both left the device suite 4/4 SUCCESS and a 3M-shape fuzz clean. The guards stay because they are free; the comment now says belt-and-braces, bounded by construction. A comment that overstates what it protects is worse than none, because the next reader trusts it. 6. "Strictly cheaper" is now qualified as a comparison to the OLD kernel only, not fitness for the real geometry: this is one thread per token row with a dependent global load per iteration, so at V4-Pro's index_topk=1024 the last row of a 4k prompt is ~4.2M serial loads in one thread. 7. Trivia: the spec cited the pre-fix span as `:624-665` (actual 624-669), and the kernel comment called `n > topk` the overflow condition -- it only selects the branch; the overflow needs `n > 512` for `chosen` or `topk > 64` for `picked`, which is why the old topk=3/nk=5 gate shape was ASan-clean. Spec section 7 also records what the review CLOSED rather than raised: five mutations left the suite green and are semantics-preserving, not coverage gaps -- each is also undetected by a 200k-shape fuzz against the oracle, which is how the reviewer separated the two. And it confirms the tie-heavy case is uniquely load-bearing: it is the only case that reddens a tie-break inversion or a value-only threshold. Recorded, not repaired: `b649a1ea2` introduced the #505 spec in the same commit as its code, where the protocol requires the spec first. Gate-reading trap worth keeping, from the reviewer's own run: `-ts='*DSA top-k*'` (suite filter) instead of `-tc=` (case filter) printed `0 passed | 0 failed | 23 skipped` beside `Status: SUCCESS!` -- a live false green from a filter matching nothing. Any scoped run of this suite must use `-tc=` and check the case count. CPU: test_cuda_deepseek_v4 25/25 (was 23, both new cases registered), test_deepseek_v4_dsa 13/13, both SUCCESS. Device arms on dgx.casa are queued behind other agents' jobs on the shared GPU lock; results land before merge. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
No overlap with the four paths this branch touches; re-gated after the merge. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…fault -- device arms refuted finding 4's rationale (#552) Adds the device evidence for this change and corrects a claim I wrote in the previous commit, which the verification arm built to demonstrate it disproved. REFUTED: finding 4 held that a post-launch `cudaGetLastError()` would make the next fault in this kernel attributable to it, instead of surfacing later as `cudaStreamDestroy` the way #505's did. The `prefix_with_check` arm -- the pre-fix #505 kernel body WITH the new check in place -- still reports: what(): vt cuda: cudaStreamDestroy: an illegal memory access was encountered test_cuda_deepseek_v4.cpp:214: FATAL ERROR: test case CRASHED: SIGABRT The error text is UNCHANGED. A stack-overflow illegal access is an asynchronous execution fault; `cudaGetLastError()` right after a launch reports launch-CONFIGURATION errors (bad grid/block, shared memory over budget). Here it was not even caught by the following `cudaStreamSynchronize`, latching only at stream destruction. The check is KEPT -- it is free, it matches every sibling launcher, and it does cover the launch-configuration class. But its comment and the spec now say what it actually does. Claiming "this makes the next fault attributable" would have repeated finding 1's defect -- an overclaiming guard comment -- inside the very change that exists to correct one. DEVICE ARMS (dgx.casa, GB10 sm_121a, mandatory flags, fast path hard-verified in each run's own configure log; every arm a fresh nvcc rebuild from a pristine kernel with the binary mtime verified to advance; scoped with `-tc=`, never `-ts=`; run under `flock $HOME/gpu.lock`): baseline exit=0 6/6 SUCCESS no_topk_guard exit=1 FAILURE -> new refusal case: CHECK_THROWS did NOT throw no_ws_clamp exit=134 FAILURE -> new clamp case CRASHED, illegal memory access no_we_clamp exit=1 FAILURE -> new clamp case, 1052 failed assertions no_launch_check exit=0 SUCCESS -> nothing; unobservable by construction prefix_with_check exit=134 FAILURE -> refutes finding 4's rationale restored full suite exit=0 25/25, 90062 assertions, 0 skipped, SUCCESS Both new cases have teeth against the mutation each was written for. `no_launch_check` reddening nothing is the expected, honest outcome for a diagnostic with no observable behaviour on a passing run. The false-green shape recurred twice more: no_ws_clamp printed `assertions: 16865 | 16865 passed | 0 failed` and prefix_with_check `609 | 609 passed | 0 failed`, both beside `Status: FAILURE!`. Two infrastructure incidents, handled rather than absorbed. Round 1's clamp mutations were REFUSED by their own uniqueness assertion: the clamp pair appears in BOTH `DsaLogitsKernel` (:604-605) and `DsaTopkKernel` (:628-629), so a one-line anchor was ambiguous and the assert declined to edit a kernel it was not aiming at -- round 2 re-ran with a three-line anchor unique to DsaTopkKernel. Then the box REBOOTED mid-arm (`up 7 min`, the known GB10 unified-memory OOM-reboot class), killing the run with no marker written; the remaining arms were relaunched on the fresh box. CPU after the correction: test_cuda_deepseek_v4 25/25 SUCCESS, preflight clean. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
Re-gated after the merge. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
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.
Closes #552. Spec:
.agents/specs/dsa-topk-bounds.md§7. Row:MODEL-TEXT-deepseek-v4-deepseek-v4-for-causal-lm. Follow-up to #542 / #505.A fresh reviewer (never the author) reviewed #505 and returned PASS with 6 non-blocking findings. This lands all six. Worth stating plainly what the review couldn't break, because it bounds what this PR is: it found no input where the two-pass threshold selection diverges from
DsaTopkSelect— 3,000,081 fuzzed shapes across three independent implementations (the host reference, a transcription of the kernel, and its ownO(n²)rank-count oracle), output slots poisoned with-777so an unwritten slot couldn't pass as legitimate-1padding — and it reproduced both the ASan overflow and the device SIGABRT on real sm_121a.Code
1.
DsaTopkLaunchhad no post-launchcudaGetLastError()while every sibling launcher here has one. That is exactly why the #505 stack overflow surfaced ascudaStreamDestroy: an illegal memory access— an error raised by whatever ran next, and the text the entire #505 evidence chain ended up quoting. NowCheck(cudaGetLastError(), "dsa_topk launch"), so the next fault in this kernel is attributable to it.2.
topk <= 0diverged between the arms. The host reference asserts (deepseek_v4_dsa.cpp:76); the device launcher silently returned an empty vector. It now throws to match. The "two independent implementations agree" gate couldn't see this because no case passed a non-positivetopk.Tests
3. Nothing exercised
win_start < 0orwin_end > num_keys. Mutating either clamp away left all four #505 cases green, while an off-device fuzz caught both immediately; on device they are out-of-boundslogitsreads. New case drives under-run, over-run, both-at-once and an in-range control attopk=512, and asserts no emitted key escapes the clamped window.4. New case asserts both arms refuse
topk=0andtopk=-1— not merely that they agree on accepted inputs.Prose — corrections to claims that were wrong, not polish
5. The
w < topkcomment named a NaN write-past-row as the failure class it defends. That cannot occur. Pass 2's predicate is satisfied by exactlyrank(th)elements withrank(th) ≤ topk, and a NaN never satisfies it either sincebetter(x, NaN)is false, sowcannot exceedtopkfor any input. The reviewer proved it by removing the bound and by weakening it tow <= topk— both left the device suite 4/4 SUCCESS and a 3M-shape fuzz clean. The guards stay because they're free; the comment now says belt-and-braces, bounded by construction. A comment that overstates what it protects is worse than none, because the next reader trusts it.6. "Strictly cheaper" is now qualified as a comparison to the old kernel only, not fitness for the real geometry: this is one thread per token row with a dependent global load per iteration, so at V4-Pro's
index_topk=1024the last row of a 4k prompt is ~4.2M serial loads in one thread.7. Trivia: the spec cited the pre-fix span as
:624-665(actual 624-669), and the kernel comment calledn > topkthe overflow condition — it only selects the branch; the overflow needsn > 512forchosenortopk > 64forpicked, which is why the oldtopk=3, nk=5gate shape was ASan-clean.What the review closed rather than raised
Recorded in spec §7 so nobody re-opens it: five mutations left the suite green and are semantics-preserving, not coverage gaps — each is also undetected by a 200k-shape fuzz against the oracle, which is how the reviewer separated the two. Removing
w < topk; weakening it tow <= topk; simplifying pass 1's argmax tov > best_val(equivalent, the scan is ascending);n <= topk→n < topk(equivalent, atn == topkthe full path selects alln); droppingif (th_idx < 0) return(reachable only via NaN). No action owed.Also confirmed, not assumed: the tie-heavy case is uniquely load-bearing — the only case in the suite that reddens a tie-break inversion or a value-only threshold.
Recorded, not repaired
b649a1ea2introduced the #505 spec in the same commit as its code, where the protocol requires the spec first. Noted in §7 rather than quietly dropped.Gate-reading trap worth keeping
From the reviewer's own run:
-ts='*DSA top-k*'(suite filter) instead of-tc=(case filter) printedtest cases: 0 | 0 passed | 0 failed | 23 skippedbesideStatus: SUCCESS!— a live false green from a filter matching nothing. Any scoped run of this suite must use-tc=and check the case count is non-zero. The device script for this PR encodes that.Evidence
CPU:
test_cuda_deepseek_v425/25 (was 23 — both new cases registered),test_deepseek_v4_dsa13/13, both SUCCESS.scripts/agent-preflight.sh --stagedfully clean, including the contention-flappytest_cpu_x86_llamacpp_floor.Device, on
dgx.casa(GB10 sm_121a), mandatory flags, fast path hard-verified in each run's own configure log. Every arm is a fresh nvcc rebuild from a pristine kernel with the binary mtime verified to advance, scoped with-tc=(never-ts=), run underflock $HOME/gpu.lock:6 | 6 passedSUCCESSno_topk_guardCHECK_THROWS … did NOT throw at all!no_ws_clampillegal memory accessno_we_clampno_launch_checkprefix_with_check25 | 25 passed | 0 skipped, 90062 assertions, SUCCESSBoth new cases have teeth against the mutation each was written for.
Finding 4's rationale was REFUTED by its own verification
Worth reading before approving, because it changed the change. The review held that a post-launch
cudaGetLastError()would make the next fault in this kernel attributable to it, rather than surfacing later ascudaStreamDestroythe way #505's did. The arm built to demonstrate that disproved it. With the pre-fix #505 kernel and the new check both in place:The error text is unchanged. A stack-overflow illegal access is an asynchronous execution fault;
cudaGetLastError()right after a launch reports launch-configuration errors. Here it wasn't even caught by the followingcudaStreamSynchronize, latching only at stream destruction.The check is kept — free, matches every sibling launcher, and does cover the launch-configuration class — but its comment and spec §7.1 now say what it actually does. Claiming "this makes the next fault attributable" would have repeated finding 1's defect, an overclaiming guard comment, inside the very change that exists to correct one.
no_launch_checkreddening nothing is the honest counterpart: a diagnostic with no observable behaviour on a passing run.Infrastructure incidents, handled not absorbed
Round 1's clamp mutations were refused by their own uniqueness assertion — the clamp pair appears in both
DsaLogitsKernel(:604-605) andDsaTopkKernel(:628-629), so a one-line anchor was ambiguous and the assert declined to edit a kernel it wasn't aiming at. Round 2 re-ran with a three-line anchor unique toDsaTopkKernel. Then the box rebooted mid-arm (up 7 min, the known GB10 unified-memory OOM-reboot class), killing the run with no marker written; the remaining arms were relaunched on the fresh box.The false-green shape recurred twice more:
no_ws_clampprintedassertions: 16865 | 16865 passed | 0 failedandprefix_with_check609 | 609 passed | 0 failed, both besideStatus: FAILURE!.CI note:
windows-msvc-cpu/windows-msvc-vulkanare red at the repo-wide baseline, not from this change — the same two fail on #539, #541 and on #511 which already merged; the logs show every target building with noerror C####, failing in the release-packaging PowerShell step.🤖 Generated with Claude Code