Skip to content

필사 모드: 2026 年 LLM 训练技术栈地图 —— 每一层替你做了什么,又藏起了什么

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 —— 选框架,选的是要藏起什么

"该用哪个框架训练"这个问题没有单一答案,因为大家所在的层不一样。用 torchrun 自己写的循环,和一份 YAML 就能跑起来的微调,不是竞争关系,而是上下级关系。

所以本文不打算罗列框架,而是分三层画出谱系。对每一层只看三件事:这一层替你做了什么这一层藏起了什么,以及需要重新打开被藏起的东西时,有没有一个逃生舱口

这个领域按月在变。下面所有版本号都是 2026 年 8 月 2 日直接在 PyPI 发布历史和 GitHub release tag 上核实的数值,无法核实的项目已在正文中注明。等你读到这篇文章时,其中几个数字大概率已经过时了——比起数字本身,更值得带走的是这套分层结构,它更耐用。

核实基准 —— 版本与核实日期

工具核实的版本发布日期出处
PyTorch2.13.02026-07-08PyPI
DeepSpeed0.19.32026-07-23PyPI
Megatron-Core0.18.22026-07-21GitHub
Transformer Engine2.17.02026-07-09PyPI
torchtitanv0.2.22026-02-20GitHub
NeMo Toolkit2.7.32026-04-23PyPI
NeMo-RLv0.7.02026-07-29GitHub
Transformers5.14.12026-07-16PyPI
TRL1.9.22026-07-28PyPI
PEFT0.20.02026-07-28PyPI
Accelerate1.14.02026-06-11PyPI
Axolotl0.18.02026-07-17PyPI
LLaMA-Factory0.9.52026-05-30PyPI
Unsloth2026.7.62026-07-29PyPI
verl0.8.02026-06-01PyPI
OpenRLHF0.10.42026-06-08PyPI
Liger Kernel0.8.12026-07-23PyPI
torchao0.17.02026-03-30PyPI
torchtune0.6.1(最后版本)2025-04-07GitHub

第 1 层 —— 分布式执行引擎

最底下这一层负责"这个张量放在哪块 GPU 上、什么时候发起哪种集合通信"。这里有三条谱系。

PyTorch 分布式系列。 torch.distributed 把 DDP、FSDP2、DTensor、DeviceMesh、torch.distributed.pipelining 全都提供出来。以 2026 年为节点,一个标志性的变化是并行化不再是一个独立的库,而是变成了 PyTorch 本体的 API。FSDP2 的 fully_shard 不会把模块包起来,而是原地把参数变成 DTensor,所以参数名得以保留,checkpoint 兼容性问题也随之减少。官方教程已经把 FSDP1 标记为弃用状态。

DeepSpeed 系列。 ZeRO 1 到 3 阶段、offloading、序列并行(Ulysses)、推理 kernel 全部装在一个包里。常有人问"它还活着吗"——0.19.3 是 2026 年 7 月 23 日发布的,所以答案是活跃。所有权结构已经改变:GitHub 组织从 microsoft 迁到了 deepspeedai,现在既是 LF AI & Data 的孵化项目,也是 PyTorch Foundation 托管的项目。如果哪份文档里还留着旧链接和旧组织名,那份文档就是过时的。

Megatron 系列。 从 NVIDIA 的 Megatron-LM 仓库里抽出可复用部分,就是 Megatron-Core,它是张量并行、流水线并行、序列并行、上下文并行、专家并行事实上的参考实现。挂在它上面的是负责低精度训练的 Transformer Engine。FP8 训练很早就已支持,NVFP4 4-bit 预训练也在 Transformer Engine 中得到支持——NVIDIA 的论文报告称,把一个 12B 的混合 Mamba-Transformer 模型训练到 10T token,效果与 FP8 基线相当。不过这个结果能不能原样搬到你自己的模型上是另一回事——如果打算引入 4-bit 预训练,应该先跑一个短程对比,直接比较损失曲线。

直接接触这一层时的接口通常是配置文件。DeepSpeed 的话是这个样子。

{
  "bf16": { "enabled": true },
  "zero_optimization": {
    "stage": 3,
    "overlap_comm": true,
    "contiguous_gradients": true,
    "reduce_bucket_size": 5e8,
    "stage3_prefetch_bucket_size": 5e8,
    "stage3_param_persistence_threshold": 1e6,
    "stage3_gather_16bit_weights_on_model_save": true
  },
  "gradient_accumulation_steps": "auto",
  "train_micro_batch_size_per_gpu": "auto",
  "gradient_clipping": 1.0,
  "steps_per_print": 50,
  "wall_clock_breakdown": false
}

这里实务上最常出问题的一行是 stage3_gather_16bit_weights_on_model_save。在第 3 阶段,参数是按 GPU 切分的,如果不打开这个选项就直接保存,得到的会是一堆服务化时读不了的分片碎片。而打开 overlap_comm 能让通信与计算重叠从而加速,但代价是压缩了激活内存的余量——如果配置正卡在 OOM 边缘,应该先怀疑这个值。

这一层几乎什么都不藏。相应地,它也几乎不替你做任何事。数据加载器、checkpoint 格式、恢复逻辑、日志记录,全都要自己写。

第 2 层 —— 训练循环框架

在第 1 层之上加了一层"从头到尾跑完训练的循环"。

torchtitan 是 PyTorch 团队自己做的参考实现,可以组合 FSDP2、张量并行(含异步变体)、流水线并行、上下文并行、HSDP,并支持 Float8 和 MXFP8 训练。仓库把自己描述为"面向快速实验与大规模训练的 PyTorch 原生平台",并建议用 PyTorch nightly 版本才能用上最新功能。也就是说,它的设计初衷不是跑在稳定版之上的生产级技术栈。作为阅读和抄写代码的参考,它是最好的。

Megatron-Bridge 是 NVIDIA 做的一个库,负责 Hugging Face checkpoint 与 Megatron-Core 之间的双向转换,并在其上叠加了一套使用 Megatron-Core 的训练循环。这座桥之所以重要,是因为实务流程本身如此:模型以 Hugging Face 格式接收,训练用 Megatron-Core 的并行方式跑,服务化又得转回 Hugging Face 格式导出。这段转换如果自己写,张量名映射和合并/拆分规则几乎必错无疑。文档中提到 NeMo-RL 和 SkyRL 都把这个当作各自的 Megatron-Core 连接器采用了。

NVIDIA NeMo 是再往上一层的综合性框架。从预训练到对齐再到部署都被打包成了一份份 recipe,强化学习部分则拆分成了 NeMo-RL。如果能直接用 NVIDIA 的容器环境,这是最快的路径;如果不能,依赖的体量本身就会成为负担。

这一层替你做的是并行化配置、分布式 checkpoint、恢复、吞吐量日志记录。它藏起的是优化器 step 的细节和通信调度,调试损失尖峰时你得重新把它打开。

第 3 层 —— 靠配置文件驱动的后训练工具

最上面这一层把数据集格式、prompt 模板、LoRA 配置、评估钩子全都打包在一起,一条命令就能跑起来。

TRL 是 Hugging Face 生态里后训练的标准工具。以 1.9.2 的 release notes 为准,它内置了 SFT、DPO、GRPO、RLOO、KTO、奖励模型的 trainer,KTO trainer 在 1.8.0 里摘掉了实验性标签。就像 1.7.0 把默认损失换成了分块(chunked)NLL、并加入了 MoE 辅助损失那样,默认值本身会在小版本发布里发生变化。我在 release 页面上看到过一句提到实验性命名空间下还有几个额外 trainer 的说明,但没有逐一核实具体算法,请在你实际使用的版本文档里直接确认清单。TRL 是一个必须把版本号钉死的库。

直接使用 TRL 的最小写法是这样的。我把版本锁定和损失掩码检查一并放了进去。

# pip install "trl==1.9.2" "transformers==5.14.1" "peft==0.20.0" "accelerate==1.14.0"
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

ds = load_dataset("json", data_files="data/sft-train.jsonl", split="train")

cfg = SFTConfig(
    output_dir="out/sft-7b",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    learning_rate=1e-5,
    num_train_epochs=2,
    bf16=True,
    gradient_checkpointing=True,
    max_length=4096,
    packing=False,              # 在亲自验证掩码之前先关掉
    logging_steps=10,
    save_steps=200,
    report_to="mlflow",
)

trainer = SFTTrainer(
    model="meta-llama/Llama-3.1-8B",
    args=cfg,
    train_dataset=ds,
    peft_config=LoraConfig(r=32, lora_alpha=64, lora_dropout=0.05,
                           target_modules="all-linear", task_type="CAUSAL_LM"),
)

# 开始训练前,先用肉眼确认一个 batch
batch = next(iter(trainer.get_train_dataloader()))
ids, labels = batch["input_ids"][0], batch["labels"][0]
tok = trainer.processing_class
print("完整内容:", tok.decode(ids[:200]))
print("损失作用对象:", tok.decode([i for i, l in zip(ids, labels) if l != -100][:200]))

trainer.train()

最后五行是这段脚本里最重要的部分。被标记为 -100 的位置会从损失中排除,而模板哪怕只错位一点点,就会导致损失落在整个 prompt 上,或者相反——把整个回复都排除在外。两种情况下损失曲线看起来都会像模像样地下降,所以往往要等训练结束、开始评估时才会被发现。

Axolotl 把模型、数据集、并行化、LoRA 配置全部塞进一份 YAML 里。它最大的价值是能自动帮你把多种数据格式做好 tokenize,底层则在 Accelerate 和 DeepSpeed/FSDP 之间选择使用。

# axolotl==0.18.0 基准。accelerate launch -m axolotl.cli.train config.yaml
base_model: meta-llama/Llama-3.1-8B
load_in_4bit: false
strict: false

datasets:
  - path: data/sft-train.jsonl
    type: chat_template
    field_messages: messages

sequence_len: 4096
sample_packing: true
gradient_checkpointing: true
bf16: auto

adapter: lora
lora_r: 32
lora_alpha: 64
lora_dropout: 0.05
lora_target_linear: true

micro_batch_size: 4
gradient_accumulation_steps: 8
num_epochs: 2
learning_rate: 1e-5
warmup_ratio: 0.03
lr_scheduler: cosine

deepspeed: deepspeed_configs/zero3_bf16.json
output_dir: ./out/axolotl-8b

LLaMA-Factory 在类似定位上加了一个 Web UI 和更广泛的模型支持,Unsloth 则是靠 kernel 优化来提升单 GPU 微调的速度和内存表现。Unsloth 的多 GPU 支持范围随着发行形态一直在变化,写这篇文章时我没能确认它当下的确切政策——如果需要多 GPU,请在引入之前直接查阅仓库文档确认。

大规模强化学习那一侧是完全独立的谱系。verlOpenRLHF 采用的结构是把 rollout 引擎(通常是 vLLM 或 SGLang)和训练引擎(FSDP 或 Megatron-Core)拆开、再挂接在一起。如果打算在数千张 GPU 的规模上跑 GRPO 系的训练,应该看的是这条谱系,而不是 TRL。

一条消失的谱系告诉我们什么 —— torchtune 与 torchforge

这个领域里为什么选框架是件有风险的事,有一个案例把这一点说得很清楚。

torchtune 最初以 PyTorch 原生微调库的身份起步,获得了不错的评价。可到了 2025 年 7 月,仓库的 issue #2883 宣布活跃开发已经停止,PyPI 上最后一次发布停在 0.6.1,时间定格在 2025 年 4 月 7 日。作为继任者,torchforge 被发布出来。可截至 2026 年 8 月 2 日,torchforge 仓库最上方挂着这样一条横幅。

Development paused: Development in Forge has paused. LLM training at PyTorch is being consolidated in torchtitan.

也就是说,继任者的继任者,现在也正在被吸收。从这段历史里该拿走的教训,不是指责某个具体的库,而是这样一个事实:上层框架的预期寿命,比它下面的那一层更短。 PyTorch 分布式 API 和 Megatron-Core 已经存活了好几年,可它们之上那层便利工具,却按大约两年一个周期被推倒重来。

所以实务规则就变成了这样:项目真正的核心资产,不应该放在框架的配置文件里,而应该放在数据管线、评估套件、checkpoint 格式这三样东西上。把这三样放在框架之外,哪怕上层消失了,换一个就是。

微调路径与对齐阶段 —— 当下的叫法

决定训练什么,有两个维度。一个是动哪些权重,另一个是用什么信号来训练。

路径训练对象大致内存开销什么时候用
Full fine-tuning全部权重每参数 16 字节 + 激活领域本身差异很大时、数据充足时
LoRA仅低秩适配器基座模型 2 字节 + 适配器状态大多数实务微调场景
QLoRA4-bit 基座 + 适配器基座约 0.5 字节 + 适配器需要在单卡上处理大模型时

LoRA 系列的各种变体(DoRA、rsLoRA、LoRA+、PiSSA 等)一直在被 PEFT 吸收进去。哪个变体出现在哪个 PEFT 版本里,每个 release 都不一样,请查文档;实务中首先该调的旋钮不是选哪个变体,而是 rank、目标模块,以及学习率。把 QLoRA 用到真实生产服务上的过程,单独整理在《QLoRA 生产环境微调》里;而微调本身是不是正确的选择,最好先看一眼《RAG 与微调、提示工程的比较》

对齐阶段目前的叫法是这样的。

  1. 有监督微调(SFT)。 用指令-回复对来定格式和风格。体感质量的大部分在这一步就已经定型。
  2. 偏好优化。 分为直接使用偏好对的离线一派(DPO 及其变体),和从策略中采样、在组内计算优势的在线一派(GRPO、RLOO)。TRL 里两种 trainer 都有。
  3. 基于可验证奖励的强化学习。 使用数学答案是否一致、测试是否通过这类可按规则打分的奖励。因为不使用学习出来的奖励模型,奖励黑客(reward hacking)的攻击面很窄,已经事实上成为训练推理能力的标准做法。

新名称还在不断增加。每次冒出一个新缩写,值得问的问题只有三个:奖励从哪来,需不需要 rollout,要不要把一个参考模型常驻在显存里。这三个问题定下来,需要多少张 GPU 也就随之定了。

上层框架反而碍事的时刻

便利层大多数情况下都是净收益。但在以下场景中,成本会超过收益。

  • 需要改损失函数或掩码的时候。 哪些 token 承担损失,是后训练质量的核心旋钮,而上层框架把这一点藏在了数据格式背后。要确认损失掩码实际落在了哪里,只能把一个 batch 解码出来亲眼看。不做这个检查就白烧好几天的事故最常见。
  • 并行化组合超出标准范围的时候。 比如同时开启上下文并行和专家并行这类组合,上层框架的配置 schema 往往表达不出来。
  • 需要调试的时候。 要找到损失尖峰的原因,得逐 step 查看梯度范数、特定层的激活统计、优化器状态。如果能靠回调打开这些信息,算你走运;打不开的话,就得去 fork 这个框架了。
  • 版本无法锁定的时候。 好几个上层工具各自要求同一个底层库的不同版本,这种情况真实存在。除了自己构建容器镜像、提交 lockfile,没有别的办法。
# 开始训练前,至少应该把这些记进日志
python - <<'PY'
import importlib.metadata as md
for p in ("torch", "transformers", "trl", "peft", "accelerate",
          "deepspeed", "flash-attn", "datasets"):
    try:
        print(f"{p:14} {md.version(p)}")
    except md.PackageNotFoundError:
        print(f"{p:14} (未安装)")
PY
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv
python -c "import torch; print('nccl', torch.cuda.nccl.version())"

结语 —— 选一层,但别被困在那一层里

选技术栈的标准不是"哪个最好",而是"我们愿意亲自为哪一层负责"。要做预训练,就得下沉到 Megatron-Core 或 PyTorch 分布式 API 这一层;如果只是用内部数据做微调,停在 TRL 或 Axolotl 之上就是合理的选择。

但无论选哪一层,数据管线、评估套件、checkpoint 格式都应该放在框架之外。从 torchtune 到 torchforge、再到 torchtitan 这两年的历程,说的正是这件事。框架是可以更换的零件,而把它设计成"可以被更换"才是真正的设计工作。并行化本身的原理整理在《多 GPU 训练的四种并行方式》里。

현재 단락 (1/148)

"该用哪个框架训练"这个问题没有单一答案,因为大家所在的层不一样。用 `torchrun` 自己写的循环,和一份 YAML 就能跑起来的微调,不是竞争关系,而是上下级关系。

작성 글자: 0원문 글자: 9,520작성 단락: 0/148