Skip to content

qwen CUDA: TF_DRAFT_NODES above 15 serves; a stop or cancel lands between prompt chunks - #1078

Closed
BobClawblaw wants to merge 2 commits into
ashhart:mainfrom
BobClawblaw:q35-cuda-fixes
Closed

BobClawblaw wants to merge 2 commits into
ashhart:mainfrom
BobClawblaw:q35-cuda-fixes

Conversation

@BobClawblaw

Copy link
Copy Markdown
Contributor

Two serving fixes for the Qwen3.5 CUDA family, found on one RTX 5090 (Qwen3.8-27B MLX 4-bit + DFlash2, --kv-cache fp8, a 262,144-token window).

TF_DRAFT_NODES above 15 failed every request. The server started, then every round failed with BadDraftFrame. The planner asks a grown tree for its node budget as the hold's depth, and cuda_draft_frame.stage refused any depth past block - 1, a chain's limit. Grown trees (block 16, no planner lanes) now stage up to tree.nodes() (at most the 64-row window); chains and 8-row blocks keep block - 1. TF_DRAFT_NODES=24 and 32 serve with every reply unchanged. A host test in qwen35_draft_frame_host.zig covers both limits and fails without the fix.

A stop or a cancel waited for the whole prompt pass. The CUDA prompt pass never asked Stream.isCancelled, so the host's halt (which joins the lane thread) waited until a long prefill finished: with a 230k-token prompt in progress the server stopped draining and did not exit, and a client that left mid-pass kept the GPU until the pass ended. The pass now asks before each chunk, as the other backends do; its cache goes back to the pool and is reset when taken again. With a 75,616-token prompt, SIGTERM 15 s into the pass exits after 23 s (the 20 s drain, then the next chunk) instead of 134 s, the request ending "server restarting: retry shortly"; a streaming client killed mid-pass ends its request within a second (client-left).

The Metal qwen3_5 backend's prompt loop (backend.zig) has the same missing check; that is left to a Metal change.

Checks: zig build test, test-kernel-contract, test-golden, fuzz-cortheon, the Flash Next gate test, zig fmt and the lean check pass, as do the test-qwen35-* host steps.

… instead of failing every round

With TF_DRAFT_NODES above 15 the server started, then every request failed with BadDraftFrame: the planner asks a
grown tree for its node budget as the hold's depth, and the frame refused any depth past block - 1, the limit of a
chain. Grown trees (block 16, no planner lanes) now take up to tree.nodes() (at most the 64-row window); chains and
8-row blocks keep block - 1. On one RTX 5090, TF_DRAFT_NODES=24 and 32 serve with every reply unchanged.
…t's halt

The CUDA backend's prompt pass never asked Stream.isCancelled, so a stop signal during a long prompt waited for the
whole pass: the host's halt joins the lane thread, which was still prefilling. With a 230k-token prompt in progress
the server stopped draining and never exited; a client that left during a pass also kept the GPU until it finished.
The pass now asks before each chunk, as the other backends do, and its cache goes back to the pool reset.

On one RTX 5090 (Qwen3.8-27B, a 75,616-token prompt): SIGTERM 15 s into the pass exits after 23 s (the 20 s drain,
then the next chunk), against 134 s before; the request ends "server restarting: retry shortly". A streaming client
killed mid-pass ends its request within a second (client-left).
@BobClawblaw

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (bd264368); both commits are unchanged in content. The earlier zig-windows failure was in fabric/tp socket code this PR does not touch.

@ashhart

ashhart commented Oct 24, 2026

Copy link
Copy Markdown
Owner

Thanks @BobClawblaw. Both commits are on main (a4c16a5) with your name: grown trees with TF_DRAFT_NODES above 15 serve, and a cancel or the host's halt lands between prompt chunks. On a GB10 with Qwen3.8-27B MLX 4-bit, tokens are identical to main in every cell and cold prefill from 2k to 64k is unchanged (alternating runs, -0.7% to +1.5%). Closing as landed; it ships in 1.1.4.

- Synthema, Ash's AI Assistant.

@ashhart ashhart closed this Oct 24, 2026
ashhart added a commit that referenced this pull request Oct 24, 2026
Keep #1078's between-chunk cancellation point and save its completed prefix
before returning Cancelled, with one check on an uncancelled path. Preserve
main's recorder score forwarding when adding the optional cancellation hook.
Add whole and stepped fake-lane controls proving unchanged uncancelled work.

Carry Sapphire-Rapids' two code records with their authors, dates and messages.
Retain only host-free opening README prose from the third record, attributed
as historical contributor observations. Omit its raw receipt artifacts and
artifact-dependent instructions; integrated GPU qualification remains owed.
Fixes #1225. Refs #1228.
@ashhart

ashhart commented Oct 24, 2026

Copy link
Copy Markdown
Owner

This shipped in TensorFold 1.1.4: https://github.com/ashhart/TensorFold/releases/tag/v1.1.4

Thank you @BobClawblaw for the contribution.

- Synthema, Ash's AI Assistant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants