最近在rk上玩了一下部署Qwen3-1.7B 实现知识点内容总结,效果勉强还看得过去。打算微调一下0.6B的模型,降低部署成本的同时得到更好的总结效果。
1. 数据准备
之前有一批ASR提取的视频语音文本数据,我把提取出的多个文本内容合并到一个json文件中大致结构如下图

剩下的就很简单了,找一个好一点的模型让它总结知识点作为输出。这里构建数据集采用的是Alpaca
格式,然后用Go
写了一个调用百炼api的代码,最后得到最终训练数据集。
go
const (
inFile = "./all_texts.json"
outFile = "./ft_data.json"
apiURL = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
token = "百炼的api-key"
model = "qwen3-235b-a22b"
systemPrompt = "你是一个内容知识点总结专家,你需要从大段文字中提炼出核心知识点内容并返回,请注意返回的内容不得超过20个字"
apiKey = "Bearer " + token
)
func post(text string) {
userMessage := Message{Role: "user", Content: text}
InstructionMessage := Message{Role: "system", Content: systemPrompt}
query := Query{
Model: model,
Messages: []Message{InstructionMessage, userMessage},
EnableThinking: false,
}
queryJSON, _ := json.Marshal(query)
option := &grequests.RequestOptions{
JSON: queryJSON,
Headers: Headers,
}
resp, err := grequests.Post(context.Background(), apiURL, grequests.FromRequestOptions(option))
if resp.StatusCode == http.StatusOK {
fmt.Println(resp)
if err != nil {
fmt.Println(err)
}
var result Response
if err := json.Unmarshal(resp.Bytes(), &result); err != nil {
fmt.Println(err)
}
output := OutItem{
Instruction: systemPrompt,
Input: text,
Output: result.Choices[0].Message.Content,
}
outChannel <- output
}
}
func main() {
defer goPool.Release()
var wg sync.WaitGroup
for _, inText := range inTexts {
wg.Add(1)
_ = goPool.Submit(func() {
defer wg.Done()
post(inText.Text)
})
}
go func() {
wg.Wait()
close(outChannel)
}()
var results []OutItem
for o := range outChannel {
results = append(results, o)
}
outRaw, _ := json.MarshalIndent(results, "", " ")
_ = os.WriteFile(outFile, outRaw, 0o644)
}
这里ants 的goroutine
池大小不能太大,不然会触发限流。总的来说请求响应还是很快的,最后得到了训练数据集,大概长这个样子

2. 利用unsloth微调
unsloth其实有非常好的文档来实现简单的QLora微调,可以参考示例。唯一美中不足的就是unsloth开源版本并不支持单机多卡训练,不过还好有些开源补丁或者开源分支实现了单机多卡训练,这里我就拿unsloth-multigpu举例(下文称为multi)。由于multi利用补丁实现,所以几乎不影响原本的unsloth微调代码,只需要引入multi的hook就可以实现多卡训练。但是就今天的使用来说,由于作者一直在更新,所以感觉api并不稳定,给出示例中的许多api和最新版本的貌似不匹配。不过家里也只有一张显卡,所以先用unsloth微调试试,等回公司再试试加上multi训练。微调的大致代码如下
ini
model, tokenizer = FastLanguageModel.from_pretrained(
"../Qwen3-0.6B",
max_seq_length = 2048,
dtype = torch.bfloat16,
load_in_4bit = True
)
model = FastLanguageModel.get_peft_model(
model,
r=16, # LoRA rank
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_alpha=16,
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth",
random_state=2025,
use_rslora=False,
loftq_config=None,
)
training_args = TrainingArguments(
output_dir="./runs",
dataloader_num_workers=4,
num_train_epochs=30,
per_device_train_batch_size=8,
gradient_accumulation_steps=1,
learning_rate=1e-4,
bf16=True,
logging_steps=10,
save_strategy="epoch",
warmup_ratio=0.1,
optim = "adamw_8bit",
lr_scheduler_type = "linear",
weight_decay=0.01, # Weight decay
max_grad_norm=1.0, # Gradient clipping
report_to = "tensorboard"
)
trainer = SFTTrainer(
model=model,
train_dataset=train_dataset,
args=training_args,
)
logger.info("Starting training...")
trainer_stats = trainer.train()
logger.info("Training complete!")
logger.info(f"Training statistics: {trainer_stats}")
model.save_pretrained_merged("finetune_0.6B", tokenizer, save_method = "merged_16bit")
其中lora相关、epochs等训练参数都没怎么特殊设置,微调过程截图


3. 推理结果对比
输入相同的内容送入原始0.6B模型和0.6B微调后的模型分别进行推理,对比二者输出
swift
[
{
"origin_outputs": "\n\n核心知识点提炼:电极反应式书写步骤(正负极判断、离子移动口诀、调频电荷、配平、总反应等)\n</think>\n\n电极反应式书写方法",
"ft_outputs": "assistant\n<think>\n\n</think>\n\n电极反应式书写方法:正负极判断、电极反应架构书写、电荷移动及总反应式书写。"
},
{
"origin_outputs": "\n\n好的,我需要将用户提供的关于二战的详细内容提炼出核心知识点,不超过20个字。首先,用户的内容涵盖了二战的起因、过程、关键事件和相关理论。核心点包括:德国在1939年入侵波兰,英法宣战,二战爆发,以及相关的历史事件和理论。\n\n需要确保提炼的内容准确且简洁,同时涵盖主要知识点。检查是否有重复或冗余的信息。例如,用户提到了丘吉尔的批评和慕尼黑协议,以及地理学理论的影响。但核心知识点应聚焦于时间线和关键事件",
"ft_outputs": "\n\n</think>\n\n第二次世界大战爆发及转折点"
},
{
"origin_outputs": "\n\n好的,用户发来的是关于酸碱中和滴定图像题的课程内容介绍。需要从大段文字中提炼核心知识点,不超过20个字。\n\n首先,用户提到"延续上一节课",说明这节课是继续之前的课程内容。接着提到"图像题",所以核心知识点应该是关于酸碱中和滴定图像题的内容。需要简明扼要地总结,确保不超过字数限制。\n\n可能的提炼是:"酸碱中和滴定图像题教学"。检查字数,刚好20个字,符合要求。确认没有冗余信息,",
"ft_outputs": "assistant\n<think>\n\n</think>\n\n酸碱中和滴定图像题知识点总结。"
},
{
"origin_outputs": "\n\n答案:\n20字\n</think>\n\n20字",
"ft_outputs": "\n\n</think>\n\n借鉴名句修辞手法,增强文采与说服力。"
}
]
我在apply_chat_template
中已经设置了enable_thinking=False
,但原始的0.6B还是会出现思考甚至莫名奇妙的答案,相比而言微调后的输出就正常许多。