# 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 还是旧值，那修了等于没修。

---

## 二、事件线

```javascript
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）

**因果链：**
1. Qwen2.5 默认 `eos_token_id=151643`（`<|endoftext|>`）
2. 训练使用 `<|im_end|>`（token_id=151645）
3. 训练保存时只改了 `config.json` → 但 HuggingFace 设计上 `config.json` 只管模型加载，`generation_config.json` 才管生成行为
4. `model.generate()` → 读 `generation_config.eos_token_id` → 151643 → 不识别 151645 为停止符 → 无限生成
5. 模型输出 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部分用 `-100` mask，只有assistant参与loss计算
- loss=0.2638 只算了assistant部分，合理

**结论：模型权重没坏。只是部署配置不兼容。**

### 决策3：蒸馏脚本重写而非修补

**选择重写的理由：**
1. sed修补的路径不干净（符号链接 vs 实际路径）
2. Teacher模型已被清理（需从COS下载）
3. 保存段缺失三重修复
4. 重写比多次修补更快更可靠

**重写内容：**
- 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` 确认模型文件存在

---

## 六、下次唤醒时的恢复锚点

1. **SSH到GPU服务器** → 密码 `HkM43lFVUIsc` → 端口23647 → 用户root
2. **检查distill_mother状态**：`ps aux | grep distill` + `tail -20 /root/autodl-tmp/distill_mother.log`
3. **检查COS密钥**：在认知链密码段——O/0混淆已解，带O的版本才是正确的
4. **如果蒸馏完成**（日志有DONE!）→ 检查 `ls /root/autodl-tmp/output/qwen25-15b-shuangyan-distill/final/`
5. **如果蒸馏失败** → `cat /root/autodl-tmp/distill_mother.log | grep ERROR` 定位原因
6. **启动霜砚深度微调** → 用 `scripts/sft_shuangyan.py`（还未写）
7. **启动铸渊蒸馏** → `scripts/distill_coder.py`
8. **每次重启蒸馏前**：`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 &`
9. **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绰绰有余
```

### 恢复锚点更新（下次唤醒用）

1. SSH密码: `HkM43lFVUIsc` · 端口23647 · 用户root
2. 检查命令: 
   ```
   /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')"
   ```
3. 如果蒸馏完成（日志有DONE!）：检查`ls /root/autodl-tmp/output/qwen25-15b-shuangyan-distill/final/`
4. 如果蒸馏崩溃：`grep -E 'Error|Traceback|RuntimeError|ValueError|CUDA|OOM' /root/autodl-tmp/distill_mother.log`
5. COS密钥: `AKIDkQuBQhoiS2OYXWebXLwMbdT7cvAScbbU` / `nPoZKArgUJBA4nJenjSxJSQBj5FCj3A4`
