[Neuron] Add tensor parallel support for Neuron backend - #13718
[Neuron] Add tensor parallel support for Neuron backend#13718JingyaHuang wants to merge 89 commits into
Conversation
… into add-neuron-backend
… into add-neuron-backend
There was a problem hiding this comment.
Thanks a lot for the iteration, @JingyaHuang! Left some further comments.
I am also running the tests and trying out examples. Will keep this PR updated with findings from that.
Edit: Here's the report of my findings https://gist.github.com/sayakpaul/954b5d64aad648aca091f36c35f36397
|
|
||
| ## Tensor parallelism | ||
|
|
||
| [Tensor parallelism](https://huggingface.co/spaces/nanotron/ultrascale-playbook?section=tensor_parallelism) shards the weight matrices of a model across devices. Each device holds a column-wise (`"colwise"`) or row-wise (`"rowwise"`) slice of each layer, computes a partial result, and an `AllReduce`/`AllGather` at the layer boundary reconstructs the full output. Unlike context parallelism, it reduces the per-device *weight* memory, which is useful for models that do not fit on a single device. |
There was a problem hiding this comment.
Can we also provide a short section after the TP section, showing which parallelism should be preferred in what circumstances?
There was a problem hiding this comment.
Also, should there be guidance for model authors on how they should write the _tp_plan for their model? That seems non-trivial to me and some useful hints could be nice to have there.
There was a problem hiding this comment.
For guidance on which parallelism config to use, I think each section already explains briefly the trade-offs , eg. CP -> long sequences, TP -> large weights. I just added a section to summarize i bit, If we want more precise/actionable guide, we need to run experiments, since the right choice also depends on the specific model.
There was a problem hiding this comment.
Will add more explanation on contributing tp plan tho!
There was a problem hiding this comment.
I think all of that makes sense and should be documented (for example, the right choices depending on the model size, input problem space, etc.).
| to the given query or key 'x' tensors using the provided frequency tensor 'freqs_cis'. The input tensors are | ||
| reshaped as complex numbers, and the frequency tensor is reshaped for broadcasting compatibility. The resulting | ||
| tensors contain rotary embeddings and are returned as real tensors. | ||
| def apply_rotary_emb_qwen(x: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor: |
There was a problem hiding this comment.
It's not a tp required change, cuda runs the complex path fine. But for neuron, rope_params did torch.polar, so pos_freqs/neg_freqs are complex64, the Neuron compiler has no lowering for complex tensors so far. It's a workaround.
There was a problem hiding this comment.
I think this is a breaking change. So, we should perhaps have a pattern like:
ROPE_PER_DEVICE = {
"cuda": apply_rotary_emb_qwen,
"neuron": apply_rotary_emb_qwen_neuron,
}And then fetch from ROPE_PER_DEVICE in the caller site. @DN6 WDYT?
| @@ -63,7 +63,12 @@ | |||
| from ..utils.distributed_utils import is_torch_dist_rank_zero | |||
There was a problem hiding this comment.
I think the following aren't implemented at the moment (which is fine; just flagging).
- Sharded loading. from_pretrained should stream shards straight to each rank's DTensor rather than materializing the full checkpoint then slicing — otherwise TP saves you nothing at load time. And check
save_pretrained/ state_dict calls.full_tensor()or uses DCP. I think we should at least raise whensave_pretrained()is called in case TP is enabled? - LoRA loading. for a colwise base layer, lora_A replicated + lora_B colwise; for rowwise, lora_A rowwise + lora_B replicated. If the plan doesn't cover PEFT layers, loading an adapter onto a TP model will either error or be wrong. I think we should detect if the model has
peftlayers injected and raise if TP is requested? - Quantization, offloading. We should probably also raise when these are requested?
There was a problem hiding this comment.
@JingyaHuang this doesn't seem to have been resolved?
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
…into support-neuron-tp
There was a problem hiding this comment.
Looking good 👍🏽 I think we can merge quite soon. My comments are minor.
@sayakpaul Perhpas we can handle sharded TP loading/saving + LoRA and Quant support follow ups so that the scope is manageable here.
| # When text encoders are offloaded to CPU while the denoising backbone | ||
| # (transformer, unet, vae) runs on an accelerator, self.device returns CPU | ||
| # (first component). Prefer any non-CPU, non-meta component so that | ||
| # latent tensors land on the accelerator. This covers CUDA, XPU, NPU, HPU, | ||
| # and any other backend, including TP-sharded models via DTensor. | ||
| for name, model in self.components.items(): | ||
| if isinstance(model, torch.nn.Module) and model.device.type not in ("cpu", "meta"): | ||
| return model.device |
There was a problem hiding this comment.
This fix is okay with me, but is there a specific reason to include it in this PR? Does it affect TP?
200 percent. |
|
|
||
| `tp_degree` is taken from `world_size` above, so `--nproc-per-node 4` shards the transformer across 4 devices. | ||
|
|
||
| ### Writing a _tp_plan |
There was a problem hiding this comment.
@stevhliu can this first _ be escaped so that it renders properly? If so, how?
| # in the model's _tp_plan | ||
| "single_transformer_blocks.*.attn.to_out": PackedRowwiseParallel(), | ||
| ``` | ||
|
|
There was a problem hiding this comment.
(nit): Perhaps provide a reference implementation example of Flux2 (by linking the path to the modeling file)?
sayakpaul
left a comment
There was a problem hiding this comment.
Thanks a lot for iterating. Just a few remaining nits.
What does this PR do?
Adds tensor-parallel (TP) inference for diffusers models on AWS Neuron (Trainium/Inferentia).
The implementation is:
_tp_planmodel.enable_parallelism(config=TensorParallelConfig(...)).Now validated on 3 pipelines on Neuron (trn2, TP=8), in both eager and
torch.compilemode: FLUX.1-dev, FLUX.2 (Klein), and Qwen-Image.Key changes:
apply_tensor_parallelthat shards from a flat_tp_plan(Neuron pre-shard path works around the NRT consecutive-reduce_scatterbug; the defaultparallelize_modulepath is used on other backends)._tp_planadded to the FLUX.1, FLUX.2 and Qwen-Image transformers.head_dim), and its RoPE is ported from complextorch.polar/view_as_complexto real cos/sin. The RoPE change is numerically identical and unconditional — required for XLA backends (Neuron/TPU) and cleaner undertorch.compile. The same real-RoPE change is applied to NucleusMoE, which shared the code.Example scripts
Runnable
torchrun --nproc_per_node=8scripts live underexamples_tp/for each pipeline (e.g.test_neuron_flux1_dev_tp.py,test_neuron_flux2_dev_tp.py,test_qwenimage_tp.py).Quick test — Flux2 TP on Neuron (For future release)
run with
torchrun --nproc_per_node=8 flux2_tp8_neuron.pyWho can review?
Anyone in the community is free to review the PR once the tests have passed. Feel free to tag
members/contributors who may be interested in your PR.