samai-8b-M8 / PLAYBOOK.md
tchbcb's picture
M10 avoidance playbook (public copy)
e876cf0 verified
|
Raw History Blame Contribute Delete
7.03 kB
# Samai-8B 训练管线避坑手册(PLAYBOOK)
> 2026-09-21 M10 灾后重建会话沉淀。每条坑都有真实事故背书, 配避坑代码模板。
> 配套工具库: `samai_kit.py`(远程 python 进程用, 正常函数作用域)
> ⚠️ 唯一例外区: **信封 payload 内联代码**必须全平铺(见 §2)
## §1 通道层: 信封协议
- 信封 POST text/plain → `/execute?respenc=b64url&timeout=N`
- **坑: 网关 ~180s 硬超时** → 同步信封只做 <120s 的活
- **避坑**: 长任务 = 信封只负责"写脚本 + Popen 分离点火", 然后轮询日志
```python
import subprocess
subprocess.Popen(['bash', '/tmp/k8b/xxx_chain.sh'], start_new_session=True)
print('FIRED')
```
- **坑: exec 槽偶发静默失败** → 点火后必须 sleep 3 + ps 验证进程存在
## §2 远程 exec 槽编码规范(最重要)
exec(code) 的 globals/locals 分离, 三个连招坑:
1. `def` 函数体看不到顶层 import 和变量 → 函数内必须重新 `import subprocess`
2. **顶层生成器表达式/列表推导也翻车**(`sorted(x for x in ...)` 报 name not defined)
3. 嵌套函数互相不可见(rstr 在 rval 里 undefined)
**避坑三原则(内联 payload 专用)**:
- 原则A: 全平铺, 不写 def; 复用逻辑用 for 循环 + 普通 if/append
- 原则B: 每个循环体里用的模块, 在循环体前重新 import(或干脆顶层 import 且不用函数)
- 原则C: 结构复杂 → 写进 .py 文件再 `python3 /tmp/k8b/xxx.py`(文件内代码是正常作用域, 可用 def/推导式)
## §3 资产下载规范
- **坑: 并发 `curl -C -` 写同一文件** → 文件交错损坏(本会话 M9 GGUF 膨胀到 9.37G)
避坑: 下载前 `flock` 或保证单实例; 见 samai_kit.dl()
- **坑: 只需要大文件的局部**(如 GGUF 里某段张量)→ 全量下载浪费
避坑: HTTP Range `curl -r START-END`(本会话 5.78G 文件只拉 151MB 片段)
前提: 精确计算字节区间并校验 `size == end-start+1`
- **坑: tar 包内目录前缀**(bin/llama-server 解压成 bin/bin/llama-server)
避坑: 解压后必须 `test -x` 目标二进制, 失败则 flatten `mv inner/* outer/`
- **坑: snapshot_download 私仓** → `repo_type='model'`(没有 'private' 这个值)
- 下载完必校验: size 断言 + sha256
## §4 磁盘预算规范(57G 容器)
- **事故: base(19.3G) + merged(18.5G) + bf16 GGUF(18.5G) 共存 = 爆盘**
- 避坑流程化:
1. 每阶段点火脚本里 `df -h / | tail -1` 打印
2. 中间产物**用完即删**: merge 目录 convert 完 `rm -rf`; f16 GGUF quantize 完 `rm -f`
3. 大文件可用Range 拉, 不落全量
4. 预算表先算再跑: 训练期 base19.3+ckpt1 ≈ 21G; 收尾期 base19.3+merged18.5+q4 5.5 峰值 44G(逐步删回 25G)
5. 重建会话最优路径: **能拉产物就不重建过程**(拉 5.5G GGUF 而不是重下 19.3G base 重训 1.5h)
## §5 GGUF 手术规范(字节外科)
- 头布局: `magic(4) version(4) tensor_count(u64@8) kv_count(u64@16)`
**坑: patch 偏移写错一位 → kv_count 被改 → 'string length 2^58' 假错误**
- **坑: header_end 必须在"读完 KV + 全部 tensor infos"之后取 f.tell()**
(本会话把 KV 尾当 header 尾 → 411 条 info 被截断 → 'failed to read tensor info')
- tensor info 结构: `u64 name_len + name + u32 n_dims + u64 dims[nd] + u32 type + u64 offset`
- offset 是**相对数据区起点**(align32(header_end)); 追加 info 只需平移数据区起点
- 张量数据区间连续时: 起止 = 数据区起点 + 首尾张量 offset/size
- **避坑断言(每次手术必带)**:
```python
assert nt == 427 and names[-1] == 'blk.31.post_attention_norm.weight'
assert os.path.getsize(OUT) == new_data + expect_end_rel
```
- 手术后必 smoke: server 加载 + 1 题推理验证内容正确(不只 health OK)
## §6 serving/评测规范
- **坑(假 0.0% 元凶): llama.cpp 0.4.x 默认 reasoning 分离** — `<think>` 未闭合(截断)时
全文进 `reasoning_content`, `content` 为空 → 评测器提取不到 → pred=None 全 0
M8/M9 的"真实 HLE 0.0%"疑似此机制假 0
- **双保险**: ① server 加 `--reasoning-format none` ② eval 客户端拼接:
```python
m = d['choices'][0]['message']
txt = (m.get('content') or '') + '\n' + (m.get('reasoning_content') or '')
```
- **坑: 评测 max_tokens 不足** → think 不闭合 → 兜底行是垃圾 → 即使 format none 也低分
HLE 口径: max_tokens 1024 时 ~20% 题截断; 对照实验需 2048
- **坑: 端口/进程混战**(旧 server 活着 → 新 server 绑定失败 → health 误测旧实例 → 新 eval 打到旧 server)
避坑: 点火链开头 `pkill -9 -f llama-server` + `ss -ltnp | grep PORT` 验证 FREE → 起 → ps 断言唯一实例
- health grep 宽松匹配 `ok` 即可, 但 smoke 必须验证**输出内容**(如 17*23=391)
## §7 进程管理规范
- 一切后台任务: `nohup ... > log 2>&1 &` + `start_new_session=True`(Popen)
- 每条链独立日志文件, 轮询用 `tail -c N`(UTF-8 由 python decode(errors='replace') 处理)
- **坑: tail -c 切断多字节 UTF-8** → 解码端 errors='replace'
- 链内关键节点 echo 标记(HEALTH_OK / EVAL_RC / CHAIN_DONE)→ 轮询 grep 标记
## §8 灾备规范
- **产物尽早推仓**(本会话 M9 产物靠上会话推仓才免于重建)
- 里程碑即推: adapter 每epoch / GGUF+sha256 / 报告 json
- 私仓 LFS 满仓只读 → 产物推公仓; 公私分工: 私仓=脚本文档, 公仓=权重+报告
- 推送脚本自带重试(5 次) + 等待循环(产物未生成就位时轮询最多 100 分钟)
- 重建优先级: 公仓产物 > 私仓脚本重跑 > 从头再来
## §9 训练规范(M10 实证)
- **fp16=False 铁律**: Trainer fp16 的 GradScaler(65536) 放大溢出 → grad_norm=nan → 永久跳步零学习
冻结 base 只训 LoRA 时根本不需要 fp16
- nan 自杀保护: 前 4 步 3 次 loss=0/nan → 写 TRAIN_NAN + 停机(防毒 adapter 混入 final/)
- 停机后重启必须先清 `final/ TRAIN_DONE TRAIN_NAN` 毒文件
- QLoRA 环境: peft 0.19.1 与 torchao 0.10 冲突(dispatch ImportError)→ 卸 torchao
- 数据前缀 mask: prefix = 非 assistant 消息的公共前缀 token 数 → labels 前缀置 -100
- 自杀保护误触发复盘: run 自杀停机后脚本无早退检查 → 未训练 adapter 存成 final → 误 merge
## §10 M11 前瞻(多模态 + computer use)
- 训练: AutoModelForImageTextToText + AutoProcessor; LoRA regex `.*language.*\.(q|k|v|o|gate|up|down)_proj`
防误挂 ViT 的 q/k/v; vision tower + merger 显式 freeze
- merge: ConditionalGeneration 全栈(保视觉塔); convert 双产物: f16 GGUF + --mmproj
- serving: `llama-server -m q4.gguf --mmproj mmproj.gguf`
- RFT: playwright 无头 chromium, DOM 断言自动判分; 截图 ≤1120px 长边控视觉 token 预算
- 骨架四件套已上私仓: s8_m11_t4_train_v6.py / s8_m11_cu_env.py / s8_m11_merge_v3.py / s8_m11_serving_chain.sh