Chapter 04

核心抽象:DTensor / DeviceMesh / ParallelStyle

📌 commit af33f76PyTorch 原生分布式三件套

TorchTitan 不引入新概念,而是依赖 PyTorch 2.x 引入的三个原生抽象:DeviceMesh(拓扑)、DTensor(分布式张量)、ParallelStyle(切分策略)。理解这三个就懂了 TorchTitan 的全部分布式。

4.1DeviceMesh

DeviceMesh 是 PyTorch 对"多 GPU 拓扑"的抽象。可以是 1D / 2D / 3D 网格。

from torch.distributed.device_mesh import init_device_mesh

# 1D mesh:8 张 GPU 作 DP
mesh_1d = init_device_mesh("cuda", (8,))
mesh_1d["dp"]

# 2D mesh:8 GPU = 4 DP × 2 TP
mesh_2d = init_device_mesh("cuda", (4, 2), mesh_dim_names=("dp", "tp"))
mesh_2d["dp"]    # DP 通信组
mesh_2d["tp"]    # TP 通信组

# 3D mesh:DP × PP × TP
mesh_3d = init_device_mesh("cuda", (2, 2, 2), mesh_dim_names=("dp", "pp", "tp"))

TorchTitan 用 4D mesh:(dp_replicate, dp_shard, cp, tp, pp)

4.2DTensor

DTensor = "切到 mesh 上的 tensor"。每个 DTensor 包含:

from torch.distributed.tensor import DTensor, Replicate, Shard, Partial

# Replicate:每张卡都有完整副本
dt_rep = DTensor.from_local(tensor, mesh, [Replicate()])

# Shard(0):沿第 0 维切,每张卡有 1/N
dt_shard = DTensor.from_local(tensor, mesh, [Shard(0)])

# Partial(sum):每张卡持部分和,待 reduce
dt_partial = DTensor.from_local(tensor, mesh, [Partial("sum")])

4.3三种 placement 物理含义

placement每张卡持有典型用途
Replicate()完整副本DDP 的 weight、bias
Shard(dim)沿 dim 切的 1/NFSDP 的 weight、TP 的 ColumnLinear weight
Partial(reduce_op)未规约的部分结果TP allreduce 之前的中间态

4.4DTensor 算子自动通信

关键特性:DTensor 上做 op 会自动插通信。例:

# A: Shard(0) on TP mesh
# B: Replicate on TP mesh
y = A @ B
# DTensor 自动判断:
#   - A 已经按 row 切了
#   - 结果应该按 row 切(仍是 Shard(0))
#   - 不需要通信
# 但如果 A: Shard(1), B: Replicate
# y = A @ B 会自动插入 all-reduce

torch/distributed/tensor/_ops/,几乎所有常用 op 都有 DTensor 实现。

4.5ParallelStyle:把 nn.Linear 切成 DTensor

PyTorch 提供几个 ParallelStyle,用 parallelize_module() 应用:

from torch.distributed.tensor.parallel import (
    parallelize_module,
    ColwiseParallel,        # 列切
    RowwiseParallel,        # 行切
    SequenceParallel,       # 序列切
    PrepareModuleInput,
    PrepareModuleOutput,
)

# 给 attention 加 TP
attn_plan = {
    "wq": ColwiseParallel(),
    "wk": ColwiseParallel(),
    "wv": ColwiseParallel(),
    "wo": RowwiseParallel(),
}
parallelize_module(model.attention, mesh["tp"], attn_plan)

这等价于 Megatron / ColossalAI 的"按 layer 替换 ParallelLinear",但不用换 Linear 类——只是给原 nn.Linear 的 weight 重新 layout 成 DTensor。

4.6TorchTitan 怎么用三件套

torchtitan/distributed/parallelize_llama.py(简化):

def parallelize_llama(model, mesh, parallel_dims, job_config):
    # 1) 应用 TP
    if parallel_dims.tp_enabled:
        apply_tp(model, mesh["tp"])

    # 2) 应用 PP
    if parallel_dims.pp_enabled:
        model = apply_pp(model, mesh["pp"])

    # 3) 应用 CP
    if parallel_dims.cp_enabled:
        apply_cp(model, mesh["cp"])

    # 4) 应用 FSDP2
    if parallel_dims.dp_shard_enabled:
        apply_fsdp(model, mesh["dp_shard"])

    return model

整个流程没有自定义并行类,全是 PyTorch 原生 API + DTensor。

4.7跟其他框架对比

Megatron-LMColossalAITorchTitan
分布式抽象自家 ParallelLinearBooster + PluginDTensor + ParallelStyle
是否依赖 PyTorch 新版★ 高度依赖
跟 PyTorch 主分支同步★ 快(同公司)
抽象层数

4.8这章你需要带走的