Skip to content

fix(core/kv): order host-KV copy streams behind the caller's default stream - #313

Open
Andrewxu313 wants to merge 1 commit into
mainfrom
fix/host-kv-copy-stream-order
Open

fix(core/kv): order host-KV copy streams behind the caller's default stream#313
Andrewxu313 wants to merge 1 commit into
mainfrom
fix/host-kv-copy-stream-order

Conversation

@Andrewxu313

Copy link
Copy Markdown
Contributor

Description

Order the host-KV worker view's copy streams behind the caller's stream by recording a CUDA event on the issuing thread at call time (RecordProducerEvent) and waiting on it inside the copy task (WaitForProducerEvent). Applied to all five worker-view copy paths (two h2d page loads, the prefill offload, the per-layer decode append, the batched decode append) and the three compressed_state_host_manager.h paths that shared the old helper. Adds a GPU regression test that fails on main and a static contract test.

Motivation

The copy streams come from at::cuda::getStreamFromPool (cudaStreamNonBlocking). The worker runs the model on PyTorch's default stream, whose cudaStream_t handle is 0, and WaitForProducerStream returned early for a null producer handle — so no event ever ordered a copy behind the model's stream:

  • a d2h prefill offload issued right after the kernel producing its source copied the buffer's previous contents (Kimi-K3: the first sequence offloaded per MLA layer; finite-but-wrong KV with warm buffers, NaN garbage after a phase re-configure freed them);
  • a d2h decode append could read the token's K before it was written;
  • an h2d page load could land before the torch.zeros memset of a re-created K cache and be wiped.

Measured on the Kimi-K3 512-request decode contract (2x8 H200): host readback != source for the first sequence of every admission wave on all 8 ranks; a collapsed row's host page == GPU page word-for-word with garbage only in the tail of one page (a partially written buffer). MMLU 64% vs 87% for the same prompts on SGLang. With the fix: 0/512 collapsed on the 512x128 probe (was 21/512) and MMLU 90.23% (462/512) on the frozen contract corpus.

Type of Change

  • model — add/extend model support (models/** + registration seam + model kernels only)
  • kernel — add/optimize a compute kernel (batchgen_kernels/** + in-tree kernel dirs)
  • core — change scheduling/serving/runtime scaffolding (the only type that may)
  • fix — narrow bug fix (+ a regression test)
  • infra — build / CI / packaging / scripts / Docker
  • docs — documentation only

File changes

File Δ Note
core/KV_Storage/host_paged_kv_worker_view.h mod RecordProducerEvent on the issuing thread + WaitForProducerEvent in every copy task; old null-handle helper removed
core/KV_Storage/compressed_state_host_manager.h mod same helper, same three call sites
tests/integration/paged_kv/test_host_kv_copy_stream_ordering.py add GPU regression: slow producer chain on the default stream, then offload / load; fails on main (1024 of 1024 elements differ)
tests/test_host_kv_copy_stream_contract.py add static: record-before-launch, wait-before-kernel on every site; old helper absent

Checklist

PR Merge Policy Contract — pre-merge checklist

  • git diff --stat origin/main reviewed; every file traces to the task — no unrelated files (§3.1).
  • Exactly one Type of Change ticked above; all changed files are within that type's permitted set (§2.5) — no scaffolding edits in a model/kernel PR (§2.6).
  • File changes table lists every changed file and matches the diff (audit-able at draft; kept current).
  • No debug_* / check_* / scratch_* / tmp_* scripts added to a production package or repo root (§1.1). (bench_* is allowed.)
  • No test_*.py added inside the runtime package; tests are under tests/ (§1.2, §2.1).
  • No new BATCHGEN_* env-var debug guard; debug behavior is a batchgen_debug batch flag (§1.3). A genuinely-needed new env var goes in its own PR.
  • No leftover print() or commented-out code in the changed files (§1.4–§1.5). (logging.debug() is fine.)
  • No logs, profiler traces, CSVs, checkpoints, wheels, or compiled artifacts staged; nothing added with -f (§1.6).
  • Touched modules' MODULE.md updated if the public API changed (§2.2). (No public API change.)
  • One concern only; diff is surgical (§3).
  • Commits are clean: Angular format, body present, no co-author trailers (§4).
  • CI is green (format, lint, hygiene, tests).

bash .github/workflows/scripts/check-pr-hygiene.sh origin/main (PR_TYPE=fix): 0 blocking, 0 advisory.

🤖 Generated with Claude Code

…stream

The worker runs the model on PyTorch's default stream (cudaStream_t 0). The
host-KV view's copy streams come from the PyTorch pool and are
cudaStreamNonBlocking, so nothing orders them implicitly. WaitForProducerStream
returned early for a null producer handle, so:

* a d2h prefill offload issued right after the kernel producing its source
  copied the buffer's PREVIOUS contents (Kimi-K3: the first sequence of every
  MLA layer's offload; measured host != source on all 8 ranks at the first
  wave, and NaN garbage after a re-configure freed the buffers);
* a d2h decode append could read the token's K before it was written;
* an h2d page load could land before the K-cache torch.zeros memset queued
  ahead of it and be wiped (all-zero KV -> constant-token loops).

Record an event on the ISSUING thread's current stream at call time
(RecordProducerEvent, no null-handle short-circuit) and make the copy stream
wait on it inside the task, on all five worker-view copy paths and the three
compressed-state paths. GPU regression tests make the producer deliberately
slow (offload and load); the static test pins record-before-launch on every
site and the absence of the old helper.
@github-actions github-actions Bot added the ci:run Trigger build + GPU regression on H20 label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run Trigger build + GPU regression on H20

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant