这篇教程带你把 GLM-5.3-Flash 从通用对话模型,改造成一个能在端侧运行的结构化决策模型。完成后你会得到三样东西:一份从历史决策日志合成的 JSONL 训练集;一个 LoRA 适配器,可合并并量化成端侧引擎能加载的格式;一套离线评测脚本,输出 JSON 合法率、动作完全匹配率和动作混淆矩阵。这里的“Jev 式”指一种专用决策风格:输入当前状态,模型只输出一个结构化动作,例如点击哪个控件、输入什么内容、是否等待,而不是生成一段解释文字。
前置条件清单
- 一张用于微调的 NVIDIA GPU。显存要求以官方文档当前版本为准,模型越大、序列越长,占用越高。
- Python 环境,以及 PyTorch、transformers、peft、datasets、trl 或 LLaMA-Factory 等工具链。版本以官方文档当前版本为准。
- GLM-5.3-Flash 的基座权重与 tokenizer,从官方渠道获取。
- 至少几百到几千条历史决策日志。每条日志包含当时的状态观察、采取的动作、执行结果。
- 一个明确的动作空间。先定 schema,再洗数据,否则后面会反复返工。
- 端侧推理引擎,例如 llama.cpp、ONNX Runtime、MLC 或 ExecuTorch。按目标设备选。
- 一份人工标注的 hold-out 测试集,用来做离线评测。训练集和测试集按时间切分,避免泄漏。
第 1 步:定义决策 Schema
先定义一个模型必须遵守的 JSON 结构。字段不要多,端侧解析越简单越好。
```json
{
"action": "click",
"target": "confirm_button",
"params": {},
"confidence": 0.87
}
```
动作空间可以先用枚举固定下来,例如:
```python
ACTIONS = {
"click",
"input",
"scroll",
"back",
"wait",
"finish"
}
```
target 是控件标识或语义名称,params 放输入文本、滚动方向等附加信息。confidence 可选,端侧做阈值回退时有用。AI 词典:系统提示词">系统提示词写死一句:只输出 JSON,不要解释,不要 Markdown 代码块。
第 2 步:从决策日志合成训练数据
假设原始日志是 JSONL,每行类似:
```json
{"ts":"2025-01-01T10:00:00","observation":{"screen":"order_confirm","user_intent":"确认下单"},"action":{"action":"click","target":"confirm_button","params":{}},"result":"success"}
```
先过滤失败、无效、重复的记录,再做脱敏。然后把每条日志转成指令微调样本。下面是一段可直接改路径运行的 Python:
```python
import json
from pathlib import Path
def log_to_sample(rec):
obs = rec["observation"]
act = rec["action"]
instruction = "根据当前状态输出一个 JSON 决策,不要输出其他内容。"
input_text = json.dumps(obs, ensure_ascii=False)
output_text = json.dumps(act, ensure_ascii=False)
return {"instruction": instruction, "input": input_text, "output": output_text}
samples = []
for line in Path("decision_logs.jsonl").read_text(encoding="utf-8").splitlines():
rec = json.loads(line)
if rec.get("result") != "success":
continue
samples.append(log_to_sample(rec))
n = len(samples)
train, test = samples[:int(n * 0.8)], samples[int(n * 0.8):]
Path("train.jsonl").write_text(
"\n".join(json.dumps(x, ensure_ascii=False) for x in train),
encoding="utf-8"
)
Path("test.jsonl").write_text(
"\n".join(json.dumps(x, ensure_ascii=False) for x in test),
encoding="utf-8"
)
```
只保留成功动作还不够。建议加入两类纠错样本:一是从错误决策中构造“给定同样状态,应输出正确动作”;二是格式修复样本,例如输入一段带解释的脏输出,目标输出是干净 JSON。样本量少时,纠错样本对格式稳定性的帮助通常比较明显。
第 3 步:数据校验与格式转换
训练前先校验字段,避免把脏数据喂进模型。
```python
import json
ACTIONS = {"click", "input", "scroll", "back", "wait", "finish"}
REQUIRED = {"action", "target", "params"}
def valid_decision(obj):
if not isinstance(obj, dict):
return False
if not REQUIRED.issubset(obj.keys()):
return False
if obj["action"] not in ACTIONS:
return False
if not isinstance(obj["params"], dict):
return False
return True
bad = 0
with open("train.jsonl", encoding="utf-8") as f:
for line in f:
rec = json.loads(line)
out = json.loads(rec["output"])
if not valid_decision(out):
bad += 1
print("非法样本数:", bad)
```
如果微调框架要求 ShareGPT 或 Alpaca 格式,按官方文档转换。关键点是:训练和推理必须使用同一个 chat template。模板不一致是端侧决策模型掉点的常见原因。
第 4 步:LoRA 微调
用 LLaMA-Factory 这类工具可以减少样板代码。下面是一份 YAML 配置示例,路径和模型名按你的环境替换:
```yaml
model_name_or_path: /path/to/glm-5.3-flash
stage: sft
do_train: true
finetuning_type: lora
lora_rank: 16
lora_alpha: 32
lora_dropout: 0.05
lora_target: q_proj,v_proj,k_proj,o_proj
dataset: decision_sft
template: glm
cutoff_len: 1024
per_device_train_batch_size: 2
gradient_accumulation_steps: 8
learning_rate: 1.0e-4
num_train_epochs: 3
output_dir: outputs/decision-lora
```
lora_target 要按模型实际结构填写,以官方文档当前版本为准。端侧决策任务数据量通常不大,rank 不要一上来就设很高,先跑小 rank,看验证集是否过拟合。训练时建议只对 assistant 输出部分计算 loss,输入状态部分不参与损失,这样模型更容易学会“状态进、动作出”的映射。
第 5 步:合并与量化导出
训练完成后,先把 LoRA 合并回基座。不同工具命令不同,按官方文档操作。合并后用端侧引擎支持的格式导出。以 llama.cpp 为例,通用流程是:
```bash
python convert_hf_to_gguf.py /path/to/merged-model --outfile decision-f16.gguf --outtype f16
./llama-quantize decision-f16.gguf decision-q4_k_m.gguf q4_k_m
```
具体脚本名和参数以官方仓库当前版本为准。如果目标端是 ONNX Runtime,可以用:
```bash
optimum-cli export onnx --model /path/to/merged-model --task text-generation-with-past onnx_decision/
```
导出后先别急着上端侧。先在桌面环境用同一份测试集跑一遍 fp16 和量化版本,确认掉点在可接受范围内。量化会带来精度损失,决策任务对格式敏感,需要重点看 JSON 合法率。
第 6 步:端侧推理封装
端侧推理需要一个薄封装:拼 prompt、调用模型、解析 JSON、失败回退。
```python
import json
from llama_cpp import Llama
llm = Llama(model_path="decision-q4_k_m.gguf", n_ctx=1024, n_threads=4)
SYSTEM = "你是一个端侧决策器。只输出 JSON,不要解释。"
def decide(observation: dict) -> dict:
prompt = f"{SYSTEM}\n状态:{json.dumps(observation, ensure_ascii=False)}\n决策:"
out = llm(prompt, max_tokens=128, temperature=0.0, stop=["\n\n"])
text = out["choices"][0]["text"].strip()
try:
return json.loads(text)
except json.JSONDecodeError:
start = text.find("{")
end = text.rfind("}")
if start >= 0 and end > start:
return json.loads(text[start:end + 1])
return {"action": "wait", "target": "", "params": {}}
```
温度设成 0,减少随机性。max_tokens 不要太小,否则 JSON 可能被截断。回退动作建议用“等待”或“无操作”,避免误触。
第 7 步:离线评测
评测指标至少看四个:JSON 合法率、动作完全匹配率、字段级匹配率、端侧延迟。下面是一段评测骨架:
```python
import json
from collections import Counter
def exact_match(pred, gold):
return (
pred.get("action") == gold.get("action")
and pred.get("target") == gold.get("target")
)
def evaluate(model_fn, test_path):
total = json_ok = match = 0
confusion = Counter()
with open(test_path, encoding="utf-8") as f:
for line in f:
item = json.loads(line)
gold = json.loads(item["output"])
obs = json.loads(item["input"])
total += 1
try:
pred = model_fn(obs)
json_ok += 1
except Exception:
pred = {"action": "wait", "target": ""}
if exact_match(pred, gold):
match += 1
confusion[(gold["action"], pred["action"])] += 1
print(f"JSON 合法率: {json_ok / total:.3f}")
print(f"完全匹配率: {match / total:.3f}")
for k, v in confusion.items():
print(k, v)
```
同时跑一个规则基线,例如“看到确认按钮就点击”。如果模型还不如规则基线,说明数据或 schema 有问题。再跑一次基座模型,对比微调前后的提升幅度。
常见坑与排错
- 数据泄漏:训练集和测试集有时间重叠,指标虚高。按时间切分,不要随机切分。
- 动作不平衡:某些动作样本极少,模型只输出多数类。可以重采样、增广,或合并长尾动作。
- JSON 不合法:用约束解码、正则提取、重试机制;训练集里加入格式修复样本。
- 过拟合:验证 loss 上升时早停,减小 LoRA rank,增加 dropout。
- 量化掉点:先测 fp16,再测量化版;如果量化后掉点明显,换量化策略或保留更高精度。
- 端侧内存不足:减小
n_ctx、缩短 prompt、降低量化位数。 - chat template 不一致:训练和推理使用同一模板,特殊 token 要对齐。
- 特殊 token 设置错误:确认 pad、eos、bos 与官方配置一致。
下一步建议
当单步决策稳定后,可以尝试多步决策:让模型输出动作序列,并在端侧用状态机执行。还可以引入工具调用,把“查询天气”“读取剪贴板”等能力做成结构化函数。若想进一步提升,可以用更大模型蒸馏决策数据,或者用 DPO 类方法优化偏好。最后,把线上失败样本定期回流到训练集,形成“日志—微调—评测—上线—回流”的闭环。评测脚本和 schema 建议纳入版本管理,每次模型更新都跑同一套离线评测,避免凭感觉判断好坏。
