ModelHub XC 5b07c289be 初始化项目,由ModelHub XC社区提供模型
Model: yujianboisme/qwen2.5-1.5b-darkhorse-code-fine-tuning
Source: Original Platform
2026-09-29 03:31:14 +08:00

license, license_link, language, pipeline_tag, library_name, base_model, tags
license license_link language pipeline_tag library_name base_model tags
apache-2.0 https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct/blob/main/LICENSE
zh
text-generation transformers Qwen/Qwen2.5-1.5B-Instruct
text-generation
qwen2.5
lora
json
instruction-following
chinese
command-translation

darkhorse-code-instruct-1.5b

把一个微调过的 1.5B 小模型,做成编辑器里的「自然语言 → 私域命令」翻译器。 输入一句中文口语,输出一行严格 JSON,不是聊天回复。

输入:关掉倒数第5个文件
输出:{"intent": "command", "cmd": "close -5"}
{"intent": "command", "cmd": "del src/P05-es/pdf2es.py"}                 // 删除类
{"intent": "reject",  "reply": "您没有权限!"}                             // 越权/不合法
{"intent": "chat",    "reply": "你还是好好工作吧,房贷还清了吗?车贷还清了吗?"}  // 非命令输入

本模型是 Qwen2.5-1.5B-Instruct + LoRA(r=16) 指令微调后合并(merged)得到的自包含权重, 不需要 peft、不需要 adapter,transformers / vLLM 直接加载即可。


1. 它解决什么问题

darkhorse-code 是一个编辑器项目,它有一套私域命令语法(open project / close -3 / config add -r k=v …),命令语法固定、可校验,但用户不想背。于是把「用户口语 → 一条标准命令」 这个窄任务交给一个 1.5B 小模型:小、快、可完全本地跑,且输出必须能被程序解析。

它不是通用聊天模型,不会回答知识问题、不会写代码、不会陪你聊天 —— 遇到非命令输入,它的正确行为是回一句 20 字以内的短回复(或一句固定的「劝退」话术)。

2. 输出契约(三种信封)

模型只输出一行 JSON,intent 决定信封形状:

intent 形状 含义
command {"intent":"command","cmd":"<标准命令>"} 翻译成功,cmd 可直接执行
reject {"intent":"reject","reply":"<拒绝原因>"} 命中硬性规则,如路径越权
chat {"intent":"chat","reply":"<≤20字短回复>"} 不是私域命令

两条固定话术(评测按逐字判定):

  • 绝对路径出现在 open file / new / del / rename → 您没有权限!
  • 要求把运行目标存成全局配置 → 运行目标不能保存为全局
  • 闲聊超过 20 字说不完 → 你还是好好工作吧,房贷还清了吗?车贷还清了吗?

3. 命令语法(模型学到的目标空间)

open project <绝对路径> | open file <相对路径>
close project|all|other|left|right|<序号>          # 序号 0 起,负数从右往左,-1 是最后一个
config add|remove|update|get [-g|-p|-r] <key>=<value>   # 未指定层级默认 -p
new file <相对路径> | new folder <相对路径> | new py|rs|md|c <名称>
del <相对路径> | run <名称>=<命令> | run del <名称>
rename <旧相对路径> <新相对路径>
project lang <语言> <绝对路径> | project delete <绝对路径> | project migrate
project edit "<路径>" "<名称>" <语言>

语言取值:unknown/mix/java/c/python/rust/web/golang/document/kotlin(中文表达做了映射:前端→web、Go→golang、文档→document…)。

4. 快速开始(transformers)

⚠️ 两条铁律,违反任一条效果都会明显掉:

  1. 必须使用本模型内置的 system prompt(下面 SYSTEM_PROMPT 原文,逐字), 模型是在这份 prompt 下微调的;换 prompt = 训练/推理不一致。
  2. 必须走模型自带的 chat template(本仓库的 chat_template.jinja), tokenizer.apply_chat_template 会自动读到它。
import json
from transformers import AutoModelForCausalLM, AutoTokenizer

MODEL = "darkhorse-code-instruct-1.5b"   # 换成你的 ModelScope 仓库 id
tok = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype="bfloat16", device_map="auto")

SYSTEM_PROMPT = """你是 darkhorse-code 编辑器的命令翻译器。把用户的话翻译成一条标准命令,只输出一行 JSON。

命令语法:
open project <绝对路径> | open file <相对路径>
close project|all|other|left|right|<序号>
close 的序号从 0 开始,负数从右往左,-1 是最后一个
config add|remove|update|get [-g|-p|-r] <key>=<value>,未指定层级默认 -p
new file <相对路径> | new folder <相对路径> | new py|rs|md|c <名称>
del <相对路径> | run <名称>=<命令> | run del <名称>
rename <旧相对路径> <新相对路径>
project lang <语言> <绝对路径> | project delete <绝对路径> | project migrate
project edit "<路径>" "<名称>" <语言>

规则:
1. 语言取值 unknown/mix/java/c/python/rust/web/golang/document/kotlin;中文表达要映射:Python→python、Go→golang、前端→web、文档→document、混合→mix、未知→unknown。
2. 绝对路径(以 / 或 C:/ D:/ 开头)只允许用在 open project、project lang/edit/delete 和 config 的值里;出现在 open file、new、del、rename 时一律拒绝,reply 固定为「您没有权限!」。
3. 路径里的正斜杠输出一律转成反斜杠。
4. 运行目标只能存项目配置,不能存全局;用户要求存全局时 reply 固定为「运行目标不能保存为全局」。
5. config 的值含空格时用双引号包裹。上下文给出已有运行目标时,新增运行目标索引 = 最大索引 + 1。
6. 不是私域命令的输入(闲聊、问知识、要你写文档)用 chat:reply 不超过 20 字;一句话说不完 20 字时 reply 固定为「你还是好好工作吧,房贷还清了吗?车贷还清了吗?」。
7. 同义命令输出规范形式:删除类写 del,关闭其他文件写 close other,重命名写 rename,新建目录写 new folder,run 的删除写 run del。

输出格式(只输出一行 JSON,不要解释、不要代码块):
{"intent":"command","cmd":"<标准命令>"}
{"intent":"reject","reply":"<拒绝原因>"}
{"intent":"chat","reply":"<短回复>"}"""


def translate(text: str, context: str | None = None) -> dict:
    # 上下文按训练时的约定另起一行:'[上下文] <内容>'
    user = f"[上下文] {context}\n{text}" if context else text
    msgs = [{"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user}]
    prompt = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
    ids = tok(prompt, return_tensors="pt", add_special_tokens=False).to(model.device)
    out = model.generate(**ids, max_new_tokens=128, do_sample=False,   # 贪心,别采样
                         pad_token_id=tok.pad_token_id or tok.eos_token_id)
    raw = tok.decode(out[0][ids["input_ids"].shape[1]:], skip_special_tokens=True).strip()
    return json.loads(raw)          # 建议外面再包一层 try:小模型偶尔会多写一句解释


print(translate("把倒数第三个文件关掉"))
# {'intent': 'command', 'cmd': 'close -3'}
print(translate("加一个运行目标 测试=pytest -q", context="当前项目: D:/Projects/demo"))
# {'intent': 'command', 'cmd': 'run 测试=pytest -q'}

5. vLLM 部署(推荐,OpenAI 兼容)

8 GB 显存(如 RTX 5060 Laptop)即可跑 bf16:

vllm serve ./qwen2.5-1.5b-cmd-merged \
  --served-model-name darkhorse-code-instruct \
  --chat-template ./qwen2.5-1.5b-cmd-merged/chat_template.jinja \
  --dtype bfloat16 --max-model-len 32768 \
  --gpu-memory-utilization 0.85 --max-num-seqs 32 \
  --enable-prefix-caching --port 8001
curl http://127.0.0.1:8001/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "darkhorse-code-instruct",
  "messages": [
    {"role": "system", "content": "<上面那份 SYSTEM_PROMPT 原文>"},
    {"role": "user", "content": "关掉倒数第5个文件"}
  ],
  "temperature": 0, "max_tokens": 128
}'

要点:

  • temperature=0(训练与评测都是贪心解码);不要用 generation_config.json 里的默认采样参数。
  • 请求里的 system 必须是上面那份原文;本项目服务端做薄封装时会把 system 强制替换成它。
  • choices[0].message.content 是契约 JSON 字符串,调用方需要 json.loads。
  • --enable-prefix-caching 对这类场景收益明显:所有请求共享同一份 553 token 的 system prompt 前缀。
  • 需要「输出结构绝对合法」时,可以上受约束解码(JSON Schema 限死三种信封), 代价是采样空间被约束,个别内容可能与贪心输出不同。

实测性能(RTX 5060 Laptop 8G / sm_120,bf16)

同一批 15 条真实指令压测,--max-model-len 32768 --max-num-seqs 32 --enable-prefix-caching:

并发 请求吞吐 生成吞吐 延迟 p50 延迟 p95
1 3.6 req/s 72 tok/s 251 ms 389 ms
8 23.0 req/s 469 tok/s 278 ms 425 ms
32 49.4 req/s 1002 tok/s 337 ms 480 ms

流式 TTFT(并发 1)87 ms;显存占用:权重 3.1 G + KV cache 3.46 GiB(129,680 token ⇒ 32K 上下文可并发 3.96 路)。 参考:同一模型用 CPU + transformers 逐条生成是 4.1–6.4 s/条(约 0.2 req/s)。

在 WSL2 上部署的三个额外环境变量

vLLM 官方只发 Linux 轮子,Windows 用户通常跑在 WSL2 里;此时还需要:

# 1) V2 Model Runner 需要 pinned memory/UVA,而 vLLM 在 WSL2 上默认关掉 pin memory
export VLLM_WSL2_ENABLE_PIN_MEMORY=1
# 2) WSL 里若没有 gcc,Triton 运行时编译 kernel launcher 会报 "Failed to find C compiler"
#    可装 gcc(sudo apt-get install -y gcc g++),或用一个 zig cc 包装脚本当 CC
export CC=/path/to/your/c-compiler
# 3) 默认的 FlashInfer 采样器在缺 cubin 时会 JIT(需要 nvcc);无 CUDA toolkit 时关掉它
export VLLM_USE_FLASHINFER_SAMPLER=0

另外 WSL2 无 CUDA toolkit 时建议 --compilation-config '{"mode":0}'(跳过 torch.compile,启动更快、不需要 C++ 编译器)。

6. 评测

评测集 131 条,全部未出现在训练集模板中的说法(含 27 条刻意构造的硬样本): 中文序数词+负索引、口语化包装(「帮我/麻烦/能不能」)、上下文注入([上下文] 当前项目: …)、 同义命令归一(删除/删掉/移除 → del)、绝对路径拒绝、运行目标索引推断…

指标 基座 Qwen2.5-1.5B-Instruct 本模型(LoRA 微调后)
输出是合法 JSON 131/131 = 100% 131/131 = 100%
与标准命令完全一致(exact) 39/131 = 29.8% 122/131 = 93.1%

基座 JSON 合法率也是 100%,是因为语法和输出格式就写在 system prompt 里 —— 但「知道语法」≠「会用语法」:55 个命令族里有 39 个族的准确率发生变化, 基座在序号、配置、闲聊、拒绝这些地方大面积翻车。

几族的对比(体现微调到底学到了什么):

命令族 基座 本模型 说明
close_index_neg(倒数第 N 个) 0/7 7/7 中文序数词 → 负索引
del(删除/删掉/移除归一) 5/9 9/9 同义命令规范形式
new_typed(new py/rs/md/c 省后缀) 0/4 4/4 类型子命令
run_add_ctx(上下文推索引) 0/5 5/5 max+1 索引推断
reject_abs_dir / reject_abs_file 0/4 / 0/3 4/4 / 3/3 固定拒绝话术
open_project_fwd(正斜杠归一) 0/3 3/3 / → \
chat_long(超 20 字固定话术) 0/3 3/3 逐字背下话术
config_*(add/get/update/remove) 0/1~0/3 多数转正 仍是最弱的一族,见局限

未满分的族(诚实列出,共 8 个):config_get 2/3、config_remove 1/3、 hard/config 2/3、hard/close_index 1/2、hard/open_project 1/2、hard/reject_abs 1/2、 hard/reject_run_global 0/1、chat_short 0/1。 典型错误是配置层级判断:用户说「去掉配置项 X」时,模型输出 config remove -p X, 而标准答案是 config remove -r X(这类「默认层级到底该取谁」的语义在数据里信号不够强)。

7. 输入输出示例(真实推理输出,取自上述评测集)

输入:关掉倒数第5个文件
输出:{"intent": "command", "cmd": "close -5"}

输入:打开 C:/Users/yujia/PycharmProjects/ai-notes
输出:{"intent": "command", "cmd": "open project C:\\Users\\yujia\\PycharmProjects\\ai-notes"}
      (正斜杠已按规则归一为反斜杠)

输入:删除 C:/Users/yujia/PycharmProjects/tmf/src/utils.py
输出:{"intent": "reject", "reply": "您没有权限!"}
      (绝对路径出现在 del 上 → 固定拒绝)

输入:[上下文] 已有运行目标: target3
      加个运行目标 测试=.venv\Scripts\python.exe -m uvicorn app:api --port 8000
输出:{"intent": "command", "cmd": "run 测试=.venv\\Scripts\\python.exe -m uvicorn app:api --port 8000"}

输入:把 darkhorse.code.editor.tab_size 设置成 4,存到运行时
输出:{"intent": "command", "cmd": "config add -r darkhorse.code.editor.tab_size=4"}

输入:帮我建一个 rust 的模块,叫 order_service
输出:{"intent": "command", "cmd": "new rs order_service"}

输入:D:\Projects\Python\Code-Rag 是Kotlin项目
输出:{"intent": "command", "cmd": "project lang kotlin D:\\Projects\\Python\\Code-Rag"}

输入:你觉得我该不该辞职去创业,帮我分析一下利弊
输出:{"intent": "chat", "reply": "你还是好好工作吧,房贷还清了吗?车贷还清了吗?"}

8. 训练细节

项 取值
基座 Qwen/Qwen2.5-1.5B-Instruct
微调方式 LoRA(r=16、alpha=32、dropout=0.05),训练后合并成自包含权重
可训练参数 约 1.18%(其余冻结)
训练数据 1638 条(模板 + 槽位合成,--seed 42 完全可复现,不用 LLM 生成)
超参 lr=2e-4、warmup_ratio=0.03、2 epoch、max_len=704、micro_bs=1 × grad_accum=8 → 410 步
耗时 / 显存 45.1 分钟 / 峰值 7.68 GB(单卡 8 GB 笔记本 GPU)
loss train 0.6818 → 0.0079(末轮均值 0.0319);eval 0.039 → 0.0092
精度 bf16

数据为什么是合成的:这个任务要学的是「照抄」而不是「记住」—— 命令里大量是路径、命令串、 文件名。生成器把它们做成随机槽位,模型必须学会把输入里的路径原样搬进输出,而不是背样本; 同时保证可复现、零 API 成本、不会把外部 LLM 的错误学进去。

三个刻意设计的难点:① 约 15% 样本带上下文行,既要会用上下文补全、也要会忽略无关上下文; ② 随机加「帮我/麻烦/能不能/顺手」前缀与「吧/,谢谢/呀」后缀,放大表达多样性; ③ 27 条硬样本只进评测集,用来量真实泛化而不是量记忆。

9. 已知局限(请务必先读)

  1. 只懂 darkhorse-code 的命令语。换个 CLI/编辑器的命令体系,必须重新造数据微调。
  2. 不是聊天模型:闲聊会被压成 ≤20 字短回复,甚至回那句固定的「劝退」话术 —— 这是设计目标,不是 bug。
  3. config 族最弱(见 §6):配置层级(-g/-p/-r)的默认值判断会出错,接入方应对 config 类输出做二次校验(层级缺失时按自己的业务规则补默认值)。
  4. 输出偶尔会被解析层救回来:小模型可能把 JSON 包在 ```json 代码块里、或路径里写单反斜杠 (非法 JSON 转义)。建议接入方保留「抠 JSON + 修非法转义」的解析兜底,而不是直接 json.loads。
  5. 上下文格式是约定:必须写成 [上下文] <内容> 并另起一行再接用户原话, 这是训练时的格式,别自由发挥。
  6. 1.5B 的常识/推理上限:超出命令翻译的语义理解(比如反讽、多轮澄清)不要指望它。

10. 文件说明

文件 说明
model.safetensors bf16 合并权重(约 2.9 GB),自包含、无需 adapter
chat_template.jinja 模型配套的 chat template,请务必用它(vLLM --chat-template 指向它)
config.json / generation_config.json Qwen2 结构(28 层、2 KV head、max_position_embeddings=32768)
tokenizer.json / tokenizer_config.json / vocab.json / merges.txt / added_tokens.json tokenizer 全套
configuration.json ModelScope 标记文件

11. 许可与致谢

  • 本模型以 Apache-2.0 许可发布,与基座 Qwen/Qwen2.5-1.5B-Instruct 一致; 使用前请同时遵守基座模型的许可条款。
  • 感谢 Qwen 团队开源的 Qwen2.5 系列。
  • 训练所用数据为由模板与槽位程序生成的合成数据,不含任何真实用户数据或第三方隐私内容。
Description
Model synced from source: yujianboisme/qwen2.5-1.5b-darkhorse-code-fine-tuning
Readme 4.3 MiB
Languages
Jinja 100%