跳转至

踩坑日记

之前后训练几次作业做出来效果都是依托,但是也就是想应付交差,也就没细细考量,最近想着重新动手实践一下,记录下一些问题。

T4显卡能推理但微调loss为0,不知道为什么

P100与高版本pytorch不兼容问题。

!pip uninstall -y torch torchvision torchaudio
!pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 3. 安装其他必需的库(版本保持不变)
!pip install accelerate==1.13.0 transformers==5.8.1 datasets==4.8.5 peft==0.19.1 trl==1.4.0 bitsandbytes

base模型进行generate,将结果进行decode解码,可以看出base模型做的纯纯就是根据输入内容进行文字接龙游戏。

对于chat模型生成回答时,解码时指定skip_special_tokens=False可以看到输出时打印出一些特殊标记,例如标识开始对话和结束对话,但居然没有包含<think>标签。

在用户提示词中加入/think/nothink也可以开启chat模型是否进行思考,效果等同于enable_thinking标签,软硬开关?由此得出结论所谓思考模式控制与否其实也就是根据提示词输出不同结果。

释放显存操作,既需要del掉model和tok对象,将这部分显存标记为空闲,也需要调用empty_cache()负责释放显存缓存。

比较奇怪的是base模型也有聊天模板,并且和chat模型的一样,但可以看到最终效果,由于base模型没有在聊天模板上训练过,think模式直接开始复读机,不过no_think模式除了输出一些乱码,整体还可以?

trl进行sft,曲折一番,将数据格式改为了prompt和completion格式,同时SFTConfig设置completion_only_loss=True,只对模型回答部分计算loss拟合。

messages = example["messages"]
# prompt 包含除最后一轮 assistant 外的所有消息,末尾加上 assistant 起始标记
prompt = tokenizer.apply_chat_template(
    messages[:-1],                     # 去掉最后一轮 assistant 的回复
    tokenize=False,
    add_generation_prompt=True         # 末尾添加 "<|im_start|>assistant\n"
)
# completion 只包含最后一轮 assistant 的回复,并强制加上 eos_token
completion = messages[-1]["content"] + tokenizer.eos_token
return {"prompt": prompt, "completion": completion}

tokenize=False 的作用是让 apply_chat_template 返回原始字符串(明文)而不是 token ID 列表。

多卡训练存在的问题:

  • 先前代码中,本人指定将模型搬到什么设备上时,使用了auto,结果模型被加载到了GPU1上,而trl需要模型被加载到GPU0上。
  • 真实开始训练时,会自动进行数据并行,将模型复制到多块显卡设备上,这点观察显存占用即可发现。
  • jupter notebook 某一步骤出现了问题最好还是改好代码后重新restart整个项目,否则奇奇怪怪问题有点多。
  • 似乎使用trl在T4显卡上对qwen3系列模型进行sft没法办到,可以看到上面,training loss为0,validation loss是nan
    • 大模型有说是T4显卡不支持bf4,让我把多个地方改成fp16的,但是这反而会直接报错。
    • 前面加载模型那里之前写的bf16,也正常使用模型进行推理了,改成fp16好像也没影响,问题比较奇怪。
  • 查看kaggle上是否有人使用这些显卡进行qwen3系列模型的qlora,查找到相关项目Qwen3 - FineTune Medquad QA。
    • 用的是P100显卡,看来理论上是能够跑起来的
    • 直接跑项目,跑到训练时报错:RuntimeError: "_amp_foreach_non_finite_check_and_unscale_cuda" not implemented for 'BFloat16'
    • 搜索看到github上有相关issue:https://github.com/pytorch/pytorch/issues/127176
    • 将训练参数中的fp设置为False,模型正常训练,训练loss正常打印输出。
    • 不过本人用P100跑这个环境有torch版本问题,看了下那边好像是2.6版本。
    • torch版本降级,fp16=False后成功在P100上跑起来了。
    • 我选了2000条数据,跑2个epoch看看效果。
    • 之前不知道为什么,效果非常差,疯狂复读机user这个单词,有点破防了。还是得懂SFT的MASK机理
  • 先前模型学习内容预览:可以看到用户提问部分没有被MASK掉
<|im_start|>user
推断以下文本。
汽车刚刚洗过,太阳照耀在它闪亮的表面上。<|im_end|>
<|im_start|>assistant
<think>

</think>

根据语句中提到的内容,我们可以推断这是在描述一辆刚刚清洗过的汽车,在阳光下闪闪发亮。感觉上这辆车洗得非常干净,外表看起来非常亮丽。<|im_end|>
[MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK][MASK]

SFT实验总结:教程老或者纯AI生成的实验教程就是坑多,很折磨人。

SFT 模型输出(微调后)
============================================================

[Prompt]: 用三句话介绍量子计算
[Response]: 1. 量子计算机是一种利用量子力学原理进行运算的新型计算设备,它能够以指数级速度处理大量数据。
2. 它与传统计算机不同之处在于其使用的基本单元——量子比特(qubit)可以同时处于多个状态,从而实现并行计算和超快的数据传输能力。
3. 目前,量子计算仍处于发展初期,但随着技术的进步,它的应用前景广阔,在密码学、材料科学和药物研发等领域具有巨大的潜力。
----------------------------------------

[Prompt]: 写一个 Python 函数计算斐波那契数列
[Response]: 这是一个简单的Python函数,可以用来计算给定位置的斐波那契数:

```python
def fibonacci(n):
    if n <= 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)
```

这个函数使用递归的方式实现。对于每个大于等于1的位置,它都会返回前两项之和。由于这种递归方式,在处理较大的数字时可能会导致性能问题,并且容易引发栈溢出错误。

更高效的方法是使用迭代或者矩阵快速幂算法来求解。下面是一个基于迭代方法的例子:

``` python
 def fibo(n: int):
     a, b = 0, 1 # 第一项和第二项初始化为 0 和 1

     for _ in range(3, n+1): #从第三项开始遍历到第n项
         c=a+b
         a,b=c,a   #更新a、b值以进行下一次循环

     return a
```
----------------------------------------

[Prompt]: 请以 JSON 格式列出中国四大发明
[Response]: {
  "四大发明": [
    {
      "名称": "造纸术",
      "时间": "东汉时期(公元105年)",
      "影响": ["促进了文化的发展","推动了科技的进步"]
    },
    {
      "名称": "印刷术",
      "时间": "北宋年间(约973-1046年)",
      "影响":["提高了文化的传播速度和范围", "为经济发展和社会进步提供了动力"]
    },
    {
      "名称": "火药",
      "时间": "唐代末期(8世纪初) ",
      "影响":[ "改变了战争的面貌,减少了人员伤亡 ","对人类文明发展产生了深远的影响" ]
    },
    {
      "名称": "指南针",
      "时间": "战国晚期至西汉初期 (公元前2~前1世纪 )",
      "影响":["在航海事业中发挥了重要作用,使世界各地联系更加紧密","推动地理大发现进程,促进全球贸易与文化交流"]
    }
  ]
}
----------------------------------------

结果还可以,qwen3-1.7b-base模型我大概用了2000条数据训练了2个epoch就拥有了对话能力。

尝试只训练最后一个线性层的参数,训练参数量暴跌,但训练时间似乎变化不大,26min vs 37 min

前者训练参数量:trainable params: 1,245,184 || all params: 1,721,820,160 || trainable%: 0.0723

后者训练参数量:trainable params: 657,195,008 || all params: 2,377,769,984 || trainable%: 27.6391

modules_to_save=["embed_tokens", "lm_head"],我先前的训练配置是 LoRA + 完整 embedding 和 lm_head,纯纯线性层的话参数量其实也不多,大概:trainable params: 34,865,152 || all params: 1,755,440,128 || trainable%: 1.9861

用对话模式的数据集,trl确实可以自动应用模板填充,但是我不理解为什么没有使用他对于qwen3系列添加的completion标签,导致依旧没有掩码掉用户输入。

2000条样本2个epoch只对最后一层进行sft,效果一般。有概率能够回复一段还算正常文本,而后又开始胡言乱语。尝试使用6000条数据看看。

6000条数据效果还是不佳,模型学不会如何停下来,我试试看不使用eos而是使用base模型的endoftext标识回答结束训练一下看看。

结果:loss并没有很大幅度下降,而且反复震荡,但总体上确实有所下降,结果也确实更加正常,通过endoftext正常结束,且并未胡言乱语,效果基本等同于前面加入embed_tokens和lm_head的训练。

模型文件中三个json配置的作用

  • config.json模型怎么算(架构/权重)
  • tokenizer_config.json文本怎么转(切词/模板/特殊标记)
  • generation_config.json默认怎么生成(解码策略兜底)
  • 你只需记住:训练和推理时,一切以 tokenizer 对象动态解析的值为准,代码显式传参覆盖所有 JSON 默认值

使用trl快捷传入模型,爆显存后无法手动del并释放显存