1. 为什么27B这个参数量级最考验资源核算功底
Qwen3-27B 这个尺寸很有意思。它不像 7B 那样随便一张消费级卡就能塞进去,也不像 72B 那样大家默认直接上多卡或者量化,27B 恰好卡在一个"单卡勉强、双卡宽裕、量化后体验差异巨大"的尴尬区间。我前后在三种不同硬件配置上跑过这个量级的模型,每次都要重新算一遍显存、算力和吞吐量,算错一次就是几个小时的调试白费。
先把结论摆前面:27B 模型的资源核算,核心不是算"能不能跑起来",而是算"跑起来之后每秒能吐多少 token、能扛多少并发、上下文拉长之后会不会崩"。这三个问题分别对应显存、算力和吞吐量三个维度,任何一个算错,实际部署都会翻车。
这篇文章我会把这三个维度的核算方法拆开讲清楚,包括每一步的计算公式、参数取值依据、实测验证方法,以及我在实际操作中踩过的坑。不管你是用 RTX 3090、4090 还是专业卡,核算逻辑是通用的,只是代入的硬件参数不同。
1.1 先搞清楚27B模型到底占多少显存
很多人算显存只算权重,这是最常见的错误。27B 模型的实际显存占用由四部分组成:
| 组成部分 | 计算方式 | FP16 下的量级 |
|---|---|---|
| 模型权重 | 参数量 × 每参数字节数 | 约 54 GB |
| KV Cache | 层数 × 头数 × 头维度 × 序列长度 × 2 × 精度 | 随上下文线性增长 |
| 激活值 | 与 batch size 和序列长度相关 | 通常 2-8 GB |
| 框架开销 | CUDA context、通信缓冲等 | 1-3 GB |
权重部分最直接:27B 参数在 FP16 下就是 27 × 2 = 54 GB。这个数字意味着一张 24GB 的 RTX 3090 或 4090 根本装不下 FP16 的完整权重,必须走量化或者多卡。
但真正容易被忽略的是KV Cache。它的计算公式是:
KV Cache 大小 = 2 × 层数 × KV头数 × 头维度 × 序列长度 × batch_size × 精度字节数以 Qwen3-27B 的典型配置(假设 48 层、8 个 KV 头、头维度 128)为例,在 FP16 精度下,单条 8192 长度的序列:
2 × 48 × 8 × 128 × 8192 × 2 字节 ≈ 1.6 GB看起来不大,但如果你要跑 8 并发、上下文拉到 32K,这个数字会变成:
1.6 GB × 4(长度翻4倍)× 8(并发)= 51 GBKV Cache 直接超过模型权重本身。这就是为什么很多人在长上下文场景下发现显存莫名其妙爆掉——权重明明装得下,KV Cache 悄悄吃掉了所有余量。
提示:Qwen3 系列普遍采用了 GQA(分组查询注意力),KV 头数远小于注意力头数,这已经大幅压缩了 KV Cache。如果你的部署框架没有正确识别 GQA 配置,KV Cache 会按完整头数计算,显存占用直接翻好几倍。部署前务必确认框架版本支持 GQA。
1.2 量化到底能省多少,代价是什么
量化是消费级硬件跑 27B 的必经之路。但不同量化方案的显存节省和精度损失差异很大,不能只看"能省多少显存"。
| 量化方案 | 权重显存 | 相对 FP16 压缩比 | 精度损失(主观感受) | 适用场景 |
|---|---|---|---|---|
| FP16 | 54 GB | 1x | 基准 | 多卡专业部署 |
| INT8 | 27 GB | 2x | 几乎无感 | 单卡 48G 专业卡 |
| INT4 (GPTQ/AWQ) | 14-16 GB | 3.5x | 轻微,复杂推理有差异 | 单卡 24G 消费卡 |
| GGUF Q4_K_M | 约 16 GB | 3.4x | 轻微 | 本地 llama.cpp 部署 |
| GGUF Q3_K_M | 约 13 GB | 4.2x | 可感知,长文本易跑偏 | 显存极度紧张 |
我实测下来的经验是:INT4 量化在 27B 这个量级上是一个甜点。14-16 GB 的权重占用,加上 KV Cache 和框架开销,一张 24GB 的卡在 4K 上下文、单并发下能稳定跑。但如果你要上 8K 以上上下文或者多并发,24GB 就不够了,得考虑 48GB 的专业卡或者双卡。
这里有个反直觉的点:量化省的是权重显存,不省 KV Cache。KV Cache 的大小取决于层数、头数和序列长度,跟权重量化精度无关(KV Cache 可以单独量化,但那是另一回事)。所以当你把权重从 FP16 压到 INT4 省了 40GB,结果发现长上下文下 KV Cache 还是要几十 GB,这种落差感很真实。
1.3 算力核算:别被 TFLOPS 数字骗了
显卡宣传页上的 TFLOPS 是理论峰值,实际推理能用到 30%-50% 就不错了。算力核算的关键是搞清楚推理过程到底是算力瓶颈还是显存带宽瓶颈。
大模型推理分两个阶段:
- Prefill 阶段(处理输入):计算密集,矩阵乘法为主,吃算力
- Decode 阶段(逐 token 生成):访存密集,每生成一个 token 都要把全部权重读一遍,吃显存带宽
对于 27B 模型,Decode 阶段是绝对瓶颈。每生成一个 token,需要读取约 14-16 GB 的权重(INT4 量化后)。如果显卡显存带宽是 936 GB/s(RTX 4090),理论最大吞吐:
936 GB/s ÷ 15 GB ≈ 62 token/s这是单并发下的理论上限。实际因为 kernel 效率、KV Cache 读取等因素,能到 40-50 token/s 就算不错了。
如果是 RTX 3090(936 GB/s 带宽,但架构效率略低),实测单并发大概 30-40 token/s。如果是 A100 80G(2039 GB/s 带宽),单并发能到 80-100 token/s。
这就是为什么算力核算要看显存带宽而不是 TFLOPS。一张 TFLOPS 很高但带宽低的卡,跑大模型推理反而不如一张带宽高的卡。
| 显卡 | 显存 | 带宽 | 27B INT4 单并发预估吞吐 |
|---|---|---|---|
| RTX 3090 | 24 GB | 936 GB/s | 30-40 token/s |
| RTX 4090 | 24 GB | 1008 GB/s | 40-50 token/s |
| A100 80G | 80 GB | 2039 GB/s | 80-100 token/s |
| RTX 6000 Ada | 48 GB | 960 GB/s | 40-50 token/s |
注意:上表的吞吐是 Decode 阶段的稳态值,不含 Prefill。实际体验中,长输入的 Prefill 时间可能比生成时间还长,尤其是上下文超过 8K 之后。
2. 吞吐量核算:并发数不是线性叠加的
吞吐量核算最容易犯的错误是"单并发 40 token/s,那 8 并发就是 320 token/s"。实际完全不是这样。
2.1 并发下的显存和带宽双重挤压
当并发数增加时,两件事同时发生:
- KV Cache 线性增长:每个并发请求都要独立的 KV Cache,8 并发就是 8 份
- 显存带宽被分摊:所有并发请求共享同一块显存带宽,权重读取虽然可以复用,但 KV Cache 读取和激活值计算是各自独立的
实际吞吐量的增长曲线是这样的:从 1 并发到 4 并发,总吞吐大概能涨到 2.5-3 倍;从 4 到 8 并发,可能只再涨 1.5 倍;超过某个点之后,总吞吐不再增长甚至下降,因为显存带宽饱和了。
我实测过一组数据(RTX 4090,27B INT4,4K 上下文):
| 并发数 | 单请求吞吐 | 总吞吐 | KV Cache 占用 |
|---|---|---|---|
| 1 | 45 token/s | 45 token/s | 0.8 GB |
| 2 | 38 token/s | 76 token/s | 1.6 GB |
| 4 | 28 token/s | 112 token/s | 3.2 GB |
| 8 | 16 token/s | 128 token/s | 6.4 GB |
| 16 | 9 token/s | 144 token/s | 12.8 GB |
可以看到,从 1 到 8 并发,总吞吐涨了不到 3 倍,而单请求延迟下降了近 3 倍。这就是吞吐量和延迟的权衡:你要高吞吐就得牺牲单请求响应速度,要低延迟就得限制并发。
2.2 上下文长度对吞吐的隐性影响
上下文长度对吞吐量的影响比大多数人想象的大。原因有两个:
第一,Prefill 时间随上下文长度超线性增长。注意力机制的计算复杂度是 O(n²),上下文从 4K 拉到 32K,Prefill 时间可能涨 10 倍以上。这意味着用户提交一个长文档问答请求,等待首 token 的时间会非常长。
第二,KV Cache 读取量随上下文增长。Decode 阶段每个 token 都要读取完整的 KV Cache,上下文越长,每个 token 的生成越慢。
实测数据(27B INT4,单并发):
| 上下文长度 | Prefill 时间 | Decode 吞吐 |
|---|---|---|
| 2K | 0.3s | 48 token/s |
| 4K | 0.8s | 45 token/s |
| 8K | 2.5s | 38 token/s |
| 16K | 8s | 28 token/s |
| 32K | 25s | 18 token/s |
32K 上下文下,Prefill 要 25 秒,Decode 只有 18 token/s。如果用户期望的是流畅对话体验,这个配置基本不可用。
实操心得:如果你的应用场景是对话,建议把上下文限制在 8K 以内,超过这个长度用户体验会明显下降。如果是文档处理场景,可以接受长 Prefill,但要做好排队和异步处理。
2.3 用排队论估算实际服务能力
要估算一个部署能服务多少用户,不能只看吞吐量,还要看请求到达率和处理时间的分布。这里可以用一个简化的排队论模型。
假设:
- 平均请求输入 2K token,输出 500 token
- 单请求处理时间 = Prefill 时间 + Decode 时间 = 0.3s + 500/45 ≈ 11.4s
- 单卡 4 并发下,总吞吐约 112 token/s
如果每个用户平均每分钟发一次请求,每次请求消耗 500 输出 token,那么单卡能支撑的活跃用户数:
112 token/s × 60s ÷ 500 token ≈ 13.4 请求/分钟也就是说,单卡 4 并发大约能支撑 13 个每分钟发一次请求的活跃用户。如果用户更活跃,就需要加卡或者限制并发。
这个估算很粗糙,但能帮你快速判断一个配置够不够用。实际部署中还要考虑请求长度的方差、峰值流量等因素,通常要留 2-3 倍余量。
3. 不同硬件配置下的实测方案与调优
理论算完,最终要落到具体硬件上。我按显存容量分三档来讲,每档给出可落地的配置方案。
3.1 24GB 单卡:量化 + 上下文限制是唯一出路
24GB 卡(3090/4090)跑 27B,必须同时满足两个条件:INT4 量化 + 上下文不超过 8K。
具体配置:
# 以 vLLM 为例的启动参数 python -m vllm.entrypoints.openai.api_server \ --model Qwen3-27B-Int4 \ --max-model-len 8192 \ --gpu-memory-utilization 0.92 \ --max-num-seqs 4 \ --quantization gptq \ --dtype float16关键参数解释:
--max-model-len 8192:限制最大上下文,防止 KV Cache 爆显存--gpu-memory-utilization 0.92:留 8% 给框架开销,设太高容易 OOM--max-num-seqs 4:限制并发数,4 是 24GB 卡在 8K 上下文下的安全值
这套配置下,权重占约 15GB,KV Cache 4 并发 × 8K 约 6.4GB,框架开销约 1.5GB,总计约 23GB,刚好卡在 24GB 边缘。
注意:如果你的卡还要跑其他任务(比如同时做 embedding),
gpu-memory-utilization要降到 0.85 以下。我见过太多因为显存碎片导致运行几小时后 OOM 的案例。
3.2 48GB 单卡:上下文和并发都能放开
48GB 卡(RTX 6000 Ada、A6000)是 27B 模型的舒适区。可以跑 INT8 量化,上下文拉到 32K,并发开到 8。
python -m vllm.entrypoints.openai.api_server \ --model Qwen3-27B-Int8 \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 8 \ --quantization fp8 \ --enable-prefix-caching这里加了--enable-prefix-caching,对于多轮对话场景能大幅减少重复 Prefill。原理是把相同前缀的 KV Cache 缓存下来,后续请求直接复用。在多轮对话中,系统提示词和早期对话轮次的前缀是共享的,这个优化能省 30%-50% 的 Prefill 时间。
显存分配:INT8 权重约 27GB,8 并发 × 32K 的 KV Cache 约 25GB,加起来 52GB,超过 48GB 了。所以实际要把并发降到 4,或者上下文降到 16K。
这就是核算的价值:不实际算一遍,你根本不知道 48GB 卡在 32K 上下文下只能跑 4 并发。
3.3 双卡方案:并行策略决定成败
两张 24GB 卡跑 27B,有两种并行策略:
张量并行(TP):把每一层的权重切分到两张卡上,计算时通过高速互联通信。优点是单请求延迟低,缺点是通信开销大,需要 NVLink 或者 PCIe 4.0 x16。
流水线并行(PP):把不同层分配到不同卡上,数据在卡间流水线传递。优点是通信量小,缺点是延迟高,因为要等所有阶段完成。
对于 27B 这个量级,我推荐 TP=2。因为模型本身不算特别大,TP 的通信开销可以接受,而 PP 的延迟惩罚在交互式场景下很难忍受。
# 双卡 TP=2 配置 python -m vllm.entrypoints.openai.api_server \ --model Qwen3-27B-Int4 \ --tensor-parallel-size 2 \ --max-model-len 16384 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 8双卡 TP=2 下,每张卡承担约 7.5GB 权重 + 一半的 KV Cache。16K 上下文、8 并发下,每张卡 KV Cache 约 6.4GB,加上权重和开销约 16GB,24GB 卡有充足余量。
实操心得:双卡方案最大的坑是两张卡的型号不一致。我试过 3090 + 4090 混插,虽然能跑,但吞吐量受限于慢的那张卡,而且驱动兼容性问题很多。强烈建议双卡同型号。
4. 核算之外:那些实际部署才会暴露的问题
理论和实测数据都算完了,但真正部署到生产环境,还有几个核算覆盖不到的问题。
4.1 显存碎片:运行几小时后突然 OOM
这是最让人头疼的问题。启动时显存占用正常,跑几个小时后突然 OOM。原因是 PyTorch 的显存分配器会产生碎片,尤其是当请求长度变化很大时(有的请求 500 token,有的 8000 token),碎片化会越来越严重。
解决方案:
- 设置
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True,让分配器使用可扩展段,减少碎片 - 限制最大上下文和最小上下文的差距,避免极端长度混合
- 定期重启服务(比如每天凌晨),这是最粗暴但最有效的办法
4.2 首 token 延迟:用户感知的瓶颈
吞吐量数据好看不代表用户体验好。用户最敏感的是首 token 延迟,也就是从提交请求到看到第一个字的时间。这个时间主要由 Prefill 决定。
优化首 token 延迟的方法:
- Prefix Caching:多轮对话场景必开
- Chunked Prefill:把长 Prefill 拆成小块,和 Decode 交错执行,避免长请求阻塞短请求
- 限制最大输入长度:超过一定长度的输入直接拒绝或者截断
vLLM 的--enable-chunked-prefill参数就是干这个的。开启后,一个 32K 的 Prefill 会被拆成多个 chunk,每个 chunk 处理完后可以插入其他请求的 Decode,整体延迟分布更均匀。
4.3 量化模型的精度验证不能省
INT4 量化在 27B 上总体表现不错,但在某些任务上会有明显退化。我遇到过的情况:
- 数学推理:INT4 下多步计算容易出错,FP16 下正确率 85%,INT4 下掉到 70%
- 代码生成:简单代码没问题,复杂逻辑(比如递归、边界条件)容易出 bug
- 长文本摘要:超过 4K 的输入,INT4 容易丢失中间部分的细节
所以量化后一定要做任务级别的验证,不能只看困惑度(PPL)指标。PPL 涨 0.1 看起来无所谓,但实际任务准确率可能掉 10 个点。
验证方法:准备 50-100 条你实际业务场景的测试用例,分别用 FP16 和 INT4 跑一遍,对比输出质量。如果关键任务退化超过 5%,就要考虑换 INT8 或者接受精度损失。
4.4 吞吐量测试的正确姿势
最后说下怎么测吞吐量才准确。很多人测出来的数字偏高,因为测试方法有问题。
正确的测试流程:
- 预热:先跑 10-20 个请求,让 CUDA kernel 编译完成、显存分配器稳定
- 固定输入输出长度:不要用随机长度,否则方差很大
- 测量稳态吞吐:去掉前几个请求,只统计稳定后的数据
- 分别测 Prefill 和 Decode:这两个阶段的性能特征完全不同
- 多轮测试取中位数:单次测试受系统负载影响大
我常用的测试脚本逻辑:
import time import requests def benchmark(url, prompt, max_tokens=500, rounds=20): latencies = [] for i in range(rounds): start = time.time() response = requests.post(url, json={ "prompt": prompt, "max_tokens": max_tokens, "temperature": 0 }) elapsed = time.time() - start if i >= 5: # 跳过预热 latencies.append(elapsed) latencies.sort() median = latencies[len(latencies) // 2] throughput = max_tokens / median print(f"中位延迟: {median:.2f}s, 吞吐: {throughput:.1f} token/s")这个脚本测的是端到端延迟,包含了网络传输和排队时间。如果要测纯推理性能,应该用框架自带的 benchmark 工具,比如 vLLM 的benchmark_throughput.py。
5. 一套可复用的核算流程
把上面的内容串起来,形成一套可复用的核算流程。每次拿到新硬件或者新模型,按这个流程走一遍,基本不会翻车。
第一步:算权重显存。参数量 × 精度字节数。FP16 是 2 字节,INT8 是 1 字节,INT4 是 0.5 字节。27B 模型 FP16 是 54GB,INT4 是 13.5GB(实际因为量化组开销会略高,按 15-16GB 估)。
第二步:算 KV Cache。用公式2 × 层数 × KV头数 × 头维度 × 序列长度 × 并发数 × 精度字节数。这一步必须查模型的实际配置,不能拍脑袋。
第三步:加框架开销。一般留 1.5-3GB,取决于框架和是否开启额外功能。
第四步:算显存余量。总显存减去前三步,余量要大于 10%,否则运行中容易 OOM。
第五步:算吞吐上限。显存带宽 ÷ 权重读取量 = 单并发理论吞吐。实际打 6-7 折。
第六步:算并发下的总吞吐。用实测曲线估算,不要线性叠加。一般 4 并发是性价比拐点。
第七步:验证。用实际测试脚本跑一遍,对比理论值和实测值。如果差距超过 30%,说明某个环节算错了。
这套流程我用了大半年,覆盖了 7B 到 72B 多个模型、五六种硬件配置,基本没出过大错。唯一一次翻车是低估了 KV Cache 的量化开销——当时以为 KV Cache 也能跟着权重量化省显存,实际上默认是不省的,需要单独配置。
最后分享一个小技巧:如果你不确定某个配置能不能跑,先用
--max-model-len设一个很小的值(比如 2048)启动,确认权重能加载、服务能起来,再逐步往上调上下文和并发。这样比一上来就设大值然后 OOM 反复调试要高效得多。每次调整后观察显存占用,找到那个刚好不 OOM 的临界点,再往回退 10% 作为生产配置。