Repository navigation
perf(comm): LL128 protocol for decode_cp_a2a_lse_reduce, on any torch symmetric-memory backend - #5726
Draft
thanhhao98 wants to merge 2 commits into
Draft
thanhhao98 wants to merge 2 commits into
thanhhao98 wants to merge 2 commits into
Conversation
The fused decode-CP all-to-all + LSE reduce spent most of each call in its synchronization protocol: an epoch grid.sync, a system-scope fence in every thread, release/acquire readiness flags waited on by one block, and two more grid.syncs before the merge. On 8xB200 that made one call slower than the MNNVL all-to-all plus a separate combine kernel that it replaces. Data now carries its own readiness. Each (row, destination) slice is written straight into the peer's NCCL window as 128-byte lines of 15 data words plus a per-call flag word (NCCL's LL128 layout), and the row's LSE as one 16-byte LL line per group of four data lines. Receivers poll those lines directly, so the kernel has no fence, readiness word or grid-wide barrier; the cooperative launch only guarantees co-residency. One warp sends each (row, destination) and one warp receives each (row, group of four lines), polling the LSE and data lines of every source together and merging in fixed source order. Every line is read by exactly one lane, which zeroes its flag after reading, and flags are never zero, so no stale line can look ready, including after the 32-bit call counter wraps. The workspace is therefore zeroed at creation, and its size follows the new row layout. A 4-D partial_o (and 3-D partial_lse) may now be any strided view whose last dimension is contiguous, so callers can pass a view of the attention output instead of packing it into [batch, heads, cp_size, head_dim] first.
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
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. Comment |
… backend
decode_cp_a2a_lse_reduce_create_workspace called symm_mem.set_backend("NCCL").
torch's symmetric-memory backend is process-wide and frozen by the first
allocation, so the workspace either could not be created once another library
had allocated, or it moved every other user of torch symmetric memory onto
NCCL. In SGLang that disables custom all-reduce v2, whose group-named
allocation the NCCL allocator rejects.
The kernel only needs each rank's workspace address. Every backend exposes it
through SymmetricMemory::get_buffer_ptrs_dev() and get_offset(), with null for
a rank it cannot map for load/store. The kernel now takes that device array
and the payload offset instead of an NCCL window and ncclGetLsaPointer; the
LL128 protocol and the workspace layout are unchanged.
The launcher looks the handle up through the base class, checks the group
size and rank against cp_size and cp_rank, and checks that every rank's
workspace is mapped and 128-byte aligned. create_workspace allocates on the
process's current backend and never changes it.
Without NCCL's device API the module no longer needs NCCL >= 2.29 or torch
2.14's NCCLSymmetricMemory: the NCCL_HAS_DEVCOMM stub and the NCCL header and
library discovery are gone. The NCCL backend still works under its own
requirements (an existing communicator, cuMem).
The tests no longer select NCCL; FLASHINFER_TEST_SYMM_MEM_BACKEND=NCCL runs
them on it. New tests create a workspace next to an earlier group-named
allocation, check that the backend is left alone, and use a group whose ranks
differ from the global ones.
bench_dcp_lse_reduce.py gains --symm-mem-backend to time either backend and
records the one it ran on.
4 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📌 Description
decode_cp_a2a_lse_reduce(#4929) has two problems; each commit fixes one.combine kernel it is meant to replace.
barrier.
create_workspacecalledsymm_mem.set_backend("NCCL"), which is process-wide and frozen by the first symmetric allocation.NCCL, or NVSHMEM).
Results on 8× B200:
bench_dcp_lse_reduce.py, #4929 → this PRhead_dim512)The release candidates so far (0.7.1rc1–rc5, 0.7.2rc1) ship #4929's kernel and its
set_backend("NCCL"), which SGLangcannot adopt without turning off its default all-reduce. If this PR can be reviewed in time, it may be worth considering
for 0.7.1, or else 0.7.2.
Why the #4929 kernel is slow
We timed the kernel's phases with
%globaltimerin block 0 on 8×B200, batch 1, 16 heads per rank,head_dim512, µs:grid.sync__threadfence_systemin every threadgrid.syncgrid.syncgrid.sync, busy spin (27.2 vs 31.1 µs).What changes — commit 1: LL128 protocol
Transport (
include/flashinfer/comm/dcp_lse_reduce.cuh)the flag.
{lse, flag, 0, flag}) per group of four data lines.warp-coalesced 128-byte store becoming visible atomically over NVLink. Every line is 128-byte aligned.
Work split
fixed source order (deterministic), and writes the output.
cp_sizeup to 64 is still supported.Slot reuse
ready, including after the 32-bit counter wraps.
Strided input (
csrc/dcp_lse_reduce.cu)partial_o(3-Dpartial_lse) may be any view whose last dimension is contiguous.[B, cp * H, D]asout.unflatten(1, (cp, H)).permute(0, 2, 1, 3)instead ofpacking it first.
Workspace (
flashinfer/comm/dcp_lse_reduce.py)decode_cp_a2a_lse_reduce_workspace_sizefollows the new row layout: 128 bytes of metadata +2 · cp · rows · (lines · 128 + groups · 16).head_dim512 bf16 (1200 vs 1028 bytes per row per source per slot).What changes — commit 2: no forced symmetric-memory backend
Why. torch's symmetric-memory backend is process-wide and frozen by the first symmetric allocation, so
create_workspacehad two bad outcomes:Backend can not be changed after use.once another library had allocated; orSGLang's custom all-reduce v2 allocates group-named buffers, which torch's NCCL allocator rejects. SGLang therefore falls back
to its v1 all-reduce, which on its own costs +3.3% TPOT, more than this kernel saves.
What.
SymmetricMemory::get_buffer_ptrs_dev()plus
get_offset(), with null for a rank it cannot map for load/store.ncclWindow_tandncclGetLsaPointer. The LL128protocol and the workspace layout are unchanged.
cp_sizeandcp_rank, and checks that every rank's workspace is mapped and 128-byte aligned.create_workspaceallocates on the process's current backend and never changes it.NCCL_HAS_DEVCOMMstub and the NCCL header/library discovery in the JIT spec are gone.bench_dcp_lse_reduce.pygains--symm-mem-backend.Behavior and API.
decode_cp_a2a_lse_reduce_workspace_sizeis as in commit 1.Results (8× B200, one HGX node, NVLink 5; torch 2.14.1, NCCL 2.30.7)
Backend parity. Commit 2 vs commit 1 at the DeepSeek-V2 decode shape:
head_dim512, bf16, cp 8, strided input;+0.45% (strided, hot), +0.31% (strided, flushed) and ~0 (packed).
measured +0.1–0.15 µs at batch 2–32 and +0.45–1.4 µs at batch 64–256.
cuMemCreateblock separately; NCCL maps its symmetric window into one flat range.We have not pinned down which property of the mappings matters.
(current path → commit 2, CUDA backend).
benchmarks/bench_dcp_lse_reduce.py, bf16, cp 8, µs per call (max over ranks of each rank's median):shape (two jobs, palindromic order).
End to end (SGLang, DeepSeek-V2-236B, TP 8 = DCP 8, FlashInfer MLA, 256K context, ISL 260992 / OSL 1024).
e49e971131vs the same main with its DCPfi_a2abackend calling this op (strided input, no pack kernel).NCCL_CUMEM_ENABLE=0.a2abackend, main vs this branch, is +0.05% at concurrency 1.261K-token prefills.
this PR.
--parallel 1: SGLang main 0.80 and this op 0.76 in every run (2 of 50 questions; greedydecoding repeats exactly).
--parallel 32with CUDA graphs only up to batch 4, so most batches run the op eagerly:0.772 (this op) vs 0.766 (main). Without decode CUDA graphs: 0.767.
nsys (20 decode steps at concurrency 1 per arm; two captures, opposite orders). Per layer, on the critical path from the
last rank's attention end to the last rank's reduce end:
helixAllToAllKernel5.44 + combine 1.70 µsFusedKernel7.26 µsAgainst #4929 in SGLang. #4929 cannot start next to SGLang's default all-reduce (its
set_backend("NCCL")fails oncethat has allocated). So this comparison runs SGLang's v1 all-reduce in every arm. #4929 gets contiguous input (two torch
copies), since it does not accept strided views. Two jobs, opposite arm orders; nsys from a third.
Tests
tests/comm/test_dcp_lse_reduce.py:FLASHINFER_TEST_SYMM_MEM_BACKEND=NCCLruns it on NCCL;cp_rankrejected on every rank before launch.🔍 Related Issues
Follow-up to #4929.
🚀 Pull Request Checklist
✅ Pre-commit Checks
pre-commitby runningpip install pre-commit(or used your preferred method).pre-commit install.pre-commit run --all-filesand fixed any reported issues.🧪 Tests
unittest, etc.).At commit 2,
torchrun ... -m pytest tests/comm/test_dcp_lse_reduce.py: 34 passed, 0 skipped on every rank in every runbelow.
NCCL_CUMEM_ENABLE=0)FLASHINFER_TEST_SYMM_MEM_BACKEND=NCCLand viaTORCH_SYMMMEM=NCCLassert 'NCCL' == 'CUDA', andRuntimeError: Backend can not be changed after use.4 and 8 ranks.
Not in this PR
not. The op checks that every rank's workspace is mapped; like the NCCL LSA check it replaces, that does not tell NVLink
from PCIe peer-to-peer. A topology check could reuse
flashinfer/comm/ulysses_topology.py.kernel.
Reviewer Notes
warp-coalesced 128-byte store to become visible atomically. NCCL enables LL128 on NVLink on the same premise, and the
MNNVL helix kernel in FlashInfer already relies on it. Every rank's workspace must be mapped over NVLink.
so it stays correct under CUDA-graph replay.
ncclGetLsaPointer, i.e. peer addressing. torch's symmetricmemory gives the same addresses on every backend.