开发过程中遇到的问题、排查过程和解决方案。每次遇到新问题都追加到这里。
- 首次运行
training/train.py,电脑直接关机(无错误日志) - 降低参数后重跑,报
CUBLAS_STATUS_INTERNAL_ERROR - 加
CUDA_LAUNCH_BLOCKING=1后确认真实错误为CUDA error: out of memory
Qwen3.5-9B 使用混合注意力架构(标准注意力 + gated delta rule 线性注意力)。其线性注意力的 torch 回退实现会将 q/k/v/beta/g 全部转为 float32:
# transformers/models/qwen3_5/modeling_qwen3_5.py:250
x.transpose(1, 2).contiguous().to(torch.float32) for x in (query, key, value, beta, g)这导致注意力计算的显存占用翻倍,RTX 5080 16GB 即使 batch=1 + seq_len=512 也 OOM。
优化路径需要 flash-linear-attention + causal-conv1d,但这两个库都依赖 triton,而 triton 不支持 Windows。
| 方案 | 结果 |
|---|---|
| 降 seq_len 2048→1024→512 | OOM,fp32 转换是根本瓶颈 |
| 降 LoRA r 64→32 | 无济于事,瓶颈不在 LoRA |
pip install flash-linear-attention --no-deps |
装了个空壳,反而导致模型无法加载(No module named 'fla.modules') |
pip install causal-conv1d |
编译失败,Windows 上找不到 nvcc |
换基座模型为 Qwen3-8B。Qwen3 使用标准 Transformer 架构,不依赖 triton,QLoRA 兼容性好。
- 选基座模型时必须确认其训练时的依赖兼容性,不只是推理
- 混合架构(Qwen3.5、Mamba 等)在 Windows + 消费级 GPU 上微调要谨慎
CUBLAS_STATUS_INTERNAL_ERROR不一定是驱动问题,加CUDA_LAUNCH_BLOCKING=1能暴露真实错误- 电脑突然关机 ≈ GPU OOM 导致驱动崩溃,先查显存再查电源/散热
训练脚本报错:TypeError: SFTConfig.__init__() got an unexpected keyword argument 'max_seq_length'
TRL 1.0.0 将 max_seq_length 改名为 max_length。很多网上教程和旧版代码用的是 max_seq_length。
# 错误
sft_config = SFTConfig(max_seq_length=1024, ...)
# 正确(TRL 1.0.0)
sft_config = SFTConfig(max_length=1024, ...)查参数名的方法:
from trl import SFTConfig; import inspect
sig = inspect.signature(SFTConfig.__init__)
print([p for p in sig.parameters if 'max' in p or 'length' in p])- TRL 版本迭代快,参数名会变,不要盲信教程,先查当前版本的签名
- 同时注意
warmup_ratio也已 deprecated,改用warmup_steps
huggingface_hub 已安装(1.8.0),但 PowerShell 中运行 huggingface-cli 报"无法识别为 cmdlet"。
python -m huggingface_hub.commands.cli 也报 ModuleNotFoundError: No module named 'huggingface_hub.commands'。
huggingface_hub 1.8.0 使用 typer 作为 CLI 框架,入口点脚本没有正确安装到 conda 环境的 Scripts/ 目录下。这在 Windows conda 环境中偶发。
不用 CLI,直接用 Python API:
import os
from huggingface_hub import snapshot_download
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
snapshot_download(repo_id='Qwen/Qwen3-8B', local_dir='models/Qwen3-8B', token='hf_xxx')- Windows conda 环境下 CLI 入口点经常出问题,Python API 是更可靠的替代
pip install --force-reinstall huggingface_hub有时能修复,但不保证
现象:不设 HF_ENDPOINT,直接连 huggingface.co 下载,速度只有 400KB/s-2MB/s,还会被警告 "unauthenticated requests"。
解决方案:
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com' # 国内镜像
token = 'hf_xxx' # 加 token 提高速率限制现象:下载 safetensors 分片(~4-5GB/个)时反复报 The read operation timed out 和 peer closed connection without sending complete message body,需要多次重试才能完成。
根因:hf-mirror.com 的 xethub CDN 对大文件连接不稳定,超时阈值较短。
解决方案:
snapshot_download自带断点续传(resume_download在新版已默认开启),断了重跑即可- 多跑几次,每次会跳过已完成的文件,逐步下完
- token 有助于减少限速导致的超时
现象:Qwen3.5-9B 实际约 18GB,但进度条显示总大小从 19.3G → 38.6G → 52.6G → 57.9G 不断增长。
根因:snapshot_download 的进度条是所有线程的累计字节数,包含了重试下载的重复计数。不影响实际结果,文件大小是正确的。
现象:在 PowerShell 中粘贴带缩进的多行 python -c "..." 命令,报 IndentationError: unexpected indent。
解决方案:确保 python -c " 后面的代码行顶格写,不要有前导空格:
# 错误(有缩进)
python -c "
import os
print('hello')
"
# 正确(顶格)
python -c "
import os
print('hello')
"现象:之前设置的 $env:HF_ENDPOINT 在新终端窗口中失效。
根因:$env:VAR = "value" 只对当前 PowerShell 会话有效,关闭即丢失。
解决方案:
- 每次新终端重新设置:
$env:HF_ENDPOINT = "https://hf-mirror.com" - 或者直接在 Python 代码里用
os.environ['HF_ENDPOINT'] = ...(推荐,不依赖终端状态)
cd D:\Workspace\Projects\HuChatFun && python training/train.py
报错:标记"&&"不是此版本中的有效语句分隔符。
Windows PowerShell 5.x 不支持 &&(PowerShell 7+ 才支持)。
- 用
;代替:cd D:\Workspace\Projects\HuChatFun; python training/train.py - 或者先
cd,再单独运行命令
Qwen3-8B QLoRA 训练 100 步正常(loss 3.8→2.47),但第 100 步触发 eval 时 OOM:
File "transformers/loss/loss_utils.py", line 55, in ForCausalLMLoss
logits = logits.float()
torch.AcceleratorError: CUDA error: out of memory
Checkpoint 也没保存上(save 和 eval 在同一步触发,eval 先执行就崩了)。
训练时 gradient checkpointing 节省显存,但 eval 不用 gradient checkpointing,需要把完整 logits 张量转 float32。Qwen3 词表 ~152K tokens,logits 大小 = 152K × 1024(seq_len) × 4 bytes ≈ 600MB,加上模型本身占用,16GB 不够。
关掉训练中的 eval,训练完单独评估:
sft_config = SFTConfig(
eval_strategy="no", # 不在训练中 eval
...
)- 大词表模型(>100K)在消费级 GPU 上训练时,eval 容易 OOM
save_steps和eval_steps不要设成同一个值,否则 eval 崩了 checkpoint 也丢- 如果一定要训练中 eval,可以设
eval_accumulation_steps=1分批计算
V1 模型(Qwen3-8B QLoRA, 3 epoch)部署到 Ollama 后:
- Q4_K_M 量化版:直接输出乱码("嗯嗯嗯嗯"、"说说说说"、重复输入问题)
- f16 版:开头 1-2 句正常且有户晨风风格,但之后进入重复循环("苹果是好,苹果是好,苹果是好")
- 训练数据过长被截断:平均 26.3 轮/条,seq_len=1024 截断后模型没见过对话结尾,不知道何时停止
- 3 epoch 过拟合:1581 条数据跑 3 遍,过拟合了直播语料中的重复口头禅模式
- Qwen3 对 Q4_K_M 量化敏感:LoRA 微调后的权重分布不适合激进量化,f16 明显好于 Q4
- Ollama 缺少 chat template:合并模型时 tokenizer_config.json 丢失了 chat_template,需在 Modelfile 中手动指定 TEMPLATE
- Qwen3 thinking 模式干扰:在模板中加
<think>\n\n</think>反而更差(训练数据没有 think tokens)
V2 数据重构 + 重新训练:
- 加入 2024 年数据,增加多样性
- 每条对话限制 ≤8 轮,确保完整结尾在 token 范围内
- 训练改为 1 epoch,seq_len=2048
- 部署使用 f16 或 Q6_K 量化
- Modelfile 中手动指定 ChatML TEMPLATE + stop tokens
- 微调后的模型对量化更敏感,先用 f16 验证效果再量化
- chat template 必须在 Modelfile 中明确指定,不能依赖 GGUF 内嵌
- 训练数据的长度分布必须和推理时的使用场景匹配(短对话场景就训短对话)
- Qwen3 的 thinking 模式需要特殊处理,如果训练数据没用 think tokens 就不要在推理时加
V2 模型(1 epoch / 2667 条 / 8 轮上限 / seq_len=2048)相比 V1 有明显改善:
- 偶尔产出高质量回复("iPhone要5000,你月薪3000,买不起,这是事实,你明不明白啊?")
- 学到了户晨风风格词("纯纯的笑话"、"你给我记住了"、"你这个逻辑是错的")
- 但大约 70% 的回复仍然进入重复循环或回显输入
| 配置 | 结果 |
|---|---|
| mirostat 2 + repeat_penalty 1.8 | 最好,偶尔出金句 |
| temperature 0.5 + repeat_penalty 2.0 | 太压制,只会回显输入 |
| temperature 0.7 + repeat_penalty 1.3 | 有好有坏,不稳定 |
| temperature 0.7 + repeat_penalty 1.5 | 差不多,不稳定 |
用 transformers 直连(temperature=0.7, repetition_penalty=1.3)能生成较长的连贯文本,说明模型本身有能力,但 Ollama 的模板或采样实现可能有差异。
重复循环是 LoRA 微调的固有局限,不是参数能解决的:
- LoRA 容量不足:r=32 只占 0.37% 参数,不足以完全覆盖基座模型的生成行为
- 数据量仍然不够:2667 条 × 1 epoch ≈ 模型只见过每条数据一次,风格印记不够深
- 缺少负反馈:SFT 只教模型"说什么",没教"不要重复"(需要 DPO/RLHF)
- 提高 LoRA rank(r=64 或 r=128)增加微调容量
- 增加到 2 epoch(在过拟合和欠拟合之间找平衡)
- 换用 Qwen2.5-7B-Instruct(指令遵循能力更成熟,可能更鲁棒)
- 加 DPO 训练阶段,专门惩罚重复生成
- 在训练数据中加入
<|im_end|>后的截断信号,强化停止行为
pip install unsloth 成功后,torch.cuda.is_available() 返回 False。
Unsloth 依赖 torch<2.11.0,pip 从 PyPI 拉取了 CPU 版 torch 2.10.0 替换了原来的 CUDA 版 torch 2.11.0+cu128。PyPI 默认的 torch 包是 CPU 版。
安装 Unsloth 后,强制重装 CUDA 版 torch:
pip install --force-reinstall torch torchvision --index-url https://download.pytorch.org/whl/cu128虽然会产生 Unsloth 的版本兼容性警告(requires torch<2.11.0),实测 torch 2.11.0+cu128 可以正常使用。
- 安装 Unsloth 后必须检查
torch.cuda.is_available() - pip 的
--index-url只对该次安装生效,后续安装其他包时 pip 会从 PyPI 拉取 CPU 版 torch pip install torch不会重装(认为版本已满足),必须--force-reinstall
Unsloth SFTTrainer 初始化时报错:
UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f9a5' in position 0
Unsloth 的日志输出含有 🦥(树懒 emoji),Windows 默认终端编码是 GBK,无法编码 emoji。
在脚本最顶部加:
import sys
if sys.platform == "win32":
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")或者在命令行设环境变量:PYTHONIOENCODING=utf-8 python train.py
- Windows 上跑 Unsloth 必须处理编码问题
os.environ["PYTHONIOENCODING"]运行时设无效(Python 启动时就确定了 IO 编码),要用sys.stdout.reconfigure
使用 messages 格式数据时,Unsloth 的 SFTTrainer 报错:
RuntimeError: Unsloth: You must specify a `formatting_func`
Unsloth 重写了 SFTTrainer,不像原生 trl 那样自动处理 messages 格式。需要显式提供格式化函数或预处理数据。
在数据集上预先做 map,手动拼 ChatML 格式文本:
def messages_to_text(item):
parts = []
for msg in item["messages"]:
parts.append(f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>")
return {"text": "\n".join(parts)}
train_ds = Dataset.from_list(data).map(messages_to_text)不要用 tokenizer.apply_chat_template,Qwen3 的模板在 Unsloth 环境下可能报 dict object has no element 0。
- Unsloth 的 SFTTrainer 和原生 trl 的 API 不完全兼容
- 最稳妥的方式是直接给 dataset 加
text字段,避免依赖 formatting_func
model.save_pretrained_gguf() 合并权重成功,但转 GGUF 阶段报错:
RuntimeError: llama.cpp folder 'C:\Users\Merlin\.unsloth\llama.cpp' does not exist
RuntimeError: [FAIL] Command `pip install gguf protobuf sentencepiece mistral_common` failed with error
`WARNING: Failed to remove contents in a temporary directory '...\~umpy.libs'`
Unsloth 的 GGUF 导出会尝试:
- 用 winget 安装 cmake、VS Build Tools、OpenSSL
- 克隆并编译 llama.cpp 到
~/.unsloth/llama.cpp - 安装 gguf 等 Python 包
在 Windows 上第 2 步 git clone 失败(目录不存在),第 3 步 pip install 因为临时目录锁定也失败。即使依赖都装上了,Windows 编译 llama.cpp 也经常出问题。
不用 Unsloth 的 GGUF 导出。save_pretrained_gguf 在合并权重阶段已经成功,safetensors 文件已保存。用项目里现有的 llama.cpp 手动转:
python tools/llama.cpp/convert_hf_to_gguf.py models/huchat-merged-v5 --outtype f16 --outfile models/huchat-merged-v5/huchatfun-v3.1-f16.gguf- Unsloth 的
save_pretrained_gguf在 Linux 上很方便,但 Windows 上不可靠,不要依赖 - 好在合并权重(
save_pretrained_merged的逻辑)在 GGUF 转换之前执行,即使后面失败了权重也不会丢 - Windows 项目统一用
tools/llama.cpp转 GGUF,不要每次都尝试编译新的