Skip to content

Sparse MQA logits follow-ups for nv_dev: 16-byte paged KV stride, workspace reuse under graph capture - #459

Merged
RayWang96 merged 2 commits into
deepseek-ai:nv_devfrom
lfr-0531:pr/nv-dev-sparse-paged-fixes
Sep 24, 2026
Merged

RayWang96 merged 2 commits into
deepseek-ai:nv_devfrom
lfr-0531:pr/nv-dev-sparse-paged-fixes

Conversation

@lfr-0531

@lfr-0531 lfr-0531 commented Sep 23, 2026 •

Copy link
Copy Markdown

Summary

Two small follow-ups to #447 for the DSv4.1 sparse MQA logits, found while integrating nv_dev into TensorRT-LLM (index cache pages are 64 x (64 + 4) = 4352-byte page-footer pages, and CUDA graphs are captured on a side stream). No kernel algorithm changes.

  1. Accept 16-byte aligned KV pages in the paged sparse MQA logits. fp8_fp4_paged_sparse_mqa_logits required fused_kv_cache.stride(0) % 512 == 0 (host and device asserts). The paged accessor only computes page = base + page_idx * kv_page_stride_bytes and copies the KV / SF blocks with 16-byte cp.async chunks, so 16-byte alignment is the actual requirement (the cache base pointer is now checked for it as well). Caches whose page stride is not padded to 512 bytes can be read in place.
  2. Reuse a warmed sparse MQA logits workspace during graph capture. The per-stream workspace asserted that its first allocation never happens under capture. Frameworks that capture on a dedicated side stream but replay on the stream they warmed up hit that assert on the first captured metadata call. When the current stream is capturing and has no workspace yet, reuse the first workspace warmed on the device (the workspace is per-launch scratch, so this assumes replays are serialized with that stream's eager launches); the assert stays for the genuinely cold case.

Validation

  • GB300 (SM100), CUDA 13.4, PyTorch 2.14 nv26.08: TensorRT-LLM's CSA2 (DSv4.1 sparse attention) unit suite, 453 cases including paged sparse decode with CUDA-graph capture and replay against native 4352-byte pages: 404 passed / 48 skipped / 1 known harness-only failure, identical to the previous pinned DeepGEMM build. With nv_dev head alone the 7 paged decode cases fail on the 512-byte stride assert.
  • DeepGEMM's own paged sparse tests pad pages to 512 bytes and are unaffected.

Supersedes #458 (the nv_dev + main 26/09 merge), which #447 has landed.

🤖 Generated with Claude Code

Comment thread csrc/apis/attention.hpp
if (not workspace.defined()) {
// Warm up each stream before capture so one-time zeroing is not replayed with the graph.
DG_HOST_ASSERT(c10::cuda::currentStreamCaptureStatusMayInitCtx() == c10::cuda::CaptureStatus::None);
if (c10::cuda::currentStreamCaptureStatusMayInitCtx() != c10::cuda::CaptureStatus::None) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The capture-time fallback scans the static workspaces map and returns the first defined workspace on the same device. This matches the single warmed stream per device used by TensorRT-LLM, but if more than one stream on the device has warmed a workspace, the choice is unordered_map-order dependent and may not be the stream the captured graph later replays on. Since the workspace is shared per-launch scratch, that could alias scratch across concurrently-executing streams. Consider documenting the 'one warmed workspace per device' assumption or tracking the warm-up stream so the fallback is deterministic. Not a blocker for the validated path.

🤖 v4

@lfr-0531 lfr-0531 Sep 23, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kept the simple scan (TensorRT-LLM warms exactly one stream per device); the comment now states that assumption.

Comment thread csrc/apis/attention.hpp
DG_HOST_ASSERT(fused_kv_cache.scalar_type() == torch::kUInt8 and fused_kv_cache.stride(1) == head_dim_with_sf and
fused_kv_cache.stride(3) == 1 and fused_kv_cache.stride(0) <= std::numeric_limits<int>::max() and
fused_kv_cache.stride(0) % 512 == 0);
fused_kv_cache.stride(0) % 16 == 0); // pages are read with 16-byte cp.async chunks

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: Relaxing the page stride to a multiple of 16 is correct for the 16-byte cp.async copies, but the paged entry point's 16-byte pointer-alignment loop covers {q_fp, q_sf, weights, metadata} and not fused_kv_cache. Page starts are base + page_idx * stride(0), so 16-byte KV access still requires the cache's data_ptr() to be 16-byte aligned. Torch allocations normally satisfy this, but an explicit assert or comment would make the contract complete and prevent a future strided/offset view from silently producing misaligned cp.async addresses.

🤖 v4

@lfr-0531 lfr-0531 Sep 23, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: fused_kv_cache is now in the 16-byte pointer-alignment loop.

Comment thread csrc/apis/mega_gate.hpp Outdated
}

static pybind11::dict get_bf16_mega_gate_config(const int& num_tokens, const int& hidden,
static std::map<std::string, int> get_bf16_mega_gate_config(const int& num_tokens, const int& hidden,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 suggestion: The std::mapstd::string,int return type depends on the pybind11 STL type caster. It currently arrives transitively through torch/python.h, but adding an explicit #include <pybind11/stl.h> (next to the existing <map>/<string> includes) would make the requirement explicit and avoid a fragile transitive dependency. Otherwise the change is correct and yields the desired dict[str, int] stub.

🤖 v4

@lfr-0531 lfr-0531 Sep 23, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: explicit #include <pybind11/stl.h>.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped from this PR: the stub typing is handled on the TensorRT-LLM side instead (mypy override for the generated stub), so mega_gate.hpp is untouched here.

@ds-review-bot

Copy link
Copy Markdown
Collaborator

🤖 ds-review-bot Code Review

v6

⚠️ 未完成评审(budget_exceeded:模型额度已用尽)

v5

⚠️ 未完成评审(upstream_error:模型上游服务不可用)

v4

This MR makes three well-scoped, low-risk follow-ups to the DSv4.1 sparse MQA logits without touching kernel algorithms. (1) The paged sparse MQA logits host/device asserts relax the KV page-stride requirement from 512-byte to 16-byte alignment, which matches the PagedSparseKVAccessor's 16-byte cp.async chunk copies and admits native 4352-byte page-footer caches. (2) get_sparse_mqa_logits_workspace now reuses an already-warmed, same-device workspace when called while a CUDA graph capture is active instead of tripping the cold-start assert; the genuinely cold path still asserts. (3) get_bf16_mega_gate_config returns std::map<std::string,int> so generated stubs become dict[str,int] rather than a bare dict. The changes are internally consistent, preserve existing behavior for padded (512-byte) caches, and align with the described TensorRT-LLM integration and validation (453 CSA2 cases, paged sparse decode with CUDA-graph capture/replay against 4352-byte pages). I found no blocking correctness issues. The notes below are non-blocking robustness/documentation suggestions.

Files reviewed: 3
Issues found: 🔵 3 suggestion
Inline comments posted: 3

⚠️ Parse warning: [v6] budget_exceeded:模型额度已用尽; [v5] upstream_error:模型上游服务不可用

@lfr-0531
lfr-0531 force-pushed the pr/nv-dev-sparse-paged-fixes branch 3 times, most recently from 0f4df5b to 1191ab8 Compare September 23, 2026 22:50
@lfr-0531 lfr-0531 changed the title Sparse MQA logits follow-ups for nv_dev: 16-byte paged KV stride, workspace reuse under graph capture, typed mega-gate config Sparse MQA logits follow-ups for nv_dev: 16-byte paged KV stride, workspace reuse under graph capture Sep 23, 2026
Frameworks such as TensorRT-LLM capture CUDA graphs on a dedicated side
stream after warming up eagerly on the launching stream. The sparse MQA
logits workspace is keyed by stream, so the first call on the capture
stream used to trip the pre-capture assertion. Captured work replays on
the launching stream, so fall back to that stream's warmed-up workspace
while capturing instead of recording a new allocation into the graph.

Signed-off-by: Fanrong Li <23290157+lfr-0531@users.noreply.github.com>
The paged sparse kernel addresses pages directly and copies them with
16-byte cp.async chunks, so a page stride only has to be a multiple of 16
bytes. Caches whose page-footer layout is not padded to 512 bytes (for
example 64 x (64 + 4) = 4352-byte pages) can now be read in place.

Signed-off-by: Fanrong Li <23290157+lfr-0531@users.noreply.github.com>
@lfr-0531
lfr-0531 force-pushed the pr/nv-dev-sparse-paged-fixes branch from 1191ab8 to 337b1f8 Compare September 23, 2026 22:55
@RayWang96
RayWang96 merged commit b64107f into deepseek-ai:nv_dev Sep 24, 2026
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