fix: capture-poisoned engine wedge + planner-driven KV pool sizing#875
Merged
Conversation
…zing - CudaGraphRunner: abort capture + eager fallback when the forward throws mid-capture (stream was left capturing forever -> every later op failed) - invalidate_graphs(): drop prefill runner + close stray captures (safety net) - scheduler: skip prefill capture when MoE prefill is legacy host-args - /health: report unhealthy (503) once the worker declared the context poisoned - vram_budget: reserve the StoragePlanner projection for GGUF sources the nvfp4 heuristic can't see (Q4_K MoE built 0 cache tensors, 11 tok/s); KV pool keeps >= one full max_seq_len sequence Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kekzl
added a commit
that referenced
this pull request
Jul 7, 2026
…loor, honest budget logs (#877) Verified findings from the 2026-07-07 VRAM audit (docs/audit/vram_audit_2026_07_07.md): - Delete dead GDNState module (init never called, gdn_state_ always null; GDN recurrent state runs through SSMState) incl. InferenceState fields and the never-read gdn_layer_map_. - Delete DeviceAllocator/PinnedAllocator (zero runtime callers) and collapse the MemoryManager facade — Engine holds VRAMAllocator directly. - Delete stranded plan_tier_of() wrapper and the hardcoded Phase-4b bisect constant with its dead branches. - Gate moe raw_staging_buf on any_host_packed_experts (its only consumers are the host-expert branches of the legacy MoE forward) — no longer allocated on all-on-device loads. - Hoist nvfp4_beneficial() to core/qtype.h as the single policy source (was re-derived as a lambda in vram_budget.cpp — G1 regression class). - Add vram_reserve_floor() helper; convert the 6 true max(total/10,256MiB) sites and normalize the drifted 1 MiB floor in phase3_nvfp4_decode. - Stop logging a phantom fp8= reservation when FP8 prefill is resolved off (sm_120 default); KV sizing math unchanged. - Remove the vacuous !plan.failed guard on the #875 GGUF reserve branch (the budget-side plan runs unconstrained, failed is unreachable there) and document the deliberate two-plan design. - Wire MemAccount notes for the weight-cache build totals and executor workspaces; header now states exactly which pools are attributed. Perf-neutral by design: no hot-path changes; verify-fast tests green, prefill gates pass, graphs on/off re-measured 2.58x on warmed clocks (the scripted 1.26x run carried the depressed-host signature, #526). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.
Fixes #874.
Problem 1 — capture failure permanently wedged the server (#874)
Loading a GGUF Q*_K MoE model (Ornith-1.0-35B Q4_K_M,
qwen3_5_moe) with default settings: the first prefill ≥ ~1700 tokens hit the 2048-chunk prefill graph capture, cuBLAS failed under capture (status 14, VRAM nearly exhausted), the MoE host-args guard threw — and the exception unwound past the open capture.cudaStreamEndCapturewas never called, so the prefill stream stayed in capture state forever: every subsequent request failed withinternal_error("operation failed due to a previous error during capture") while/healthkept reportingok.Fixes (defense in depth):
CudaGraphRunner::execute: try/catch around the captured forward — abort the capture (CudaGraphCapture::abort_capture()), markcapture_failed_, re-run eagerly. The request succeeds; capture is permanently disabled for that runner.Engine::invalidate_graphs(): also invalidates the prefill runner and closes any stray open capture on the prefill stream (abort_stream_capture) — covers future mid-capture throw paths.moe_prefill_uncapturable()— GGUF Q*_K MoE models (no CUTLASS NVFP4 grouped workspace) always take the legacy host-args MoE prefill, which can never be captured; don't attempt it (saves a wasted forward per chunk)./healthnow returns 503unhealthyonce the worker has declared the CUDA context poisoned and stopped (previously hard-codedok).Problem 2 — KV pool ate all post-weight VRAM, weight caches built 0 tensors
compute_vram_budgetreserved only the nvfp4 heuristic (~459 MiB) before letting the KV backstop fill all remaining VRAM. Q4_K sources are notnvfp4_beneficial, so the heuristic misses them entirely — but the StoragePlanner (source-aware) routes them to the FP16 cache (4088 MiB projected, logged as "diagnostic only (5.1.5)"). Result on Ornith-35B Q4_K_M: KV pool 9970 blocks / 6.2 GiB (~159k tokens nobody asked for), FP16/NVFP4 caches 0 tensors, decode 11 tok/s via on-the-fly dequant.Fix: mirror the existing native-NVFP4 reserve pattern for GGUF sources — reserve the planner projection (plus phase reserve) before the KV backstop, gated on a 2× planner/heuristic divergence so tuned Q6_K/Q8 configs keep their pools unchanged, and capped so one full max_seq_len sequence always fits (concurrency stays bounded by scheduler admission).
Verification (local GPU, RTX 5090)
CaptureAbort.ThrowDuringCaptureLeavesStreamUsable(test-kv),VramBudgetReserve.*(test-e2e) — green; fullmake test-unit+make test-gpugreen.degen_suite33 checks / 0 FAIL including full long-context, server stays healthy afterwards. Startup now showsplanner-driven weight-cache reserve 5410.8 MiB, KV 30720→2048 blocks, caches build (fp16=132, nvfp4=27, nvfp4_moe=15 tensors). Decode 11 → 22 tok/s on the same probe.VramBudgetReserve.BeneficialSourcesKeepFullKvPool).🤖 Generated with Claude Code