“私有”意味着数据不出内网,“轻量”意味着单卡或消费级显卡能跑。这条路不需要从头预训练,正确姿势是:选一个 1B–8B 的开源基座 → QLoRA 微调注入领域能力 → 量化压缩 → 本地推理服务 → 评估闭环。下面给出每一环的可运行代码。
选型三原则:许可证可商用(Apache-2.0 优先)、中文能力够用、生态工具齐(transformers / llama.cpp 都支持)。常见候选:Qwen2.5-1.5B/7B、Llama-3.1-8B、MiniCPM。显存预算:7B 模型 QLoRA 微调约需 16–24GB,1.5B 只需 8GB。
pip install torch transformers peft bitsandbytes datasets accelerate trl
# 量化与部署
pip install llama-cpp-python vllm fastapi uvicorn私有大模型的价值全在数据。指令数据统一成 {"instruction", "input", "output"},用基座自带的 chat template 拼接,不要自己手写特殊 token。
from datasets import load_dataset
def to_chat(example, tokenizer):
msgs = [
{"role": "system", "content": "你是公司内部技术助手,只依据给定资料回答。"},
{"role": "user", "content": example["instruction"] + "\n" + example.get("input", "")},
{"role": "assistant", "content": example["output"]},
]
text = tokenizer.apply_chat_template(msgs, tokenize=False)
ids = tokenizer(text, truncation=True, max_length=2048)
# 只对 assistant 部分算 loss:把 prompt 段 label 置 -100
prompt_ids = tokenizer(
tokenizer.apply_chat_template(msgs[:-1], tokenize=False, add_generation_prompt=True)
)["input_ids"]
ids["labels"] = [-100] * len(prompt_ids) + ids["input_ids"][len(prompt_ids):]
return ids数据量不必大:500–3000 条高质量样本,远胜 10 万条噪声。务必留 10% 做评估集。
4-bit 量化基座 + 低秩适配器,只训练 1% 左右的参数,单卡可跑。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer, SFTConfig
BASE = "Qwen/Qwen2.5-1.5B-Instruct"
tok = AutoTokenizer.from_pretrained(BASE, trust_remote_code=True)
bnb = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
BASE, quantization_config=bnb, device_map="auto", trust_remote_code=True,
)
model = prepare_model_for_kbit_training(model)
lora = 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)
model.print_trainable_parameters() # 约 1%
ds = load_dataset("json", data_files="data/train.jsonl")["train"]
ds = ds.map(lambda x: to_chat(x, tok), remove_columns=ds.column_names)
trainer = SFTTrainer(
model=model, train_dataset=ds,
args=SFTConfig(
output_dir="out/lora", num_train_epochs=3,
per_device_train_batch_size=2, gradient_accumulation_steps=8,
learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03,
logging_steps=10, save_strategy="epoch", bf16=True,
gradient_checkpointing=True, max_length=2048,
),
)
trainer.train()
model.save_pretrained("out/lora"); tok.save_pretrained("out/lora")超参经验:学习率 1e-4~2e-4、r=8~32、epoch 2~3。过拟合表现为评估集 loss 回升、回答开始复读训练原句。
部署前把 LoRA 合并回基座,再转 GGUF 做 4-bit 量化,体积可压到原模型的 1/4。
from peft import PeftModel
from transformers import AutoModelForCausalLM
base = AutoModelForCausalLM.from_pretrained(BASE, torch_dtype=torch.bfloat16)
merged = PeftModel.from_pretrained(base, "out/lora").merge_and_unload()
merged.save_pretrained("out/merged"); tok.save_pretrained("out/merged")# 转 GGUF 并量化(llama.cpp)
python convert_hf_to_gguf.py out/merged --outfile out/model-f16.gguf
./llama-quantize out/model-f16.gguf out/model-q4_k_m.gguf Q4_K_MQ4_K_M 是质量/体积的平衡点;若对精度敏感,用 Q5_K_M 或 8-bit。
轻量场景用 llama.cpp 起 OpenAI 兼容服务,内网零依赖;高并发换 vLLM。
python -m llama_cpp.server --model out/model-q4_k_m.gguf \
--host 0.0.0.0 --port 8000 --n_ctx 4096 --n_gpu_layers -1 \
--chat_format qwenfrom openai import OpenAI
cli = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="none")
def ask(q: str) -> str:
r = cli.chat.completions.create(
model="local",
messages=[{"role": "system", "content": "你是公司内部技术助手。"},
{"role": "user", "content": q}],
temperature=0.2, max_tokens=512,
)
return r.choices[0].message.content外网隔离时它就是一台内网 API;配合 FastAPI 加鉴权、限流、日志即可交付。
微调后必须量化对比:基座 vs 微调后,在留出集上人工打分 + 规则校验。
def score(pred, ref):
# 简易指标:关键词命中 + 长度合理 + 无复读
hit = sum(k in pred for k in ref["keys"]) / max(len(ref["keys"]), 1)
rep = 1 - pred.count(pred[:20]) / max(len(pred) // 20, 1) # 复读惩罚
return 0.7 * hit + 0.3 * max(rep, 0)
base_avg = sum(score(ask_base(c["q"]), c["ref"]) for c in cases) / len(cases)
ft_avg = sum(score(ask(c["q"]), c["ref"]) for c in cases) / len(cases)
print(f"base={base_avg:.3f} finetuned={ft_avg:.3f}")同时记录首 token 延迟、吞吐(tokens/s)、显存占用——私有部署的验收标准是“业务可用 + 成本可算”。
数据格式不跟 chat template 对齐导致效果暴跌;全参数微调硬上消费卡;量化后不重测;只测“看起来对”不做留出集;忽略 -100 掩码导致模型学会复述问题。记住:从 0 到 1 的难点从来不是训练脚本,而是数据质量与评估纪律。把这两件做扎实,1.5B 的小模型在内网场景完全可以替代通用大模型。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。