Skip to content

perf(pcie): add exact DCP top-k owner exchange#79

Open
voipmonitor wants to merge 3 commits into
masterfrom
perf/dcp-topk-owner-candidate-clean-20260725
Open

perf(pcie): add exact DCP top-k owner exchange#79
voipmonitor wants to merge 3 commits into
masterfrom
perf/dcp-topk-owner-candidate-clean-20260725

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Add a graph-safe CUDA-IPC transport that stages exact sparse-indexer candidates directly into each DCP row owner's rank-major input table.

Each source rank writes its disjoint (indices, FP32 score bits) column into the destination owner's double-buffered slab. The owner can run the existing exact row-top-k without a pack tensor, NCCL all-to-all, or unpack kernel.

Supported DCP world sizes are 2, 3, 4, 6, 8.

Scope

This PR intentionally contains only candidate owner exchange.

An exact int24 final-index gather was implemented and measured during development, but it took 3.260 ms versus 1.254 ms for NCCL and regressed 64k E2E prefill by 5.45%. It has therefore been removed rather than carried as unused code. Final selected indices remain a standard NCCL all-gather in the vLLM integration.

Results

TP8/DCP4 production-shape candidate phase:

  • NCCL owner staging: 1.4233 ms
  • this transport: 1.0342 ms
  • candidate-phase delta: -27.33%

After adding the cross-rank graph-replay reuse guard, the same eager shape
measured 1.0338 ms versus 1.0336 ms before the guard (+0.013%, noise). The
guard therefore has no measurable eager-path cost.

GLM-5.2 64k E2E prefill was 5,766.3 tok/s with NCCL owner staging and 5,784.7 tok/s with this path (+0.32%, within run variance). The persistent double buffer reduced KV capacity by 10,240 tokens (0.47%). This is therefore a lower-traffic optional transport, not an E2E throughput claim.

Double buffering is required: a single payload can be overwritten by a faster producer while a peer still consumes the previous layer.

Validation

  • 6 passed CPU contract and packaging tests.
  • Multi-GPU exactness plus CUDA graph replay passed for:
    • TP8/DCP4
    • TP6/DCP3
    • TP6/DCP6
  • Candidate score bits are compared as int32 views with rtol=0, atol=0.
  • A deliberately skewed graph-replay test delays one rank's consumer and
    queues four replays without host synchronization; every generation remains
    exact.
  • Variable row counts and all supported dispatch implementations are covered.
  • ruff check and ruff format --check pass.

The corresponding vLLM owner algorithm is tracked independently in local-inference-lab/vllm#178.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a PCIe/CUDA-IPC owner exchange for exact DCP sparse top-k candidates, including public exports, shared-buffer lifecycle management, synchronized CUDA staging, validation, CPU tests, GPU integration tests, and packaging updates.

Changes

DCP TopK owner exchange

Layer / File(s) Summary
Public API and staging contracts
sparkinfer/comm/pcie/__init__.py, sparkinfer/comm/pcie/api.py, sparkinfer/comm/pcie/pcie_dcp_topk.py
Exports DcpTopKOwnerExchange and owner_stage_reference, adds int32 support, validates staging layouts, and defines the reference owner-plane transformation.
IPC allocation and lifecycle
sparkinfer/comm/pcie/pcie_dcp_topk.py
Loads the CUDA extension, allocates double-buffered IPC slabs, manages channel cleanup, and validates and dispatches candidate staging.
CUDA kernel and extension bridge
sparkinfer/comm/pcie/pcie_dcp_topk.cu
Adds synchronized rank-to-owner candidate staging, launch validation, IPC-backed tensor views, disposal, metadata, and PyBind11 bindings.
Unit, GPU, and packaging validation
tests/comm/test_pcie_dcp_topk.py, tests/comm/test_pcie_dcp_topk_gpu.py, tests/test_packaging.py
Tests layout, bit-preserving references, dispatch, invalid contracts, NCCL multi-process execution, CUDA Graph replay, cleanup, and CUDA source packaging.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PCIeDCPTopKOwnerExchange
  participant stage_candidates
  participant stage_owner_candidates
  participant IPCStagingBuffers
  PCIeDCPTopKOwnerExchange->>stage_candidates: submit local indices and scores
  stage_candidates->>stage_owner_candidates: launch CUDA staging
  stage_owner_candidates->>IPCStagingBuffers: write owner candidate planes
  stage_owner_candidates-->>stage_candidates: return staging slot
  stage_candidates-->>PCIeDCPTopKOwnerExchange: return candidate tensors
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an exact DCP top-k owner exchange in PCIe.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/dcp-topk-owner-candidate-clean-20260725

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
sparkinfer/comm/pcie/pcie_dcp_topk.py (1)

274-288: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Consider asserting cross-rank agreement on max_rows/topk.

layout.slab_bytes is derived from rank-local max_rows/topk, and the slab allocation is collective. If one rank is constructed with a different geometry, every rank's peer pointers still map successfully but the owner planes disagree, which surfaces later as silent candidate corruption rather than a construction error. A small all-reduce/broadcast check of (max_rows, topk, ext.meta_size()) before allocating would fail fast.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sparkinfer/comm/pcie/pcie_dcp_topk.py` around lines 274 - 288, Before the
collective allocation in the candidate staging setup, add a cross-rank agreement
check for max_rows, topk, and ext.meta_size(). Use the existing exchange_group
collective communication utilities to validate these values match on every rank,
and fail construction immediately with a clear error before calling
_allocate_shared_buffer when they differ.
sparkinfer/comm/pcie/pcie_dcp_topk.cu (1)

66-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Document (or enforce) the grid-symmetry invariant this barrier depends on.

block_pair_barrier only completes if every rank in the DCP group launched the identical number of blocks, since block i waits on peer block i's counter slot. blocks is computed per-rank from caller-supplied rows/threads/block_limit (Line 181), so any asymmetry silently deadlocks the whole group with no timeout or diagnostic. Consider stating the invariant in the header comment and having the Python layer assert agreed-upon rows/threads/block_limit across the exchange group at construction time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sparkinfer/comm/pcie/pcie_dcp_topk.cu` around lines 66 - 89, Document the
grid-symmetry invariant required by block_pair_barrier: every rank in the DCP
group must launch the same number of blocks because block indices synchronize
directly. Add a clear header comment near block_pair_barrier, and update the
Python construction/exchange-group validation to assert matching rows, threads,
and block_limit across all ranks before launching work.
tests/comm/test_pcie_dcp_topk_gpu.py (1)

126-160: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Add a large-offset case and a back-to-back replay case.

With MAX_ROWS=64 / TOPK=2048 the largest staged element offset is ~1.3e5, so the kernel's Int64 offset math (owner_row * output_row_packs, int64_t(destination) * owner_packs) is never exercised anywhere near the 2^31 / stride boundary — a future 32-bit narrowing would pass this suite. A capacity-scaled case (large topk/max_rows, gated on available memory) would close that gap.

Separately, torch.cuda.synchronize(device) after every graph.replay() means two consecutive replays never overlap, which is precisely the pattern that would expose the pinned-slot hazard noted in sparkinfer/comm/pcie/pcie_dcp_topk.cu Line 184. Consider a replay pair without an intervening full sync.

As per coding guidelines: "Every test or repro for a kernel indexing a paged pool must include a big-pid case with live pages beyond the 2^31 / stride boundary."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/comm/test_pcie_dcp_topk_gpu.py` around lines 126 - 160, Add coverage in
the graph replay test around `_inputs` and `owner_stage_reference` for a
memory-gated, capacity-scaled large TOPK/MAX_ROWS configuration whose live page
IDs exceed the 2^31/stride boundary, while preserving the existing assertions.
Also add a back-to-back replay pair that calls `graph.replay()` twice without an
intervening `torch.cuda.synchronize(device)`, then synchronize and validate both
outputs to exercise the pinned-slot hazard.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sparkinfer/comm/pcie/pcie_dcp_topk.cu`:
- Around line 181-185: Update the slot selection around slot_++ so the counter
uses a non-overflowing representation with explicit two-slot wrapping instead of
signed modulo arithmetic. Ensure captured CUDA graph replays obtain a fresh
staging slot only after the owner consumer retires the prior output, preserving
the two-slab handoff and preventing writes from clobbering live staging data.

---

Nitpick comments:
In `@sparkinfer/comm/pcie/pcie_dcp_topk.cu`:
- Around line 66-89: Document the grid-symmetry invariant required by
block_pair_barrier: every rank in the DCP group must launch the same number of
blocks because block indices synchronize directly. Add a clear header comment
near block_pair_barrier, and update the Python construction/exchange-group
validation to assert matching rows, threads, and block_limit across all ranks
before launching work.

In `@sparkinfer/comm/pcie/pcie_dcp_topk.py`:
- Around line 274-288: Before the collective allocation in the candidate staging
setup, add a cross-rank agreement check for max_rows, topk, and ext.meta_size().
Use the existing exchange_group collective communication utilities to validate
these values match on every rank, and fail construction immediately with a clear
error before calling _allocate_shared_buffer when they differ.

In `@tests/comm/test_pcie_dcp_topk_gpu.py`:
- Around line 126-160: Add coverage in the graph replay test around `_inputs`
and `owner_stage_reference` for a memory-gated, capacity-scaled large
TOPK/MAX_ROWS configuration whose live page IDs exceed the 2^31/stride boundary,
while preserving the existing assertions. Also add a back-to-back replay pair
that calls `graph.replay()` twice without an intervening
`torch.cuda.synchronize(device)`, then synchronize and validate both outputs to
exercise the pinned-slot hazard.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f9b9e671-002e-4146-b387-d6a3c1a78201

📥 Commits

Reviewing files that changed from the base of the PR and between c39b806 and 04d8019.

📒 Files selected for processing (7)
  • sparkinfer/comm/pcie/__init__.py
  • sparkinfer/comm/pcie/api.py
  • sparkinfer/comm/pcie/pcie_dcp_topk.cu
  • sparkinfer/comm/pcie/pcie_dcp_topk.py
  • tests/comm/test_pcie_dcp_topk.py
  • tests/comm/test_pcie_dcp_topk_gpu.py
  • tests/test_packaging.py

Comment thread sparkinfer/comm/pcie/pcie_dcp_topk.cu
@voipmonitor

voipmonitor commented Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-ups 19dca9a and 88a0418 define and enforce the staging lifetime contract.

  • Eager slot selection is an explicit non-overflowing two-state toggle.
  • CUDA graph replay keeps its capture-stable slab and uses a capture-only pre-write group barrier. Each rank reaches it after its prior same-stream consumer, preventing a faster peer from overwriting a slower owner's live slab.
  • Eager execution pays no additional barrier.

Validation:

  • CPU contract and packaging tests: 6/6.
  • Ruff check/format clean.
  • Exact GPU + CUDA graph replay: TP8/DCP4, TP6/DCP3, TP6/DCP6 all passed.
  • The graph test delays one owner, preloads all GPU inputs, queues four replay generations without intermediate synchronization, snapshots each generation, and verifies bit-exact indices and FP32 score bits.
  • The same asynchronous skew test without the graph reuse guard stalls in peer handoff; the guarded implementation completes.

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