# 33 - 微调显存优化与多卡训练
---
**本章课程目标:**
- 能根据报错发生的阶段,选择第一步检查,分清权重、批次计算与参数更新的显存压力。
- 能调整常见单卡设置,说明省下哪部分显存、可能付出什么代价。
- 能解释多卡的分工与 ZeRO 的分片对象,按实际数据并行份数计算有效批次。
- 能用相同工作量下的显存与耗时记录,决定继续训练、调整方案还是停止试验。
**本章围绕一个问题展开:资源不够时,先查什么,怎样调整才值得?** 第 1~2 节先解决常见单卡问题;第 3~4 节理解多卡怎样协作;第 5 节把这些知识用于试跑和选择。
首次学习要能说清排查顺序,并读懂第 2.6 节的真实资源对照。多卡部分先掌握“各张卡分别做什么、存什么”,不要求立即配置多卡。量化、DeepSpeed 和大模型试跑的完整操作按需展开,没有相应硬件也可以用课程记录完成判断练习。
---
## 1、训练资源问题的判断
### 1.1 从小模型练习到实际任务
前几章使用 Qwen3-0.6B 完成关键词抽取任务,学会了数据准备、训练和验证。现在继续用这个任务,考虑输入更长或模型更大时的资源需求。
假设现在要处理更长、专业词汇更多的文章。按第 32 章的方法检查后,发现小模型仍经常漏掉主题,就可以继续比较更合适的候选模型。
但这里有两件事需要分开:
- **回答是否更好:** 用未参与训练的输入检查,比较关键词内容和格式。
- **当前机器能否训练:** 看模型是否能加载、训练是否报错,以及需要多长时间。
本章集中解决第二个问题:判断资源是否够用,以及怎样调整。例如,模型已经加载,处理第一批文章时才报显存不足,先检查 batch 和实际长度;如果连权重都没加载完,就换一个排查方向。下面先把这两种情况分清,回答质量继续按第 32 章的方法比较。
### 1.2 区分加载失败、训练报错与速度过慢
启动训练后,程序要先加载模型、读取和处理数据,然后才进入计算损失、计算梯度、更新参数的训练过程。
同样一句“跑不动”,发生的位置不同,处理办法也不同:
| 看到的现象 | 先判断什么 | 优先检查 |
| ---------------------------------- | -------------------------- | ------------------------------------------ |
| 模型权重还没加载完,就出现显存不足 | 模型或加载过程是否放得下 | 模型路径、加载精度、GPU 已有占用 |
| 权重已加载,处理训练批次时显存不足 | 同时参与计算的数据是否太多 | 单卡 batch、实际序列长度、中间计算结果 |
| 前向和反向计算后,在参数更新时报错 | 更新参数所需空间是否不足 | 优化器状态、训练方式,必要时考虑卸载或分片 |
| step 持续增长,但每一步都很慢 | 单位时间实际完成了多少工作 | 样本数、速度、数据准备与卡间通信 |
| 训练结束,关键词仍不合要求 | 这不是单纯的显存问题 | 数据质量、模板、Adapter 加载与验证方法 |
**OOM** 是 _out of memory_ 的缩写,意思是内存不足。这里看到的 `CUDA out of memory`,通常指 **GPU 显存**不够,不是硬盘满了。
第 31 章的 AutoDL 页面同时显示显存、系统内存和数据盘容量。它们分别用于 GPU 计算、CPU 侧运行和文件存储,不能直接互相替代。增加数据盘容量能多存模型文件,但不会让显卡多出计算空间。
### 1.3 先检查已有占用
假设一张 32 GB 显存的卡,另一个模型服务已经占用了其中一部分。此时训练报错,不一定说明目标模型本身无法在这张卡上训练。
在 AutoDL 的 JupyterLab 中另开一个终端,执行:
```bash
nvidia-smi
```

按从上到下的顺序看这张图:
1. **GPU 型号:** `Tesla V100-PCIE-32GB`,先确认程序看到的是哪张卡。
2. **Memory-Usage:** `0MiB / 32768MiB`,斜线前是此刻已用的显存,后面是总容量。
3. **Processes:** `No running processes found`,表示这次检查没有列出正在使用 GPU 的进程。
这张图展示空闲状态。如果自己的页面已有明显占用,就继续核对它来自哪个任务。
容器中有时会出现“显存已有占用,但进程列表没有列出任务”的情况。因此还要结合上方的显存读数和训练日志判断,不能仅凭 `No running processes found` 就认定 GPU 空闲。
若之前在 LLaMA-Factory 的 **Chat 页**加载过模型,先点击该页的 **“卸载模型”**。仍在运行的训练与推理服务,要先确认是否可以结束,再按[第 5.6 节](33-微调显存优化与多卡训练.md?id=_56-停止任务、关机与文件保存)选择对应的停止方法;不能只关闭网页。
排除额外占用之后,再调整训练设置。这样才能分清,是同一张卡上任务太多,还是当前训练本身确实需要更多显存。
---
## 2、单卡显存排查与优化
第 30 章已经介绍过:训练显存中有模型权重,也有梯度、优化器状态和中间计算结果。现在把这些概念用于排查:

图中的优化项不是要求全部打开。先找出占用来自哪里,再选择对应的方法。
### 2.1 单卡 batch 与梯度累积
沿用第 31 章的配置,单卡 batch 为 4,梯度累积为 8。

在截图下方找到 **批处理大小**和**梯度累积**。它们分别对应配置中的 `per_device_train_batch_size` 和 `gradient_accumulation_steps`;本节先只看这两个输入框,不重新讲其他训练参数。
可以先用文章数量理解:每次处理 4 篇文章,连续处理 8 批以后,再更新一次参数。因此一次更新合计使用:
```text
4 篇 / 批 × 8 批 = 32 篇
```
现在假设换了一个更大的模型,**权重已经加载,但第一批训练出现 OOM**。可以先把 batch 从 4 改为 2,让一次同时参与计算的文章少一些。
若这样能够继续训练,再把梯度累积从 8 改为 16:
| 设置 | 每次同时处理 | 更新前累计处理 | 有效批次 |
| ---------------- | ------------ | -------------- | -------- |
| 调整前 | 4 篇 | 8 批 | 32 篇 |
| 只降低 batch | 2 篇 | 8 批 | 16 篇 |
| 同时补足累积次数 | 2 篇 | 16 批 | 32 篇 |
**减少 batch 是为了降低一次计算的占用;增加累积次数,是为了保持每次更新所用的数据量。** 这是两个作用,不要把显存下降归因于梯度累积次数变大。
实际调整时,先把“批处理大小”改为 `2`;需要保持 32 篇的有效批次时,再把“梯度累积”改为 `16`。点击“预览命令”,核对这两个字段,并为试跑使用新的输出目录。上图展示的是调整前的设置。
有效批次相同,也不表示速度与训练结果完全相同。处理批次的次数、计算顺序等已经变化,仍要观察日志和验证表现。
如果权重本体就放不下,batch 从 4 改成 1 也不会把权重变小,应先处理流程图中的加载分支。
### 2.2 长度上限与实际训练长度
接着上面的例子:如果 batch 降到 2 后仍然报错,就检查长样本,再判断是否需要调整截断长度(`cutoff_len`)。
先回顾第 29~30 章的一点:训练序列包含模板、用户内容和参考答案,要按它们合计产生的 token 数判断长度。**长度上限只是最多保留多少,不等于每条样本都已经这么长。**
先看第 30 章已经得到的预处理检查结果:

截图上方的 `train`、`validation` 两行显示:训练集 1,600 条,最长 `957` token;验证集 200 条,最长 `604` token。`truncated=0` 表示本次检查没有截断样本。
这份结果使用 Qwen3-0.6B、`qwen3_nothink` 模板和当前数据;具体方法见[第 30 章“检查实际训练长度”](30-模型训练原理与高效微调.md?id=_42-检查实际训练长度)。
同一批次的样本通常需要整理成相同长度。例如,两条样本分别有 200 和 500 个 token,按批次最长样本**填充(padding)**时,会在短样本上补 300 个占位,让两条都按 500 个位置组织计算;占位不增加文章内容。
在这个例子中,长度上限从 2048 改成 1024,两条样本仍保留原内容,填充后的批次也仍是 500 个位置。当前课程数据最长只有 957 token,同样不能仅凭上限减半就推断显存减半。
若把上限降到 512,则可能截掉长文章的后半段或参考答案。调整前要检查保留下来的训练内容;换模型、分词器或数据后,重新统计实际长度。任务确实需要长文章时,继续考虑下面的显存方案。
### 2.3 混合精度与梯度检查点
前面两项调整的是“每次放进多少数据”。接下来两项,调整的是“计算时怎样表示数值、保存多少中间结果”。
**混合精度:**
混合精度让适合的运算使用较低精度,必要部分仍保留较高精度,原理见[第 30 章](30-模型训练原理与高效微调.md)。本课 V100 基线已使用 `fp16: true`、`bf16: false`,梯度检查点也已启用;排查时先核对现状,不能把已经打开的选项当成新优化。换环境再按第 31 章的 GPU 实算检查确认适用精度。
**梯度检查点:**
反向传播要用到前面计算的中间结果。梯度检查点选择少保存一些,需要时重新计算,以额外计算换取显存空间。

跟着图中的 `a2` 看:不使用这项方法时,把它保存下来,反向时直接取用;采用图示的保存方式后,需要 `a2` 时,就用保留的 `a1` 重算第 2 层。省下的是部分中间结果的存储,代价是多做一次相应的计算。
课程已有训练日志中出现了:
```text
Gradient checkpointing enabled.
```
这条日志可用来确认梯度检查点已启用。它负责减少计算中间结果的存储;每隔 50 步保存的 `checkpoint` 文件则用于训练存档。
这两种方法不必同时改变。混合精度在合适硬件上还可能提速;梯度检查点通常需要增加重算,实际速度要看运行结果。
### 2.4 CPU 卸载:用系统内存分担显存
AutoDL 实例除了 GPU 显存,还有 CPU 使用的系统内存。**CPU 卸载(CPU offload)**就是让部分训练数据或状态不一直留在 GPU 上。
它适合分担选定状态或参数的占用,但搬运数据和 CPU 计算需要时间。先确认是哪部分放不下:LoRA 的可训练参数少,优化器状态可能本来就不大,仅卸载优化器不一定能解决基础权重过大的问题。
进一步理解:GPU 与 CPU 分别保存什么、计算什么
以优化器为例。第 30 章讲过,优化器会保存一些帮助后续更新的信息。在支持优化器卸载的方案中,这些状态可以放在 CPU 内存中,有些实现还会让 CPU 执行相应的更新计算。
下图以优化器状态和更新计算都卸载到 CPU 的方案为例:

读图时沿右侧的两条箭头走一遍:GPU 完成前向与反向计算,把需要更新的参数的梯度交给 CPU;CPU 利用优化器状态计算更新,再把更新后的参数传回 GPU,供后续计算使用。基础模型仍然参与 GPU 上的计算,并不是整套训练都搬到了 CPU。
在 LoRA 中,基础权重被冻结,优化器主要处理新增的可训练参数。因此,卸载优化器能省多少,取决于这部分原本占了多少空间;不能照搬全参数训练的节省比例。图示原理可对照 [DeepSpeed ZeRO-Offload 官方说明](https://www.deepspeed.ai/tutorials/zero-offload/)。
如果还启用了参数卸载,模型参数的存放与取用也会变化。后面介绍 DeepSpeed 时,会分别看到 **优化器卸载**和**参数卸载**,两者不能混写。
系统内存也有容量限制;CPU 处理和数据传输也需要时间。模型能放下以后,若每步明显变慢,就要判断这种取舍是否能接受,而不是只看显存数字变小。
### 2.5 从普通 LoRA 到 QLoRA(选做)
如果主要压力来自基础模型权重,可以考虑第 30 章介绍的 QLoRA:用低位数保存基础权重,仍训练新增的 LoRA 参数。
普通 LoRA 使用未量化的基础权重;QLoRA 增加量化加载环节,两者都训练新增的 LoRA 参数。
先掌握选择依据:基础权重占用大时,量化值得考虑;如果主要是长序列的中间结果放不下,还要继续处理长度和批次。4 bit 描述权重的存储方式,训练计算仍可以使用 FP16。
量化可能节省显存,也可能影响速度与输出质量。准备比较时,保持其他条件一致,分别记录资源与实际回答;安装、NF4 和双重量化字段在下面查阅。
选做实操:检查依赖、设置 4 bit 量化并准备试跑
**先检查量化依赖。**
在 AutoDL 的 LLaMA-Factory 目录中,进入第 31 章创建的 Python 环境:
```bash
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
uv pip show torch bitsandbytes
sed -n '1,20p' requirements/bitsandbytes.txt
```
`bitsandbytes` 是这里用于量化的依赖。若没有安装,只在准备选做量化实验时处理它,不需要为了普通 LoRA 提前安装。
可以先查看安装会带来哪些变化:
```bash
uv pip install --dry-run -r requirements/bitsandbytes.txt
```
若计划替换当前已验证的 PyTorch 或 CUDA 相关包,先停下来核对兼容性,不要直接升级。确认适合当前环境后,才执行实际安装:
```bash
uv pip install -r requirements/bitsandbytes.txt
uv pip check
```
安装完成后,还要用短训练检查量化加载、前向和反向计算是否正常。
**再对照页面与参数。**
先在模型设置区找到“量化等级”和“量化方法”:

上图仍是普通 LoRA 的设置:量化等级为 `none`。即使旁边显示了 `bnb`,也不表示已经启用了 4 bit 量化。
准备选做 QLoRA 时,保持模型、数据和 LoRA 设置,将量化等级从 `none` 改为 `4`,量化方法选择 `bnb`(即 bitsandbytes)。再预览命令,检查量化字段。该版本的相关配置可写成:
```yaml
# 量化相关设置片段,不是完整训练配置
# 微调方式:仍然训练 LoRA 新增参数
finetuning_type: lora
# 量化工具:bnb 是 bitsandbytes 的简称
quantization_method: bnb
# 量化位数:基础权重采用 4 bit 编码保存
quantization_bit: 4
# 量化类型:NF4,针对权重分布设计的 4 bit 表示方式
quantization_type: nf4
# 双重量化:进一步量化缩放因子等辅助数据,减少存储开销
double_quantization: true
# 计算精度:本次使用 FP16 混合精度,不启用 BF16
fp16: true
bf16: false
```
其中 `nf4` 与双重量化沿用该版本的默认设置;预览命令可能省略未改动的默认值。
`4 bit` 说的是权重存储方式;`fp16` 说的是相应训练计算的精度设置。它们不是二选一,也不表示所有计算都使用 4 bit。
为 QLoRA 练习另设输出目录,例如 `saves/Qwen3-0.6B-Thinking/lora/keywords-qlora-trial/`。此路径由训练程序创建,不能当作已经包含权重的模型目录。
比较普通 LoRA 与 QLoRA 时,尽量保持模型、数据、batch、长度和累积次数相同,再看显存与速度。**量化可能节省显存,但不保证更快,也不保证回答质量完全不变。**
配置字段可核对[本课程版本的量化参数定义](https://github.com/hiyouga/LLaMA-Factory/blob/dced5f8804bfbf7109ef7c14401db6bd5cce7e53/src/llamafactory/hparams/model_args.py);硬件要求见[bitsandbytes 官方说明](https://huggingface.co/docs/transformers/quantization/bitsandbytes)。
### 2.6 怎样判断调整是否有效
权重已加载、第一批才报 OOM 时,先降低 batch,观察能否通过原来的报错位置;需要保持有效批次,再增加累积次数。仍报错就继续检查实际长度和已有优化。每次只围绕一个问题调整,才能知道哪项改变起了作用。
自己试跑时:监控显存并覆盖容易出问题的阶段
观察 GPU 使用情况时,在 **AutoDL 的 JupyterLab 中另开一个终端**,不要占用正在运行训练的终端,执行:
```bash
watch -n 1 nvidia-smi
```
这表示每秒刷新一次。在这个监控终端按 `Ctrl+C` 只会退出 `watch`,不会停止另一个终端中的训练。
还要继续留意:
- 更长的样本进入批次时,会不会重新报错;
- 验证开始后,占用是否变化,验证 batch 是否需要单独调整;
- 首次更新参数、保存检查点时,是否出现新的问题。
**实际对照:batch 减半以后,发生了什么?**
下面两组使用同一张 V100 32 GB、Qwen3-0.6B 和 `keywords-clean` 数据,都做普通 LoRA 训练,其他设置保持一致。只把 batch / 累积从 4 / 8 改成 2 / 16,再比较相同工作量下的结果:
| 配置 | 全程最高采样显存 | 整次命令耗时 | 运行结果 |
| ---------------- | -------------------------- | ------------------------ | ------------------------------------------------------ |
| batch 4,累积 8 | 28,588 MiB(约 27.92 GiB) | 300.54 秒,约 5 分钟 | 出现过显存分配告警,随后正常结束并保存 `checkpoint-50` |
| batch 2,累积 16 | 22,230 MiB(约 21.71 GiB) | 560.27 秒,约 9 分 20 秒 | 正常结束,保存 `checkpoint-50` |
两组都完成 50 个更新步,有效批次都是 32,相当于各处理 1,600 条训练样本。耗时包含加载、训练、验证和保存;显存是全程每 200 毫秒采样的最高读数,可能漏掉瞬时高点。
把两组数字相减,可以看清这次调整的代价:**batch 2 的最高采样占用减少了 6,358 MiB,约 6.21 GiB;整次命令则多用了 259.73 秒,约 4 分 20 秒。**
核对证据:两组原始日志、截图与计时差异
两组使用第 29 章的 `keywords-clean` 数据:1,600 条训练样本、200 条验证样本。模板为 `qwen3_nothink`,长度上限为 2048,计算精度为 FP16,LoRA rank 为 8、alpha 为 16,学习率为 `5e-5`。模型、数据和其他设置保持一致,各自从原始模型新建 Adapter,使用不同的输出目录。
先看第一组实际保存的日志。找到 `Instantaneous batch size`、`Gradient Accumulation steps` 和 `Total train batch size`,分别是 **4、8、32**:

图中 `train_runtime: 258.4547` 是 Trainer 自身记录的运行时间,`Num Epochs = 1` 对应这次 50 步短跑。正文表格以 `300.54 秒` 记录从命令启动到退出的整体耗时,包含 Trainer 计时范围之外的工作,两者的起止范围不同。
再看第二组运行时的截图。三个位置变成 **2、16、32**,也就是说,每次同时处理的文章少了,但更新一次参数前仍累计处理 32 篇:

两组都运行 **50 个更新 step**:`50 × 32 = 1,600` 条,恰好练完当前训练集一轮;在第 50 步验证并保存 `checkpoint-50`。配置中的 `max_steps: 50` 优先于 `num_train_epochs: 3`,这次对照按 50 步结束。
**统计口径:** 耗时包含命令启动后的加载、训练、验证和保存;表中显存取同一范围内每 200 毫秒采样的最高读数,可能漏掉采样间隔中的瞬时高点。第二张图里的 `16066MiB` 只是截图当时的占用。两组使用相同的 Trainer 内存统计设置;其分阶段指标与这里的整卡显存读数分别记录。
batch 4 的日志在第 30 步附近出现过一次约 2.17 GiB 的显存分配失败,随后仍继续到第 50 步并正常退出。这说明它虽然完成了任务,运行中仍遇到过显存压力。
为什么同样处理 1,600 篇文章,小 batch 仍可能更慢?每次更新都累计 32 篇,但训练时执行的小批次数量变了:
```text
batch 4、累积 8:50 次更新 × 8 批 = 400 个小批次
batch 2、累积 16:50 次更新 × 16 批 = 800 个小批次
```
第二组每个小批次只处理一半文章,却需要执行更多次。单批更省显存,有效批次相同也不保证速度相同;实际耗时还受计算效率、加载、验证和保存影响。
如果 batch 4 在自己的任务中能稳定运行,可以保留它的速度优势;若已出现告警,或接下来需要处理更长文章,就应考虑是否值得用额外时间换取显存余量。这里的差值只属于本次模型、数据和环境,换任务后仍需重新比较。
需要复算资源对照时,从[配套文件说明](案例与源码-4-微调/README.md)进入 `results/batch-comparison/`,查看 `comparison.json`、配置、日志和采样数据。这里保留资源统计、日志和曲线,不含 Adapter 权重或完整续训检查点;自己训练后应检查实际输出目录中的文件。
两组验证 Loss 都约为 2.171,关键词质量仍需按第 32 章检查实际回答。资源记录到这里应得出一个选择:**自己需要多少显存余量,愿意为此多花多少时间。** 不要求照搬本例的某一组设置。
选做:在原 V100 实例复现这组对照
先按第 31 章准备环境、模型和当前课程数据,确认没有其他 GPU 任务。在 **AutoDL 的 JupyterLab 终端**中准备配置目录:
```bash
cd /root/autodl-tmp/LLaMA-Factory
source .venv/bin/activate
mkdir -p configs
```
然后在 JupyterLab 文件面板中上传两份本机文件:
| 本机 `案例与源码-4-微调/` 中的文件 | 上传到 AutoDL 的位置 |
| ----------------------------------- | ------------------------ |
| `compare_batch_resources.py` | `LLaMA-Factory/` 根目录 |
| `configs/keywords_clean_train.yaml` | `LLaMA-Factory/configs/` |
第 31 章走 WebUI 路线时,可能没有上传过这份 YAML;不能只传脚本就直接运行。这里使用课程附带的 CLI 配置,不使用 WebUI 的“保存训练参数”文件。同名文件已经存在时,先检查是否还是课程配置,不覆盖自己改过的版本。
脚本固定检查 `model_name_or_path: Qwen/Qwen3-0.6B`,并按仓库 ID 查找 ModelScope 缓存;不能直接换成本地路径或 8B 模型。若前面移动过默认缓存,先确认仓库 ID 仍能找到模型,否则可能重新下载。
回到刚才的 AutoDL 终端,核对文件和配置:
```bash
ls compare_batch_resources.py configs/keywords_clean_train.yaml
ls data/keywords-clean/keywords_train.jsonl data/keywords-clean/keywords_validation.jsonl
sed -n '1,100p' configs/keywords_clean_train.yaml
```
全部准备好,且确实要启动两组训练时,再执行:
```bash
python compare_batch_resources.py \
--config configs/keywords_clean_train.yaml \
--output-root /root/autodl-tmp/keywords-batch-trial \
--steps 50
```
`--output-root` 必须是尚不存在的新目录。脚本会核对单张 V100、模型、精度和课程数据指纹,再依次启动两组训练,不会安装依赖或修改原训练配置。每组命令最多运行 10 分钟;失败或超时后停止,不自动重试。**这个限制不会替你关闭 AutoDL 实例**,仍要按第 5.6 节保存结果并关机。
输出中的 `comparison.json` 汇总结果;两组各有 `config.yaml`、`train.log` 和 `gpu-samples.csv`。采样文件保存时间、显存占用(MiB)、GPU 利用率(%)和功率(W),统计口径见上表说明。
---
## 3、多 GPU 的分工方式
单卡调整仍不够,或希望缩短训练时间时,才需要进一步考虑多卡。先理解三种分工:数据并行分文章,张量并行分同一层的计算,流水线并行分模型层。
这一节读完,能用文章例子解释三者,并判断增加卡数是否解决当前问题即可。阅读图时始终问:**两张卡收到的是不是同一批文章?卡之间传递的是什么?**

_图:三种并行的分工示意。张量并行与流水线并行面板只画前向数据流。_
### 3.1 数据并行:同一个模型处理不同文章
假设单卡已经能训练,只是速度不够快。现在有两张 GPU,可以让它们同时处理不同文章。
先让两张卡各自拥有同样的模型,再这样分工:
```text
GPU 0:处理文章 1~4 → 算出这批文章的梯度
GPU 1:处理文章 5~8 → 算出另一批文章的梯度
↓
汇总梯度,再更新两边的参数
```
第 30 章把梯度解释为参数更新所需的方向信息。这里要汇总,是因为两张卡练习的文章不同;如果各自更新、从不交流,模型参数就会逐渐不同。
在常见的分布式数据并行训练中,两边同步梯度,并按一致的规则更新,共同训练**同一个模型**。
这种分工叫**数据并行(Data Parallelism,DP)**;常见的实现之一叫 **DDP**,即分布式数据并行。
**普通 DDP 中,每张 GPU 仍要放下一份模型。** 因此,两张 24 GB 的卡不会自动变成一张可任意使用的 48 GB 显卡。若单卡连基础权重都放不下,单纯增加数据并行副本不能解决它。
### 3.2 张量并行:一起计算同一层
第 30 章用矩阵解释过模型层的计算。张量并行(Tensor Parallelism,TP)不是让两张卡各读四篇不同文章,而是把**同一层的一次计算**分给它们。
例如,一层需要算出若干组输出特征,可以让 GPU 0 算其中一部分,GPU 1 算另一部分,再按切分方式拼接或汇总结果,继续下一层。
可以把差别说成两句话:
- 数据并行:你处理这几篇,我处理另外几篇,更新前交流。
- 张量并行:我们一起处理这份输入,你算这一部分,我算另一部分。
一层的计算分开以后,相关权重也能分散存放。但每层之间往往需要交换结果,通信会影响速度。
所以 TP 不只是“多选几张卡”。模型结构、训练框架和配置都要支持这种切分,不能把 LLaMA-Factory 中显示的 GPU 数量直接当作 TP 数量。
### 3.3 流水线并行:按模型层接力
流水线并行(Pipeline Parallelism,PP)采用另一种拆法:把前面若干层放在 GPU 0,后面若干层放在 GPU 1。
一份输入先经过 GPU 0,再把中间结果传给 GPU 1。这就像两道连续的加工步骤,后一道必须等前一道给出结果。
按层接力能分担模型,但后面的卡需要等待前面的结果。实际训练会交错安排多份输入来减少等待,具体时间轴见下图;不能只按 GPU 数量估算加速比例。
进阶阅读:微批次怎样减少等待,什么是流水线气泡
为了减少等待,可以把一个批次再拆成几个小份,让不同 GPU 交错处理。这些小份常称为**微批次(micro-batch)**。下面把三份输入记作 A、B、C,对照两种安排:

先横着跟踪蓝色的 A:它始终先经过 GPU 0,再经过 GPU 1。再看交错接力的第 2 个时段:GPU 1 处理 A 的同时,GPU 0 已经开始处理 B,不必等 A 全部完成才接下一份。
开头和末尾仍有卡等待,这种空闲间隙常称为**流水线气泡**。图中假设每个阶段耗时相同,只画前向计算;实际训练还有反向传播和数据传输,不能直接把图中的 6 个时段变成 4 个时段当作训练加速比例。
### 3.4 多卡有效批次的计算
先计算最容易遇到的情况:两张卡做数据并行,每张卡一次处理 4 篇文章,累积 4 次后更新。
```text
有效批次 = 单卡 batch × 数据并行卡数 × 梯度累积次数
= 4 × 2 × 4
= 32 篇
```
它和第 31 章单卡 `4 × 1 × 8 = 32` 一样,每次更新合计使用 32 篇文章。
差别在于,两卡方案每次同时处理 8 篇,累积 4 次;单卡方案每次处理 4 篇,累积 8 次。两卡需要通信,所以耗时不一定减半。
如果两张卡使用 TP 合作计算同一份模型,它们处理的是同一批输入,不能再把样本数乘以 2。

图中三种安排都得到 32 篇,但只有数据并行那一行把组数乘以了 `2`。判断时数的是**独立处理不同文章的数据并行组**,不是机箱里装了几张显卡。
选读:数据并行与模型切分一起使用时,怎样计算?
假设有四张卡:GPU 0、1 用 TP 合作计算一份模型;GPU 2、3 合作计算另一份。两组处理不同文章。
这时数据并行组数是 2,而不是 4。若每组一次处理 4 篇,累积 4 次:
```text
有效批次 = 每组一次处理的样本数 × 数据并行组数 × 累积次数
= 4 × 2 × 4
= 32
```
TP、PP 分担同一批输入的模型计算,不能重复计入新样本数。后面的 ZeRO 则是在数据并行中分片训练状态,不要因此把数据并行组数误算成 TP 组数。
### 3.5 多卡不一定更快
多张卡协作,除了计算,还要交换数据。卡间连接速度、传输量、各卡工作是否均衡,都会影响训练耗时。
GPU 之间需要交换梯度、参数或中间结果。实例说明中的 NVLink 等互联信息,与这种交换能力有关。
先检查各张 GPU 是否按预期参与计算,再比较**相同工作量下的训练耗时**;具体检查方法见第 5 节。
也不要看到多张卡,就直接从 LoRA 改成全参数训练。全参数训练要为更多参数保存梯度和优化器状态,资源要求会明显改变。
若只是检查多卡是否能正常启动,先保持已有 LoRA 任务,调整必要的分布式配置。只有任务确实需要、显存和时间也能接受,才继续比较全参数方案。
选读:MoE 模型的总参数量与激活参数量
有些模型说明会同时给出“总参数量”和“每个 token 激活的参数量”。这是因为模型内部有多组“专家”,每次只选择其中一部分参与计算;这种结构叫 **混合专家模型(MoE)**。
可以先把它理解为:模型有许多可用的处理分支,但不是每次都经过所有分支。选择分支的过程通常称为路由。
“本次只用了部分专家”主要影响参与的计算量,并不等于训练时只需要保存这些专家的全部相关文件和状态。不能只按激活参数量估算整个模型的加载显存。
把不同专家分到不同设备,又会涉及专家并行。本课程的单卡关键词主线不使用这种配置。
---
## 4、DeepSpeed 与 ZeRO
前一节问“计算怎样分工”,这里问“训练中哪些内容不必每卡都保存一整份”。先读懂 Stage 1、2、3 的存储表;只有准备做相应训练时,再展开具体配置。
### 4.1 从重复保存到分片保存
前面介绍的普通 DDP,每张卡各有一份模型;训练时,还可能重复保存梯度和优化器状态。
假设 GPU 0、1 都保存着同样一套优化器状态。训练需要这套信息,但未必需要每张卡始终各存一整套。能不能每张卡负责其中一部分,需要时协作?
**ZeRO 就利用这种分片思路减少重复存储。** DeepSpeed 是支持这种训练方式的工具之一。
“分片”可以先理解为:把一套数据分成几份,由不同 GPU 分别负责。
第 30 章已经解释了权重、梯度和优化器状态。下表对照各个 Stage 分别拆分哪些部分。
### 4.2 ZeRO Stage 1、2、3
| 方式 | 模型参数 | 梯度 | 优化器状态 |
| -------------------------- | ------------ | ------------ | ------------ |
| 不使用 ZeRO 的普通数据并行 | 每卡完整保存 | 每卡完整保存 | 每卡完整保存 |
| Stage 1 | 每卡完整保存 | 每卡完整保存 | 分片保存 |
| Stage 2 | 每卡完整保存 | 分片保存 | 分片保存 |
| Stage 3 | 分片保存 | 分片保存 | 分片保存 |
可以按顺序阅读:Stage 1 先处理优化器状态;Stage 2 再处理梯度;Stage 3 连参数也分片。

_图:同一套训练状态在两卡间的存放示意;图中的“状态”指优化器状态,色块不表示真实字节比例。_
**Stage 3 为什么不是张量并行?**
Stage 3 会在计算需要时聚合相关参数,算完后再按框架安排管理它们;TP 则把同一层的计算分给不同 GPU。两者都可能让权重不再完整常驻一张卡,但分工方式不同。
分片也不是没有代价。参数和状态需要跨卡交流,尤其 Stage 3 要配合计算取用参数,通信方式与开销会变化;不能只看“3 比 2 大”就认为 Stage 3 总是最好。
还要注意两个限制:
- 激活值、临时缓冲区等不会因为使用 ZeRO 就全部按卡数等比例缩小。
- LoRA 只训练少量新增参数,本来就比全参数训练少很多可训练状态;Stage 1、2 的收益不能直接照搬全参数训练的示例。
如果显存已经够用,不需要为了使用更高 Stage 增加配置。若模型权重本身放不下,才更需要考虑 Stage 3 这类参数分片方案。
原理与参数聚合方式可参阅 [DeepSpeed ZeRO 官方教程](https://www.deepspeed.ai/tutorials/zero/)。
### 4.3 LLaMA-Factory 中的配置位置(选做)
页面上的 Stage 选择分片级别,offload 决定是否把相应状态或参数交给 CPU 内存分担,两者不是同一个开关。课程版本的页面提供 `none`、`2`、`3`;首次阅读无需记住 JSON 字段。
实际选做前,先确认可用 GPU 和依赖,再预览将要使用的配置。截图仅展示入口,多卡是否生效要看启动后的进程、设备日志和运行结果。
选做实操:页面入口、依赖检查与实际配置文件
回到第 31 章的 **Train** 页面,向下滚动至底部,在“开始”按钮下方找到“设备数量”“DeepSpeed stage”和“使用 offload”。

本课程采用的 LLaMA-Factory 版本中,页面项目如下:
| 页面项目 | 实际含义 |
| --------------- | ----------------------------------------------- |
| 设备数量 | 只读的数量框;实际可用设备还需通过 GPU 检查确认 |
| DeepSpeed stage | 下拉选项为 `none`、`2`、`3` |
| 使用 offload | 是否使用对应 Stage 的卸载配置 |
打开 Stage 下拉框,可以看到实际的三个选项:

`none` 表示不启用 DeepSpeed;本页提供 Stage 2、3 两种分片选项。
_截图用于辨认配置位置。界面中的“设备数量”不能替代运行环境检查;按第 31 章确认实际可用 GPU、显存与计算能力。_
**选做前检查依赖。** DeepSpeed 是额外安装的工具,先在当前 `.venv` 中检查:
```bash
uv pip show deepspeed
```
若未安装,先看项目内的依赖清单及预计安装变化:
```bash
sed -n '1,40p' requirements/deepspeed.txt
uv pip install --dry-run -r requirements/deepspeed.txt
```
该版本的依赖清单要求 `deepspeed>=0.10.0,<=0.18.4`。版本范围只是安装约束,还需要确认具体版本与 PyTorch、CUDA 兼容,再按清单安装:
```bash
uv pip install -r requirements/deepspeed.txt
uv pip check
```
这些依赖在实际选做时安装,阅读配置可直接继续;版本要求见[该版本依赖清单](https://github.com/hiyouga/LLaMA-Factory/blob/dced5f8804bfbf7109ef7c14401db6bd5cce7e53/requirements/deepspeed.txt)。
项目的 `examples/deepspeed/` 中提供了配置示例。打开 Stage 2 的普通配置和卸载配置,可以对照看出差别:
```bash
sed -n '1,160p' examples/deepspeed/ds_z2_config.json
sed -n '1,160p' examples/deepspeed/ds_z2_offload_config.json
```
后者的核心设置包括:
```json
{
"zero_optimization": {
"stage": 2,
"offload_optimizer": {
"device": "cpu",
"pin_memory": true
}
}
}
```
这只是说明卸载位置的片段,完整文件还包含批次、精度等设置。按 JSON 的层级逐项看:
| 配置字段 | 中文含义 | 本例作用 |
| ------------------- | ---------------- | ------------------------------------------------------------------- |
| `zero_optimization` | ZeRO 优化设置 | 将分片级别和卸载选项放在同一组中 |
| `stage` | ZeRO 分片级别 | `2` 表示对梯度和优化器状态分片;区别于训练配置中的 `stage: sft` |
| `offload_optimizer` | 优化器卸载设置 | 将优化器相关状态及更新计算安排到 CPU |
| `device` | 卸载目标设备 | `cpu` 表示使用 CPU 和系统内存 |
| `pin_memory` | 是否使用锁页内存 | `true` 表示使用不会被系统换出的内存页,方便 CPU 与 GPU 间的数据传输 |
因此,这组设置改变的是优化器的存放与计算位置,模型层并没有全部搬到 CPU。
Stage 3 的卸载配置还可包含 `offload_param`,即参数卸载。具体采用哪些字段,应打开实际配置确认,而不是把所有 offload 都理解成一种动作。
**准备选做时,按下面的顺序检查:**
1. 保持当前模型、数据和 LoRA 任务,确认可见 GPU 数量。
2. 按显存问题选择 Stage,每次调整一项优化设置,便于判断效果。
3. 在 LLaMA-Factory 的 **Train 页**点击「预览命令」,找到命令中的 `deepspeed` 配置路径,再按下面的方法打开它,核对 `stage`、卸载与批次设置。预览不会启动训练。
4. 启动后查看分布式进程和设备日志,确认确实使用预期的 GPU;再观察是否进入正常训练。
例如,Stage 2 + offload 的预览命令指向 `llamaboard_cache/ds_z2_offload_config.json`,就在 **AutoDL 终端**中打开这份文件:
```bash
cd /root/autodl-tmp/LLaMA-Factory
sed -n '1,160p' llamaboard_cache/ds_z2_offload_config.json
```
以自己预览命令中的路径为准,核对实际文件中的 `stage`、卸载与批次设置。点击 Train 页的「开始」后,WebUI 才按所选配置启动训练;使用 YAML 命令行时,按[官方分布式训练说明](https://llamafactory.readthedocs.io/zh-cn/latest/advanced/distributed.html)启动,并通过日志确认参与计算的 GPU。
选读:WebUI 配置文件的来源
本课程固定版本在启动 WebUI 时,于 `LLaMA-Factory/llamaboard_cache/` 中生成配置。Stage 2 的普通配置为 `ds_z2_config.json`,勾选 offload 后使用 `ds_z2_offload_config.json`;Stage 3 对应文件名中的 `z3`。
前面的 `examples/deepspeed/` 提供阅读示例,`llamaboard_cache/` 则存放 WebUI 命令实际引用的配置。生成与选择过程可查看[配置生成实现](https://github.com/hiyouga/LLaMA-Factory/blob/dced5f8804bfbf7109ef7c14401db6bd5cce7e53/src/llamafactory/webui/common.py)、[WebUI 初始化入口](https://github.com/hiyouga/LLaMA-Factory/blob/dced5f8804bfbf7109ef7c14401db6bd5cce7e53/src/llamafactory/webui/engine.py)和[页面源码](https://github.com/hiyouga/LLaMA-Factory/blob/dced5f8804bfbf7109ef7c14401db6bd5cce7e53/src/llamafactory/webui/components/train.py)。
**进行多卡实验时,先确认环境中有多张可用 GPU,并安装匹配的依赖。** 启动后核对分布式进程、参与计算的 GPU,以及实际显存和速度;页面选项只能表达配置,不能证明多卡训练已经生效。
### 4.4 训练方式改变后,输出也要重新检查
第 31 章保存的是 LoRA Adapter。使用 DeepSpeed 后,不能仅凭目录里出现了 `checkpoint`,就认定它和之前完全一样。
先看本次训练方式:
- **LoRA:** 关注新增参数的 Adapter,以及恢复训练需要的其他状态。
- **全参数训练:** 保存目标是完整模型的参数,不是只输出一份 LoRA Adapter。
- **ZeRO 分片检查点:** 可能包含分散保存的训练状态,不一定能直接作为普通模型目录加载。
保存 Stage 3 权重时:核对参数聚合设置
Stage 3 保存完整权重时,还会涉及聚合参数的设置,例如 `stage3_gather_16bit_weights_on_model_save`。是否启用、最终保存哪些文件,要结合所用框架与训练方式确认。
确定文件含义以后,按第 32 章做重新加载与回答检查;交付时需要能够完整加载的模型文件。
---
## 5、大模型试跑与结果判断
更换模型前,先安排一次范围明确的短跑,回答“能否运行、预计花多久、是否值得继续”。本节用 8B 候选模型作配置推演,用教学日志练习判断;没有随附这次 8B 训练的实测结果。
可以直接读准备表、日志与选择方法。决定试用 8B 时,再展开下载和配置说明;第 2.6 节的小模型资源数字不能直接套用。
### 5.1 准备一次范围明确的试跑
短训练首先回答:**当前模型与设置能否运行,完成计划大约需要多久。**
仍使用第 29 章的 `keywords-clean` 数据划分:训练用 `keywords_train`,过程中检查用 `keywords_validation`,不要把测试集混入训练,也不要每次试跑都重新随机划分。
先检查模型文件大小、下载位置和加载精度,再准备模型与数据。下载方法见[第 31 章“提前下载模型,不启动训练”](31-LLaMA-Factory环境搭建与微调实战.md?id=_51-模型与微调方式),无卡模式也可完成。大模型要按下面的文件大小检查剩余空间,并选择合适的下载位置。
配置可以以配套的 `案例与源码-4-微调/configs/keywords_clean_train.yaml` 为起点,在编辑器中另存实验副本,再调整:
| 要调整的位置 | 怎样处理 |
| ------------------ | ---------------------------------------------------- |
| 模型路径与模板 | 指向准备试用的模型;重新确认其分词器、模板和长度 |
| batch 与累积 | 从能够放下的 batch 开始,再算有效批次 |
| 训练方式 | 明确普通 LoRA、QLoRA 或全参数,不同时试多种方案 |
| 输出目录 | 使用新的试跑目录,不覆盖第 31 章训练结果 |
| 训练轮数或步数上限 | 写清这次只测短跑,还是观察计划中的完整训练后提前停止 |
先记住一条估算原则:**模型文件能存进磁盘、权重能放进显存、整个训练能运行,是三次不同检查。** 训练还需要中间结果和训练状态,不能用模型文件大小直接回答需要多少显存。
选做实操:8B 权重估算、下载位置与配置副本
例如,候选模型可以是同系列的 `Qwen/Qwen3-8B`。[官方模型说明](https://huggingface.co/Qwen/Qwen3-8B)列出的参数量约为 8.2B,[模型文件页](https://huggingface.co/Qwen/Qwen3-8B/tree/main)显示文件总量约为 16.4GB。下载前先用 `df -h / /root/autodl-tmp` 检查剩余空间,将新模型放在数据盘,还要为下载过程和训练输出留出余量;不能只看磁盘的总容量。
按每个参数占 2 字节粗算,仅这份模型权重就约占 15.3GiB。**这不是训练所需的全部显存**:还要放中间计算结果、LoRA 参数和训练状态,也不能直接沿用小模型的 batch。官方配置标注的权重精度为 BF16,而本课程 V100 的训练计算采用 FP16;下载完成后仍需核对实际加载精度,不能只凭页面勾选了 FP16 就认定所有权重都以 FP16 加载。[官方模型配置](https://huggingface.co/Qwen/Qwen3-8B/blob/main/config.json)。
决定下载时,在第 31 章的 `snapshot_download` 调用中改用候选模型 ID,设置 `cache_dir="/root/autodl-tmp/model-cache"`,让新文件落到数据盘。记下函数返回的具体模型目录,随后填入配置。下面只展示模型与输出位置:
```yaml
# 只列出模型与输出位置,不是可直接启动的完整配置
model_name_or_path: /root/autodl-tmp/model-cache/替换为下载返回的模型子目录
finetuning_type: lora
output_dir: saves/Qwen3-8B/lora/keywords-trial
```
把第一项替换为下载返回的模型子目录。再按第 2 节检查实际加载精度、重新统计 token 长度,并从能放下的小 batch 开始试跑;其余任务设置保持一致,便于判断模型变化的影响。
没有合适硬件时,可先完成配置阅读和下面的日志练习,理解怎样估算资源、识别报错和判断结果。
### 5.2 “五分钟”从哪里开始算
第一次启动可能花时间加载权重、准备缓存,然后才出现不断增长的训练进度。要估算训练速度,应从正常处理训练 step 后观察,不是从点击按钮或下载模型时开始计时。
可以先运行几分钟,看看:
- step 是否持续增长,有没有重复报错;
- 速度是否逐渐稳定;
- 显存采样值有没有明显逼近上限;
- 预计剩余时间是否在合理范围。
“五分钟”只是一个便于入门的短跑长度,不是可靠性保证。它可能还没遇到最长样本、验证阶段和第一次保存。
例如第 31 章每 50 步保存一次。如果短跑在第 30 步停止,目录中可能还没有可恢复的 `checkpoint-50`,此时就不能从这份尚未生成的检查点恢复训练。
若希望验证保存流程,应在允许的运行范围内观察实际保存,或在新的试跑配置中明确设置保存时机;这项改变也会增加保存开销。
### 5.3 读懂进度条与预计时长
下面是**教学示例**,不是本课程某个 8B 模型的实测成绩:
```text
30/150 [04:00<16:00, 8.00s/it]
```
先按位置拆开:
| 内容 | 怎样读 |
| ---------- | ------------------------------------ |
| `30/150` | 已完成 30 个更新 step,计划共 150 个 |
| `04:00` | 已运行约 4 分钟 |
| `<16:00` | 根据当前进度估计还需约 16 分钟 |
| `8.00s/it` | 平均每次进度迭代约 8 秒 |
这里讨论的是 Trainer 的更新进度条,不是模型下载或数据预处理的进度条。结合第 30 章的梯度累积,更新一步之前可能已经处理了多批数据。
按示例速度做粗略计算:
```text
剩余步数 = 150 - 30 = 120
预计剩余 = 120 × 8 秒 = 960 秒 = 16 分钟
预计总计 = 4 + 16 = 20 分钟
```

_图:先读进度和速度,再判断是否继续。所有时间均为教学算例,不是指定硬件的性能承诺。_
有些进度条显示 `it/s`,意思是每秒完成多少次,数值越大通常越快;`s/it` 则是每次花多少秒,数值越小通常越快。先看单位,不能只比较数字大小。
预计时间会受长短样本、验证、保存和运行波动影响。不要把刚启动时的一个估计写成最终训练耗时。
如果在试跑配置里直接设置 `max_steps: 30`,总进度就可能是 30 步。这时进度条估算的是这次短任务,不能把它的剩余时间当作三轮完整训练的剩余时间。
### 5.4 比较速度前,先比较工作量
“每步快了一倍”,可能只是每步处理的文章少了一半。
假设两种方案都能正常运行,得到下面这组**教学数据**:
| 方案 | 每次更新处理 | 每个更新 step 耗时 | 平均处理速度 |
| ---- | ------------ | ------------------ | ------------ |
| 甲 | 32 篇 | 8 秒 | 4 篇 / 秒 |
| 乙 | 16 篇 | 4 秒 | 4 篇 / 秒 |
乙的 step 时间更短,但单位时间处理的文章没有增加。若要完成相同的数据轮数,它还需要更多更新步。
因此,对比单卡与多卡、LoRA 与 QLoRA 时,至少先对齐数据范围、实际长度和有效批次,再看耗时。不能一边改 batch,一边仅用 `s/it` 宣称加速比例。
文章长短差异很大时,也可以参考工具报告的 `tokens/s`,即每秒处理多少 token;比较时仍要确认它是否包含填充位置、统计的是哪一段运行。
在同一份数据、同一套任务设置下,结合完整耗时与样本处理速度判断性能变化。
### 5.5 根据结果决定下一步
试跑后,把原配置与调整后的结果放在一起,比较报错是否消失、速度和显存怎样变化,再决定下一步。
[第 2.6 节的单卡对照](33-微调显存优化与多卡训练.md?id=_26-怎样判断调整是否有效)已经展示了一种取舍:batch 4 更快,batch 2 留下更多显存余量。选择时要结合自己的运行情况,判断增加的时间是否值得,而不是只选显存数字最小的一组。
不同结果,下一步也不同:
- **路径、数据或模板错误:** 先停止,回到第 29~31 章修正;暂时不用 GPU 就关机。
- **仍然 OOM:** 回到本章第 2 节,按报错阶段继续排查。
- **能运行但很慢:** 先按相同工作量比较,再判断是否需要调整资源方案;QLoRA 不应被直接当作提速开关。
- **能运行,预计时长也能接受:** 可以继续当前任务,或保存配置后安排完整训练。
- **训练完成但质量不够:** 按第 32 章检查回答,不把它简单归为“显卡不够好”。
短跑回答的是资源问题。完整训练后,还要用验证集选择方案;方案固定后,再用独立测试集比较结果。资源允许和质量合格,两项都不能省略。
若因改变模型、量化方式或数据而重新实验,应作为新任务启动;只有兼容的配置和完整恢复状态,才适合从检查点继续。不要把“加载 Adapter”与“恢复原训练进度”混为一谈。
把这个决定写入实验记录卡:这次瓶颈是什么、改了哪项、在相同工作量下多花或节省多少时间、还需验证什么。面试或向同事说明方案时,这些依据比罗列所有优化开关更能解释你的选择。
### 5.6 停止任务、关机与文件保存
先分清任务从哪里启动,再到对应位置停止:
| 当前运行的任务 | 到哪里操作 | 如何确认 |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| LLaMA-Factory WebUI 启动的训练 | 回到 **Train 页底部**,点击“开始”右边的红色 **“中断”** | 日志显示中断,训练进度不再增长;继续检查训练进程是否已退出 |
| 第 31 章 YAML 命令或本章对照脚本启动的训练 | 回到**启动它的 AutoDL 终端**,按 `Ctrl+C` | 命令结束并返回提示符,相关训练进程已退出 |
| Chat 页加载的模型 | LLaMA-Factory **Chat 页 → 卸载模型** | 页面提示卸载完成,再核对显存变化 |
| 后台 WebUI 或 vLLM 服务 | 按[第 31 章后台 WebUI](31-LLaMA-Factory环境搭建与微调实战.md?id=_43-后台运行(选读))或[第 32 章后台 vLLM](32-微调效果评估与模型部署.md?id=_64-后台运行与本机访问)的步骤,在 AutoDL 终端核对 PID 后停止 | 对应进程退出、监听端口关闭;加载过模型时还要检查显存 |

在看日志的 `tail -f` 终端、监控显存的 `watch` 终端或**本机 SSH 隧道终端**按 `Ctrl+C`,都不等于停止远端训练。训练结束后,可在 AutoDL 终端用 `ps -eo pid,ppid,args | grep '[l]lamafactory'` 查看相关进程,再用 `nvidia-smi` 核对占用;WebUI 服务本身仍在列表中,不代表训练还在运行,要看具体命令。
决定不继续以后,按下面的顺序收尾:
1. 保存当前配置、日志和报错;截图隐藏连接地址、账号等信息。
2. 按上表停止对应任务,确认训练进程已经退出。关闭网页本身不会停止训练。
3. 查看输出目录实际有哪些文件。只备份已经产生的日志、Adapter 或检查点,不假定短跑必然保存了模型。
4. 按第 31 章的方法下载到本机或其他独立存储,并确认备份可以读取。
5. 暂时不用 GPU 时,在 AutoDL 控制台关机并确认状态。
**停止训练**只结束训练任务,不会自动停止实例计费。**关机**与**释放实例**也不同:释放会清空实例本地数据,不能用来代替普通停机。
保存在同一实例数据盘中的文件,不算独立备份。若以后需要释放实例,应先核实备份,再按控制台提示操作;数据保留条件以 [AutoDL 官方说明](https://www.autodl.com/docs/instance_data/)及当前控制台为准。
---
**章节思考题:**
1. 加载模型时就 OOM,与处理长样本时才 OOM,可能对应哪些不同占用?为什么排查前还要看显卡上已有的进程?
**参考思路:** 加载阶段先看基础权重、加载精度、设备分配与已有占用;批次计算阶段重点看实际长度、单批样本数与中间结果。其他进程可能已占用显存,应先确认归属和用途,再判断当前任务需要怎样调整。减 batch 不会缩小基础权重本体,也不应直接结束用途不明的进程。
2. 模型能加载,但训练批次放不下;当前 batch=4、累积=8。怎样先减小单批压力并保持有效批次?长度上限是否也应该直接减半?
**参考思路:** 可先改为 batch=2、累积=16,保持有效批次 32,核对实际配置和运行结果。长度上限应根据实际 token 数、填充和截断判断;本课最长训练序列为 957,2048 改为 1024 不会自动让计算长度减半,改为 512 则可能丢掉文章或答案。先确认问题所在,再分别调整。
3. 梯度检查点、QLoRA 和 CPU 卸载分别适合缓解哪部分显存压力?各自需要付出什么代价?
**参考思路:** 梯度检查点减少保存的中间结果,代价是重算;QLoRA 低位量化基础权重,引入近似误差和相应计算、依赖要求;CPU 卸载把选定状态或参数移到系统内存,可能增加传输与 CPU 开销。优化器卸载与参数卸载的对象不同,具体收益要看哪部分占用最大,量化后也仍有激活和可训练参数的开销。
4. 数据并行、张量并行和流水线并行,分别怎样安排文章与模型计算?为什么增加 GPU 数量之前要先分清这三种方式?
**参考思路:** 普通数据并行让模型副本处理不同样本,张量并行让多个 GPU 分担同一层计算,流水线并行让不同设备负责不同层并接力。数据并行不自动解决单份模型权重放不下的问题;后两种还涉及模型支持和通信安排。先明确要提高吞吐还是分担模型与计算,再选择分工。
5. 两张卡做数据并行,每卡 batch=4、累积=4,有效批次是多少?两张卡用 TP 合作处理同一批 4 篇文章、仍累积 4 次,又是多少?
**参考思路:** 前者为 4×4×2=32,后者为 4×4=16。有效批次计入的是不同样本的份数,不能把 TP 合作处理的相同输入重复计算。混合并行时要按实际数据并行组数计算,不能直接乘全部 GPU 数量。
6. ZeRO Stage 1、2、3 分别分片什么?Stage 3 与 TP 有什么区别,为什么不必总选最高阶段?
**参考思路:** Stage 1 分片优化器状态,Stage 2 再分片梯度,Stage 3 再分片参数。ZeRO 组织训练状态的分片与按需获取,TP 拆分同一层的计算。更高阶段可能带来更多通信,激活占用也仍需处理;LoRA 可训练参数较少时,部分状态原本已较小,应根据实际显存与耗时选择。
7. 原来每步处理 32 篇用 8 秒,调整后处理 16 篇用 4 秒,吞吐是否提高?再看第 2.6 节的两组实测,你会怎样比较并选择?
**参考思路:** 两者都是每秒 4 篇,不能只按每步时间判断加速。正文两组都完成相同的 50 步,batch=2、累积=16 比 batch=4、累积=8 更省显存,但命令耗时更长。比较时固定模型、数据与工作量,记录测量范围,再按所需显存余量、时间与任务效果作选择;不同长度的任务还可比较 token 吞吐。
8. 准备换大模型或多卡前,短跑应覆盖哪些阶段、记录什么?什么时候才有依据决定完整训练?
**参考思路:** 从环境就绪后观察加载、训练批次、验证和保存,尽量覆盖代表性长样本,记录配置、实际样本与步数、显存和耗时。根据这些记录判断可行性并估算同等工作量成本;五分钟未报错不代表覆盖了所有阶段。完整训练后的模型仍需按第 32 章检查任务效果和旧能力。
**本章小结:**
- 显存排查从报错阶段和已有占用开始。权重加载、批次计算与参数更新的主要压力不同,应分别分析权重、梯度、优化器状态和中间结果。
- 减 batch 可配合梯度累积维持有效批次,缩短序列要检查截断。混合精度按硬件与计算需求选择,重算、量化和卸载则分别用计算、数值近似或数据搬运换取部分显存空间。
- 数据并行处理不同样本,TP 分担同层计算,流水线并行按层接力。有效批次中的倍数取决于实际数据并行份数。
- ZeRO 逐阶段分片优化器状态、梯度和参数,减少重复保存的训练状态。ZeRO 把状态分散到多张 GPU,CPU 卸载则把选定状态或参数移到系统内存;是否使用要结合具体占用、通信和耗时判断。
- 资源比较要固定工作量并说明测量范围,短跑用于确认已覆盖阶段的可行性。决定完整训练时还要考虑成本,训练完成后继续用实际回答与指标验证质量。
**建议下一步:** 回看第 28–33 章,整理一份简短项目复盘:用任务约定与数据说明解释为什么微调、怎样准备样本;用配置、日志和资源记录解释训练选择;用预测与报告说明效果、限制和下一轮计划。练习向同事讲清这些判断,并用自己的文件与结果回答实操和面试中的追问。没有亲自运行的部分注明来自课程案例。