先交代我为什么会折腾这套东西。前一阵要把 AI 编程助手放进公司的纯内网环境,也就是研发服务器和办公电脑之间完全不能碰外网那种网络,外部大模型 API 用不了,代码也不能流出机器。查了一圈后,最后落地方案就是标题里这套组合:Qwen Code 负责交互和智能体调度,vLLM 负责推理服务,模型用 Qwen3-Coder 系列。整套跑起来之后,体验接近公网编程助手,同时代码全程留在内网,响应速度在某些场景下甚至更快。
这篇文章不是概念介绍,是我实际部署和调优的记录。我会把硬件准备、离线安装、vLLM 启动参数、Qwen Code 指向本地模型、性能调优、团队共享、踩坑排障都过一遍,尽量做到照做就能复现。如果你只是在自己电脑上随便跑个小模型,这篇也有参考价值,但重点场景是给团队用的内网私服级服务。
1. 为什么内网编程助手要拆成“Qwen Code + vLLM + Qwen3-Coder”三部分
1.1 纯内网部署要解决的三件事
想在断网环境里给团队提供像样的 AI 编程助手,核心不是“能不能生成代码”,而是三个现实问题要同时解决。
第一,模型推理能力要够。写代码不像聊天,经常要处理几千行上下文、跨文件搜索、多轮修改,小模型很容易丢上下文或者生成半吊子代码。所以模型选型不能太凑合。
第二,多人同时用的时候不能互相卡死。如果只是一个人单机用,模型怎么慢都能忍。但团队场景下,不同人同时请求,推理服务必须能排队、批处理、尽量利用显存,否则后发请求会全部超时,体验直接崩塌。这一步就是 vLLM 的活儿。
第三,前端交互得是个“能干活”的智能体。能读懂仓库结构、能自己跑命令、能根据报错改代码,而不是只能在网页上问答。Qwen Code 这类命令行智能体在这里承担的是 IDE 之外的独立助手角色。
这三件事,单一工具都做不全。vLLM 不做交互,Qwen Code 不带模型,Qwen3-Coder 只是模型权重。所以最好的方式是组合:模型权重放在推理服务里,推理服务暴露一个私有 API,客户端只和这个 API 通信。整体就是私有化部署里最常见的“客户端+网关+模型”三层结构。
1.2 三个组件各管哪一段
打个比方,你可以把内网编程助手理解成一个只对你内网开放的“编程外包团队”。
Qwen3-Coder 是这个团队里真正写代码的程序员,它提供写代码、改代码、解释代码、写测试这些能力。但我不能直接把程序员拉去线上开会,它需要一个接待入口。
vLLM 就是那个接待入口。它不仅接待我,还能同时接待团队里的其他人。谁来了先排队,能几个人凑一起处理的请求就合并处理,显存不够的时候自动协调。更重要的是,不管内部用什么方式并行计算,对外都只暴露一个稳定统一的服务接口。
Qwen Code 是“项目经理”。我直接操作它,告诉它需求,它拆解需求、看仓库文件、执行命令、调用工具,把任务一步步拆给模型去完成。它不关心模型跑在哪台机器上,只要一个可以访问的接口。
所以从架构上看,数据和指令流向是:我在终端里操作 Qwen Code,Qwen Code 把临时任务发给 vLLM 暴露的接口,vLLM 把请求真正交给 Qwen3-Coder 的模型权重去推理,再把结果返回。整个过程所有流量都走内网,不经过任何外部节点。
1.3 我为什么不直接用网页版或者 IDE 插件方案
很多人会问:直接用开源项目里的 Web 界面,或者在 IDE 里装个支持自定义模型的插件,不是更省事吗?我的实际感受是,这两类东西适合“轻量自用”,不适合“团队私服”。
Web 项目通常只解决问答或单文件代码补全,缺少对仓库整体结构的理解。让它改一个跨模块的功能,它每次都拿不到完整信息。IDE 插件虽然体验好,但配置灵活度参差,很多插件对自定义模型支持只停留在“能填一个 API 地址”的层面,工具调用、长上下文、函数定义这些高级能力经常对不上。
Qwen Code 这类独立智能体不一样,它把自己定位成一个能用终端工具的智能体,能自己读文件、搜索代码、跑测试、看报错,然后把多步推理串联起来。这正好把 Qwen3-Coder 的代码理解能力发挥出来。单独问一句话生成一段函数,显不出它的优势;但让它“去项目里找到 Redis 缓存那块代码,把并发问题修掉,然后跑一遍单测”,这种多步骤任务才是它的强项。
后面所有章节,我都会围绕这个分工关系展开。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硬件与离线资源准备:先把基础设施搞定
2.1 一个可以直接参考的硬件配置
先说明白,内网私服级编程助手的体验上限,一半由硬件决定。我用的目标是单机多卡服务器,这张表是可以直接抄的参考:
| 项目 | 最低配置(个人使用) | 推荐配置(团队使用) |
|---|---|---|
| GPU | 单张 24GB 显存(如 RTX 4090 / L20) | 2-4 张 24GB 或更高显存 NVIDIA 卡 |
| 内存 | 64GB | 128GB 及以上 |
| 系统盘 | 100GB SSD(系统+缓存) | 500GB NVMe SSD |
| 模型盘 | 预留 60GB | 预留 100GB 以上(含多个量化版本) |
| CPU | 8 核以上 | 16 核以上 |
| 网络 | 不用外网,内网千兆即可 | 内网万兆更好,尤其多人同时拉大上下文 |
为什么显存这么关键?Qwen3-Coder 系列里,30B-A3B 这种量级的模型是内网部署比较常见的选择,既保证了代码能力,又因为 MoE 结构,实际推理时的活跃参数少,生成速度尚可。但权重文件依然要几十 GB 显存,再加上推理中要占用 KV Cache(可以粗暴理解成模型的临时记忆区),而且编程场景的上下文通常拉得很长,KV Cache 占用更大。所以单张 24GB 卡跑小量化版本可以做个人实验,团队共享就老老实实上多卡。
如果你手里只有 16GB 甚至更小的显卡,也不是完全不能跑,可以选更小的量化版本,并把上下文长度设短。但我的建议是不要把期待放太高,编程助手最怕的就是改到一半忘了前面的需求。
2.2 纯离线环境里怎么搬运软件和模型
内网环境最大的难题不是“不会配”,而是“东西进不来”。我当时的网络情况是:内网机器不能访问外网,但我有一台可以出网的临时跳板机,可以下载内容后通过内部文件服务器拷进去。
整个搬运分三层:
第一层是 Docker 镜像。vLLM 官方提供的推理镜像体积不大,但如果你在离线机器上直接 docker pull 是拉不下来的。我是在跳板机上先 docker pull vllm/vllm-openai:latest,再 docker save -o vllm-image.tar vllm/vllm-openai:latest,把 tar 包传到内网服务器,最后内网 docker load -i vllm-image.tar。这里注意传输期间生成校验值,避免大文件传坏。
第二层是模型权重。模型文件通常包括 config.json、多个分片权重 .safetensors、分词器文件等。如果后面要用量化版,还要准备量化后的权重目录。同样先下载到外部机器,再打包传输。模型文件和 Docker 镜像的区别是它会频繁更新,建议在内网搞一个专门存模型文件的目录,别散落在各处。
第三层是客户端。Qwen Code 如果是 Node.js 或者二进制分发,就把对应平台的版本下载下来再拷贝进去;如果它还要拉 npm 依赖,就需要在外部把整个依赖目录完整打包,直接解压到内网。
提示:离线搬运时最容易忽略的是“镜像里可能还需要额外下载东西”。比如有些 vLLM 镜像在容器启动时如果检测到模型不在本地,会尝试去 Hugging Face 或 ModelScope 下载,内网没有外网就会一直卡住。所以模型路径一定要用本地路径,并在启动命令里写清,绝对不能让服务端自己联网找模型。
2.3 模型和镜像的文件目录规划
我的习惯是把所有内网 AI 服务相关的文件放在一个统一的 /data/ai 目录下,下面分几个子目录:
bash复制/data/ai/
├── docker-images/ # 存放 docker load 用的 tar 包
├── models/
│ └── qwen3-coder-30b/ # 模型权重目录
├── vllm-cache/ # vLLM 运行时的缓存目录
├── qwen-code-client/ # Qwen Code 客户端目录
└── logs/ # 日志输出
规划的作用在后排障时非常明显。模型路径错了、缓存盘满了、日志找不到,这些问题在目录混乱时很容易让人血压上升。把路径固定下来,每个组件用独立的存储位置,后面改配置、加模型版本都会从容很多。
3. 用 vLLM 把 Qwen3-Coder 跑起来:启动参数逐项拆解
3.1 为什么推理层必须选 vLLM
如果你只是在单机上试玩模型,用 Transformers 库写个脚本也能出结果。但 vLLM 的价值在于它把推理性能优化做到了工程级:PagedAttention 显存管理、Continuous Batching 连续批处理、前缀缓存,这些都是多用户并发场景下的关键能力。
直接说我的结论:在团队共享的私服场景里,vLLM 几乎是必选项。它的意思是说,当多人同时请求时,vLLM 会把多个请求动态拼成一个批次执行,而不是一个个傻等。打个比方,就像一个柜台服务员不再是一对一服务,而是手里同时处理多张单子,哪个能先做就先做,这样整体吞吐量立刻就不一样了。
3.2 启动 vLLM 的完整命令
假设我现在已经用 Docker 镜像把 vLLM 加载好了,模型权重放在 /data/ai/models/qwen3-coder-30b 目录。下面是启动命令,我会逐段拆解:
bash复制docker run -d \
--name vllm-qwen-code \
--gpus all \
--ipc=host \
-v /data/ai/models/qwen3-coder-30b:/root/model \
-v /data/ai/vllm-cache:/root/.cache \
-v /data/ai/logs:/logs \
-p 8000:8000 \
--shm-size 16g \
--restart unless-stopped \
vllm/vllm-openai:latest \
--model /root/model \
--served-model-name qwen3-coder-30b \
--task generate \
--trust-remote-code \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \
--max-model-len 32768 \
--max-num-seqs 256 \
--enable-prefix-caching \
--enforce-eager
看着很吓人,其实关键就几个参数。我按重要性逐个说明。
3.3 最容易理解错的几个启动参数
--served-model-name 是这个服务对外暴露的名字。我把它设置成 qwen3-coder-30b,后面 Qwen Code 客户端连接时填的模型名必须和这里一致。为什么要单独设置?因为模型权重目录里注册的原始名字通常很长,客户端填起来容易错,自定义一个短名字更保险。
--tensor-parallel-size 是张量并行度,简单说就是把模型切到几张卡上一起跑。如果服务器上有 4 张 GPU 且显存可共享,就填 4。但注意:单卡放得下的时候别盲目开大,跨卡通信有额外开销,可能反而变慢。30B 量级用 2-4 卡都合理,具体以 nvidia-smi 的显存占用为准。
--max-model-len 是模型上下文窗口最大长度。我填 32768 是因为编程场景确实需要长上下文,但又不建议一上来就拉满 131072。上下文越长,KV Cache 占的显存就越多,服务支持的最大并发数就越低。先设 32768 跑稳定,再根据实际显存余量调整是一个稳妥策略。
--gpu-memory-utilization 表示允许 vLLM 使用多大比例的显存,我填 0.90。不要填 0.98 这种极端值,显存里还要留一点给驱动、CUDA context 和其他进程。如果后续跑的时候看到 CUDA out of memory,第一件事就把这个参数降下来。
--enable-prefix-caching 开启前缀缓存。它在代码场景里很有用,因为经常有多个会话都读同一个项目文件,如果请求前缀相同,缓存可以复用计算,减少重复推理,团队场景收益很大。
--enforce-eager 不是必须,但如果你遇到模型加载后报各种 CUDA 图相关错误,可以先加上它绕过去。代价是性能略降。新版 vLLM 我对这个参数比较保守,一般只在排障时才加。
3.4 离线场景下的健康检查
服务启动后别急着接客户端,先做两层验证。第一层是看模型加载日志,确认权重从本地路径读出来;第二层是调 OpenAI 兼容接口。
bash复制curl http://127.0.0.1:8000/v1/models
正常情况下会返回一个 JSON 列表,里面有 qwen3-coder-30b 这个名字。然后试着发一个最简单的对话请求:
bash复制curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3-coder-30b",
"messages": [{"role": "user", "content": "用python写一个去重函数"}],
"max_tokens": 1024,
"temperature": 0.2
}'
这里如果返回带 content 字段的正常响应,就说明推理链路通了。然后才轮到客户端接入。
4. 让 Qwen Code 客户端连上 vLLM,进入真正的私服模式
4.1 Qwen Code 到底是个什么样的客户端
简单说,Qwen Code 是一个偏向智能体的命令行编程工具。它不止能做“问答式补全”,还能在授权范围内读取本地项目文件、搜索内容、执行命令,相当于一个住在终端里的编程代理。
它的价值和“内网私服”这个场景非常契合:因为它本身就是命令行工具,不依赖任何云端账号,只需要配置一个模型服务地址就能工作。网络上没有外呼,所有数据都打到内网 vLLM 服务上。
4.2 把模型地址改成 vLLM 的设备地址
假设我已经把 Qwen Code 的客户端包拷贝进内网,并且完成基础安装。下一步就是配置模型接入。
常见的方式是设置环境变量,不同版本可能叫法略有差异,但核心通常是这两个:
bash复制export QWEN_CODE_BASE_URL=http://127.0.0.1:8000/v1
export QWEN_CODE_MODEL=qwen3-coder-30b
注意 QWEN_CODE_BASE_URL 填的是 vLLM 服务上加了 /v1 的完整路径。这是 OpenAI 兼容接口的固定前缀,很多人在这一步少写了 /v1,导致客户端反复报 404。
如果 Qwen Code 是全局配置文件方式,也可以直接写到它的配置文件里。无论哪种方式,本质都是告诉客户端两件事:去哪个地址找模型,以什么名字找模型。只要这两个值和 vLLM 启动时的参数对得上,连接就成功了一半。
我建议先不加任何额外参数,在 Qwen Code 里发一句简单请求,比如“你好”或“解释一下你的功能”,确认链路通了,再进入复杂的工具调用配置。
4.3 编程智能体最关键的一步:工具调用
这里要提到一个很多初学者根本不知道的坑:Qwen3-Coder 这样的模型在接收普通对话请求时,只返回文本;但在接收“带有工具调用格式”的请求时,它会在返回内容里夹一段结构化的 JSON,用来表示“我要读取某个文件”或“我要执行某个命令”。
如果客户端和服务端的工具调用格式对不上,表现就是:模型明明会写代码,但客户端无法理解它想调用什么工具,或者模型返回了一堆 JSON 但没有被解析,直接当成普通文本糊在代码里。
Qwen Code 通常内置了对模型格式的处理逻辑,它会根据模型名字自动选解析器。不过接入 vLLM 自建服务时,模型服务端返回的元数据可能会让客户端无法自动识别,这时候就需要手动指定工具调用解析方式。常见做法是在 Qwen Code 的环境变量或配置里设置工具解析为 qwen 风格(类似 tool-call-parser),让客户端按 Qwen 模型习惯的的格式来解析函数调用。
我第一次接的时候就是没意识到这个配置,模型回答里一直出现 <tool_call> 标签,客户端完全不执行工具,看起来像是“模型变笨了”,其实是解析器没配对。
提示:如果你用其他客户端(比如自己写的脚本或别的开源 IDE 插件)也要做同样一件事:服务端提供的模型可能不会自动声明 tool 格式,必须由客户端按模型特性做解析配置。基础模型是“给出文本”,智能体是“把文本中的工具调用意图解析出来并执行”,这一步是最容易出问题的。
4.4 验证整个链路能真正干活
配置完成后,不要只发一个“你好”,要真刀真枪测一遍智能体能力。
我常用的验证方式是创建一个临时目录,放一个故意留 bug 的 Python 文件,然后给 Qwen Code 下指令:“读取当前目录下所有 Python 文件,找到会导致空列表索引越界的代码,修复它,然后运行测试验证。”
如果这条指令能被完整执行,说明三个环节都打通了:
- 模型能理解代码库结构并生成修复方案,这是 Qwen3-Coder 的能力。
- Qwen Code 能正确调用读文件、写文件、执行命令等工具,这是客户端智能体调度能力。
- vLLM 的并发和响应速度能支撑这种多轮工具调用场景,不会中途卡死。
我自己第一次跑通这个流程时,是从“检查文件列表”开始,看到它真的调用了 ls 工具,再读到文件内容,再修改,最后运行了测试脚本并汇报结果。那一刻才觉得整套私服真能干活了。
5. 把私服调到“顺手”的状态:vLLM 性能与稳定性实战
5.1 三个性能指标先对齐
我衡量内网编程助手的表现,主要看三个数字:
- 首 Token 时延:发出请求到收到第一个字的时间。编程场景里用户一句“把这段代码改成异步”大概有几百个输入 token,如果首 Token 要等 5 秒以上,是人都会不耐烦。
- 生成吞吐量:每秒生成多少个 token,直接反映多人并发时的响应速度。
- 最长等待时间:高峰时段排在最后面的人等了多久。如果超过 20 秒,基本等于不可用。
调优的目标不是把某个指标拉满,而在这三个数字间找平衡。
5.2 显存规划和量化:别让显存成为瓶颈
模型权重本身占显存,KV Cache 也会占显存,两者是此消彼长的关系。如果发现并发一高就报显存不足,最简单有效的方法是降低 --max-model-len。比如从 32768 降到 16384,KV Cache 占用量几乎减半,能服务的人数立刻上来。代价是上下文变短,有得就有失。
如果团队确实需要很长上下文,另一种思路是使用量化模型。Qwen3-Coder 系列通常提供不同精度的版本,比如 FP8 或 INT8 量化版,能让权重体积显著下降,把省下来的显存让给 KV Cache。实测下来,量化的代码生成质量影响通常可接受,尤其在统一规范场景下差异更小。
怎么检查当前显存够不够?最简单的方法是在压力测试时持续跑 nvidia-smi 看显存使用率。如果长期在 95% 以上但没报 OOM,说明显存利用合理;如果频繁 OOM,就把 --gpu-memory-utilization 降一档或缩短上下文。
5.3 前缀缓存与并发参数,直接改动体验的旋钮
--enable-prefix-caching 是我强烈建议开启的参数。常见代码仓库里的文件在请求中被反复读取,如果多个用户同时读同一个文件,前面对系统提示词和省略掉的用户字段经常一样,前缀缓存可以避免每次都重新计算模型前面那些轮次的注意力,效果立竿见影。我开启之后,单机实测首 Token 时延在重复场景下能下降三到四成。
--max-num-seqs 是允许同时处理的序列数,默认值如果是 256,在单卡上往往过大了。设太大不一定是好事,因为并发序列过多时,每个序列都要公平分配显存,单条请求的响应会被拉长。我自己调试时倾向于设 128 到 256,然后观察显存和响应延迟的平衡。如果你的场景是个人使用,设 64 甚至 32 会快很多,因为不追求吞吐,只追求单条任务尽快完成。
5.4 多卡并行以后,几个不能忽略的小细节
多卡部署下最隐蔽的问题是 CPU 和内存瓶颈。GPU 的处理速度再快,如果 CPU 来不及把请求预处理成 token,GPU 也在空转。vLLM 容器启动时我用 --shm-size 16g,就是给共享内存加量,避免数据处理时把共享内存打满而报错。IPC 也要打开,即 --ipc=host,这对 vLLM 内部进程通信有直接影响。
还有一个容易被忽略的是磁盘性能。模型启动要从磁盘加载权重,如果从机械硬盘读几十 GB 模型,单是启动就要熬很久。准备一块 NVMe SSD 专门存模型能明显缩短启动等待。多人并发时日志读写也可能成为瓶颈,所以日志目录我单独挂到独立磁盘。
5.5 稳定性优先:服务起不来时先砍参数
如果你第一次启动 vLLM 就各种报错,先别急着研究复杂调优,按照下面顺序做减法:
bash复制# 第一步:砍掉所有“加速型参数”
--enable-prefix-caching
--enforce-eager
# 第二步:调低显存占用目标
--gpu-memory-utilization 0.85
# 第三步:缩短上下文
--max-model-len 8192
先让服务在最低配置下稳定跑起来,再逐步加上功能参数。每次只加一个参数,观察一两小时,稳定后再动下一个。这个方法虽然糙,但对排查问题非常有效。这个原则我用了很多年,从来不会在环境没稳定前就追求极限性能。
6. 团队共享与内网服务化:不只是“一个人连得上”
6.1 我如何给团队分配访问方式
私服级的 AI 编程助手一落地,问题就来了:不止我自己要用,后端研发、前端研发、测试都可能问“这个接口能给我用吗”。vLLM 本身就支持并发,多个人同时访问同一个模型完全没问题。但我不能让每个人都直接 SSH 到服务器上去跑客户端,这样后端端口暴露过大,也不方便管理。
我的方案是在内网一台通用机器上放一个统一的代理入口,团队里所有成员把 Qwen Code 的 QWEN_CODE_BASE_URL 指向这个内网代理。代理后面连着 vLLM 服务,这样用户的请求不会直接触及 GPU 服务器的管理端口。这层代理同时可以加轻量的访问控制,防止内网任何人都能乱调模型接口。
6.2 多用户并发时的资源隔离
vLLM 会做连续批处理,也就是所有用户请求在模型层未必完全隔离。如果一个人发了一个超长上下文的请求,占掉大部分 KV Cache,其他人可能被挤到等待队列里。这就是多用户场景下会出现的真实问题。
解决思路有几个。保守的是在代理层限制单请求最大 token 数,避免单个任务吃掉全部资源。激进的是跑两套 vLLM 实例:一套长上下文低并发给代码深度分析用,一套短上下文高并发给日常问答用。两套共用模型目录但显存各自划分,互不干扰。缺点是成本高,如果你们的 GPU 资源不宽裕,更实际的做法是限制上下文长度到 16384,或者用共享前缀缓存降低重复计算。
6.3 代码安全与审计:让内网两个字真正落地
纯内网部署的最大价值就是数据不出内网。但安全不是“网络不通”就结束了,还要防止另一种风险:AI 助手在客户端侧读取了敏感模块并在响应里展示给不该看的人,以及用户的代码提示会进入服务日志文件。
我建议把 vLLM 的访问日志开启,记录来源 IP、请求时间、token 数,但不记录消息里的代码正文。然后在 Qwen Code 的客户端说明里提醒团队不要在内网服务里问外部开源协议问题,也不要问与项目无关的个人信息。模型权重是本地部署的,不会把代码发给外部,但日志里如果存了完整代码,就失去了私有化的一部分意义。
6.4 日常维护要盯的三件事
内网服务跑起来以后,日常维护其实很简单,我只看三个东西。
第一,显存和温度。GPU 长期在 90% 以上跑没问题,但温度如果超过 85 度就要检查散热。定期 nvidia-smi 看一次就够了。
第二,日志大小。vLLM 和代理的日志如果不轮转,一个月就能把磁盘撑爆。我用一个简单的定时任务清理超过 7 天的日志。
第三,模型版本更新。Qwen3-Coder 系列如果发布了新的更强版本,我一般先在非生产环境跑一周,对比几次真实代码任务的效果,再迁移。不要一有新版就立刻把手上的稳定服务升级,离线环境升级成本高,回滚也麻烦。
7. 踩过的坑:内网环境特有的问题解剖
7.1 类型一:模型加载无限卡在“Downloading”
这个问题离线环境几乎人人都会遇到。启动命令里写了 --model Qwen/Qwen3-Coder-30B 这种带组织名的路径,vLLM 一看这不是本地路径,就尝试去 Hugging Face 下载,然后在内网里无限重试。
排查链路:先看启动日志,是不是在重试连接外网域名;再用 ls 确认本地模型目录里的文件是否完整;最后把 --model 改成绝对路径 /data/ai/models/...。
根因:很多人习惯从文档复制命令,忘了 vLLM 对“模型名”的处理逻辑是:本地路径找不到就尝试远程拉取。内网没有外网,这个行为就会变成无响应。
7.2 类型二:客户端能连上,但工具调用每次都返回原始 JSON
这时是最容易误判为“模型能力不行”的。表现为 Qwen Code 请求模型后,模型在当前回答的 content 里带着 tool_calls 或 <tool_call> 这样的结构化标记,但客户端不认,直接把标记当普通文本展示。
排查链路:先在 Qwen Code 里看系统日志,是否有“failed to parse tool call”之类的提示;再确认服务端返回的响应里 finish_reason 是否等于 tool_calls;最后检查 Qwen Code 对模型格式的自动识别是否成功。
根因:客户端默认按某个预设模型语法解析工具调用,而 vLLM 自定义模型名时,客户端无法根据模型名推断这是哪个系列,于是解析方式没生效。解决办法就是我前面说的,手动配置工具解析为 qwen 风格或 simple 方式。你可以多试几种解析方式,配一次就能稳定。
7.3 类型三:首 Token 延迟高得离谱,但吞吐量正常
这是一种很有迷惑性的表现。如果日志里看到总生成速度并不慢,但每个请求的第一响应特别慢,多半是队列里堆了太多并发请求。
排查链路:看服务日志确认并发序列数是不是已经打满;用 nvidia-smi 看显存是否被大量 KV Cache 占满;再观察上下文长度,如果某人一次性传了 2 万 token 的代码文件,那么第一次推理必然要处理完整的输入序列,速度慢是物理规律。
根因:一个超长上下文的请求会把计算资源包圆,其他短请求只能等它做完。优化手段是把上下文限制调低,或者使用前缀缓存,让相同前缀的请求可以复用公共计算。如果是团队共享,建议在客户端约定:不要把整个仓库一次性丢给模型,而是先让它在仓库里搜索定位到相关文件,再基于单个文件做修改。
7.4 类型四:服务器内存被吃满,服务假死
有次我排查了很久,发现 GPU 显存明明还有余量,但服务就是不响应,系统负载特别高。后来才发现是 CPU 内存不够,因为 vLLM 在做 token 预处理、采样时都需要 CPU 内存配合,并发一高,内存直接打满。
排查链路:free -h 看内存余量;dmesg 看有没有 OOM killer 杀进程;查看容器内存限制。
解决方法是调高机器的物理内存或限制单容器内存。vLLM 启动时可以设置环境变量,或者通过 Docker 的 --memory 限制进程内存。建议给容器单独分配足够内存,别让它和宿主机其他服务抢。
7.5 内网环境调优必须先做“网络基线”
还有一个在纯内网特有的问题:你以为自己在调模型性能,结果卡在网络层。内网速度测试一下,如果同一个服务器上从客户端到模型服务的网络延迟偏高,甚至不如跨公网访问外部模型的体验,那就是内部的交换机或防火墙配置有问题。
先做基线测试:直接 curl 模型服务接口,看看首 Token 时间是多少。然后在 Qwen Code 里做同样请求,对比时间差。如果差异大,问题在客户端与模型服务之间的网络或代理层,而不是模型推理性能。别一上来就调模型参数,否则怎么做都白费。
最后再分享两个小经验
第一,我调试时一定会保留一个最小可用的模型版本和配置备份。不知道有多少次因为调参数把服务搞挂,最后是靠快速恢复旧配置才保住当天的可用性。大模型服务不像普通 Web 服务,启动加载一次模型就要好几分钟,折腾一两次就会意识到备份配置的重要性。
第二,Qwen Code 这类智能体工具的能力上限,其实不全在模型本身。更关键的是提示词里有没有把项目上下文传递清楚,以及给它的弹窗权限是不是够它完成多步骤工作。内网部署后,我反而花了很多时间在怎么让 Qwen Code 正确理解我们的仓库分支规范和提交规范上。工具决定了下限,而使用方式决定上限。
