Transformers documentation

Song song tensor cho suy luận

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v5.22.2).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Song song tensor cho suy luận

Tensor parallelism chia một lớp của mô hình thành các phần để nhiều bộ tăng tốc phần cứng xử lý chúng đồng thời. Điều này cho phép bạn chạy các mô hình vượt quá dung lượng bộ nhớ của một GPU duy nhất và đạt được thông lượng cao hơn. Bạn sẽ cần liên lạc nhanh trong node vì các GPU trao đổi kết quả trung gian ở mỗi lớp.

Một model hỗ trợ tensor parallelism nếu config của nó định nghĩa base_model_tp_plan. Kiểm tra một model đã được nạp bằng thuộc tính supports_tp_plan.

from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
print(model.supports_tp_plan)

Hướng dẫn này bao gồm cách bật song song tensor trong Transformers và các chiến lược phân chia có sẵn.

Chia nhỏ một mô hình

Cấu hình số lượng thiết bị song song tensor bằng tp_size trong DistributedConfig.

  • Đặt DistributedConfig(tp_size=N) để sử dụng kế hoạch được định nghĩa trước của mô hình.
  • Định nghĩa tp_plan thủ công và truyền nó vào DistributedConfig cùng với tp_size.

Bạn cũng có thể đặt tp_plan="auto" để yêu cầu kế hoạch được định nghĩa trước một cách rõ ràng. Khi tp_size bị bỏ qua và một tp_plan được đặt, tp_size sẽ được suy ra từ WORLD_SIZE chia cho các kích thước song song khác. Việc truyền tp_plan trực tiếp vào from_pretrained() đã bị ngừng hỗ trợ và sẽ bị loại bỏ trong v5.18.

auto plan
kế hoạch thủ công
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, DistributedConfig

# model_id = "meta-llama/Llama-4-Scout-17B-16E-Instruct" # better to visualize all the possible strategies
distributed_config = DistributedConfig(tp_size=4)
model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Meta-Llama-3-8B-Instruct",
    dtype=torch.bfloat16,
    distributed_config=distributed_config,
)
print(model.tp_plan)

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
prompt = "Can I help"
inputs = tokenizer(prompt, return_tensors="pt").input_ids.to(model.device)

# distributed run
outputs = model(inputs)

Chạy script suy luận bằng torchrun. Sử dụng một tiến trình cho mỗi GPU.

torchrun --nproc-per-node 4 demo.py

Chiến lược phân vùng

ALL_PARALLEL_STYLES, một ParallelInterface được định nghĩa trong distributed/parallel_utils.py, ánh xạ mỗi tên chiến lược mà bạn có thể sử dụng trong một kế hoạch đến một instance chiến lược đã được cấu hình. Bạn không tương tác trực tiếp với nó để shard một model, nhưng đây là danh sách chính thức các tên khả dụng. distributed/tensor_parallel.py đăng ký các chiến lược tensor parallel vào đó:

ALL_PARALLEL_STYLES.register("embedding_rowwise", EmbeddingParallel(shard_dim=0))
ALL_PARALLEL_STYLES.register("embedding_colwise", EmbeddingParallel(shard_dim=1))
ALL_PARALLEL_STYLES.register("colwise_gather_output", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise_rep", ColwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("colwise", ColwiseParallel(input_layouts=Replicate(), output_layouts=Shard(-1)))
ALL_PARALLEL_STYLES.register("rowwise", RowwiseParallel(input_layouts=Shard(-1), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_split_input", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("rowwise_rep", RowwiseParallel(input_layouts=Replicate(), output_layouts=Replicate()))
ALL_PARALLEL_STYLES.register("packed_colwise", PackedColwiseParallel())
ALL_PARALLEL_STYLES.register("packed_rowwise", PackedRowwiseParallel())
ALL_PARALLEL_STYLES.register("sequence_parallel", SequenceParallel(use_local_output=True))
ALL_PARALLEL_STYLES.register("replicated_with_grad_allreduce", ReplicatedWithGradAllReduce())
ALL_PARALLEL_STYLES.register("mla_kv_a_proj", MlaKvAProjParallel())
ALL_PARALLEL_STYLES.register("all_reduce", AllReduceParallel())
ALL_PARALLEL_STYLES.register("moe_tp_experts", MoeExpertsParallel())
ALL_PARALLEL_STYLES.register("moe_identity_expert", MoeIdentityParallel())
ALL_PARALLEL_STYLES.register("local_params", LocalParamsParallel())

Mọi chiến lược song song tensor đều là lớp con của TensorParallelLayer trong tệp đó. Các chiến lược song song chuyên gia, chẳng hạn như ep_dispatch_experts, nằm trong distributed/expert_parallel.py, kế thừa từ ExpertParallelLayer, có cùng các hook, và được đăng ký vào cùng một ALL_PARALLEL_STYLES. Hai chiến lược bạn sẽ dùng thường xuyên nhất, ColwiseParallel và RowwiseParallel, nhận các bố trí input_layouts và output_layouts, đây là cách một lớp bao phủ nhiều tên kế hoạch. colwise giữ đầu ra của nó được chia mảnh trên chiều cuối để một lớp rowwise tiếp theo tiêu thụ, trong khi colwise_gather_output thu thập tất cả (all-gather) nó trở lại thành một tensor đầy đủ.

Bảng dưới đây mô tả từng chiến lược.

Chiến lượcMô tả
ColwiseParallelTách một trọng số tuyến tính theo chiều đặc trưng đầu ra (Shard(0) cho trọng số nn.Linear 2 chiều) và tách một bias 1 chiều. Phân phối lại đầu vào sang Replicate(), sau đó đặt đầu ra tại output_layouts.
RowwiseParallelTách một trọng số tuyến tính theo chiều đặc trưng đầu vào (Shard(-1)) và nhân bản bias, vốn được cộng sau khi giảm. Forward của mỗi rank tạo ra một đầu ra Partial() chứa phần tổng của rank đó, và transform_output_post_forward phân phối lại phần đó sang output_layouts. Giảm về Replicate() sẽ phát sinh một all-reduce, còn giảm về Shard(1) sẽ phát sinh một reduce-scatter.
EmbeddingParallelTách nn.Embedding với các token id được nhân bản. shard_dim=0 (embedding_rowwise, lựa chọn thông thường) tách bảng từ vựng và all-reduce kết quả tra cứu từng phần của mỗi rank thành đầu ra đầy đủ. shard_dim=1 (embedding_colwise) tách chiều ẩn và all-gather phần đầu ra của mỗi rank.
PackedColwiseParallelMột biến thể của ColwiseParallel cho các trọng số đã hợp nhất, chẳng hạn như up_proj và gate_proj được đóng gói vào gate_up_proj. Sử dụng split_factor khi một trọng số đóng gói một số lượng lớp khác với hai.
PackedRowwiseParallelBản tương ứng theo hàng, dành cho các trọng số được đóng gói dọc theo chiều cuối. Nhân bản các tham số 1D.
SequenceParallelNhân bản các tham số của module và chia nhỏ đầu vào của nó theo sequence_dim (mặc định là 1). Được sử dụng cho các chuẩn hóa hoạt động theo từng token, chẳng hạn như LayerNorm và RMSNorm.
ReplicatedWithGradAllReduceNhân bản một tham số nhưng thực hiện all-reduce cho gradient của nó. Cần thiết cho các lớp chuẩn hóa nằm giữa một lớp theo cột và một lớp theo hàng, chuẩn hóa dọc theo một trục được chia nhỏ, trong đó mỗi rank chỉ nhìn thấy các head của riêng nó.
AllReduceParallelThực hiện all-reduce trên đầu ra forward của Partial() của một module để chuyển sang Replicate(). Sử dụng nó như một điểm đồng bộ hóa cho module mà phép tính kết thúc bằng một tổng cục bộ.
MlaKvAProjParallelTách đầu ra của kv_a_proj_with_mqa của cơ chế chú ý MLA kiểu DeepSeek-V2 và thực hiện all-reduce cho gradient của nửa RoPE, qua đó bỏ qua kv_b_proj và tránh việc giữ lại một phần gradient. Yêu cầu có qk_rope_head_dim trong cấu hình mô hình.
MoeExpertsParallelCác chuyên gia MoE song song tensor. Thực hiện all-reduce cho đầu ra của chuyên gia trong quá trình truyền thẳng và thêm các all-reduce ngược cho các trạng thái ẩn và trọng số định tuyến.
MoeIdentityParallelChia trước đầu vào của một chuyên gia zero hoặc identity cho kích thước mesh, triệt tiêu phép all-reduce mà moe_tp_experts áp dụng ở phía sau.
LocalParamsParallelChạy forward của một module với các tham số phân mảnh của chính nó dưới dạng tensor cục bộ. Sử dụng nó trên phần tử sở hữu một nn.Parameter trần, chẳng hạn như attention sinks, mà mục kế hoạch của chính nó phân mảnh.
MoEParamShardPhân mảnh trọng số chuyên gia MoE trên một vị trí đặt đã cho. Hỗ trợ tên grouped_gemm, trong đó shards_expert_dim=True cũng viết lại module.num_experts thành số lượng chuyên gia trên mỗi rank.
EpRouterParallelChe điểm router cho các chuyên gia không cục bộ và ánh xạ lại ID chuyên gia toàn cục thành ID cục bộ, để mỗi rank chỉ chạy các chuyên gia mà nó sở hữu. Yêu cầu num_experts phải chia hết cho kích thước mesh.
RouterParallelMegaMoeBiến thể Router cho DeepGEMM Mega MoE, trong đó các chuyên gia được điều phối bên trong kernel, vốn yêu cầu đầu ra của router không bị thay đổi.
MoeTensorParalellMegaMoeExpertsTầng chuyên gia chỉ dành cho suy luận cho DeepGEMM Mega MoE. Bỏ qua các bước đồng bộ gradient và truyền nhóm tiến trình vào module để kernel có thể thiết lập các bộ đệm chia sẻ trong lần forward đầu tiên.

Chiến lược đóng gói

Weight packing kết hợp nhiều lớp tuyến tính thành một lớp lớn hơn duy nhất. Các chiến lược PackedColwiseParallel và PackedRowwiseParallel chia nhỏ (shard) các trọng lượng đã đóng gói một cách chính xác. Các chiến lược ColwiseParallel hoặc RowwiseParallel cơ bản sẽ chia nhỏ các trọng lượng đã đóng gói một cách không chính xác.

Ví dụ dưới đây đóng gói up_proj và gate_proj thành một module gate_up_proj duy nhất và yêu cầu chiến lược packed_rowwise để chia nhỏ gate_up_proj.

class Llama4TextExperts(nn.Module):
    ...
    self.gate_up_proj = nn.Parameter(torch.zeros(self.num_experts, self.hidden_size, 2 * self.expert_dim))

Sử dụng phép nhân ma trận theo lô trong bước forward để tính toán đầu ra của module gate_up_proj.

def forward(self, hidden_states):
    ...
    gate_up = torch.bmm(hidden_states, self.gate_up_proj) # Compute the output of the gate_up_proj module
    gate, up = gate_up.chunk(2, dim=-1) # Split the output into gate and up

Một Shard thông thường chia chiều đó thành các khối liên tục, do đó một rank sẽ nhận được phần cuối của gate và phần đầu của up thay vì một lát cắt của từng phần. Các chiến lược đóng gói sử dụng _StridedShard với split_factor (mặc định là 2) để xen kẽ việc chia, đảm bảo mỗi rank nhận được một lát cắt tương ứng của cả hai nửa để chunk vẫn khớp nhau sau khi sharding.

Các chiến lược phân vùng tùy chỉnh

Kế thừa từ TensorParallelLayer trong distributed/tensor_parallel.py để tạo chiến lược phân vùng tùy chỉnh. Chỉ ghi đè các hook mà chiến lược của bạn cần, vì mỗi hook đều có mặc định không thực hiện gì (no-op).

HookMục đích
validate_paramTừ chối một tham số mà chiến lược này không thể chia nhỏ, trước khi tải bất kỳ trọng số nào.
shard_paramThay thế một tham số bằng một placeholder DTensor để loader biết shard nào thuộc về rank này.
transform_inputs_pre_forwardPhân phối lại các đầu vào của module theo bố cục mà hàm forward của nó mong đợi.
context_around_forwardBọc forward trong một trình quản lý ngữ cảnh, ví dụ để phơi bày các tensor cục bộ cho một kernel.
transform_output_post_forwardPhân phối lại hoặc giảm đầu ra của module.
should_use_local_tensorsBáo cáo rằng forward của module này cần các tensor thông thường thay vì DTensor.
install_forwardThay thế hoàn toàn module.forward. Chỉ ghi đè hàm này khi các hook ở trên không đủ, như cách ReplicatedWithGradAllReduce làm để đăng ký một backward hook.

Ví dụ dưới đây đi qua một phiên bản rút gọn của ColwiseParallel.

  1. Kế thừa từ TensorParallelLayer và lưu trữ các vị trí (placements) mà chiến lược làm việc. Lớp cơ sở không định nghĩa __init__, vì vậy không có gì để gọi super().

    class ColwiseParallel(TensorParallelLayer):
        def __init__(self, *, input_layouts=None, output_layouts=None, use_local_output: bool = True):
            self.input_layouts = input_layouts or Replicate()
            self.output_layouts = output_layouts if output_layouts is not None else Shard(-1)
            self.use_local_output = use_local_output
  2. Thực hiện shard_param để bọc một tham số thành một placeholder DTensor. Nó chạy trên các meta tensor, do đó distribute_tensor chỉ xây dựng metadata và không di chuyển dữ liệu. Truyền src_data_rank=None vì hiện tại chưa có tensor đầy đủ để phân tán.

    def shard_param(self, module, param, mesh):
        meta = module._parameters.get(param)
        if meta is None:
            return
        # Output features live on dim 0 for a 2D weight and dim -1 for a 1D bias
        placement = Shard(1) if isinstance(module, torch.nn.Embedding) else Shard(meta.ndim - 2)
        module._parameters[param] = torch.nn.Parameter(
            distribute_tensor(meta, mesh, [placement], src_data_rank=None),
            requires_grad=meta.requires_grad,
        )
  3. Thực hiện các phép biến đổi đầu vào và đầu ra. install_forward gọi chúng bao quanh hàm forward gốc của module, vì vậy chúng chỉ cần di chuyển tensor giữa các bố cục. Việc phân vùng theo cột kỳ vọng đầu vào được nhân bản và tạo ra đầu ra được chia mảnh trên chiều cuối.

    def transform_inputs_pre_forward(self, module, args, kwargs, mesh):
        x = args[0]
        if not isinstance(x, DTensor):
            x = DTensor.from_local(x, mesh, [self.input_layouts], run_check=False)
        if x.placements != (Replicate(),):
            x = x.redistribute(placements=[Replicate()])
        return (x,) + args[1:], kwargs
    
    def transform_output_post_forward(self, module, output, mesh):
        if not isinstance(output, DTensor):
            output = DTensor.from_local(output, mesh, [Shard(-1)], run_check=False)
        if output.placements != (self.output_layouts,):
            output = output.redistribute(placements=[self.output_layouts])
        return output.to_local() if self.use_local_output else output

    ColwiseParallel được cung cấp sẵn bổ sung các đường dẫn nhanh trên cơ sở này, bỏ qua vòng lặp DTensor cho suy luận nn.Linear thông thường và cho các module lượng tử hóa cần tensor cục bộ. Hãy đọc mã nguồn trước khi sao chép nếu chiến lược của bạn cần xử lý tương tự.

  4. Đăng ký chiến lược để tp_plan có thể gọi tên nó. Việc đăng ký nhận một instance của chiến lược, do đó các placements bạn truyền vào __init__ sẽ được lưu trữ cùng với tên. Lưu ý () bên dưới.

    import torch
    
    from transformers import AutoModelForCausalLM, DistributedConfig
    from transformers.distributed.parallel_utils import ALL_PARALLEL_STYLES
    
    ALL_PARALLEL_STYLES.register("colwise_custom", ColwiseParallel())
    tp_plan = {
        "model.layers.*.self_attn.q_proj": "colwise_custom",
        ...
    }
    distributed_config = DistributedConfig(tp_size=4, tp_plan=tp_plan)
    model = AutoModelForCausalLM.from_pretrained(
        "meta-llama/Meta-Llama-3-8B-Instruct",
        dtype=torch.bfloat16,
        distributed_config=distributed_config,
    )

Benchmarks

Song song tensor tăng tốc độ suy luận đáng kể, đặc biệt là với kích thước lô lớn hoặc các chuỗi dài.

Biểu đồ này cho thấy tốc độ tăng tốc dự kiến cho một lần truyền tiến đơn lẻ trên Llama với độ dài chuỗi là 512.

Thiết kế triển khai

Transformers triển khai tensor parallelism theo cách độc lập với framework. Nó dựa vào DeviceMesh và DTensor từ torch.distributed để cung cấp một giao diện đơn giản, có thể mở rộng.

DeviceMesh

DeviceMesh tạo ra một lưới thiết bị nhiều chiều giao tiếp với nhau. Các chiến lược song song hóa khác nhau đòi hỏi các mẫu giao tiếp khác nhau. Hãy tạo một DeviceMesh với nhiều lưới con để xử lý các mẫu này.

import torch
from torch.distributed.device_mesh import init_device_mesh

# Create a 1D mesh of 4 accelerators
device_type = torch.accelerator.current_accelerator().type
device_mesh = init_device_mesh(device_type, (4,), mesh_dim_names=["tp"])

Hầu hết các chiến lược song song hóa torch.distributed được áp dụng cho chính lưới (mesh) hoặc các lưới con của nó. Lưới tự động xử lý các mẫu giao tiếp.

DTensor

DTensor (Distributed Tensor) xử lý logic phân tán trên các thao tác tensor thông thường. Trọng số mô hình dưới tensor parallelism được lưu trữ dưới dạng DTensor, điều này cho phép một chiến lược mô tả giao tiếp như một sự thay đổi layout thay vì một tập thể hợp (collective) tường minh.

Thuộc tính placements cho PyTorch biết cách một tensor được bố trí trên các thiết bị trong một DeviceMesh. Nó chấp nhận các giá trị sau:

  • Shard(dimension) chia một DTensor trên một chiều cho trước theo DeviceMesh mà nó được xây dựng. Việc phân vùng theo cột chia chiều output-feature của trọng số và chiều duy nhất của bias.

    weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(0)]) # Shard the output features
    bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Shard(-1)]) # Shard the ONLY dimension

    Phân vùng theo hàng (row-wise partitioning) chia nhỏ chiều input-feature thay vào đó và nhân bản bias, vì bias được cộng một lần sau khi thực hiện phép giảm (reduction) chứ không phải trên mỗi rank.

    weight = DTensor.from_local(weight, device_mesh["tp"], placements=[Shard(-1)]) # Shard the input features
    bias = DTensor.from_local(bias, device_mesh["tp"], placements=[Replicate()]) # Replicate bias across all GPUs
  • Replicate() nhân bản một DTensor trên DeviceMesh, tạo ra một bản sao đầy đủ của tensor trên mỗi thiết bị.

  • Partial() đánh dấu một tensor đang chờ giảm (reduction). Đầu ra forward của một lớp row-wise là Partial(), và việc phân phối lại nó sang Replicate() chính là thao tác phát lệnh all-reduce.

Tài nguyên

  • Phần Ultra-Scale Playbook về song song tensor cung cấp thêm chi tiết.

  • Kiểm tra hướng dẫn expert parallelism nếu bạn đang sử dụng mô hình mixture-of-experts (MoE). Các mô hình này hỗ trợ tensor parallelism và expert parallelism.

  • Đọc bài đăng trên blog Tensor Parallelism (TP) in Transformers: 5 Minutes to Understand để có cái nhìn tổng quan nhanh về tensor parallelism và tìm hiểu cách các cấu hình song song cột và hàng khác nhau.

  • Xem hướng dẫn huấn luyện Tensor parallelism để tìm hiểu cách sử dụng nó trong môi trường huấn luyện.

Update on GitHub