1. 为什么我在这个时间点把 LLM 预训练迁移到 MindSpore 生态
先说背景。我手上有一个中文 LLM 的预训练项目,7B 参数规模,训练数据大概 200B token。最早这套流程跑在 PyTorch + HuggingFace Transformers 上,单机 8 卡 A100,后来因为硬件资源调整,训练环境换到了昇腾集群,才被迫系统性接触 MindSpore 生态。原本以为是一次折腾的迁移,结果跑完一个完整周期之后,我对 MindSpore Transformers 这套预训练方案的看法有了明显变化——它不是一个"能用"的替代品,而是真的能做事、能提效的训练框架。
这篇文章不讲空话,直接把我在迁移、配置、训练、排错过程中沉淀下来的东西拆开讲。适合三类人看:第一类,公司在做大模型预训练,正在评估 MindSpore 作为底层框架的工程团队;第二类,已经从 PyTorch 切到昇腾硬件、但训练代码还没理顺的个人开发者;第三类,想了解 LLM 预训练完整链路,包括数据管线、并行策略、混合精度、checkpoint 的同学。无论你是哪种,这篇文章都能帮你少踩几个坑。
先说一个可能反直觉的结论:MindSpore 在 LLM 预训练这件事上的核心优势,不在于某个算子比 PyTorch 快多少,而在于显存管理和并行策略的系统级优化。预训练最烧钱的是显存,最怕的是多卡通信瓶颈,这两块 MindSpore 都花了很大功夫。Transformer 结构的模型(GPT、LLaMA、Bloom 这类)在 MindSpore 里基本都有现成的 Transformers 实现,你可以直接基于 mindformers 仓库去改配置,不用从零写模型代码。
但是,迁移过程绝对不是零成本的。PyTorch 生态里你熟悉的很多东西,比如 torch.distributed、HuggingFace Trainer、deepspeed,在 MindSpore 里对应的是另一套 API 和配置体系。你在 PyTorch 里写惯了的训练循环、梯度累积、混合精度逻辑,换个框架都得重新学。所以这篇文章的主线就是:从 PyTorch 思维切换到 MindSpore 思维,预训练全链路怎么做最高效。
2. 版本匹配与开发环境:装错一个版本,排查一整天
MindSpore 生态发展很快,版本节奏也快,导致最大的坑就是版本不匹配。很多人的第一反应是"pip install mindspore"直接装,但装上之后跑起来各种报错,几乎所有报错最后都能归结到一个原因:框架版本和配套工具链版本对不上。
2.1 我验证过的版本组合
我自己测试过几条稳定的组合路径,这里给出一个参考表。注意这只是基于我个人实践的验证结论,不代表官方唯一推荐,但至少能让你少走弯路。
| 组件 | 推荐版本组合 A | 推荐版本组合 B | 备注 |
|---|---|---|---|
| MindSpore | 2.2.14 / 2.3.1 | 2.3.1 | 2.2.14 稳定,2.3.1 对新硬件适配更好 |
| Python | 3.9 或 3.10 | 3.10 | 3.11 以上部分算子编译有问题 |
| 昇腾 CANN | 6.3.RC2 / 7.0.RC1 | 7.0.RC1 | 必须和 MindSpore 官方公告对齐 |
| mindformers | 1.0 及以上(对应 MindSpore 2.3) | r1.x 分支 | 直接拉 GitHub 对应分支 |
| Tokenizer | 对应模型的 vocab.json / tokenizer.model | 同上 | 别混用 LLaMA 和 Bloom 的词表 |
有一个经验:MindSpore 版本和 CANN 版本是强绑定的,昇腾硬件上跑,CANN 版本不对,经常出现算子执行报错,错误信息还特别隐晦,比如"op not supported"或者直接 core dump。所以装完 MindSpore 之后第一件事不是跑模型,而是先跑一遍官方提供的环境自检脚本,确认框架能正常调用 NPU 设备。
2.2 VS Code 里跑 MindSpore 内核的配置细节
热词里有个"vscode使用mindspore内核",这个我确实折腾过。在 VS Code 里写 MindSpore 代码,如果你直接在 Jupyter 里选 Python 内核,然后import mindspore,大概率你会碰到一个很常见的问题——MindSpore 装在了某个 conda 环境里,但 VS Code 的 kernel 没指向那个环境。
我的做法是:
# 创建独立 conda 环境 conda create -n mindspore python=3.10 -y conda activate mindspore pip install mindspore==2.3.1 pip install mindformers然后在 VS Code 里 Ctrl+Shift+P,选择 "Python: Select Interpreter",指定到 mindspore 环境的 python 路径。再打开 Jupyter notebook,kernel 选择上面的解释器。如果还不生效,直接在终端里用conda activate mindspore启动 code,这样环境变量一定对。
还有个细节:MindSpore 在 Jupyter 里的第一行import mindspore会有点慢,因为框架初始化要加载很多运行时库,这正常,别以为卡死了。
2.3 冒烟测试:用 3M 参数模型验证环境
不管你准备训练多大的模型,我强烈建议先跑一个极小规模的冒烟测试,验证环境没问题,再往上加规模。你不需要一开始就加载 7B 权重,直接创建一个几百万参数的小模型,跑一个 step,看看 loss 能不能正常下降。
import mindspore from mindspore import nn from mindspore import context context.set_context(mode=context.GRAPH_MODE, device_target="Ascend") class TinyLLM(nn.Cell): def __init__(self, vocab_size=32000, hidden_size=64, num_layers=2): super().__init__() self.embedding = nn.Embedding(vocab_size, hidden_size) self.linear = nn.Dense(hidden_size, vocab_size) def construct(self, input_ids): x = self.embedding(input_ids) return self.linear(x)这里有个 MindSpore 与 PyTorch 的核心差异:MindSpore 用construct方法而不是forward,而且默认在图模式(GRAPH_MODE)下执行。刚开始很容易惯性写成forward,然后报错找不到方法。这种小坑其实特别多,后面踩坑章节我会集中讲。
冒烟测试通过后,再走正式的数据和模型配置,整个流程会顺很多。
3. 数据接入与预处理:预训练 70% 的隐藏耗时在这块
很多人以为预训练效率瓶颈在模型计算,实际跑起来才发现,数据链路才是最容易拖后腿的环节。MindSpore Transformers 对数据格式有明确要求,你的原始文本如果不转成规范的 MindRecord 格式,训练脚本跑起来可能会反复卡在数据读取上。
3.1 预训练数据的基本格式与转换链路
MindSpore Transformers(mindformers)的标准做法是:
- 原始数据整理成 JSON 或 JSONL,每一行是一段完整的文本,比如一个网页正文、一篇文章、一个对话片段。
- 使用 mindformers 提供的数据预处理脚本,把 JSONL 转成 MindRecord。这个过程中会完成 tokenization、按 max_length 切分、生成 attention mask。
- 训练时通过
MindDataset直接读取 MindRecord 文件,不再走在线 tokenizer。
为什么一定要转 MindRecord?因为预训练数据量动辄几十 GB 甚至几百 GB,如果每个 step 都走一遍 tokenizer,计算资源会浪费在重复的字符串处理上。MindRecord 是二进制格式,数据读取效率和缓存友好度比在线处理高一个数量级。
我自己的做法是,对原始文本做一次离线 tokenization,把 token ids 落盘保存,训练时纯读取。这一步看似多了一道工序,实际上能省下大量时间,尤其是当你的语料库包含大量重复文本时,离线处理可以做到一次 tokenize、无限次复用。
3.2 Tokenizer 选择的细节
LLM 预训练用哪种 tokenizer,直接决定词表大小、序列长度和训练效率。常见选择是:
- LLaMA 系列用 SentencePiece 训练的 tokenizer,词表常用 32000 或 128000。
- Bloom 系列用 Byte-level BPE,词表 250680。
- 中文场景下,如果语料以中文为主,建议直接用中文语料重新训练一个 tokenizer,或者在已有中文开源 tokenizer 基础上做扩展,比如基于 chinese-roberta-wwm 的词表再扩充领域词。
这里有一个容易忽略的点:vocab 文件必须和模型配置里的 vocab_size 完全一致。我用过一个项目,模型配置写 32000,tokenizer 实际词表 32001,结果 embedding 层和输出层对不上,报错信息还特别诡异。所以无论是自己改配置还是加载别人仓库,第一步就是比对 vocab_size。
3.3 数据加载参数调优
MindSpore 的MindDataset支持配置并行读取和预取,这几个参数对训练吞吐影响巨大:
train_dataset: data_loader: dataset_dir: "/data/pretrain_records" num_parallel_workers: 8 shuffle: True sampler: - type: MindShuffleSampler dataset_sampler: - type: DistributedSampler num_shards: 8 shard_id: 0num_parallel_workers是 CPU 侧数据加载的线程数,调大能加快数据读取,但也会占用 CPU 资源,影响其他算子计算。我实测下来 8~16 个 worker 是个合理区间,超过 16 个收益很小,反而可能引入 CPU 争抢。
shuffle在预训练阶段建议保持开启,但要注意 seed 固定,否则断点续训时数据顺序变了,影响 loss 曲线的可比性。
4. 模型配置与训练编排:让 7B 模型在 MindSpore Transformers 里跑起来
MindSpore Transformers 的模型配置走的是 YAML 文件路线,这和 HuggingFace 的transformers库用 Python 字典传参的风格很不一样。一开始你会觉得不习惯,但熟悉之后会发现,YAML 方式对训练复现和版本管理非常友好。
4.1 一个 7B 模型的配置骨架
我以 LLaMA 结构为例,展示 mindformers 里一个典型 7B 模型配置的关键部分:
model: model_config: type: LlamaConfig vocab_size: 32000 hidden_size: 4096 num_hidden_layers: 32 num_attention_heads: 32 intermediate_size: 11008 seq_length: 4096 max_position_embeddings: 4096 rms_norm_eps: 1.0e-6 ignore_index: -100 use_flash_attention: True use_past: False arch: type: LlamaForCausalLM这里有几个字段值得展开讲。
use_flash_attention决定是否启用 Flash Attention。这个开关在昇腾上对应的是专门的融合算子,能显著降低 attention 的显存占用和计算耗时。我实测在 7B 模型上,打开 flash attention 之后同样的 batch size 能装下的样本量大约提升 30%,而 loss 曲线没有明显变化。如果你的硬件和框架版本支持,务必打开。
seq_length是序列长度,预训练阶段一般从 2048 起步,数据量足够的话再逐步拉长到 4096 或 8192。序列长度直接决定显存占用,所以这个值要和你的硬件显存、batch size 放在一起统筹考虑。
4.2 注意力机制与 key/query/value 的角色
配置模型的时候,你得理解注意力层在做什么。热词里有一个比喻我觉得很到位:key 是"我是谁",query 是"我在找什么",value 是"我能提供什么"。
展开说:在 transformer 的 self-attention 里,每个 token 生成三个向量——query 代表这个位置主动去"询问"其他位置的需求;key 代表这个位置被匹配时的身份特征;value 代表这个位置真正提供给别人的内容。注意力权重就是 query 和所有 key 做点积之后 softmax 的结果,最后把各位置的 value 按照注意力权重加权求和。
预训练的本质就是让每个 token 学会"该关注谁、不该关注谁",从而预测下一个 token。这个机制听起来简单,但参数量上去之后,矩阵乘法的规模极其庞大。7B 模型里,注意力部分占了相当大一部分计算量,这也是为什么 Flash Attention 这类融合算子能带来如此明显的收益。
4.3 优化器、学习率与混合精度配置
预训练的优化器基本标配 AdamW。MindSpore 里可以用mindformers内置的优化器配置:
optimizer: type: AdamWeightDecay beta1: 0.9 beta2: 0.95 eps: 1.0e-8 weight_decay: 0.1注意weight_decay设为 0.1 是当前大模型训练的通行做法,和很多传统小模型训练的习惯不同。传统训练里 weight_decay 常设 0.01 或 0.001,但在 7B 以上规模的预训练中,0.1 的权重衰减能有效抑制过拟合,loss 曲线更稳。
学习率调度用的是 warmup + cosine decay:
lr_schedule: type: CosineDecayLR learning_rate: 3.0e-4 warmup_steps: 2000 min_lr: 3.0e-57B 规模的预训练,峰值学习率我建议 3e-4 起步,warmup 步数根据总步数 1% 左右来定。如果你的数据量非常大(比如 200B token),warmup 可以长一些,让初期梯度的方差收敛得更平滑。
混合精度这块,MindSpore 的amp_level参数建议直接上 O2,即"自动混合精度 + 部分算子保持 FP32"。O2 在保证数值稳定性的前提下,能显著降低计算和显存开销。打开方式通常是:
from mindspore import amp model = amp.auto_mixed_precision(model, amp_level='O2')如果 loss 出现异常波动,第一件事是检查 O2 模式下哪些算子被降成了 FP16,必要时手动把这些算子改回 FP32。这个排查过程我在踩坑章节里会详细讲。
5. 高效并行策略的取舍:单卡、多卡与集群的边界在哪里
预训练速度上不去,90% 的情况不是单卡算力不够,而是并行策略没选对。MindSpore Transformers 支持几种并行方式,我把它们的使用边界和实测感受讲清楚。
5.1 数据并行:最省事,但扩展性有上限
数据并行就是把训练数据切给多张卡,每张卡持有一份完整的模型副本,各自算梯度,然后做梯度同步。
MindSpore 里通过配置parallel_config开启:
parallel_config: data_parallel: 8 model_parallel: 1 pipeline_parallel: 1 optimizer_parallel: 1数据并行的优点是实现简单、通信量可控(只同步梯度),在集群规模不大(8~32 卡)时性价比最高。缺点也很直接:单卡显存放不下模型参数 + 梯度 + 优化器状态时,数据并行救不了你。7B 模型在 FP16 下光参数就有约 14GB,加上梯度和优化器状态,单卡 40GB 显存根本塞不下,更别说还有中间激活值。所以到了这个规模,必须上张量并行。
5.2 张量并行:把大矩阵切碎了分给多张卡
张量并行(有时也叫模型并行)是把 attention 里的 QKV 矩阵和 MLP 里的大矩阵按列/按行切到不同卡上,每张卡只算自己那一块。MindSpore 里的配置方式是在模型 config 里加:
parallel_config: data_parallel: 4 model_parallel: 2 pipeline_parallel: 1这里的语义是:一共 8 张卡,数据并行度 4,模型并行度 2,相当于 4 组模型副本、每组横跨 2 张卡。张量并行的通信量比数据并行大很多,因为每层都要做 AllReduce 同步中间结果,所以model_parallel不建议超过 8,再高通信开销会吃掉算力收益。
我自己在 8 卡环境上的实测:7B 模型用data_parallel=4, model_parallel=2的组合,吞吐约等于纯数据并行效果不错时的 95%;但model_parallel=4之后,loss 下降速度反而略降,原因就是通信变多了。
5.3 流水线并行:用层切分降低显存墙
流水线并行是把模型按层切成几段,每张卡负责其中一段,数据按 micro-batch 依次流过各段。好处是显存占用大幅下降,坏处是有流水线气泡(bubble),GPU 空闲率上升。
MindSpore 里配置也一样,三行搞定:
parallel_config: data_parallel: 2 model_parallel: 2 pipeline_parallel: 2流水线并行的核心调参点是 micro-batch 数量。micro-batch 越多,流水线越满,气泡越小,但梯度同步的步调会更复杂。我建议 micro-batch 数量至少是流水线并行度的 4 倍,才能保证流水线接近满载。
5.4 显存占用估算方法
无论选哪种并行策略,都要先估算一下显存需求。经验公式是:
- 模型参数:参数量 × 2(FP16)字节
- 梯度:和参数同级,约 2 字节/参数
- 优化器状态:AdamW 需要 fp32 的 momentum 和 variance,约 8 字节/参数
- 激活值:取决于 batch size × seq_length × hidden_size,这部分弹性最大
一个 7B 模型,单卡至少要吃掉: 参数量 14GB + 梯度 14GB + 优化器状态 56GB,总计约 84GB(FP16 下)。这就是为什么纯数据并行在 7B 完全跑不动——单卡 80GB 都捉襟见肘。张量并行的价值就在于把这份账单分摊到多张卡上。
我估算显存时习惯先按"参数 + 梯度 + 优化器状态"算出固定部分,再根据 batch size 和 seq_length 估算激活值部分。如果超出了可用显存,优先调 batch size,其次开重计算(下一章会讲),最后才考虑调整并行配置。
6. 重计算、梯度累积与通信优化:榨干硬件性能的实操手段
并行策略解决的是"能不能跑"的问题,下面这三个手段解决的是"跑得够不够快"的问题。
6.1 重计算:拿算力换显存
重计算(Recompute / Activation Checkpointing)的思想很简单:前向传播时,中间层的激活值不保留,等反向传播需要时再重新算一遍。这样显存占用大幅下降,代价是大约多出 30%~40% 的计算量。
MindSpore Transformers 里打开重计算一般是在模型 config 里配recompute: True,或者通过Cell的set_recompute方法指定哪些层重计算。我一般会把 attention 层和 MLP 层都打开重计算,只在最后输出层保留完整激活值。
实测数据:7B 模型、batch size 从 2 提升到 4,同样的显存,重计算帮我多塞了一倍样本。对于千卡级别的预训练任务来说,这个收益非常可观。
6.2 梯度累积:绕过 batch size 的物理极限
即使有重计算,单卡的 batch size 还是有上限,尤其当 seq_length 很长(8192)时,一张卡可能只能塞下 1 个样本。梯度累积的思路是:连续算 N 个 micro-batch 的梯度,累加之后再更新一次参数,等效于把 batch size 放大 N 倍。
MindSpore 里的配置:
gradient_accumulation_steps: 16注意梯度累积和并行策略的组合规律:全局有效 batch size = 单卡 batch size × 梯度累积步数 × 数据并行度。比如单卡 batch size=2,梯度累积 16,数据并行 8,相当于全局 batch size 256。
这里有个容易犯的错:梯度累积改变了学习率的敏感性。batch size 从 128 涨到 256,按线性缩放规则,学习率也应该适当上调。不过实际预训练中,很多人不动学习率也能跑通,我更建议的做法是保持学习率不变,先观察 loss 曲线是否正常,再决定调整方向。
6.3 通信优化:多卡训练的隐形瓶颈
大规模并行下,通信耗时占比会越来越高。MindSpore 提供了梯度压缩、通信缓存(bucket)合并等手段。
我特别想说一下 bucket 合并。默认情况下,每个参数的梯度可能单独做一次通信,这会产生大量小消息,网络往返延迟非常高。把梯度按层合并到一个大的 bucket 里再做 AllReduce,可以显著降低通信次数。MindSpore 里有相关配置项可以开启,效果非常明显,尤其在千兆以太网环境下。
还有一个点:梯度压缩。在通信带宽不足的集群上,可以对梯度做 FP16 压缩传再转回 FP32,对收敛影响很小,但通信量直接减半。如果你的集群是万兆以内的网络,强烈建议开启。
6.4 数据读入与计算重叠
最后一个优化点是数据加载和模型计算的重叠。MindSpore 的MindDataset天然带 pipeline 并行能力,数据预取是异步的,但你要确保数据加载线程数足够,不要让它成为计算空等的理由。
我在 7B 训练时遇到过一种现象:每个 step 计算很快,但 step 之间有明显间隙,GPU/NPU 利用率掉到 60% 以下。排查之后发现是数据预处理在 step 边界同步等待。解决方法是把num_parallel_workers调大,并把数据集的 shuffle buffer 调大(比如 10000 条),让 prefetch 始终有足够的数据排队。
7. 踩坑实录:命名冲突、Loss 不收敛与 OOM 的完整排查链路
这一章节是全文最有实操价值的部分。我把预训练过程中遇到的三个典型问题完整复盘一遍,包括问题现象、排查思路、最终解法,希望你能直接复用这套方法论。
7.1 "aimv2 is already used by a transformers config, pick another name" 命名冲突
这个问题出现在模型加载阶段。当时我在用 mindformers 加载一个自定义模型配置,AutoConfig.from_pretrained 报了这样一个错:'aimv2' is already used by a transformers config, pick another name.
先说结论:这是配置注册名冲突。HuggingFace Transformers 生态里有一个全局的配置注册表,每个模型架构的名字必须是唯一的。如果别人的代码已经注册了aimv2这个名字,或者框架内置配置里已经有它,你再试图用同样的名字注册自己的配置类,就会直接拒绝。
实际排查过程:
- 我先全局搜了代码库,发现同名配置类出现在另一个第三方模块里。两个模块都调用了注册函数,把同一个名字绑定到了不同的配置类上。
- 定位到错误之后,解法其实很简单——给自己的模型配置类换一个新名字,比如在子类里重新声明
model_type = "my_aimv2_v1"。
这个问题的价值不在于"怎么把这个报错消掉",而在于提醒你:LLM 框架里的配置注册名是全局资源,命名要有项目前缀。我后来给自己的所有模型配置统一加了项目代号前缀,再也没有踩过同类的坑。
如果你的报错不是命名冲突,而是"checkpoint 里没有这个 key",那多半是权重加载时 key 映射对不上。MindSpore 权重和 PyTorch 权重的转换有一个专门的脚本,路径上写错了层级会导致部分参数随机初始化,loss 会降得极其缓慢。这种坑我不会展开,但提醒一句:加载权重后立刻检查参数是否真的加载成功,用一个小批次跑前向,对比输出和原始 checkpoint 的输出是否一致。
7.2 Loss 不下降或者直接 NaN:先查数据,再查学习率,最后查精度
Loss 不降是预训练最头疼的问题。我梳理一下我的排查顺序,你按这个顺序排查,效率会高很多。
第一步:检查数据环节。我最常见的一个 bug 是分词后序列长度不够,导致大量样本其实是空序列或长尾序列。当时一个数据清洗脚本把过短文本都丢掉了,但保留了一条"空文本"作为分隔符,结果 tokenizer 把它切成一个空序列,模型学到的全是 padding 位置的噪声。检查方法是:随机抽 100 条训练样本,打印 token 数量和内容,肉眼看一下有没有异常空样本。
第二步:检查学习率。如果数据没问题,loss 一直不降,最常见的元凶就是学习率过大或 warmup 过短。我试过在 7B 模型上把学习率从 3e-4 调到 6e-4,结果训练到 500 步时 loss 开始剧烈震荡,然后掉进 NaN。这个现象的根源是 FP16 混合精度下,大学习率容易让梯度溢出到无限值。解决方式很朴素:学习率调回 3e-4 以下,warmup 拉长到 3000 步,问题消失。
第三步:检查混合精度。如果 loss 是缓慢下降一段时间之后突然变 NaN,大概率是混合精度下某些算子的梯度溢出。我的排查方法是把amp_level从 O2 降回 O1,如果 NaN 消失,说明 O2 下某个算子被错误降到了 FP16。接下来逐个算子检查,手动设置这些算子保持 FP32。
这里分享一个高效做法:训练脚本里加一个梯度检查函数,每 N 步打印一次梯度范数。如果发现某个参数梯度的 L2 范数超过 1e4,立刻停下来检查。这个检查对定位"哪个层先炸"非常有用,可以大大缩短排查时间。
7.3 显存溢出 OOM:四个手段依次启用
显存溢出几乎是每一个做大模型预训练的人都跑不掉的经历。我的处理顺序很固定:
- 先减小单卡 batch size,让训练先跑起来。
- 开启重计算(recompute),这一步能释放的显存最多。
- 如果还不行,把序列长度临时缩短,比如从 4096 缩到 2048,确认模型结构没问题。
- 最后才考虑调整并行策略,比如把模型并行度从 2 提到 4。
有一个细节需要额外提醒:MindSpore 在昇腾上的显存碎片率和 PyTorch 不同,有时候明明显存还有剩余,但分配大块内存失败。这种情况下,除了调小 batch size,可以尝试把allocator的 block size 调大一点,减少碎片化的概率。这个具体参数和版本相关,建议直接参考官方 release note。
8. 最后再分享一个我自己的实战检查清单
写完上面的技术细节,我最后想给你一份可以直接抄走的检查清单。这不是什么官方标准,纯粹是我踩了无数坑之后沉淀下来的个人习惯,每次开新任务都会照着过一遍。
第一,环境层:MindSpore 版本、CANN 版本、Python 版本三者对齐;跑通一个 3M 参数小模型的冒烟测试再上正式任务。
第二,数据层:原始语料做过离线 tokenization 并转成 MindRecord;vocab_size 和模型配置完全一致;采样 100 条样本人工检查序列质量。
第三,模型层:配置了 flash attention 和重计算;混合精度 O2 开启;并行策略的四维参数(data / model / pipeline / optimizer)和集群规模匹配。
第四,训练层:学习率 3e-4 起步、warmup 约总步数 1%;梯度累积步数设置了之后全局 batch size 符合预期;checkpoint 保存间隔不超过 1 小时,避免突然断电导致重跑。
第五,监控层:每步记录 loss、梯度范数、NPU 利用率;如果 NPU 利用率低于 70%,优先查数据加载和通信;loss 出现异常波动,第一时间冻结当前状态,保存好现场。
预训练一个 7B 模型是个很长周期的工程,中间会遇到无数意外,但只要你前期的环境验证和数据管线做得足够扎实,后面的路其实是稳的。这套经验如果对你启动自己的 LLM 预训练项目有参考价值,我就觉得很值了。