在基于 Qwen2.5-coder 模型进行继续预训练(continual pre-trian)后,保存的模型权重,多了整整一倍(原始 Qwen2.5-coder 的 3b 模型是 5 个 GB,训练后保存的 safetensor 体积是 10 多个 GB)。刚训练完就发现这个问题了,由于用 vllm 加载是能正常推理的且当时有些其他事情,就搁置没处理,这两天要准备部署相关的事情,就调查一下模型权重异常的问题。
有两个可能的因素会导致模型权重在训练前后发生变化:
- 权重中为 0 的值的数量发生了变化
- 保存的精度发生了变化
第一个原因不太可能导致整个模型的体积增倍,所以优先调查模型保存精度的问题。
Qwen2.5 系列模型应该使用的 fp16 精度来保存,我先使用了如下代码打印模型的精度:
from transformers import AutoModelmodel = AutoModel.from_pretrained("PATH")
print(model)
with open("qwen2.5-coder-3b-model_parameters.txt", "w") as f:for name, param in model.named_parameters():f.write(f"Layer: {name}\n")f.write(f"Shape: {param.shape}\n")f.write(f"Dtype: {param.dtype}\n")f.write(f"Number of parameters: {param.numel()}\n")f.write("---\n")
运行后发现训练前后的 Shape 和 Dtype 都是完全相同的,这就说不通了,莫非是 from_pretrained
方法默认指定了加载精度(torch_dtype)?
换一个方式查看模型的精度:
from safetensors import safe_openwith safe_open("PATH.safetensors", framework="pt") as f:for tensor_name in f.keys():tensor = f.get_tensor(tensor_name)print(f"Tensor: {tensor_name}")print(f"Data type: {tensor.dtype}")
果然结果发生了变化:
#训练前的精度
Data type: torch.bfloat16# 训练后的精度
Data type: torch.float32
由于训练框架使用的是 llama-facotry
,所以先检查训练参数有没有问题。目前只能查到一个训练参数是跟精度有关的:
bf16: true
但显然结果没有按预期的来,要么是有什么参数没有配置,要么就是 llama-factory 的 bug。
先简单地转换一下精度:
from transformers import AutoModel
import safetensors.torchmodel = AutoModel.from_pretrained("PATH")
model = model.half()
safetensors.torch.save_file(model.state_dict(), 'new_model.safetensors')
但这样出来的模型有两个问题:
- 模型精度是 fp16,虽然用于推理没问题,但是要后续继续训练的话,就需要是 bf16,不容易出现梯度消失或爆炸,更稳定
- 模型只有单个 safetensor 文件,不方便使用
换一种方式保存:
import torch
from transformers import Qwen2ForCausalLMpath = "PATH"
model = Qwen2ForCausalLM.from_pretrained(path)
model = model.to(dtype=torch.bfloat16)
model.save_pretrained("NEW_PATH")
这样保存的结果是一个文件夹,包含了 safetensor, config.json, generation_config.json 等主要的文件,可以用 vllm 直接推理。但是当我去运行推理的时候,vllm 又报错了:
OSError: Can't load tokenizer for '...'. If you were trying to load it from 'https://huggingface.co/models', ma ke sure you don't have a local directory with the same name. Otherwise, make sure '...' is the correct path to a directory containing all relevant files for a Qwen2TokenizerFast tokenizer.
好吧,tokenizer 也是需要单独保存的,修改代码如下:
import torch
from transformers import AutoTokenizer, Qwen2ForCausalLMpath = "PATH"
model = Qwen2ForCausalLM.from_pretrained(path)
tokenizer = AutoTokenizer.from_pretrained(path)
model = model.to(dtype=torch.bfloat16)
model.save_pretrained("NEW_PATH")
tokenizer.save_pretrained("NEW_PATH")
这样改下来,模型就能顺利使用 vllm 加载推理了。