feat: add symmetric-memory LM-head all-gather - #4915
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new symmetric-memory all-gather implementation contains a correctness bug (type(group) is dist.ProcessGroup) that will fail for valid process groups and prevent the feature from functioning when enabled.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR introduces an opt-in symmetric-memory (“multimem.st”) all-gather fast path for tensor-parallel ParallelLMHead logits to reduce per-step collective overhead in small-token decode, while preserving the existing NCCL all_gather_into_tensor path as the default/fallback. It also adjusts Ray worker GPU visibility/device binding to support symmetric-memory rendezvous requirements.
Changes:
- Add a Triton-based symmetric-memory last-dimension all-gather implementation (
MultimemAllGatherer) and integrate it intoParallelLMHead.all_gather_logitswhen enabled and eligible. - Add environment flags to control the feature (
LMDEPLOY_ENABLE_SYMM_MEM_LMHEAD,LMDEPLOY_SYMM_MEM_LMHEAD_MAX_MB). - Update Ray executor initialization to keep peer GPUs visible and bind each worker to its assigned CUDA device when symmetric-memory features are in use.
File summaries
| File | Description |
|---|---|
| lmdeploy/pytorch/nn/embedding.py | Adds TP-wide opt-in admission and a symmetric-memory all-gather fast path for ParallelLMHead, with NCCL fallback. |
| lmdeploy/pytorch/envs.py | Introduces env toggles and sizing for symmetric-memory LM-head arena. |
| lmdeploy/pytorch/engine/executor/ray_executor.py | Preserves full CUDA visibility and enforces unique per-node device binding for Ray workers when symmetric-memory is needed. |
| lmdeploy/pytorch/backends/cuda/comm/symm_mem_allgather.py | New Triton + symmetric-memory implementation for last-dim all-gather, plus guarded wrapper for safe fallback/capture behavior. |
Review details
Suppressed comments (1)
lmdeploy/pytorch/nn/embedding.py:185
- This warning message is emitted both when TP ranks have inconsistent arena config and when
max_tokens <= 0(arena too small for even one token row), which makes the log misleading and harder to debug.
same_config = _tp_same_config((capacity, gathered_width, max_tokens), device, self.tp_group)
if max_tokens <= 0 or not same_config:
if self.tp_rank == 0:
logger.warning('symmetric-memory LM-head disabled because TP ranks have inconsistent arena config')
return
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if self.all_reduce and self.weight.device.type == 'cuda': | ||
| device = self.weight.device | ||
| if device.index is None: | ||
| device = torch.device('cuda', torch.cuda.current_device()) | ||
| requested = _envs.enable_symm_mem_lmhead and self.weight.dtype == torch.bfloat16 | ||
| if not _tp_agree(requested, device, self.tp_group): | ||
| return |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Motivation
Tensor-parallel
ParallelLMHeadcurrently materializes each vocabulary shard and usesdist.all_gather_into_tensorto reconstruct full logits. For the small token counts common in decode/speculative decode, collective launch, allocation, and rank-layout overhead are a meaningful fraction of LM-head latency.This PR adds an opt-in Hopper/NVLink symmetric-memory V1 path. Each rank writes its BF16 logits shard through
multimem.stinto a symmetric full-logits arena, then returns an owning output tensor. The existing NCCL implementation remains the default and fallback.Modification
multimem.stlast-dimension all-gather for TP2/TP4/TP8 BF16 logits.ParallelLMHead.forwardpath after the native local GEMM.The feature is disabled by default:
Token-level performance
The following is a synthetic kernel-level benchmark, not an end-to-end serving
benchmark. It measures the TP-local BF16 logits all-gather at the GLM5.2
shape (
V=154880,H=6144) on 8x NVIDIA H200 with TP8.Mis the number oflogits rows submitted to one collective. Each value is the rank-wise maximum
latency across the eight ranks; the reported value is the median of 150
iterations after 30 warmup iterations.
dist.all_gather_into_tensorplus the existing output layout.MultimemAllGatherer(multimem.st), with the production-safe owningoutput (
safe=True, including the output clone).bitwise comparison (
rtol=0,atol=0).MM=1is representative of a single-token decode step;M=6is representative of one target token plus five speculative draft tokens.Larger
Mvalues represent batched decode or chunked/prefill-sized work. The tabledoes not include model scheduling, KV-cache work, sampling, or network/server
overhead, so it must not be interpreted as TTFT/TPOT or serving throughput.
The static input contract (BF16, contiguous/aligned local logits, device and shard width) is collectively admitted once and must remain unchanged for that LM-head instance.
ParallelLMHeadprovides this invariant; token count can vary and uses the NCCL fallback when it exceeds arena capacity.BC-breaking
None. The optimization is opt-in and the existing NCCL path remains unchanged when it is disabled or unavailable.
Use cases
Enable
LMDEPLOY_ENABLE_SYMM_MEM_LMHEAD=1for tensor-parallel CUDA deployments on supported Hopper/NVLink systems. Unsupported environments continue through the existing NCCL implementation.Checklist
CuTeDSL/TileLang fused-epilogue experiments are intentionally out of scope for this V1 PR.