Skip to content

Prompt path: a stager buffer's first job of a generation waits for the previous DMA from it (a narrow race with unpinned blobs) - #385

Closed
sergqwer wants to merge 1 commit into
Niko1221:mainfrom
sergqwer:pr/stager-dma
Closed

sergqwer wants to merge 1 commit into
Niko1221:mainfrom
sergqwer:pr/stager-dma

Conversation

@sergqwer

@sergqwer sergqwer commented Oct 1, 2026

Copy link
Copy Markdown

Stager::work waits for a buffer's DMA only from a generation's kRing-th job on (j >= kRing). A generation's first kRing jobs memcpy into their pinned buffers straight away.

So the previous generation's last DMAs from those buffers are only known to be done where something else waits for them:

  • staged layers (chunks below stream_all_min): the routing sync (cudaStreamSynchronize(m.cs)) before the layer's start();
  • stream_all chunks: the sync at the end of a chunk, which runs only with an MTP drafter (on_chunk) or a layer split (on_stage_chunk).

That leaves a window in two cases:

  • a stream_all chunk that ends without that sync (no --mtp);
  • a ring entry the routing skipped. Its copy was issued, but nothing waits for it, so even a chunk-end sync of the compute stream does not cover it.

In that window, the next chunk's first jobs can overwrite a pinned buffer that a cudaMemcpyAsync on the copy stream is still reading. It only matters with unpinned blobs, which go through the stager: the RAM-budget tier, or a partially pinned arena.

The fix: the event is now waited for on every job. An event that was never recorded, or has already completed, returns at once. For j < kRing, no job of the current generation can have recorded the event, so the wait is exactly the previous generation's last DMA from that buffer.

We found it in an audit of our fork. We have not seen it corrupt a run: it needs that timing and the unpinned path.

Measured:

  • IQ2_XS with --mmap-experts --resident-budget-gib 16: 12,144 of the 48,668 streamed experts went through the stager.
  • The tokens on a 32K prompt are the same as on 0.1.31.
  • Prefill took 6,546 ms vs 6,491 ms (noise).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio

…e previous DMA from it

Stager::work waited for a buffer's DMA only from the generation's kRing-th job on (j >= kRing); a generation's first
kRing jobs wrote their buffers straight away. The previous generation's last DMAs from those buffers are only
known to be done where something else waits for them: the routing sync before a staged layer's start(), or the
sync at the end of a chunk with an MTP drafter or stages. A stream_all chunk that ends without one (no --mtp), or a
ring entry the routing skipped (its copy is never waited for), leaves a window where the next chunk's first jobs
overwrite a pinned buffer a DMA may still read. The event is now waited for on every job; an event never recorded,
or already complete, returns at once.

IQ2_XS with --mmap-experts --resident-budget-gib 16 (12,144 of 48,668 streamed experts through the stager): the
same tokens on a 32K prompt, prefill 6,491 vs 6,546 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio
@sergqwer

sergqwer commented Oct 1, 2026

Copy link
Copy Markdown
Author

Rebased on 0.1.32 (c499bd1), no conflicts.

Checked on 0.1.32: IQ2_XS (ISTA), RTX 5090, a fixed --expert-cache, --vram-reserve-mib 1500. The first generated token's logits (STRATA_DUMP_FIRST_LOGITS, now in main) are byte-identical to main's after a 2K and a 32K prompt. Every target builds with STRATA_BUILD_TESTS.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio

@BlueKingMuch

Copy link
Copy Markdown
Contributor

Data point for a setup that really streams through the stager: on this 64 GB Windows 11 box cudaHostRegister of the whole 46.8 GiB arena fails and 47 slices (45 GiB) are pinned, so the rest of the experts goes through the stager (RTX 4080 SUPER 32 GB, IQ3_S, 262K, --mtp, --prefill 32768). With this PR merged together with #372, #374, #378 and #407 on 0.1.32: the deterministic decode requests (greedy and sampled, 256 tokens) and the answers after 32K and 64K prompts give the same tokens as before, and the prompt path got faster overall (from #372 and #374), so the extra wait does not show here.

@sergqwer

sergqwer commented Oct 2, 2026

Copy link
Copy Markdown
Author

@BlueKingMuch thank you for checking this on a setup that really streams through the stager.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VZy1yKaDDiA8a7svdwaHio

maxfridbe pushed a commit to maxfridbe/Strata_B70 that referenced this pull request Oct 2, 2026
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35

One dpct run of upstream main + the seven PRs, 3-way merged into the files they
touch (sycl/tools/merge_upstream.py); their header changes are forked into
sycl/include until upstream merges them.

- Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq)
- Niko1221#463 decode waits for an adaptive swap before reading residency
- Niko1221#453 drafter's batched K/V on the KV-streaming ring
- Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read
  starts at c0 + T (the port's first chunk is shorter), and the upload is
  marked by a polled sequence number instead of a host event wait, which
  deadlocked prompts past ~4K tokens under the Level Zero v2 adapter
- Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied
  to the port's lane kernels (fp contract off, as upstream's HIP form)
- Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via
  STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding
  parity bench not ported)

Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid,
quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and
40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s;
128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363).

Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rb4SRxFuLpffQAAXBf4HxA
@Niko1221

Niko1221 commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Released in 0.1.38: the stager waits for a buffer's previous DMA on every job. Merged under your name (GitHub doesn't show it as merged because it went in by hand). Thank you!

@Niko1221 Niko1221 closed this Oct 3, 2026
Niko1221 pushed a commit that referenced this pull request Oct 6, 2026
…0.1.35

One dpct run of upstream main + the seven PRs, 3-way merged into the files they
touch (sycl/tools/merge_upstream.py); their header changes are forked into
sycl/include until upstream merges them.

- #385 stager waits for a buffer's previous DMA on every job (port: polled seq)
- #463 decode waits for an adaptive swap before reading residency
- #453 drafter's batched K/V on the KV-streaming ring
- #374 first chunk's PLE rows read beside layer 0; port fixes: the next read
  starts at c0 + T (the port's first chunk is shorter), and the upload is
  marked by a polled sequence number instead of a host event wait, which
  deadlocked prompts past ~4K tokens under the Level Zero v2 adapter
- #363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied
  to the port's lane kernels (fp contract off, as upstream's HIP form)
- #407 --adapt-tuned (opt-in), #413 key-head DeltaNet recurrence (opt-in via
  STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding
  parity bench not ported)

Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid,
quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and
40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s;
128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (#363).

Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35

One dpct run of upstream main + the seven PRs, 3-way merged into the files they
touch (sycl/tools/merge_upstream.py); their header changes are forked into
sycl/include until upstream merges them.

- Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq)
- Niko1221#463 decode waits for an adaptive swap before reading residency
- Niko1221#453 drafter's batched K/V on the KV-streaming ring
- Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read
  starts at c0 + T (the port's first chunk is shorter), and the upload is
  marked by a polled sequence number instead of a host event wait, which
  deadlocked prompts past ~4K tokens under the Level Zero v2 adapter
- Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied
  to the port's lane kernels (fp contract off, as upstream's HIP form)
- Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via
  STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding
  parity bench not ported)

Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid,
quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and
40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s;
128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363).

Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
anon761 pushed a commit to anon761/Strata-upstream that referenced this pull request Oct 6, 2026
…iko1221#374 Niko1221#363 Niko1221#407 Niko1221#413 onto 0.1.35

One dpct run of upstream main + the seven PRs, 3-way merged into the files they
touch (sycl/tools/merge_upstream.py); their header changes are forked into
sycl/include until upstream merges them.

- Niko1221#385 stager waits for a buffer's previous DMA on every job (port: polled seq)
- Niko1221#463 decode waits for an adaptive swap before reading residency
- Niko1221#453 drafter's batched K/V on the KV-streaming ring
- Niko1221#374 first chunk's PLE rows read beside layer 0; port fixes: the next read
  starts at c0 + T (the port's first chunk is shorter), and the upload is
  marked by a polled sequence number instead of a host event wait, which
  deadlocked prompts past ~4K tokens under the Level Zero v2 adapter
- Niko1221#363 group stride + fused SwiGLU/q8_1 for the PCIe expert call, re-applied
  to the port's lane kernels (fp contract off, as upstream's HIP form)
- Niko1221#407 --adapt-tuned (opt-in), Niko1221#413 key-head DeltaNet recurrence (opt-in via
  STRATA_GDN_KEYHEAD=1: 8% slower on the B70; its NVIDIA gate and SM-holding
  parity bench not ported)

Tested on the B70: native_grouped_parity, iq_multi, gr, qsa, kv_hybrid,
quantize_act, qsa_prompt_attn parity pass; Coder 19/2,184-token, IQ2_XS and
40K outputs identical to 0.1.33; Coder 2,184-token prompt 784 -> 825 tok/s;
128K int8 KV streaming 895 / 67.0 tok/s; IQ2_XS decode +1.7% (Niko1221#363).

Co-authored-by: sergqwer, constantindjonkam, architectds, BlueKingMuch (upstream PR authors)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rb4SRxFuLpffQAAXBf4HxA
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.

3 participants