OpsLM Phase3:一次夭折的 Instruct 实验——为什么 33M Base 没被 SFT 救回来
前两阶段,我已经从零实现了一个 Decoder-only Transformer,并完成了一轮面向 Linux、Kubernetes、Docker、Prometheus、vLLM 等技术文档的领域预训练。
到 Phase2 结束时,我得到了一版大约 33.66M 参数的模型:
OpsLM-V2-Base-33M
它可以续写 Kubernetes、Linux、AI Infra 等领域文本,也确实学到了一些技术词之间的关联。
但它有一个非常明显的问题:
它更像一个“技术文档续写模型”,而不是一个能回答 SRE 问题的助手。
所以 Phase3 的目标很自然:
在 OpsLM-V2-Base-33M 上进行 SFT,让它从 Base Model 变成 Instruct Model。
结果却并不理想。
这篇文章记录的不是一次成功训练,而是一次最终决定放弃的 V3 实验,以及我如何一步步排除数据集、Loss、Label Mask、Optimizer 等问题,最后发现真正的瓶颈很可能已经进入了 Base Model 本身。
1. Phase3 想解决什么问题?
Phase2 有一个很典型的例子。
输入:
服务器 CPU 使用率不高,但是 load average 非常高,可能是什么原因?
模型并不会真正分析 Linux Load Average,而是倾向继续生成 Kubernetes、CPU Policy、Requests/Limits 等相关技术文本。
也就是说,它已经知道:
CPU
Kubernetes
Pod
Resource
Policy
这些 Token 经常一起出现。
但它还没有学会:
用户提出问题
↓
理解问题
↓
检索相关知识
↓
组织成回答
所以 Phase3 引入 SFT。
训练格式非常简单:
用户:<instruction>
助手:<answer>
并且只对 Assistant 的回答部分计算 Loss。
User Prompt 对应 Label 全部设置为:
-100
核心 Loss:
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, vocab_size),
shift_labels.view(-1),
ignore_index=-100,
)
也就是说:
模型看到完整的“用户问题”,但训练目标只有“助手应该怎么回答”。
这是标准的 Causal LM SFT 思路。
2. 第一次 Seed SFT
为了先验证链路,我人工准备了大约 20 条 SRE 问答。
包括:
- Linux Load Average
- OOMKilled
- Pod Pending
- CrashLoopBackOff
- inode
- TCP
- Redis
- MySQL
- Prometheus
- GPU
- vLLM
- systemd
- Docker
Seed 实验很快出现了一个有意思的变化。
原来的 Base Model 更像:
Kubernetes documentation continuation...
经过几十步 SFT 后,模型开始输出:
建议:
1.
2.
3.
这说明:
SFT 确实改变了模型的行为模式。
但内容依旧经常是错的。
例如 Linux Load Average 问题,它仍然会把答案引向 Kubernetes CPU Requests/Limits。
结论也很明显:
20 条数据只能教模型“回答的样子”,不可能真正建立大规模 SRE 问答能力。
于是开始正式构建 Phase3 数据。
3. 引入 Hugging Face DevOps 数据
这一步我没有继续完全依靠 Teacher Model 合成,而是优先寻找现成公开数据。
最终主要使用了:
Skilln/devops-qa-dataset
pavanmantha/devops-v1
经过:
质量过滤
Exact Dedup
SimHash Near Dedup
长度过滤
Train / Val Split
最后正式训练集得到:
Train Samples: 19,071
序列长度:
seq_len = 512
随机检查 SFT Dataset:
input: torch.Size([512])
labels: torch.Size([512])
train tokens: 233
也就是说一个 512 Token 的 Sample 中,真正计算 Loss 的只有 Assistant Answer 部分。
至此数据管线看起来一切正常。
4. 第一轮正式 SFT:LR = 3e-5
第一轮参数:
Base:
OpsLM-V2-Base-33M
Train:
19,071 samples
Batch:
32
LR:
3e-5
约:
3 epochs
到 Step 1800:
Train loss: 3.9621
Val loss: 3.7762
Loss 在下降。
Validation Loss 也没有出现明显过拟合。
但真实生成非常差。
例如:
How to: How do I debug a Pending pod?
模型输出:
kubectl run -it --image=busybox:1.28 --restart=Never
问题在于:
这甚至就是训练集中的原题。
而训练集的真正答案明确包括:
kubectl describe pod
检查 CPU / Memory Requests
检查 Taints
检查节点是否被 Cordon
检查 Node Ready 状态
也就是说:
模型训练了三轮之后,连训练集里见过的问题都没有正确学会。
这时候第一个怀疑自然是:
SFT 实现是不是有 Bug?
5. 最关键的实验:单样本过拟合
这是整个 Phase3 最有价值的排错实验之一。
我把:
How to: How do I debug a Pending pod?
和它的标准答案单独提取出来。
整个训练集:
1 sample
Batch:
1
然后故意反复训练这一条数据。
结果:
模型可以完整背下来。
输入同一个 Prompt 后,可以正确生成训练答案。
这一刻基本可以排除:
Tokenizer ❌
Label Mask ❌
Causal Shift ❌
CrossEntropy ❌
Backward ❌
Optimizer ❌
Checkpoint ❌
Generation ❌
整个 SFT Pipeline 是工作的。
这也说明:
OpsLM-33M 并不是“完全学不会”。
它可以记忆。
真正的问题是:
当 19K 个不同任务同时进入训练以后,它没有能力把这些知识稳定组织起来。
6. 第二轮:把学习率提升到 1e-4
于是我重新从:
OpsLM-V2-Base-33M
开始训练,而不是继续第一轮 checkpoint。
参数调整为:
batch_size = 16
lr = 1e-4
max_steps = 3600
19,071 条数据,Batch 16:
19071 / 16
≈ 1192 steps / epoch
3600 Step 大约:
≈ 3 epochs
第二轮 Loss 明显比第一轮下降得快。
例如:
step=1900
Train loss:
3.3702
Val loss:
3.6015
随后:
step=2300
Val loss:
3.5568
到 Step 2900:
Train loss:
3.5573
Val loss:
3.5208
从曲线来看:
第二轮显然比
3e-5的训练有效。
但真正的问题仍然存在。
7. Step 2900,再次测试训练集原题
输入:
How to: How do I debug a Pending pod?
一次输出:
kubectl describe pod mypod -o yaml | less
另一次:
kubectl run -it \
--image=busybox:1.28 \
--restart=Never
相比最开始已经有进步。
至少模型已经知道:
Pending Pod
→ kubectl
→ describe
但是它依然没有形成完整的排障回答。
更加严重的一次生成甚至出现:
--command=Never
--command=Never
--command=Never
--command=Never
...
即典型的 Repetition Collapse。
调整:
temperature
top-k
top-p
只能改变复读形式,并不能解决回答能力本身的问题。
8. 为什么 Loss 在下降,模型却还是不会回答?
这是我在 Phase3 里真正理解的一件事:
Validation Loss 下降,不代表模型一定拥有了我想要的任务能力。
Loss 衡量的是:
给定前面的 Token
↓
预测下一个 Token 的概率有没有变好
但我真正想要的是:
理解 Pending 的语义
↓
识别这是 Scheduler 问题
↓
知道“节点有资源”不代表一定可调度
↓
想到 Requests
↓
想到 Taints / Tolerations
↓
想到 Affinity
↓
想到 PVC
↓
想到 Node Ready / Cordon
↓
按照合理优先级组织回答
两者并不是同一件事情。
一个模型完全可能:
Loss ↓
同时:
真实 QA 能力仍然很弱
Phase3 就出现了这个现象。
9. V3 为什么最终决定停止?
目前我认为主要有三个原因。
原因一:33M 参数的容量太小
当前模型只有:
33.66M Parameters
却希望同时学习:
Linux
Kubernetes
Docker
Network
Prometheus
Database
systemd
GPU
CUDA
vLLM
AI Infra
中文
英文
故障排查
命令分析
概念解释
单独背一道题没有问题。
但让这些知识同时存在,并根据自然语言问题正确选择知识,对 33M 模型已经非常困难。
这种现象在全量 SFT 时尤其明显:
Pending
↓
模型知道和 Kubernetes 有关
↓
模型知道 kubectl 有关
↓
却无法稳定选择正确排障路径
也就是:
相关性学到了,组合能力没建立起来。
10. 更严重的问题:Base 预训练不足
其实参数量还不是唯一问题。
OpsLM-V2 的原始预训练数据只有:
≈ 23M Tokens
虽然训练跑了大约十几遍,累计 Token Presentation 达到数亿:
23M
×
约 14 passes
但:
反复看 23M Token
和:
真正看过数亿个丰富的新 Token
完全不是一回事。
当前 Base Model 本质上更像:
一个看过大量 Kubernetes/Linux 技术文档的小型文本续写器。
而不是:
一个先掌握语言理解、知识表示和基本组合能力,然后再学习 SRE 的 Base Model。
这也是为什么 Phase2 的模型非常容易:
看到 Pod
↓
进入 Kubernetes Documentation 模式
而不是首先理解:
用户到底想问什么?
11. SFT 无法凭空创造 Base Model 能力
这是 Phase3 最大的收获。
最开始我潜意识里有一种期待:
Base 不太会回答
+
大量 QA SFT
=
会回答
但实验告诉我不是这么简单。
更加准确的关系应该是:
Pretraining
负责形成:
语言
知识
概念
关联
表示能力
一定的泛化能力
SFT
负责形成:
如何响应用户
如何组织回答
遵循什么格式
如何调用已有能力
SFT 更像:
教一个已经掌握知识的人怎么参加考试。
而不是:
靠几万道考试题,从零补完他的基础教育。
当前 OpsLM-V2 的 Base 能力不够成熟,所以 V3 的 SFT 实际承担了太多任务。
最终效果自然有限。
12. 数据质量也存在问题
这轮使用的 19K 数据虽然数量很大,但并不是全部都是理想 SRE Instruction。
随机检查可以发现很多:
How to: Lab 5.2. Managing Pods with YAML file
或者:
kubectl run ...
这类:
教程
实验
命令示例
文档片段
而不是:
真实故障
→ 分析
→ 定位
→ 解决
对于大型成熟 Base Model,这种噪声可能不是致命问题。
但对于 33M 模型:
每一条训练数据都会明显影响它的行为分布。
所以才可能出现:
Pending Pod
↓
kubectl run busybox
这种“技术上有关,但回答完全不对”的现象。
13. 为什么我不继续把 V3 训练到 10000 Step?
因为问题已经不再是:
是不是少训练了几轮?
我们已经确认:
- 单样本可以完全拟合;
- 全量训练 Loss 持续下降;
- 更大学习率确实改善 Loss;
- 训练集原题依然不能完整回答;
- 真实问题泛化更差;
- 模型开始出现命令模式竞争和重复退化。
继续:
5000
10000
20000
更多是在反复强化当前数据分布。
它不会自动把一个 33M、只看过 23M 独立预训练 Token 的 Base Model 变成成熟的 SRE 推理模型。
所以 V3 到这里正式停止。
14. Phase3 最终结论
这轮实验虽然“夭折”,但得到的结论非常明确。
已验证
Transformer 实现 ✅
Pretraining Pipeline ✅
SFT Dataset ✅
Assistant-only Loss ✅
Causal Shift ✅
Optimizer ✅
Checkpoint ✅
Generation ✅
单样本 Memorization ✅
未达到
大规模 Instruction Learning ❌
SRE QA 泛化 ❌
稳定多步排障 ❌
中文 SRE 问答 ❌
复杂知识组合 ❌
最终判断:
Base Model 能力不足
+
模型容量有限
+
预训练数据量不足
+
SFT 数据存在噪声
↓
OpsLM-V3 未达到预期
15. OpsLM-V3 正式归档
这一版我不会删除。
它会作为:
OpsLM-V3-Instruct-Failed-Experiment
保留下来。
因为它证明了一件非常重要的事情:
“能训练”与“有能力”是两回事。
以及:
SFT 不是魔法。
这是从零训练语言模型时非常值得亲自踩一次的坑。
16. 下一站:OpsLM-V4
V3 之后,我决定暂时不再折腾 33M 模型的学习率。
下一阶段重新训练 Base Model。
目标大约:
100M ~ 150M Parameters
并将独立预训练数据从:
23M Tokens
提高到:
约 1B Tokens
数据也不再只有技术文档,而是:
通用英文
+
通用中文
+
高质量教育文本
+
Linux / Kubernetes / SRE
+
GPU / CUDA / AI Infra
+
少量代码与配置
下一版真正想验证的是:
33M + 23M Tokens
VS
100M+ + 1B Tokens
在完全相同的问题上:
How do I debug a Pending pod?
究竟会出现怎样的差距。
如果下一版 Base 在 SFT 后能够稳定回答:
先 kubectl describe pod 查看 Events
检查 Requests 是否满足
检查 Taints / Tolerations
检查 NodeSelector / Affinity
检查 PVC
检查节点 Ready / Cordon 状态
那么这整个 OpsLM 项目最重要的一课可能就是:
真正决定模型上限的,首先是 Base Model。
而不是 SFT 技巧。
Phase3 到此结束
V3 没有成为一个好用的 SRE Assistant。
但它成功暴露了 V2 的能力边界。
所以它不是没有价值。
相反,它告诉了我:
下一步到底应该把算力和时间花在哪里。
下一篇:
《OpsLM-V4:重新训练 Base Model——从 33M / 23M Token 走向亿级参数与 10 亿 Token》