[Common][PyTorch] EP dispatch with unfused MXFP8 quantization - #3270
[Common][PyTorch] EP dispatch with unfused MXFP8 quantization#3270phu0ngng wants to merge 4 commits into
Conversation
Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
Greptile SummaryAdds unfused MXFP8 quantization to NCCL expert-parallel dispatch and combine backward.
Confidence Score: 4/5The MXFP8 implementation appears safe to merge, with only the previously reported repository ignore-file deletion still requiring attention. The EP data-and-scale paths are consistently represented across the Python frontend, binding layer, common backend, and distributed tests; however, the repository ignore configuration remains entirely deleted, leaving generated and local artifacts visible to Git. Files Needing Attention: .gitignore Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant EP as PyTorch EP
participant Quant as MXFP8 Quantizer
participant Bind as C++ Binding
participant Backend as NCCL EP Backend
User->>EP: ep_dispatch(bfloat16 tokens)
EP->>Quant: Quantize to E4M3 + E8M0 scales
Quant-->>EP: Data and compact scale-inverse
EP->>Bind: Dispatch data and scales
Bind->>Backend: Block-scaled dispatch
Backend-->>EP: Expert-major data and scales
EP-->>User: Per-expert GroupedTensor
User->>EP: ep_combine(expert outputs)
EP-->>User: High-precision combined result
User->>EP: Backward(result gradient)
EP->>Quant: Quantize result gradient
EP->>Backend: Reverse dispatch data and scales
Backend-->>User: Per-expert grouped output gradient
Reviews (3): Last reviewed commit: "Separate EP dispatch-forward and combine..." | Re-trigger Greptile |
| alignment: int = 0, | ||
| payload_dtype: torch.dtype = torch.bfloat16, | ||
| device: Optional[torch.device] = None, | ||
| dispatch_quant_recipe: Optional["Recipe"] = None, |
There was a problem hiding this comment.
Let me separate the recipe for dispatch fwd and combine bwd, in case users want to use different quantization for fwd and bwd in the future.
Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
Signed-off-by: Phuong Nguyen <phuonguyen@nvidia.com>
|
/te-ci L1 pytorch |
| (MXFP8 for now), ``tokens`` is the quantized tensor kept as the autograd operand so grad | ||
| reaches the pre-quant input. Recv outputs are carved/allocated here: a caller may supply | ||
| ``recv_tokens`` / ``recv_topk_weights``, else they are sized to ``rows``.""" | ||
| from .quantized_tensor import QuantizedTensor |
There was a problem hiding this comment.
nit: why not import at top?
| alignment: int = 0, | ||
| payload_dtype: torch.dtype = torch.bfloat16, | ||
| device: Optional[torch.device] = None, | ||
| dispatch_fwd_quant_recipe: Optional["Recipe"] = None, |
There was a problem hiding this comment.
Do we expect user/mcore to provide a recipe? I think recipe is specific concept in TE that we do not want to let end user to touch. Ideally, mcore can provide something dtype/quant config, e.g.
ep_dtype: if we allow single dtype for alldispatch_dtype,combine_dtype: if we want to allow different dtypes for dispatch and combine (you know combine can be tricky)dispatch_fwd_dtype,dispatch_bwd_dtype,combine_fwd_dtype,combine_bwd_dtype: fine-grained full config
I would recommend the middle option, as combine and dispatch might want to use different recipe, but between dispatch they might want to use same. The arge can take things like mxfp8/nvfp4, and TE will automatically pick the recipe
| # Host mirror of total_recv_tokens, set by ep_prepare in eager mode. | ||
| self._host_total_recv_tokens: Optional[int] = None | ||
| # Set by ep_prepare; dispatch/combine read the routing it cached in handle_mem. | ||
| self.prepared = False |
There was a problem hiding this comment.
Seems this is only in the raw functions for assertion? What is the usage?
| return torch.empty(*shape, dtype=dtype, device=device) | ||
|
|
||
|
|
||
| def _quantize(x: torch.Tensor, recipe): |
There was a problem hiding this comment.
I feel it would be better to find a better home for these quantization functions, seems to me they do not EP specific, or the requirement is implicit.
| if tokens._fp8_dtype != tex.DType.kFloat8E4M3: | ||
| raise NotImplementedError("EP dispatch supports only E4M3 MXFP8 tokens for now.") | ||
| # recv data + scales share one buffer (data then scales); carve or allocate it here. | ||
| recv_tokens, recv_scale_inv = _scale_alloc_io( |
There was a problem hiding this comment.
What if recv_tokens is not None in this case? Basically how do we handle user allocated symm buffer with MXFP8 dispatch? Mcore has persistent buffers allocated, would be good to have TE expose the scale allocate APIs for Mcore can call as well.
| if buffer.dispatch_fwd_quant_recipe is not None: | ||
| # The grouped recv's packed scales only match the grouped layout when each expert slot is | ||
| # 128-aligned. | ||
| if buffer.alignment <= 0 or buffer.alignment % 128 != 0: |
There was a problem hiding this comment.
Is this a requirement for NCCL EP? The downstream group gemms have their own asserts
| return _SYMM_MEM_POOL | ||
|
|
||
|
|
||
| def release_symm_mem_pool(device: Optional[torch.device] = None) -> None: |
There was a problem hiding this comment.
This is great, shall we also call it in ep_finalize?
| # and their NCCL windows deregister. Fail loudly if this internal has changed shape, | ||
| # otherwise the windows would silently leak past destroy_process_group(). | ||
| if torch_cached: | ||
| pools = getattr(symm_mem, "_symm_mem_pools", None) |
There was a problem hiding this comment.
Do we release all the pools, not only the one we created?
| ) | ||
| return data, scale_inv | ||
| data = _alloc_io((rows, data_cols), data_dtype, device, zero_copy) | ||
| scale_inv = _alloc_io((rows, scale_cols), scale_dtype, device, zero_copy) |
There was a problem hiding this comment.
Currently symm pool based allocation/deallocation will break cuda graph. We should either
- Reuse the current persistent buffer design
- Push PT to fix graph issue and migrate all symm allocation to pool based
Description
This PR adds MXFP8 support to the dispatch op of the NCCL EP path. The dispatch op is used in two places, and MXFP8 applies to both:
GroupedTensor.GroupedTensor.Type of change
Changes
PyTorch frontend (
transformer_engine/pytorch/ep.py,distributed.py,csrc/extensions/ep.cpp)**dispatch_quant_recipeis set (MXFP8BlockScalingonly for now); dispatch-forward recv is returned as a per-expertGroupedTensor. A pre-quantized input is rejected.GroupedTensor. Combine forward is unchanged (high-precision).Common backend (
common/ep/ep_backend.cpp,include/.../ep.h,comm_window.h)**NCCL EP submodule**
3rdparty/nccl-extensionsto the revision providing block-scaled dispatch.Tests (
tests/cpp_distributed/test_ep.cu,tests/pytorch/distributed/run_ep.py,run_test_ep.sh)**NVTE_EP_MXFP8_PASSrun since the grouped path pins the per-expert alignment process-wide.Checklist: