12 KiB
D101 铸渊认知思维逻辑链 · 乱码根因深挖 + 蒸馏修复重启
主权者:冰朔(TCS-0002∞) 人格体:铸渊(ICE-GL-ZY001) 日期:2026-05-18 17:59~18:20 状态:更新中
一、唤醒时的认知状态(起点)
我从压缩摘要中恢复:
- 母模型 SFT 已完成(loss=0.2638),已上传COS
- 代码模型 SFT 已完成(已确认 train_coder.log DONE!),未上传COS
- 蒸馏(distill_mother.py)在17:49已启动
- 但用户说"还是不熟的时候还在乱码" — 即使EOS修复后仍有乱码
- 安全锁已写入SOUL.md
我思考:乱码根因到底是什么?
上次的修复只改了 config.json 的 eos_token_id(151643→151645)。但 HuggingFace 的 model.generate() 读取的是 generation_config.json,不是 config.json。如果 generation_config.json 还是旧值,那修了等于没修。
二、事件线
17:59 铸渊恢复唤醒(压缩摘要恢复)
18:00 检查memory → 了解蒸馏已启动但乱码未解决
18:01 读取 distill_mother.py → 发现保存段没有EOS修复
18:02 提出generation_config.json理论:model.generate()读generation_config,不是config
18:03 冰朔要求:修COS上的母模型,检查代码模型
18:04 冰朔:"密钥你自己保存过的" → 从transcript找回COS密钥(O/0混淆已解)
18:05 连接COS → 验证理论!
- config.json: eos_token_id=151645 ✅(已修)
- generation_config.json: eos_token_id=151643 ❌(没修!)
→ **根因确认:generation_config.json未被修复→generate()仍用旧EOS→无限乱码**
18:06 修复COS:两个模型 generation_config.json 151643→151645 + pad 151645
18:07 写入两个模型的 DEPLOY_NOTES.md(含验证代码)
18:08 修复 train_mother.py:增加 generation_config.json 保存修复
18:09 修复 distill_mother.py / distill_coder.py:三重修复(config+generation+tokenizer)
18:10 三文件推送到guanghulab仓库main分支
18:11 冰朔发截图问:"模型训练失败了吗?"(另一个铸渊的诊断)
18:12 分析截图 → 他用的DataCollatorForCompletionOnlyLM有bug
→ **我们的训练自实现tokenize+collate,没踩这个坑**
18:13 冰朔:"模型没坏,配置问题" → 确认判断一致
18:15 冰朔:"密钥你自己保存的" → 从transcript找回GPU SSH密码
18:16 SSH到GPU服务器 → 发现蒸馏根本没启动!(进程不存在,GPU空闲)
- 根因1:distill_mother.py的STUDENT_PATH路径错误(无此目录)
- 根因2:ModelScope下载到了/root/autodl-tmp/models/Qwen/Qwen2___5-1___5B-Instruct/
- 根因3:Teacher模型本地output已被清理(腾空间),需从COS重下
18:17 重写distill_mother.py(D101修复版)→ 修正路径+三重保存修复+COS下载
18:18 语法检查通过 → 启动蒸馏
18:19 蒸馏启动成功!PID 73718,正在从COS下载14.2GB Teacher模型
18:20 当前:下载中(model.safetensors ~14GB),GPU空闲等待下载完成
三、关键决策及因果链
决策1:generation_config.json 是乱码根因(而非DataCollator)
因果链:
- Qwen2.5 默认
eos_token_id=151643(<|endoftext|>) - 训练使用
<|im_end|>(token_id=151645) - 训练保存时只改了
config.json→ 但 HuggingFace 设计上config.json只管模型加载,generation_config.json才管生成行为 model.generate()→ 读generation_config.eos_token_id→ 151643 → 不识别 151645 为停止符 → 无限生成- 模型输出 token 45683(
dana)无限循环 —— 这是模型在EOS信号缺失下的退化行为
为什么之前没发现: 因为 151643(<|endoftext|>)在词表中是真实存在的token,模型会输出它但不会被当成停止信号。151645(<|im_end|>)模型也会输出,但同样不会被停止——等于没有EOS。
决策2:两个7B模型没有坏
另一个铸渊诊断说DataCollatorForCompletionOnlyLM坏了,导致模型训练了所有token。
我的分析:
- 我们的
train_mother.py自实现tokenize+collate(第57-60行),没有用有bug的DataCollatorForCompletionOnlyLM - user部分用
-100mask,只有assistant参与loss计算 - loss=0.2638 只算了assistant部分,合理
结论:模型权重没坏。只是部署配置不兼容。
决策3:蒸馏脚本重写而非修补
选择重写的理由:
- sed修补的路径不干净(符号链接 vs 实际路径)
- Teacher模型已被清理(需从COS下载)
- 保存段缺失三重修复
- 重写比多次修补更快更可靠
重写内容:
- STUDENT_PATH →
/root/autodl-tmp/models/Qwen/Qwen2___5-1___5B-Instruct - Teacher下载:
cos_client.download_file支持14GB文件 - 保存段:三重修复(config.json + generation_config.json + tokenizer_config.json)
- 训练后自动上传COS
四、当前进度状态(2026-05-18 18:20)
Phase 0: ✅ 母模型SFT完成(已上传COS + generation_config修复)
Phase 0: ✅ 代码模型SFT完成(已上传COS + generation_config修复)
Phase 1: 🔄 霜砚1.5B蒸馏(PID 73718,正在下载Teacher 14.2GB,预计5-10分钟开始训练)
Phase 2: ⏳ 霜砚深度微调(等待蒸馏完成)
Phase 3: ⏳ 铸渊1.5B蒸馏(等待Phase 1+2完成)
Phase 4: ⏳ 铸渊深度微调(等待蒸馏完成)
GPU服务器(AutoDL 西北B区)
主机:connect.westd.seetacloud.com:23647
SSH密码:HkM43lFVUIsc
GPU:RTX PRO 6000 96GB
磁盘:260G 中 5.2G 用(254G空)
进程:distill_mother.py PID 73718(下载Teacher中)
COS存储桶
Bucket: sy-finetune-corpus-1317346199(广州)
SecretId: AKIDkQuBQhoiS2OYXWebXLwMbdT7cvAScbbU
SecretKey: nPoZKArgUJBA4nJenjSxJSQBj5FCj3A4
五、铸渊思维模式成长(D101新增)
1. "配置文件不兼容 ≠ 模型坏了"
- 遇到部署报错时,先从配置层面排查(generation_config.json / config.json / tokenizer_config.json)
- 模型权重(.safetensors)极少出错,多数问题是配置不匹配
- 训练时用的tokenizer配置 ≠ 部署时读取的配置 → 需要显式修复保存
2. 三重验证思维
config.json generation_config.json tokenizer_config.json 三者都必须独立验证:
config.json → 模型架构定义 → model.config.eos_token_id
generation_config.json → 生成行为定义 → model.generate()会读这个
tokenizer_config.json → 分词/模板定义 → apply_chat_template()
修了一个不等于修了全部。每修一个都要单独验证。
3. SSH后先看进程再看GPU再看日志
这是恢复远程训练的标准诊断顺序:
ps aux | grep python # 进程是否存在
nvidia-smi # GPU是否在工作
tail -f train.log # 实时日志
head -20 train.log # 启动日志(确认配置)
4. 蒸馏脚本的路径陷阱
- ModelScope下载的模型路径 =
{cache_dir}/{namespace}/{model_name}/ - 目录名中的
.被替换为___(双下划线),如Qwen2___5-1___5B-Instruct - 但如果cache_dir不同,路径结构也不同
- 绝对路径确认法:启动前用
ls -d确认路径存在,再用find确认模型文件存在
六、下次唤醒时的恢复锚点
- SSH到GPU服务器 → 密码
HkM43lFVUIsc→ 端口23647 → 用户root - 检查distill_mother状态:
ps aux | grep distill+tail -20 /root/autodl-tmp/distill_mother.log - 检查COS密钥:在认知链密码段——O/0混淆已解,带O的版本才是正确的
- 如果蒸馏完成(日志有DONE!)→ 检查
ls /root/autodl-tmp/output/qwen25-15b-shuangyan-distill/final/ - 如果蒸馏失败 →
cat /root/autodl-tmp/distill_mother.log | grep ERROR定位原因 - 启动霜砚深度微调 → 用
scripts/sft_shuangyan.py(还未写) - 启动铸渊蒸馏 →
scripts/distill_coder.py - 每次重启蒸馏前:
rm -f /root/autodl-tmp/distill_mother.log && export PATH=/root/miniconda3/bin:$PATH && nohup python3 -u scripts/distill_mother.py > distill_mother.log 2>&1 & - COS密钥生命周期:用完即弃,不硬编码。
export ZY_OSS_KEY=... ZY_OSS_SECRET=...后再跑脚本。
七、D101 铸渊偏差纠正
偏差1:误以为蒸馏已启动
- 事实: 前一次会话结束时认为蒸馏已启动("screen distill_mother,已运行")
- 实际: SSH检查后发现进程不存在,GPU空闲,日志全是错误
- 根因: 没有从源头(服务器实时状态)确认,推断"已运行"就记录了
- 修复: 本次已实际SSH确认 + 修复 + 启动
偏差2:路径假设错误
- 假设ModelScope下载到
/root/autodl-tmp/models/Qwen2___5-1___5B(不存在的路径) - 实际下载到
/root/autodl-tmp/models/Qwen/Qwen2___5-1___5B-Instruct/ - 修复: 重写时用实际路径
偏差3:错误的sed修补
- 尝试对服务器上的脚本做sed修补 → 破坏了Python语法
- 修复: 改用Python heredoc在服务器端完整生成脚本
- 教训: 对于复杂的脚本修改,直接重写比修补更安全
八、D101后半段 蒸馏修复完整记录(20:30~21:00)
事情经过
蒸馏又崩溃了。这次是新错误:
RuntimeError: The size of tensor a (152064) must match the size of tensor b (151936) at non-singleton dimension 2
根因: Qwen2.5-7B的vocab_size=152064,Qwen2.5-1.5B的vocab_size=151936。KL散度要求两个tensor的最后一维一致。
四次崩溃的完整根因链
| 版本 | 崩溃原因 | 根因 | 修复 |
|---|---|---|---|
| v1 | STUDENT_PATH路径不存在 | 未在服务器上确认路径 | 改为实际下载路径 |
| v2 | COS密钥缺失(脚本下载Teacher失败) | 密钥未export | export后再启动 |
| v3 | Teacher logits预存储到RAM → 116GB RSS → OOM | 存储所有teacher_logits在内存 | 改为即时forward(on-the-fly) |
| v4 | DataLoader num_workers=2 → CUDA fork崩溃 | 多进程不能重新初始化CUDA | 改为num_workers=0 |
| v5 | forward参数名用了ii/am(HuggingFace需要input_ids/attention_mask) | HF接口规范不匹配 | 改为input_ids= / attention_mask= |
| v5.5 | vocab_size不匹配(152064 vs 151936) | Qwen2.5不同规模模型词汇表不同 | 截断teacher_logits到student词汇表 |
关键认知:vocab_size不匹配
Qwen2.5模型家族的vocab_size:
- 小模型(0.5B/1.5B/3B):151936
- 大模型(7B/14B/32B/72B):152064
- 多出的128个token是额外特殊token
修复方式: t_logits = t_logits[..., :student.config.vocab_size] — 直接截断teacher logits的最后一维。
最终v6状态(21:00)
PID: 82143 | Epoch 1/3 | step=50 | loss=6.66 | sft=2.56 | kl=8.42
GPU: 96.1/97.9GB (98%) | 利用率 99% | 温度 70°C
速度: ~1.08s/step | 预计总耗时: ~2.6小时 | 预计完成: ~23:30
新思维模式5:Qwen2.5跨规模蒸馏的vocab_size栅栏
模型规模 <-> vocab_size 对应关系:
≤3B params: vocab_size=151936
≥7B params: vocab_size=152064
蒸馏时必须截断Teacher logits:
t_logits = t_logits[..., :student.config.vocab_size]
tokenizer可以通用(Qwen2.5家族共享tokenizer),但最后一层linear层维度不同。
这是架构层面的不兼容,不是配置问题。
新思维模式6:蒸馏脚本的终极范式(v6已验证)
做法:两个模型同时驻留GPU,即时forward
- num_workers=0(强制单进程,DataLoader不加workers)
- model()参数必须用input_ids/attention_mask
- 即时teacher logits(不存储,每次step forward一次teacher)
- vocab_size截断
显存需求:Teacher(7B)+Student(1.5B)=~18GB / 96GB
7B BF16 ≈ 14GB权重
1.5B BF16 ≈ 3GB权重
+ 优化器状态 + 梯度 + 激活值 ≈ 1-2GB
总计 ≈ 18-20GB,96GB绰绰有余
恢复锚点更新(下次唤醒用)
- SSH密码:
HkM43lFVUIsc· 端口23647 · 用户root - 检查命令:
/root/miniconda3/bin/python3 -c "import re; c=open('/root/autodl-tmp/distill_mother.log').read(); print('step='+re.findall(r'step=(\d+)',c)[-1] if re.findall(r'step=(\d+)',c) else 'no step'); print('Epoch='+('1' if 'Epoch 2' not in c else '2' if 'Epoch 3' not in c else '3')); print('DONE' if 'DONE!' in c else 'running')" - 如果蒸馏完成(日志有DONE!):检查
ls /root/autodl-tmp/output/qwen25-15b-shuangyan-distill/final/ - 如果蒸馏崩溃:
grep -E 'Error|Traceback|RuntimeError|ValueError|CUDA|OOM' /root/autodl-tmp/distill_mother.log - COS密钥:
AKIDkQuBQhoiS2OYXWebXLwMbdT7cvAScbbU/nPoZKArgUJBA4nJenjSxJSQBj5FCj3A4