Skip to content

fix(kernel): order the batch-memcpy probe against the current stream - #414

Open
tspeaks wants to merge 1 commit into
FlashML-org:mainfrom
tspeaks:fix/batch-memcpy-probe-stream-race
Open

fix(kernel): order the batch-memcpy probe against the current stream#414
tspeaks wants to merge 1 commit into
FlashML-org:mainfrom
tspeaks:fix/batch-memcpy-probe-stream-race

Conversation

@tspeaks

@tspeaks tspeaks commented Sep 8, 2026

Copy link
Copy Markdown

_probe allocates its destination with torch.zeros, which enqueues the fill on the current stream, then enqueues the verification copy on a fresh probe stream. Nothing joins the two. When the current stream has a backlog the copy completes first on the independent probe stream and the fill lands on top of it, so the probe reads back zeros and load_batch_memcpy raises "cudaMemcpyBatchAsync probe copied wrong bytes" on a GPU that supports the API.

OffloadMoeCache catches that and falls back to full-layer copies, so --moe-prefill-hit-d2d silently does nothing whenever the current stream is busy as the probe runs -- in practice during prefill warmup, which is exactly when the flag is first exercised.

A cold process hides the bug: the first torch.zeros pays a cudaMalloc and the first torch.cuda.Stream() populates the per-device stream pool, and each of those synchronizes the device, draining the backlog before the copy is enqueued. That is why the probe passes when run standalone and fails inside a warmed-up server.

Join the probe stream to the current stream before the copy.

_probe allocates its destination with torch.zeros, which enqueues the fill on
the current stream, then enqueues the verification copy on a fresh probe
stream. Nothing joins the two. When the current stream has a backlog the copy
completes first on the independent probe stream and the fill lands on top of
it, so the probe reads back zeros and load_batch_memcpy raises
"cudaMemcpyBatchAsync probe copied wrong bytes" on a GPU that supports the API.

OffloadMoeCache catches that and falls back to full-layer copies, so
--moe-prefill-hit-d2d silently does nothing whenever the current stream is busy
as the probe runs -- in practice during prefill warmup, which is exactly when
the flag is first exercised.

A cold process hides the bug: the first torch.zeros pays a cudaMalloc and the
first torch.cuda.Stream() populates the per-device stream pool, and each of
those synchronizes the device, draining the backlog before the copy is
enqueued. That is why the probe passes when run standalone and fails inside a
warmed-up server.

Join the probe stream to the current stream before the copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant