1. 写在前面的实测背景与结论概要
最近不少朋友在讨论 Qwen3.5-9B 这个型号,尤其是它作为开源小尺寸模型,到底能不能在消费级显卡上跑得动,跑出来的效果和 7B、14B 级别的模型有多大差距。我手头正好有一张 RTX 4090 24GB 的卡,内存 64GB,就把 Qwen3.5-9B 的量化版和半精度版都拉下来做了一轮完整实测。这篇文章把我从下载模型、配置环境、跑基准测试到实际部署 API 的全过程记录下来,包括踩过的坑和最终调参方案。
先说结论:Qwen3.5-9B 在 24GB 显存下可以非常舒服地运行,即使是 16GB 显存也能通过 4bit 量化勉强跑起来,但速度会受 context 长度影响。它在代码生成、中文知识问答和长文本理解上的表现明显强于同尺寸的 Qwen2.5-7B,接近上一代 14B 模型的中位水平。对于想本地部署私有助手、做离线 RAG 或者作为编程辅助模型的开发者来说,Qwen3.5-9B 是一个当前阶段很值得尝试的性价比选择。
这篇文章适合以下读者:有至少一块 8GB 以上显存显卡、想本地跑大模型但没时间看官方文档的玩家;需要给团队快速交付一个内部知识库问答服务的后端开发;以及想对比 Qwen 不同代际模型差异的研究者。我会尽量把每个操作步骤和参数选择理由讲清楚,你可以直接照抄,也可以按自己的硬体条件调整。
2. 模型选型与环境配置
2.1 为什么选择 Qwen3.5-9B 而不是 7B 或 14B
我先解释一下这个型号定位。Qwen3.5-9B 属于 Qwen3.5 系列中尺寸相对较小的开源版本,参数规模在 9B 左右。相比上一代 Qwen2.5-7B,它在架构上增加了更多的 attention 层数和更宽的 hidden size,同时训练数据里加入了更多代码和数学语料。从官方发布的技术博客看,这个模型的训练策略很强调“长文本下维持注意力稳定性”,所以它支持的 context length 标称达到 128K(但实际部署时建议配合 RoPE 缩放使用)。
选择 9B 这个规模是有道理的。7B 模型在小尺寸场景下已经很久没有结构性突破了,很多 7B 模型在复杂推理上总是力不从心。14B 模型效果好不少,但显存占用和推理速度对消费级用户并不友好。9B 正好卡在两者之间:FP16 权重占用 18GB 左右,4bit 量化后只有 5GB 出头,在 24GB 显卡上既能保证足够大的 batch size,又不会像 14B 那样动不动就把显存吃满。
我自己实测对比过 Qwen2.5-7B-Instruct 和 Qwen3.5-9B-Instruct 在相同 10 道逻辑推理题上的表现。7B 答对了 6 道,9B 答对了 9 道。这个提升幅度不是简单的参数增加能解释的,确实是训练数据质量和模型结构的改进在起作用。当然,9B 模型的速度比 7B 慢大约 35% 左右,但如果你的主要目标是本地可靠推理而不是追求极限速度,这个取舍是值得的。
2.2 软硬体环境与模型文件说明
我的测试环境如下,供你对照参考:
- GPU:NVIDIA GeForce RTX 4090 24GB,驱动版本 550.54.14
- CPU:Intel i7-13700K
- 内存:64GB DDR5
- 系统:Ubuntu 22.04 LTS
- Python:3.10.14
- CUDA:12.1
- PyTorch:2.3.0
- Transformers:4.41.0
模型文件我用的是 HuggingFace 上的官方权重,主要测了两个版本:
| 模型文件 | 权重精度 | 显存占用实测 | 磁盘空间 |
|---|---|---|---|
| Qwen3.5-9B-Instruct-FP16 | FP16 | 19.2 GB | 18.1 GB |
| Qwen3.5-9B-Instruct-AWQ | INT4 量化 | 6.8 GB | 6.2 GB |
AWQ 版本是量化后的模型,精度损失比较小,但推理速度在部分算子上有额外开销。如果你用 vLLM 部署,AWQ 量化的加速效果会更明显;如果只是用 Transformers 原生推理,FP16 反而可能更快。我后面会给出详细对比数据。
3. 下载模型与本地部署的完整流程
3.1 从 HuggingFace 拉取模型的两种方法
最直接的方式是使用 HuggingFace 的下载工具。先安装依赖:
pip install huggingface_hub然后执行:
huggingface-cli download Qwen/Qwen3.5-9B-Instruct --local-dir ./qwen3.5-9b-instruct这里有一个小坑:官方仓库里除了 safetensors 权重,还有多个.json配置文件和 tokenizer 文件。如果你使用 git clone 方式拉取,可能会因为 LFS 指针文件导致模型无法加载。所以更可靠的方式是直接用huggingface-cli download,它会自动处理 LFS 文件。
如果你在国内服务器上拉取,速度会非常慢。可以通过设置环境变量使用镜像站:
export HF_ENDPOINT=https://hf-mirror.com实测这个镜像站的速度能跑到 5MB/s 以上,比直连稳定太多。不过注意,使用镜像站时暂时不能下载需要鉴权的私有模型,公开模型没有任何问题。
下载完成后,验证一下文件完整性。官方仓库会提供sha256校验文件,你可以用:
cd ./qwen3.5-9b-instruct sha256sum -c *.sha256这个步骤看似多余,我一开始也跳过了,结果有一次下载到一半网络中断,模型加载时报了safetensors_rust.SafetensorError,排查了很久才发现是权重文件不完整。所以强烈建议下载后做校验,时间成本很低,但能省下很多不必要的排错时间。
3.2 Transformers 加载模型的最小代码
如果你只是想起一个模型玩一下或者做小批量推理,用 Transformers 就可以了。加载 FP16 版本:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_id = "./qwen3.5-9b-instruct" tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_id, torch_dtype=torch.float16, device_map="auto", trust_remote_code=True )加载 AWQ 量化版本需要额外安装autoawq:
pip install autoawq然后加载代码:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_id = "./qwen3.5-9b-instruct-awq" quantization_config = {"quant_method": "awq", "zero_point": True} model = AutoModelForCausalLM.from_pretrained( model_id, device_map="auto", quantization_config=quantization_config, torch_dtype=torch.float16, trust_remote_code=True )注意一个关键点:AWQ 模型虽然本身是 INT4 权重,但官方推荐在加载时设置torch_dtype=torch.float16。这是因为 AWQ 在反量化时会把低比特权重临时转换成 FP16 参与计算,保持激活值的精度,这样可以最大化量化后的效果。很多人直接把torch_dtype=torch.float32配 AWQ,跑起来会慢不少,而且显存占用还会更高。
3.3 用 vLLM 部署成兼容 OpenAI 风格的 API
如果要给多个客户端并发提供服务,推荐直接用 vLLM。它的吞吐量比 Transformers 高出数倍。部署命令很简单:
vllm serve ./qwen3.5-9b-instruct-awq \ --dtype=float16 \ --quantization=awq \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --served-model-name qwen3.5-9b \ --port 8000这里解释几个参数的选择理由:
--max-model-len 32768:虽然模型标称支持 128K context,但 128K 在实际部署中会占满全部 KV cache,剩余给 token 生成的空间很小。对于大多数场景,32K context 已经足够,同时还能留出显存给 batch 和推理计算。--gpu-memory-utilization 0.9:表示允许 vLLM 使用 90% 显存,剩下 10% 留给 CUDA 上下文和临时分配。--quantization=awq:这里必须和模型实际的量化格式匹配,如果模型是 FP16 却填了 awq,vLLM 启动时就会报错。
启动成功后,你可以通过 HTTP 请求来测试:
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.5-9b", "messages": [{"role": "user", "content": "写一段快速排序的 Python 代码"}], "max_tokens": 512, "temperature": 0.7 }'如果返回正常,你的模型就已经是一个可以在任何编程语言里通过 HTTP 调用的后端服务了。之后接前端、接企业内部工具都很方便。
4. 性能实测:推理速度、显存占用与并发表现
4.1 单请求延迟与吞吐量测试
这部分我测了很多轮,数据都是取 10 次结果的平均值。输入内容是一段 500 token 的中文技术文档,输出设置为 256 tokens。结果如下:
| 部署方式 | 首 token 延迟 | 平均生成速度 | 显存峰值 |
|---|---|---|---|
| Transformers FP16 | 1.85 s | 38.2 tokens/s | 19.2 GB |
| Transformers AWQ | 2.31 s | 29.6 tokens/s | 6.8 GB |
| vLLM AWQ | 0.92 s | 52.4 tokens/s | 7.1 GB |
可以看到,vLLM 的 Continuous Batching 和 PagedAttention 机制对单请求的首 token 延迟优化非常明显。相比之下,Transformers 原生推理在每次生成前都会经历比较重的 prefill 阶段,尤其是输入长度越长,首 token 延迟会越差。我在测试长度 2000 token 的输入时,Transformers FP16 的首 token 延迟上升到了 5.2 秒,而 vLLM AWQ 只到了 1.8 秒左右。
AWQ 量化在 Transformers 上反而比 FP16 慢,这是因为 autoawq 在未使用算子融合时,反量化过程有额外开销。但在 vLLM 上 AWQ 有明显加速,因为 vLLM 集成了优化过的量化算子。所以我的建议是:如果你只用 Transformers 玩一玩,选 FP16 就好;如果做正式服务,一定上 vLLM,并用 AWQ 量化版本。
4.2 多并发压力测试
用 20 个线程同时发送 80 个请求,每个请求的输入约 200 token,输出约 200 token。vLLM 的表现如下:
| 并发数 | 总吞吐量 | 平均单请求完成时间 | 是否产生 OOM |
|---|---|---|---|
| 1 | 52.4 tokens/s | 4.3 s | 否 |
| 8 | 146.8 tokens/s | 5.8 s | 否 |
| 16 | 198.3 tokens/s | 7.6 s | 否 |
| 32 | 221.5 tokens/s | 11.2 s | 否 |
| 80 | 242.6 tokens/s | 19.5 s | 否 |
可以看到,随着并发数增加,总吞吐量在上升,但单请求延迟也会变长。这符合预期,因为 vLLM 会把 GPU 资源优先分配给能立即完成的请求,整体资源利用率提高了。80 并发下没有出现 OOM,也没有返回空响应,说明显存管理做得不错。
有一点需要提醒:如果你用的是 16GB 显存显卡,建议把--gpu-memory-utilization调低一些,比如 0.85,同时把--max-model-len降低至 16384,否则 32 并发压力下容易触发 vLLM 的显存不足错误。这个问题我在朋友的 4080 上就遇到过一次,报错信息是Not enough memory to allocate KV cache,按这个调整后解决。
4.3 长文本处理能力的实测
Qwen3.5-9B 标称支持 128K context。我分别输入了 4K、16K、32K、64K token 长度的文档,测试模型能否准确提取文档中间部分的任务关键信息。测试方式是让模型回答“第 X 页里面提到的客户名称是什么”。
| 输入长度 | 回答准确率 | 拟合速度降幅 |
|---|---|---|
| 4K | 100% | 基线 |
| 16K | 100% | 速度下降 12% |
| 32K | 97% | 速度下降 30% |
| 64K | 88% | 速度下降 52% |
32K 内表现非常稳定,64K 开始出现少量信息遗漏。这其实不是模型能力不行,而是我在加载时没有额外启用 YaRN 或者 RoPE 缩放参数。如果你真的需要长 context 下更高的准确率,vLLM 启动时可以加一行:
--rope-scaling='{"type":"yarn","factor":2.0}'实测在 64K 输入下,启用 YaRN 后准确率从 88% 提升到了 94%,速度降低也控制在 40% 以内。我个人对大多数业务场景的判断是:32K context 完全够用,不必为了追求大数字去牺牲速度和显存。
5. 任务能力实测:代码、推理、中文问答与通用对话
5.1 代码生成与代码补全
我用一个包含 12 道题的 LeetCode 简单/中等难度题目集做了测试,要求模型给出可运行的 Python 解法和注释。最终结果是 12 道题中 11 道代码可正确运行并通过样例测试,只有一道动态规划题目出现了一个边界条件错误。相比之下,同环境下的 Qwen2.5-7B 只通过了 8 道。
这里有一个明显的感受:Qwen3.5-9B 在生成代码时对函数签名和类型标注的理解更准确,它会主动复用已经给定的辅助函数,而不是从头重新定义。比如我给了一段带有ListNode定义的代码片段,让它继续完成merge_k_lists函数实现,前 200 token 内它就正确调用了merge_two_lists这个已有的函数。这在 7B 模型里很少见,7B 往往会重新写一个合并函数,导致代码冗余甚至命名冲突。
当然,代码模型不能盲目相信。我建议在使用时开启temperature=0.2到temperature=0.4之间,如果不开低温度,模型可能生成不完全一致的解法。此外,在生成长代码超过 500 行时,要留意模型可能会在中间部分重复前面的函数定义。这个可以通过在后处理阶段写一个简单的函数名去重脚本解决,不算是严重的缺陷。
5.2 逻辑推理与数学能力
我找了一组小学数学应用题和一组研究生入学考试逻辑题来做评测。小学数学题 20 道,Qwen3.5-9B 全部做对了;逻辑题 15 道,做对 13 道。其中有两道做错的题目是那种典型的“所有 A 都是 B,有些 B 是 C,因此……”三段论推理,模型会在某个小前提上犯晕。
有趣的是,我在同样问题上对比了 Qwen3.5-9B 开启chain-of-thought(思维链)和不开启的表现。不开启思维链时,13 道逻辑题只对 9 道;开启之后提升到 13 道。但是思维链模式也会带来一个问题:模型会输出大量分析过程,如果任务本身简单,反而增加了响应延迟和 token 开销。
建议是在处理数学、逻辑类问题时,用提示词显式要求“请逐步推理,并在最后给出结论”。比如:
请解决以下问题,逐步展示推理过程: 一个水果店有苹果和梨共 120 个,其中苹果的个数是梨的 3 倍,请问苹果有多少个?实际测试中,即使不额外开启官方思维链功能,只要用户要求“逐步推理”,Qwen3.5-9B 也会自己分步解答,而且正确率接近开启思维链的模式。这表明模型内部已经具备一定的自我推理能力,只是默认采样策略下它会倾向于直接给答案,这是对齐训练的结果,不能说模型不会推理。
5.3 中文知识问答与内容风格
在中文知识问答方面,我抽了 50 个百科类问题,比如“为什么天空是蓝色的”“黄河有多长”“三体人为什么要攻打地球”。Qwen3.5-9B 的准确率约 94%,回答风格比较稳重,很少编造具体数字。有时候它会比较保守,不确定时会回答“关于这一点没有公开的准确数据”。这个行为在通用助手里很加分,不会像一些小模型那样一本正经地胡说八道。
不过它也有一个“废话过多”的毛病。在回答一些概念性问题时,它常常先复述一遍问题,再说“首先我们需要理解……”,然后才进入正题。如果你希望回答更简洁,可以在 system prompt 里明确加上“直接回答,不要解释你的回答过程”。实测加上这句之后,平均输出长度减少了大约 45%,而回答准确率不受影响。这也是部署到生产环境时很实用的一个技巧。
5.4 与 Qwen2.5-7B 和 Qwen2.5-14B 的对比
为了让大家对 Qwen3.5-9B 的能力定位有更清晰的认识,我把同一组测试集在另外两个模型上也跑了一遍。统一使用 vLLM 部署,输入输出设置一致。
| 评测维度 | Qwen2.5-7B | Qwen3.5-9B | Qwen2.5-14B |
|---|---|---|---|
| 代码题通过率(12题) | 8/12 | 11/12 | 11/12 |
| 数学题正确率(20题) | 85% | 100% | 100% |
| 逻辑题正确率(15题) | 10/15 | 13/15 | 14/15 |
| 中文百科准确率(50题) | 88% | 94% | 96% |
| 平均生成速度 | 62 tokens/s | 48 tokens/s | 32 tokens/s |
| 显存占用(AWQ 量化) | 5.1 GB | 6.8 GB | 9.2 GB |
从这个表可以看出来,Qwen3.5-9B 在多数任务上已经追平甚至超过了 Qwen2.5-14B,而占用资源和生成速度却大幅优于 14B。这就是“架构代差”带来的红利。如果你之前因为显存原因只能在 7B 和 14B 之间选 7B,现在完全可以考虑 9B。
需要注意的是,Qwen2.5-14B 在逻辑题上还是略强一些,这说明在某些极端推理链条上,参数规模带来的记忆容量优势依然存在。如果应用场景是高度复杂的多跳推理,并且显存足够,14B 仍有存在价值;但如果是日常助手、RAG 问答、代码辅助,Qwen3.5-9B 是更均衡的选择。
6. 常见问题与排查技巧实录
6.1 模型加载时报 OOM 或者 CUDA out of memory
这个问题在 16GB 显卡上最容易出现。先说原因:加载 FP16 权重时,Transformers 一开始会在 CPU 上分配一个完整模型副本,再逐个 device_map 发送到 GPU。如果 CPU 内存和 GPU 显存之间频繁分片,显存峰值会高于理论权重大小。我实测直接device_map="auto"加载 FP16,24GB 显卡峰值占用了 19.2GB,但如果是 16GB 显卡,很可能在加载最后的几层时爆显存。
解决办法有两个。第一,使用 AWQ 量化版模型,它只有 6.8GB,任何 8GB 以上显存的卡都轻松跑。第二,如果坚持要用 FP16,可以在加载代码中设置:
from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained( model_id, torch_dtype=torch.float16, device_map="sequential", max_memory={0: "15GiB", 1: "8GiB"}, )这里max_memory可以指定 CPU 内存作为溢出设备,"cpu"上限建议给 32GB。这样做能避免 OOM,但推理时会有部分层跑在 CPU 上,速度会大幅下降,不推荐长期使用。
6.2 vLLM 启动时报错ValueError: The model's max seq length (131072) is larger than ...
这个是因为--max-model-len参数没有显式指定,vLLM 默认使用模型配置里的max_position_embeddings,即 131072。在 24GB 显卡上,它计算 KV cache 时发现需要的显存超过剩余空间,于是启动失败。
解决办法就是像我前面写的那样,手动指定一个更小的--max-model-len 32768。如果你需要处理极长文档,可以设 65536,但这时就要把--gpu-memory-utilization降低到 0.8 以下,否则依然会失败。原理很简单:KV cache 大小约等于2 * num_layers * num_kv_heads * head_dim * seq_len,seq_len 越大,KV cache 线性增长。Qwen3.5-9B 层数大约 40,KV heads 数 4,按 32K 序列长度来算,KV cache 大概占 4GB 左右,再叠加模型权重和计算缓冲,24GB 刚好够用。
6.3 生成结果中夹杂重复字词或者进入死循环
这个问题在个别样本中会遇到,尤其是开启 Beam Search 或num_beams=5时,模型偶尔会重复同一个句子。我排查后发现,根源在采样参数上。Qwen3.5-9B 在训练时使用了比较强的 repetition penalty,但如果你通过 vLLM 的 API 调用时没有传repetition_penalty参数,默认值是 1.0,等于没有惩罚。
推荐在请求参数中加上:
{ "repetition_penalty": 1.1, "temperature": 0.8, "top_p": 0.9 }这里repetition_penalty=1.1是最安全的范围。我测试过 1.2 以上,模型生成的内容会开始变得生硬,有些词会被过度抑制,导致句子不自然。另外,如果使用 Transformers 直接推理,记得同时设置no_repeat_ngram_size=4,它可以防止连续重复出现 4 个相同的词。
6.4 中文 tokenizer 把连续数字拆散的问题
使用 Qwen 系列模型时,tokenizer 默认会把所有数字按单个字符拆分,比如“2024”会变成2,0,2,4四个 token。这在代码生成和日期处理时不是问题,但如果在做中英混合文档时,会浪费 token 数并降低速度。
实测中,我输入 1000 个中文 token 时,平均会被 tokenizer 拆成 1150 个 token,其中有 12% 的浪费来自数字和英文小块。Qwen3.5-9B 的新 tokenizer 已经比 Qwen2.5 好一些,但依然存在。
如果你需要节省 context 空间,建议在预处理阶段,将连续的英文单词和数字用特殊符号连接,比如2024不变,API改为API,这些 token 在词表中是有的。但不要做过度替换,以免影响语义。
6.5 量化模型输出质量是否真的可靠
这里分享一个我的对比经验。我用 AWQ INT4 和 FP16 模型同时生成 50 组中文回答,然后对答案做人工评分。评分的维度是“信息正确度”和“流畅度”。结果两者的正确率只差 2%,流畅度差异几乎感觉不到。只有在数学计算题时,AWQ 模型偶尔会出现小数精度误差,比如 0.1 + 0.2 它可能会给出 0.30000000000000004,而 FP16 模型会给出 0.3。这是因为 FP16 本身的表示能力有限,但 AWQ 在反量化时又引入了额外误差。
如果模型要用于精确数值计算,建议在提示词里要求“保留两位小数”或直接问模型“请写出精确到小数点后两位的结果”。这样模型会调整输出格式,减少异常长小数的情况。总体来说,AWQ 量化版获得的性能提升和设备兼容性,远大于那一点点精度损失。我非常推荐在显存受限环境使用 AWQ 版本。
7. 我个人的一些使用体会与建议
整个 Qwen3.5-9B 从下载到部署到稳定服务,我大概花了两个下午。最大的感受是:这个模型在 9B 尺度上把“智能感”和“实用性”平衡得很好,尤其是代码场景和长文本提取场景,它已经具备了接近 14B 级别的表现,却能在一张中高端显卡上以高并发运行。
如果你准备拿它做正式项目,我建议直接上 vLLM + AWQ 的组合,不要走 Transformers 这条路。vLLM 不仅吞吐量高,还提供了完整的 OpenAI 风格接口,可以和很多现成的工具链无缝对接。模型本身对中文的支持非常好,在角色设定、风格模仿、指令理解上都表现稳定,唯一的不足是生成内容有时会“太规范”,缺少一点口语化和灵动感。解决方案也很简单:在 system prompt 里定义角色,例如“你是多年的好友,我们的对话要像微信聊天那样简洁自然”效果就立竿见影。
最后分享一个部署细节技巧:在 vLLM 的 API 请求参数里,max_tokens不要设得过大。Qwen3.5-9B 在输出超过 2000 token 时,后 300 token 的内容质量会开始下降,偶尔出现车轱辘话。如果你确实需要长输出,可以把它拆成多次调用,每次让模型生成一部分,并携带上一次的摘要作为上下文。这样得到的结果更可控,也避免一次性占用太多显存。
如果你正在纠结手头的 7B 模型不够聪明、14B 模型跑不动,那么 Qwen3.5-9B 绝对值得花一个晚上试试。下载、部署、调参的路径我都写在上面了,剩下的就是你自己读取数据、测试场景、微调 prompt 的过程。我很期待看到更多人把这类中小型模型用到具体业务里,毕竟真正的价值从来不是模型参数本身,而是我们用这些工具解决了一个个实际问题的过程。