106 lines
2.9 KiB
Markdown
106 lines
2.9 KiB
Markdown
# Day 2 Deep Dive: Dataset 到 Loss 的路径
|
||
|
||
## 深挖问题 1:PretrainDataset 做了什么?
|
||
|
||
代码位置:`dataset/lm_dataset.py` 的 `PretrainDataset`
|
||
|
||
流程:
|
||
|
||
```text
|
||
sample["text"]
|
||
-> tokenizer(..., add_special_tokens=False)
|
||
-> [BOS] + tokens + [EOS]
|
||
-> pad 到 max_length
|
||
-> labels = input_ids.clone()
|
||
-> labels[PAD] = -100
|
||
```
|
||
|
||
关键点:
|
||
|
||
- pretrain 让模型学习所有非 PAD token 的 next-token prediction。
|
||
- BOS/EOS 也参与训练,帮助模型学习开始和结束。
|
||
|
||
## 深挖问题 2:SFTDataset 为什么更复杂?
|
||
|
||
代码位置:`SFTDataset.create_chat_prompt` 和 `SFTDataset.generate_labels`
|
||
|
||
SFT 输入包含 system/user/assistant/tool 等角色。训练目标不是复述完整对话,而是让模型在看到上下文后生成 assistant 输出。
|
||
|
||
因此:
|
||
|
||
- user/system/tool context 作为条件。
|
||
- assistant 内容作为监督目标。
|
||
- 非 assistant token label 设为 `-100`。
|
||
|
||
## 深挖问题 3:`generate_labels` 如何找到 assistant span?
|
||
|
||
代码里构造:
|
||
|
||
```python
|
||
self.bos_id = tokenizer(f'{tokenizer.bos_token}assistant\n', add_special_tokens=False).input_ids
|
||
self.eos_id = tokenizer(f'{tokenizer.eos_token}\n', add_special_tokens=False).input_ids
|
||
```
|
||
|
||
然后扫描 input_ids:
|
||
|
||
```text
|
||
找到 assistant 起点
|
||
-> 从 assistant 内容开始
|
||
-> 直到 eos
|
||
-> 这些 token 的 label = input_ids[j]
|
||
-> 其他位置 label = -100
|
||
```
|
||
|
||
你要特别注意:模板变化会直接影响这段扫描逻辑。如果 tokenizer template 改了,但 `bos_id/eos_id` 匹配没改,SFT loss mask 就可能错。
|
||
|
||
## 深挖问题 4:Tool Call 样本为什么要完整保留?
|
||
|
||
`pre_processing_chat` 里:
|
||
|
||
```python
|
||
if any(conv.get('tools') for conv in conversations): return conversations
|
||
```
|
||
|
||
原因:
|
||
|
||
- tool use 样本依赖 system 中的 tools schema。
|
||
- 随机加 system prompt 可能破坏工具调用上下文。
|
||
- tool call 训练要求模板结构严格。
|
||
|
||
## 今日小实验
|
||
|
||
不修改文件,运行:
|
||
|
||
```bash
|
||
python - <<'PY'
|
||
from transformers import AutoTokenizer
|
||
from dataset.lm_dataset import SFTDataset
|
||
|
||
tokenizer = AutoTokenizer.from_pretrained("model")
|
||
sample = {
|
||
"conversations": [
|
||
{"role": "user", "content": "你好"},
|
||
{"role": "assistant", "content": "你好,我是 MiniMind。"}
|
||
]
|
||
}
|
||
|
||
ds = SFTDataset("dataset/sft_t2t_mini.jsonl", tokenizer, max_length=128)
|
||
prompt = ds.create_chat_prompt(sample["conversations"])
|
||
ids = tokenizer(prompt).input_ids[:128]
|
||
labels = ds.generate_labels(ids)
|
||
print(prompt)
|
||
for i, (tid, lab) in enumerate(zip(ids, labels)):
|
||
tok = tokenizer.decode([tid])
|
||
flag = "LOSS" if lab != -100 else "MASK"
|
||
print(i, repr(tok), flag)
|
||
PY
|
||
```
|
||
|
||
如果数据文件不存在,可以先等下载后再做。
|
||
|
||
## 复盘问题
|
||
|
||
- assistant 的 eos 是否参与 loss?为什么?
|
||
- 如果 `max_length` 在 assistant 回答中间截断,会发生什么?
|
||
- 多轮对话中每个 assistant span 是否都参与 loss?
|