【昇腾】本地 27B 编程模型接入 Claude Code 实战:Qwopus3.6-27B-Coder 在 vLLM-Ascend 上的部署与踩坑
四张 910B4 跑 27B 模型,接入 Claude Code,效果比预期要好——虽然慢
一、为什么是 Qwopus3.6-27B-Coder?
项目背景
Qwopus 是一个由社区开发者 Jack Rong 自费训练的开源模型系列,名字是 Qwen + Opus 的组合——用高质量推理轨迹对 Qwen 基座模型做微调,目标是在本地硬件上达到接近 Claude Opus 的推理和编程能力。
这个系列经过多轮迭代,目前最新版本是 Qwopus3.6,基于阿里刚发布的 Qwen3.6-27B 稠密模型,使用 GSPO(Generalized Supervised Preference Optimization)训练方法。其中 Coder 变体在 SWE-bench Verified 上达到了 67.0%(无 thinking 模式,Q5_K_M 量化),和 Claude 4.0 Opus 的 67.6% 非常接近,作为本地模型来说相当亮眼。
为什么选择这一版?
Qwopus 项目以 GGUF 格式为主要分发形式,生态上主要面向 llama.cpp 部署。这个路线的优势是量化版本丰富(Q4_K_M / Q5_K_M / Q6_K / Q8_0),单卡即可运行。但我手头是 4 张 Ascend 910B4(各 64GB HBM),llama.cpp 目前对昇腾的支持还不够成熟。
另一边,昇腾上最成熟的推理框架是 vLLM-Ascend,它对 Hugging Face 原生的 safetensors 格式(即 BF16 权重)支持最好。Qwopus3.6-27B-Coder 在 Hugging Face 上同时发布了 BF16 版本,因此最终的选择是:
使用 BF16 版 Qwopus3.6-27B-Coder,通过 vLLM-Ascend 部署在 4×910B4 上
这个选择的一个额外好处是:Qwen 本身就是 vLLM 社区支持最完善的架构之一,各种高级特性(prefix caching、async scheduling、tool call 等)都能直接使用。
二、vLLM-Ascend 部署
部署本身并没有花太多时间。vLLM-Ascend 官方提供了 openEuler 基础镜像 quay.io/ascend/vllm-ascend:v0.20.2rc1-openeuler,Qwen3.6 的架构在其中已经得到了原生支持,直接拉起容器即可。
关键启动参数:
sudo docker run -d --name vllm-qwopus-coder \
--network host \
--device=/dev/davinci0 ...(全部 8 个 NPU 设备) \
-v /data/models:/data/models \
-e ASCEND_RT_VISIBLE_DEVICES=0,1,2,3 \
quay.io/ascend/vllm-ascend:v0.20.2rc1-openeuler \
vllm serve /data/models/Qwopus3.6-27B-Coder \
--port 8001 \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.9 \
--max-num-seqs 8 \
--trust-remote-code
首次启动后需要等待 3-5 分钟,CANN 会对 27B 模型进行 torch.compile 编译预热,之后请求就正常了。
三、接入 Claude Code:协议层的坑
这才是最花时间的部分。整个链路走通涉及三个协议层问题,逐一拆解。
链路全貌
先看一下最终跑通的架构:
┌────────────────────────────┐
│ Claude Code CLI │
│ ANTHROPIC_BASE_URL │
│ = http://localhost:18002 │
└──────────┬─────────────────┘
│ POST /v1/messages
│ (Anthropic Messages API)
▼
┌─────────────────────────────────────┐
│ anthropic-system-promote.py :18002 │ ← 自定义代理
│ │
│ 功能: │
│ 1. system 字段上提 │
│ 2. max_tokens 强制 ≤ 4096 │
│ 3. 禁用 thinking 模式 │
│ 4. greedy 解码 │
└──────────────┬──────────────────────┘
│ 透传(修改后的请求)
▼
┌─────────────────────────────────────┐
│ vLLM-Ascend 容器 :8001 │
│ OpenAI API /v1/chat/completions │
│ + /v1/messages (兼容) │
└─────────────────────────────────────┘
坑 1:Claude Code 的 system 消息在 vLLM 上会 400
Claude Code SDK 按 Anthropic API 标准发送消息,其中角色为 system 的内容有时放在 messages[] 数组里,有时放在顶层 system 字段。而 vLLM-Ascend v0.20.2 虽然新增了 /v1/messages 端点(对标 Anthropic Messages API 的名字),但底层校验仍然沿用了 OpenAI Chat Completions 的 schema —— messages[].role 只接受 "user" 和 "assistant",收到 "system" 就直接返回 400:
API Error: 400 "Input should be 'user' or 'assistant'"
坑 2:vLLM 默认关闭 tool call
Claude Code 为了支持 MCP 工具调用,每次请求都会发 tools: [...] 和 tool_choice: {type: "auto"}。vLLM 默认不开启 tool call parser,收到工具参数就会报 400。
坑 3:Proxy 不是真正的流式转发,导致超时
最初写的 anthropic-system-promote.py 用的是 Python 标准库的 http.client.HTTPConnection,其 resp.read() 会缓冲整个 chunked 响应后才返回。
vLLM 以 ~8 tokens/s 的速度生成 token,一个 519 tokens 的 thinking 响应需要 ~65 秒才能生成完。期间 proxy 不向 Claude Code 发任何数据——因为 resp.read() 还在等 vLLM 发完。Claude Code 的客户端超时(约 60 秒)就会触发。
坑 4:Thinking 模式输出过长,放大延迟
Qwopus3.6-27B-Coder 默认会输出冗长的 <thinking>...</thinking> 链。实测一个简单的 “你好” 问候,thinking 模式下输出了 519 tokens,而非 thinking 模式只有 18 tokens——缩短 96%。
坑 5:input + output 超出上下文限制,vLLM 报 500
vLLM 有一个硬约束:input_tokens + output_tokens ≤ max-model-len。在编程使用场景,上下文会积累,多轮对话后,输入会不断增长,上下文就会超过 max-model-len 上限,vLLM 直接返回 500:
API Error: 500 ... 30000+4096 > 32768 max-model-len
解决方案:写一个 30 行的代理做协议适配
与其在 litellm 这种重量级代理上修 bug,不如自己写一个轻量级的代理做协议适配,放在 Claude Code 和 vLLM 之间。最终写了一个 Python 脚本 anthropic-system-promote.py,监听 18002 端口,做了五件事来修上面五个坑:
| 坑 | 解决方案 |
|---|---|
| 坑 1:system 字段 400 | 把 messages[] 中的 system role 内容提取出来,合并到顶层 system 字段,消息体中的 role 只保留 user/assistant |
| 坑 2:tool call 报 400 | vLLM 启动时加 --enable-auto-tool-choice --tool-call-parser qwen3_coder(必须是 qwen3_coder,因为 Qwopus 使用 Qwen3-Coder 风格的 XML tool call 格式) |
| 坑 3:proxy 不流式导致超时 | 重写 proxy 用 socket 直连 vLLM,手动解码 chunked 编码,收到一个 chunk 就立刻转发给客户端 |
| 坑 4:thinking 输出过长 | 注入 chat_template_kwargs = {enable_thinking: false},回复从 519 tokens 压缩到 18 tokens(缩短 96%) |
| 坑 5:input+output 超出上下文 | 增大 vLLM 的 --max-model-len:32K → 64K → 256K(需要 4 张 910B4) |
消息流变成:
Claude Code ──POST /v1/messages──→ system-promote:18002 ──→ vLLM:8001
↑ │
└──────────────────── chunked stream ────────────────────┘
四、配置 Claude Code
Claude Code 本身配置很简单。环境变量设置如下:
export ANTHROPIC_BASE_URL="http://localhost:18002" # 指向代理,注意不要带 /v1
export ANTHROPIC_AUTH_TOKEN="dummy"
export ANTHROPIC_MODEL="Qwopus3.6-27B-Coder"
需要注意一个坑:~/.claude/settings.json 的 env 段优先级高于 shell 环境变量。如果用 cc-switch 切换过 provider,它会把配置写进 settings.json,shell export 就不再生效。
解决方式是用 switch_provider.sh 脚本直接 patch settings.json,或者手动编辑 .claude/settings.local.json:
{
"env": {
"ANTHROPIC_BASE_URL": "http://localhost:18002",
"ANTHROPIC_AUTH_TOKEN": "dummy",
"ANTHROPIC_MODEL": "Qwopus3.6-27B-Coder",
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "65536",
"CLAUDE_CODE_AUTO_COMPACT_WINDOW": "40000",
"API_TIMEOUT_MS": "600000"
}
}
五、上下文大小问题
64K 不够用
日常编程任务中,我们几乎习惯了主流模型通常在 200K tokens 左右,甚至到1M tokens ——代码库摘要、多轮对话历史、MCP 工具调用结果等。实测 64K 上下文的配置下,大约 4-5 轮 tool call 交互后上下文就消耗得差不多了,Claude Code 开始频繁触发 compact 压缩历史,影响对话连贯性。
256K 配置与权衡
Qwopus3.6-27B-Coder 的原生 max_position_embeddings 是 262144(256K),因此在 vLLM 中可以直接配置:
--max-model-len 262144
但 256K 配置有代价。KV cache 的大小与序列长度线性相关。27B 模型 + 256K 上下文意味着每张卡需要约 50GB 显存来存放 KV cache(Qwen3.6 的 head_dim=256、num_kv_heads=4、64 层)。虽然 4 张 910B4 总共 256GB,但模型权重本身也占 ~54GB(BF16),留给 KV cache 的空间并没有想象中充裕。
实际测试下来,256K 配置的权衡如下:
| 上下文 | 优点 | 缺点 |
|---|---|---|
| 64K | 延迟低,首次 prefill 快 | 4-5 轮交互后开始 compact |
| 128K | 可支持 8-10 轮交互 | prefill 慢,内存压力大 |
| 256K | 接近原生上限,长对话无压力 | prefill 极慢(数分钟),跑满 4 卡也只能支持 4 个并发 |
六、性能问题与优化
基线速度
初始部署(TP=2,2 卡)的生成速度:
| 场景 | 速度 |
|---|---|
| 非流式请求 | ~8 tokens/s |
| 流式请求 | 1.6-7.6 tokens/s |
| 单次 4096 tokens 回复 | ~8.5 分钟(最慢情况) |
这个速度对于交互式编程来说显然太慢了。一个 claude --print 请求动辄 30-60 秒才能拿到完整回复。
做了哪些优化
Step 1:Prefix Caching(✅ 已启用,有效)
Claude Code 每轮对话都会重发约 2K tokens 的 system prompt + tool definitions。开启 --enable-prefix-caching 后,这部分 KV cache 得以复用,每轮对话的 prefill 开销只计算一次。Qwen3.6 架构的 prefix-caching align 阈值是 528 block,而 Claude Code 的 system prompt 远超这个阈值,确认受益。
Step 2:Speculative Decoding(❌ 尝试后放弃)
Qwopus3.6-27B-Coder 原生支持 MTP(Multi-Token Prediction) 头。理论上 MTP 可以在不降低质量的前提下实现约 2 倍的加速(llama.cpp 上实测确实如此)。
但 vLLM-Ascend 上的情况不同:
- MTP:vLLM 能识别到 Qwen3_5MTP 架构并启动成功,但输出始终是
response而非真实内容。推测是 Unsloth 微调版的 MTP 头权重与上游不一致。 - N-gram:启动后 vLLM 日志显示 “Async scheduling not supported with ngram, will be disabled”,关闭 async scheduling 后速度反而下降到 3.5 t/s。
结论:投机解码暂时关闭,待后续优化改进。
Step 3:Greedy 解码(✅ 已启用,有效)
在代理中注入 temperature=0, top_p=1, top_k=-1。跳过采样运算,decode 阶段加速 10-30%。代价是输出失去多样性,同输入永远同输出。
Step 4:禁用 Thinking 模式(✅ 效果显著)
Qwopus3.6-27B-Coder 默认会输出冗长的 <thinking>...</thinking> 链。注入 chat_template_kwargs = {enable_thinking: false} 后:
测试结果:519 tokens → 18 tokens,缩短 96%,回答质量无可见下降
优化前后对比
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 单次回复时间(4096 tokens) | >8 分钟 | ~3 分钟 | ~2.6× |
| stream first token | 15-30s | 5-10s | ~3× |
| 上下文利用率 | thinking 占 500+ tokens | 几乎为零 | tokens 利用效率提升 |
还有哪些优化空间
当前最大瓶颈仍然是 生成速度。8 tokens/s 在交互式场景中意味着每次回复都要等 1-3 分钟。后续优化方向:
- MTP 修复——vllm-ascend 对 Qwen3.6 MTP 架构的支持完善后,理论上能实现 2× 加速
- 量化——BF16 权重是最大的性能开销。如果 vllm-ascend 支持 FP8 或 INT4 量化,显存带宽利用率可以大幅提升
- 更小的模型——Qwopus3.6-35B-A3B(MoE,3B active)如果接入,推理速度会是 27B 的数倍
七、实际效果与感受
说实话,以目前的速度(1~3 分钟一次回复),还 不能用在大规模编程任务上。做一次代码审查、跑一遍完整测试再修 bug,可能需要等十几分钟甚至半小时。更深层次的评测需要等效率问题解决后才能进一步展开。
但日常跑下来,我自己感觉是 有戏的。简单总结:
- 代码库理解:给一个中小项目做文档总结,能准确提取关键模块的职责和调用关系
- BUG 修复:定位和修复一些中等难度的 bug(单个函数级别的逻辑错误)基本没问题
- 工具调用:走通了 MCP 工具链路,包括检索知识库、执行 Shell 命令、读写文件等
直觉上,比之前用过的 Kimi-K2.5 要好——尤其是在代码生成质量和对复杂指令的理解上,明显更接近云端模型的水准,如果不考虑效率,实测过claude code接入模型后能自主运行 5 小时直至任务完成(不过实际工作量可能只有云端模型1.5小时的工作量。
更重要的是,这条路如果走通,意味着 充分释放本地算力,完全替代云端 API 调用。毕竟本地闲置的算力也是闲置,可以节省下云端 API 的订阅费用,对于需要大量迭代的编程任务来说,边际成本几乎为零。
更多推荐


所有评论(0)