写在前面
最近想自己微调一个小模型玩玩,选了 Qwen3-0.6B,主要是因为穷(划掉)。试了好几个平台,最后发现 Modal 的 P100 还挺好用的,不过余额烧得也挺快。后来发现 Kaggle 有免费的 P100 可以用,就是要验证手机号,稍微麻烦点,但白嫖真香。
折腾了一晚上总算是跑通了,踩了不少坑,写个教程记录下,也给后来人省点时间。
1. 环境准备
先检查下 GPU
这步别忘了,不然跑起来发现是 CPU 那就哭了:
import torchprint("torch.cuda.is_available():", torch.cuda.is_available())print("cuda device count:", torch.cuda.device_count())!nvidia-smi如果输出 False,那就赶紧去 Kaggle 设置里开启 GPU,或者检查下 Modal 的配置。
装依赖
这里有个坑,transformers 版本很重要。我一开始用的最新版结果各种报错,后来降到 4.44.2 就好了:
pip install --no-cache-dir --force-reinstall --no-deps \ "protobuf==3.20.3" \ "transformers==4.44.2" \ "accelerate==0.33.0"--force-reinstall 是为了确保版本对,别偷懒省略这个参数。
2. 数据准备
去哪找数据?
说实话,找合适的数据集挺费劲的。我的建议是直接去 HuggingFace 搜 “sft” 或者 “chinese sft”,能找到一大堆。
我用的是这个思维链数据集,质量还不错:
from datasets import load_dataset
ds = load_dataset("Jackrong/Chinese-Qwen3-235B-Thinking-2507-Distill-100k")下载的时候可能会比较慢,耐心等等。Kaggle 上下载速度还行,Modal 有时候会快一些。
数据长什么样?
这个数据集挺有意思的,有三个字段:
Input: 用户的问题CoT_content: AI 的”内心独白”(思考过程)Answer_content: 最后给用户看的回答
就是那种让模型学会”先思考再回答”的套路,挺酷的。
3. 开始微调
加载模型
import osimport torchfrom transformers import AutoModelForCausalLM, AutoTokenizer
# 这个配置能避免显存碎片化,建议加上if torch.cuda.is_available(): os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
model_name = "Qwen/Qwen3-0.6B"device = "cuda" if torch.cuda.is_available() else "cpu"
tokenizer = AutoTokenizer.from_pretrained(model_name)model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16 if device == "cuda" else torch.float32,).to(device)model.train()第一次下载模型会比较慢,大概 1GB 多,可以去泡杯咖啡。
数据预处理(重点!)
这部分是最容易出错的,我当时在这卡了好久:
from transformers import DataCollatorForLanguageModeling
# 先取一小部分数据试试,别一上来就全量train_size = 1000raw_train = ds["train"].shuffle(seed=42).select(range(train_size))
def preprocess_function(example): """ 把数据转成 Qwen3 能懂的格式 """ user_input = example["Input"] cot = example["CoT_content"] answer = example["Answer_content"]
# 关键在这里,用 <think> 标签包裹思考过程 assistant_content = f"<think>{cot}</think>{answer}"
messages = [ {"role": "user", "content": user_input}, {"role": "assistant", "content": assistant_content}, ]
# 应用聊天模板,记得开启 thinking text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=False, enable_thinking=True, # 这个很重要! )
tokenized = tokenizer( text, truncation=True, max_length=1024, # 根据你的数据调整 ) tokenized["labels"] = tokenized["input_ids"].copy() return tokenized
# 处理数据tokenized_train = ds["train"].map( preprocess_function, remove_columns=ds["train"].column_names,)
data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=False,)配置训练参数
这里的参数我调了好几次才找到比较合适的:
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments( output_dir="qwen3-0.6b-finetuned", per_device_train_batch_size=1, # P100 显存有限,只能设 1 gradient_accumulation_steps=4, # 累积 4 步相当于 batch_size=4 learning_rate=2e-5, # 不要太大,容易炸 num_train_epochs=1, # 一个 epoch 就够了 fp16=True, # 混合精度,省显存 gradient_checkpointing=True, # 这个也很重要,再省点显存 logging_steps=10, # 每 10 步打印一次 loss save_steps=50000, # 我不想频繁保存 save_total_limit=2, report_to="none", # 不用 wandb 之类的)
trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_train, data_collator=data_collator,)开始训练!
# 深呼吸,开始trainer.train()
# 保存模型trainer.save_model("qwen3-0.6b-finetuned")tokenizer.save_pretrained("qwen3-0.6b-finetuned")1000 条数据大概要跑 20-30 分钟,可以去刷刷手机。记得看着点,别让 Kaggle 断连了。
4. 部署成 API(可选)
训练完了想测试效果,可以起个简单的 API 服务:
装依赖
pip install -U fastapi uvicorn[standard] nest_asyncio写个简单的服务
import torchfrom fastapi import FastAPIfrom pydantic import BaseModelfrom transformers import AutoTokenizer, AutoModelForCausalLM
device = "cuda" if torch.cuda.is_available() else "cpu"
# 加载刚训练好的模型tokenizer = AutoTokenizer.from_pretrained("qwen3-0.6b-finetuned")model = AutoModelForCausalLM.from_pretrained( "qwen3-0.6b-finetuned", torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32,)model.to(device)model.eval()
app = FastAPI()
class GenerateRequest(BaseModel): prompt: str max_new_tokens: int = 128 temperature: float = 0.7 top_p: float = 0.9
@app.post("/generate")async def generate(req: GenerateRequest): messages = [{"role": "user", "content": req.prompt}] text = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, )
inputs = tokenizer(text, return_tensors="pt").to(device)
with torch.no_grad(): output_ids = model.generate( **inputs, max_new_tokens=req.max_new_tokens, do_sample=True, temperature=req.temperature, top_p=req.top_p, pad_token_id=tokenizer.eos_token_id, )
gen_ids = output_ids[0][inputs["input_ids"].shape[-1]:] output_text = tokenizer.decode(gen_ids, skip_special_tokens=True) return {"output": output_text}启动服务
import nest_asyncioimport uvicorn
nest_asyncio.apply()uvicorn.run(app, host="0.0.0.0", port=8000)然后就可以用 curl 或者 Postman 测试了。
5. 如果显存不够…
我知道很多人跟我一样都是穷学生,只能用免费的 GPU。如果上面的方法还是爆显存,试试 QLoRA:
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_trainingfrom transformers import BitsAndBytesConfig
# 4-bit 量化,超级省显存bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.bfloat16,)
model = AutoModelForCausalLM.from_pretrained( model_name, quantization_config=bnb_config, device_map="auto",)
model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True)
lora_config = LoraConfig( r=16, lora_alpha=32, lora_dropout=0.05, bias="none", task_type="CAUSAL_LM", target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],)
model = get_peft_model(model, lora_config)用这个方法,理论上 6GB 显存都能跑。
我踩过的坑
- transformers 版本问题:最新版有时候会出奇怪的 bug,建议用稳定版
- 忘记设置 enable_thinking=True:导致模型学不到思维链,白训练了
- batch_size 设太大:直接 OOM,老老实实用 1 吧
- 学习率太高:模型直接崩了,loss 变成 NaN
- Kaggle 自动断连:记得时不时动动鼠标或者刷新页面
一些建议
- 刚开始先用小数据集(100-500 条)快速迭代,确认流程没问题
- 定期看看 loss,如果不降了就可以提前停止
- 保存好中间的 checkpoint,万一训练崩了还能恢复
- 训练完记得在验证集上测试,别过拟合了
总结
整个流程其实不复杂,主要就是:
- 准备好数据
- 正确处理成模型能吃的格式
- 调好参数开始训练
- 祈祷别 OOM
0.6B 这个模型虽然小,但用几千条高质量数据微调后,在特定领域表现还是挺不错的。关键是快,本地跑都行,不用一直盯着云平台的余额。
有问题欢迎在评论区问,我看到了会回复的。祝大家微调顺利,少踩坑!
Some information may be outdated









