适用场景
团队希望把编码助手的推理能力放在自己的机器上:代码不出内网、可对接内部仓库与工单系统、按并发自由扩容。本文以 Kimi K3.1 Pro 的开放权重为例,走完「下载 → 校验 → 量化 → 多卡起服务 → 压测 → 接入 Claude Code / Cursor → 真实仓库任务验证」全流程,最终得到一个兼容 OpenAI Chat Completions 的私有编码 API 端点。
如果你只是想在本地跑个小模型玩玩,这套流程属于杀鸡用牛刀;但只要涉及团队共享、长上下文读仓库、稳定并发,这些步骤就都会用上。
环境与前置条件
硬件
- GPU:企业级卡,单机 8 卡或双机 8 卡以上。多卡切分要靠 NVLink 或高速互联,纯 PCIe 也能跑,但张量并行通信会明显拖慢吞吐。
- 显存估算分两块,先算清楚再买机器:
- 权重显存 ≈ 权重文件字节数 ÷ 张量并行度。BF16 每参数 2 字节,FP8 每参数 1 字节,4bit 量化约 0.5 字节。MoE 模型注意:激活参数少不等于权重少,全量专家权重都要驻留显存,除非开 CPU offload。
- AI 词典:KV Cache">KV Cache 显存 ≈ 2 × 层数 × KV 头数 × head_dim × 精度字节数 × 序列长度 × 并发数。长上下文是吃显存的大头,128K 上下文下的 KV 占用可能比你想的更夸张。
- 磁盘:权重目录预留权重体积的 2 倍(下载缓存 + 最终目录),量化还要额外一份。
- 内存:不少于单卡显存 × 2,加载权重时会有 CPU 侧峰值。
软件
- Linux(Ubuntu LTS 系发行版即可),内核版本与驱动匹配。
- NVIDIA 驱动 + CUDA 运行时,版本要求以所用推理框架官方文档当前版本为准。
- Docker + NVIDIA Container Toolkit(推荐容器化部署,避免 Python 依赖地狱)。
- Python 3.10 以上(若不用容器)。
- 推理框架:SGLang 或 vLLM,两者都提供 OpenAI 兼容接口。版本以官方文档当前版本为准,不要凭记忆写死 tag。
网络
- 能从 Hugging Face 或 ModelScope 拉权重;内网机器建议用一台跳板机下载后 rsync 过去。
- 对外开放端口前先想清楚鉴权方案。
分步骤部署
第 1 步:确定并行与量化策略
先做一张纸面推演,避免反复起服务试错:
| 场景 | 张量并行 | 建议精度 | 说明 |
|---|---|---|---|
| 单机 8 卡,显存充裕 | 8 | BF16 | 精度最好,速度基准 |
| 单机 8 卡,想吃更长上下文 | 8 | FP8(在线) | 权重减半,KV 精度一般保持 BF16 |
| 显存紧张 | 8 | 4bit 离线量化 | 需重新校准,代码任务上要重点回归 |
| 双机 | TP=8 + 多机 | BF16/FP8 | 跨机通信敏感,优先 IB/RoCE |
编码任务对精度比较敏感,尤其是长文件里的细微符号、缩进、变量名。量化方案上线前必须用固定评测集对比,别只看「能跑通」。
第 2 步:准备系统环境
```bash
确认驱动与 GPU 可见
nvidia-smi
确认卡间互联拓扑,重点看是否 NVLink
nvidia-smi topo -m
```
nvidia-smi 能列出全部卡、驱动版本正常、显存为空闲,这步就算成功。topo -m 里若大量出现 SYS(跨 NUMA)而不是 NV#,多卡并行的通信开销要按更悲观估计。
装容器运行时:
```bash
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
```
容器里能打印出 GPU 列表,说明 NVIDIA Container Toolkit 正常。CUDA 基础镜像 tag 请按官方仓库当前可用版本替换。
第 3 步:下载权重
```bash
pip install -U "huggingface_hub[cli]"
export HF_HOME=/data/hf-cache
huggingface-cli download <官方权重仓库ID> \
--local-dir /models/kimi-k3.1-pro \
--local-dir-use-symlinks False
```
国内网络可换 ModelScope 的下载工具,命令形式类似。仓库 ID 以官方 model card 给出的为准,不要猜。
成功标志:/models/kimi-k3.1-pro 下出现 config.json、tokenizer_config.json、generation_config.json 和若干 *.safetensors 分片。
第 4 步:校验完整性
权重下载中断是最高频的坑,务必先校验再起服务:
```bash
cd /models/kimi-k3.1-pro
ls -lh *.safetensors | wc -l # 分片数量是否与官方索引一致
python -c "
import json,sys
idx=json.load(open('model.safetensors.index.json'))
print('weight_map entries:', len(idx['weight_map']))
"
sha256sum -c checksums.sha256 2>/dev/null || echo "无校验文件,跳过"
```
再确认 config.json 里的 num_hidden_layers、num_attention_heads、num_key_value_heads、max_position_embeddings,这几个值直接决定你的显存估算和上下文上限。若官方提供校验和文件,逐项比对通过才算成功。
第 5 步:起服务(SGLang)
```bash
docker run --gpus all --ipc=host --shm-size=64g \
-v /models:/models \
-p 30000:30000 \
-e NCCL_SOCKET_IFNAME=bond0 \
<sglang官方镜像tag> \
python -m sglang.launch_server \
--model-path /models/kimi-k3.1-pro \
--served-model-name kimi-k3.1-pro \
--tp-size 8 \
--host 0.0.0.0 --port 30000 \
--context-length 131072 \
--mem-fraction-static 0.85 \
--max-running-requests 32 \
--chunked-prefill-size 8192 \
--enable-prefix-caching \
--api-key "$PRIVATE_API_KEY"
```
几个参数的含义:
--tp-size:张量并行度,一般等于单机卡数。多机场景另加分布式参数,以官方文档为准。--context-length:服务端允许的最大序列长度。先按实际需求给,不要一上来拉满,它直接抬高 KV Cache 预留。--mem-fraction-static:静态分配的显存占比。留 10%~15% 给 CUDA context 和临时 buffer。--max-running-requests:同时在跑的请求数上限。这是控制长上下文 OOM 的关键阀门。--chunked-prefill-size:把长 prompt 的预填充切块,降低首 token 前的显存尖峰。--enable-prefix-caching:编码场景收益很大——同一份仓库上下文被反复引用时,公共前缀只算一次。
成功标志:日志里逐卡打印加载进度,最后出现类似 The server is fired up and ready to roll 的提示,且 nvidia-smi 显示各卡显存占用接近一致。
第 6 步:起服务(vLLM 备选)
```bash
vllm serve /models/kimi-k3.1-pro \
--served-model-name kimi-k3.1-pro \
--tensor-parallel-size 8 \
--max-model-len 131072 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 32 \
--enable-chunked-prefill \
--enable-prefix-caching \
--api-key "$PRIVATE_API_KEY" \
--port 30000
```
在线 FP8 量化可在两条命令里分别加 --quantization fp8(SGLang)或 --quantization fp8(vLLM),首次加载会做一次权重转换,启动时间变长属于正常现象。是否支持该精度、具体参数名,以当前版本 --help 输出为准。
第 7 步:加一层网关与鉴权
单机直连够用,但只要多人共享,建议前面挂一层轻量网关:
```nginx
server {
listen 443 ssl;
server_name code-api.internal;
location /v1/ {
proxy_pass http://127.0.0.1:30000/v1/;
proxy_read_timeout 600s;
proxy_buffering off;
client_max_body_size 64m;
}
}
```
proxy_read_timeout 必须放大:长上下文 + 长输出的请求可能跑好几分钟,默认 60s 会直接 504。proxy_buffering off 保证流式输出不被缓冲卡住。
第 8 步:吞吐压测
用框架自带的压测脚本,按并发梯度扫一遍:
```bash
python -m sglang.bench_serving \
--backend sglang \
--base-url http://127.0.0.1:30000 \
--model kimi-k3.1-pro \
--dataset-name random \
--random-input-len 8000 \
--random-output-len 800 \
--num-prompts 200 \
--max-concurrency 1 4 8 16 32
```
关注四个数:
- TTFT(首 token 延迟):决定「按下回车到开始出字」的体感。编码场景建议 P99 控制在个位数秒。
- TPOT / ITL(每 token 间隔):决定出字是否跟手。
- 输出吞吐(output tokens/s):决定单位时间能服务多少人。
- 请求吞吐(req/s):决定排队长度。
再单独跑一组长上下文:把 --random-input-len 提到 60000~100000,观察 TTFT 是否指数上升、是否 OOM。长上下文下的并发上限往往比短请求低一个数量级,这个数才是你真正能承诺给团队的值。
第 9 步:折算成本
```
每百万输出 token 成本 =
(GPU 每小时成本 × 卡数) ÷ (实测输出吞吐 tok/s × 3600) × 1,000,000
```
把 TTFT 要求和成本放在一起看,就能回答「该开多少并发、要不要上量化」。注意压测用的是随机文本,真实代码的 token 分布不同,实测值建议再打个折。具体卡价、电价以实际采购为准。
第 10 步:接入 Claude Code
Claude Code 走的是 Anthropic Messages 协议,而你的服务是 OpenAI 兼容接口,中间需要一个转换层。常见做法是用 LiteLLM 之类的网关做协议映射:
```yaml
litellm config.yaml
model_list:
- model_name: claude-sonnet-4-5
litellm_params:
model: openai/kimi-k3.1-pro
api_base: http://127.0.0.1:30000/v1
api_key: os.environ/PRIVATE_API_KEY
```
```bash
litellm --config config.yaml --port 4000
export ANTHROPIC_BASE_URL=http://127.0.0.1:4000
export ANTHROPIC_AUTH_TOKEN=sk-local-anything
claude
```
model_name 写客户端会请求的模型名,让它映射到你的私有模型。工具调用(tool use)、图片、prompt caching 等特性是否完整透传,取决于转换层版本,以官方文档为准;建议先用纯文本问答跑通,再逐步启用工具。
第 11 步:接入 Cursor
在 Cursor 设置里找到模型配置,覆写 OpenAI Base URL 为 http://<内网地址>:30000/v1,填入 API Key,然后手动添加自定义模型名 kimi-k3.1-pro。
需要提醒:自定义端点下,部分编辑器内置能力(行内补全、索引等)可能不完全可用,具体支持范围以官方文档为准。把自定义模型用在 Chat / Agent 面板里做代码问答,是更稳妥的用法。
第 12 步:真实仓库任务验证
压测数字好看不代表能干活。挑一个真实仓库,跑这几类任务并记录主观可用度:
1. 单文件修 bug:给一个函数 + 报错栈,看能否定位到行。
2. 跨文件重构:要求把某工具函数的所有调用点改名并保持行为一致。
3. 写测试:基于现有测试风格补 3 个用例。
4. 长文件理解:贴 2000 行文件,问「这个模块的初始化顺序」。
5. 提交信息生成:给 diff,看输出的信息是否准确。
每一项都记录:是否需要重试、是否改错文件、是否幻觉出不存在的 API。这套清单建议固化成回归集,升级框架或换量化方案时重跑一遍。
验证部署是否成功
1. 模型列表
```bash
curl -s http://127.0.0.1:30000/v1/models \
-H "Authorization: Bearer $PRIVATE_API_KEY" | python -m json.tool
```
预期返回包含 kimi-k3.1-pro 的 data 数组。
2. 短请求
```bash
curl -s http://127.0.0.1:30000/v1/chat/completions \
-H "Authorization: Bearer $PRIVATE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kimi-k3.1-pro",
"messages": [{"role":"user","content":"用 Python 写一个带重试的 HTTP 请求函数,只给代码"}],
"max_tokens": 256,
"temperature": 0.2
}' | python -m json.tool
```
预期 choices[0].message.content 是一段可直接运行的代码,usage 字段里 prompt/completion token 数都大于 0。
3. 长上下文
```bash
python - <<'PY'
import json, urllib.request, os
filler = "def f(x):\n return x + 1\n" * 4000
body = {
"model": "kimi-k3.1-pro",
"messages": [{"role":"user","content": filler + "\n上面这段代码重复了多少次?只回答数字。"}],
"max_tokens": 32, "temperature": 0
}
req = urllib.request.Request(
"http://127.0.0.1:30000/v1/chat/completions",
data=json.dumps(body).encode(),
headers={"Content-Type":"application/json",
"Authorization":"Bearer "+os.environ["PRIVATE_API_KEY"]})
print(json.load(urllib.request.urlopen(req, timeout=600))["choices"][0]["message"]["content"])
PY
```
预期返回 4000 附近的数字。答对说明长上下文没有截断、位置编码正常;答错或胡言乱语,先怀疑 --context-length 设小了或 KV 精度有问题。
4. 资源观测
```bash
nvidia-smi --query-gpu=index,memory.used,memory.total,utilization.gpu --format=csv
```
请求结束后各卡显存仍保持高位(KV Cache 池未释放)属于正常设计,不是泄漏;要看的是它是否长期贴着上限不回落。
常见报错与解决
报错 1:torch.OutOfMemoryError: CUDA out of memory(发生在加载权重或首个长请求时)
→ 原因:权重 + KV Cache 超过单卡可用显存,常见于上下文拉满或并发给太大。
```bash
依次尝试:降显存占比 → 降并发 → 降上下文 → 上量化
--mem-fraction-static 0.80
--max-running-requests 16
--context-length 65536
--quantization fp8
```
报错 2:NCCL timeout / Watchdog caught collective operation timeout
→ 原因:多卡通信没走对网卡,或防火墙拦了 NCCL 端口,或某张卡被别的进程占着。
```bash
nvidia-smi | grep python # 清掉残留进程
export NCCL_SOCKET_IFNAME=bond0 # 换成本机实际内网网卡
export NCCL_DEBUG=INFO # 起服务时打印通信拓扑
export NCCL_IB_DISABLE=1 # 无 IB 环境时先关掉
```
报错 3:400 Bad Request: This model's maximum context length is N tokens
→ 原因:请求输入 + max_tokens 超过服务端 --context-length。注意输出也算在里面。
```bash
要么调大服务端
--context-length 262144
要么在客户端裁剪:统计 token 后预留输出额度
```
报错 4:jinja2.exceptions.TemplateError 或输出里出现 <|im_start|> 之类的原始标记
→ 原因:chat template 与权重不匹配,或 tokenizer 文件没下全。
```bash
ls tokenizer_config.json tokenizer.json special_tokens_map.json
手动指定模板
--chat-template /models/kimi-k3.1-pro/chat_template.jinja
```
报错 5:输出重复、乱码、代码块闭合错乱
→ 原因:量化精度损失过大,或 --dtype 与权重精度不匹配。
```bash
先回退到 BF16 复现,确认是否为量化导致
若是,换更保守的量化策略或提高校准样本量
--dtype bfloat16
```
报错 6:Error: address already in use
→ 原因:上一个进程没退干净。
```bash
lsof -i :30000
kill -9 <PID>
```
报错 7:Claude Code 报 404 或 not_found_error
→ 原因:ANTHROPIC_BASE_URL 指向了 OpenAI 兼容端点,协议对不上。
```bash
确认中间层已启动并监听 /v1/messages
curl -s http://127.0.0.1:4000/v1/messages -H "Content-Type: application/json" -d '{}'
base_url 不要带 /v1,客户端会自己拼
export ANTHROPIC_BASE_URL=http://127.0.0.1:4000
```
后续维护
备份
- 权重目录单独一块盘,用 rsync 增量同步到对象存储或另一台机器,别和系统盘混在一起。
- 启动脚本、网关配置、压测脚本全部进 Git 仓库,提交信息里写清对应的框架版本。
- 记录一份「已知可用组合」清单:镜像 tag + 框架版本 + 量化方案 + 上下文长度,出问题时按清单回滚。
升级
- 镜像 tag 固定写死,不用
latest。 - 升级走灰度:新起一个端口,用同一套压测脚本和真实仓库回归集对比,TTFT、吞吐、任务通过率三项都不劣化才切流量。
- 框架大版本升级常伴随参数改名,升级前先看官方 release notes 的 breaking changes。
日志
- 打开请求级访问日志,至少记录:请求 ID、输入/输出 token 数、TTFT、总耗时、状态码。
- 日志按天切分并集中收集,编码 API 的日志里可能含源码片段,注意脱敏和访问权限。
- 错误日志单独告警,尤其是 5xx 和超时。
监控
核心指标建议做成看板:
| 指标 | 说明 | 告警思路 |
|---|---|---|
| GPU 利用率 | 长期低于某阈值说明资源浪费 | 持续偏低 → 缩容 |
| 显存使用率 | 贴近上限时有 OOM 风险 | 持续 >95% → 降并发 |
| KV Cache 命中率 | 前缀复用效果 | 骤降 → 查客户端是否每次重传全量上下文 |
| 排队请求数 | 容量是否够 | 持续 >0 → 扩容 |
| TTFT P99 | 体感核心指标 | 超过阈值 → 查长请求 |
| 错误率 | 稳定性 | 突增 → 回滚 |
安全
- API Key 定期轮换,按人/按项目分发,别全组共用一个。
- 服务只监听内网,公网入口必须过网关和鉴权。
- 按 Key 做速率限制,防止单个脚本把并发打满拖垮所有人。
