1. 为什么单卡跑 7B 模型,TPOT 卡在 19.7ms 下不来
如果你在 RTX 3090 这类单卡上部署过 Qwen2.5-7B-Instruct,大概率遇到过这个场景:模型能跑起来,首 token 也还行,但一旦进入连续对话,每个 token 的生成时间(TPOT)就稳定在 20ms 上下,怎么调--max-num-seqs、怎么改 batch size 都压不下去。我试过把gpu-memory-utilization从 0.8 拉到 0.95,TPOT 只从 19.7ms 降到 19.2ms,基本没动。
这不是参数没调好,而是撞到了显存带宽的物理墙。BF16 精度下,7B 模型的权重约 14GB,每次 decode 都要把这 14GB 权重从显存完整读一遍。RTX 3090 的显存带宽是 936 GB/s,理论下限就是 14 / 936 ≈ 15ms,实测 19.7ms 已经达到 76% 的带宽利用率(MBU),纯软件调参的空间基本到顶了。
想继续压 TPOT,只有一条路:减少每次 decode 需要搬运的权重字节数。这就是 AWQ INT4 量化要解决的问题——把每个参数从 2 字节压到 0.5 字节,权重体积直接砍掉 75%。本文会从校准数据准备、权重分组、kernel 选择一路讲到可复制的量化配置,最后用 TPOT 和吞吐对比脚本验证:单卡上 TPOT 降 65%、有效吞吐提升 5.5 倍到底是怎么跑出来的。
适合谁看:已经在单卡上部署过 LLM、想进一步压延迟和提吞吐的工程师;对量化原理有基本概念、但没实际跑过 AWQ 落地流程的同学;以及正在做推理成本优化、需要一份可复现实验记录的人。
2. AWQ INT4 量化前置:原理、模型与 TaoToken 接入准备
2.1 量化到底在做什么:从 BF16 到 INT4 的线性映射
量化的核心思想很朴素:用更少的 bit 表示原本高精度的浮点数。BF16 是 16 bit,INT4 是 4 bit,存储和带宽都变成原来的 1/4。代价是精度损失——用有限个离散整数去近似连续浮点区间,必然有舍入误差。
量化本质是一个线性映射,把浮点数 r 映射到整数 Q(r):
量化: Q(r) = clamp(round(r / S) - Z, -2^(n-1), 2^(n-1) - 1) 反量化:r_hat = S × (Q(r) + Z)其中 S 是缩放因子(决定每个整数格对应多大浮点范围),Z 是零点偏移,n 是位宽(INT4 时 n=4)。clamp 是截断函数,保证量化后的整数不超出表示范围。
截断是精度损失的主要来源。举个例子:对称量化范围 [-7, 7],S=0.5,量化 r=3.7 时,3.7/0.5=7.4,round 后是 7,没超范围,反量化回来是 3.5,误差 0.2。但如果量化 r=4.2,4.2/0.5=8.4,round 后是 8,超出上限 7 被截断到 7,反量化回来还是 3.5,误差直接变成 0.7。截断阈值的选择是个 trade-off:阈值大则舍入误差大,阈值小则截断误差大。
2.2 为什么用对称量化:矩阵乘法的化简
实际部署中更常用对称量化(Z=0),原因在矩阵乘法里看得很清楚。非对称量化下 A = S_A·Q_A + Z_A,B = S_B·Q_B + Z_B,展开乘法会多出三项额外计算。而对称量化下 Z=0,A×B = S_A·S_B·Q_A·Q_B,只剩一项纯整数乘法加一次缩放,硬件实现更简单更快。
对称量化的代价是要用 [-127, 127] 而不是 [-128, 127],牺牲 1/256 的表示范围(INT4 时是 1/16),换取无偏差的矩阵乘法。如果强行用 [-128, 127],正负不对称会引入系统偏差——比如 A=[-2.2,-1.1,1.1,2.2] 和 B=[0.5,0.3,0.3,0.5] 的点积理论值是 0,用 [-128,127] 量化后 2.2 被截断到 127 而 -2.2 保留 -128,结果会变成 -0.00853,偏向负无穷。改用 [-127,127] 后结果精确为 0。
2.3 AWQ 的核心改进:保护重要通道
普通 PTQ 对所有权重一视同仁地量化。AWQ(Activation-aware Weight Quantization)的改进在于:先用校准数据跑一遍前向传播,找出每层中对激活值影响最大的重要权重通道,对这些通道做保护性缩放后再统一量化。这样在同样的 INT4 精度下,能显著减少精度损失。AWQ 属于 PTQ(训练后量化),不需要重新训练模型,用少量校准数据找到每层最优缩放因子即可。
2.4 模型下载与 TaoToken 接入准备
Qwen 官方在 ModelScope 上发布了预量化好的 AWQ INT4 版本,直接下载即可:
modelscope download \ --model qwen/Qwen2.5-7B-Instruct-AWQ \ --local_dir /root/autodl-tmp/models/qwen2.5-7b-instruct-awq下载完成后验证量化配置:
cat /root/autodl-tmp/models/qwen2.5-7b-instruct-awq/config.json | grep -A5 quantization看到"bits": 4说明是 INT4 量化模型。模型文件总大小约 4~5 GB,对比 BF16 的 14 GB 压缩了约 70%。
如果你在本地做量化实验的同时,还需要调用云端大模型做对比评测或辅助生成校准数据,可以通过 TaoToken 统一接入。它的 API 地址是https://taotoken.net/api,兼容 OpenAI 接口格式,模型对话入口在https://taotoken.net/models,API Key 在https://taotoken.net/api-keys管理。这样本地跑 AWQ 量化、云端调模型做对照,两套流程互不干扰。
3. 可复制配置:vLLM 启动参数与量化 kernel 选择
3.1 vLLM 启动 AWQ 模型
vLLM 会自动识别 AWQ 量化格式,不需要额外参数。启动命令如下:
python -m vllm.entrypoints.openai.api_server \ --model /root/autodl-tmp/models/qwen2.5-7b-instruct-awq \ --served-model-name qwen2.5-7b-awq \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.85 \ --max-model-len 4096这里有个容易踩的坑:量化模型的权重从 ~14GB 降到 ~4GB,但nvidia-smi显示的显存占用不会明显减少。因为 vLLM 的预分配机制会把省出来的 ~10GB 显存全部分配给 KV cache 池。这意味着系统能同时处理更多并发请求,吞吐天花板会大幅提升——这是量化收益的第二重来源,很多人只看到 TPOT 下降,忽略了 KV cache 扩容带来的吞吐红利。
3.2 量化 kernel 选择与配置片段
vLLM 加载 AWQ 模型时,底层会根据 GPU 架构自动选择量化 kernel。在 RTX 3090(Ampere 架构)上,默认走的是awq_marlin或awq_gemm路径。如果你需要显式指定,可以在启动时加--quantization awq,但通常不需要。
对于需要精细控制 kernel 的场景,可以在模型目录下放一份quant_config.json,内容如下:
{ "quant_method": "awq", "bits": 4, "group_size": 128, "zero_point": true, "version": "gemm" }group_size是权重分组大小,128 是 AWQ 的默认值。分组越小,缩放因子越精细,精度越高,但反量化开销也越大。实测下来 128 是精度和速度的平衡点,改成 64 精度提升不到 0.3%,但 TPOT 会涨 5% 左右。
如果你用的是 Cline MCP 或 Claude Code 这类工具做辅助开发,需要配置三件套:Base URL 填https://taotoken.net/api,Key 从https://taotoken.net/api-keys获取,Model ID 填你实际调用的模型名。Coding Plan 适合长期编码和 Agent 场景,入口在https://taotoken.net/coding-plan。
3.3 精度评测配置
评测设置与基线完全一致(不加--apply_chat_template,0-shot),仅修改模型名和输出路径:
export OMP_NUM_THREADS=16 export HF_DATASETS_OFFLINE=1 export HF_DATASETS_CACHE=/root/autodl-tmp/hf_cache lm_eval \ --model local-completions \ --model_args "model=qwen2.5-7b-awq,base_url=http://localhost:8000/v1/completions,tokenizer_backend=huggingface,tokenizer=/root/autodl-tmp/models/qwen2.5-7b-instruct-awq,num_concurrent=32,max_length=4096" \ --tasks hellaswag,arc_easy,arc_challenge,gsm8k \ --num_fewshot 0 \ --output_path /root/autodl-tmp/results/lm_eval_awq \ --log_samples3.4 性能基线测试配置
export HF_HUB_OFFLINE=1 export TRANSFORMERS_OFFLINE=1 vllm bench serve \ --backend openai-chat \ --base-url http://localhost:8000 \ --endpoint /v1/chat/completions \ --model qwen2.5-7b-awq \ --tokenizer /root/autodl-tmp/models/qwen2.5-7b-instruct-awq \ --dataset-name sharegpt \ --dataset-path /root/autodl-tmp/datasets/sharegpt.json \ --num-prompts 200 \ --request-rate 4 \ --max-concurrency 16 \ --save-result \ --result-dir /root/autodl-tmp/results/perf_awq3.5 参数扫描脚本
与基线相同的扫描方案(实验组 A 扫描 request-rate,实验组 B 扫描 max-concurrency),仅替换模型名和输出路径:
#!/bin/bash mkdir -p /root/autodl-tmp/results/sweep_awq COMMON_ARGS=" --backend openai-chat --base-url http://localhost:8000 --endpoint /v1/chat/completions --model qwen2.5-7b-awq --tokenizer /root/autodl-tmp/models/qwen2.5-7b-instruct-awq --dataset-name sharegpt --dataset-path /root/autodl-tmp/datasets/sharegpt.json --num-prompts 200 --save-result " export HF_HUB_OFFLINE=1 export TRANSFORMERS_OFFLINE=1 echo "=== 实验组 A:扫描 request-rate ===" for rate in 1 2 4 8 16 inf; do vllm bench serve $COMMON_ARGS \ --request-rate $rate \ --max-concurrency 32 \ --result-dir /root/autodl-tmp/results/sweep_awq \ --result-filename "expA_rate${rate}_conc32.json" sleep 5 done echo "=== 实验组 B:扫描 max-concurrency ===" for conc in 1 4 8 16 32 64 128; do vllm bench serve $COMMON_ARGS \ --request-rate inf \ --max-concurrency $conc \ --result-dir /root/autodl-tmp/results/sweep_awq \ --result-filename "expB_rateinf_conc${conc}.json" sleep 5 done4. 验证请求与成功结果:TPOT 降 65%、吞吐提升 5.5 倍
4.1 精度对比结果
| 数据集 | 指标 | BF16 基线 | AWQ INT4 | 绝对下降 | 相对下降 |
|---|---|---|---|---|---|
| HellaSwag | acc_norm | 80.5% | 79.6% | -0.9% | -1.1% |
| ARC-Easy | acc_norm | 81.0% | 78.8% | -2.2% | -2.7% |
| ARC-Challenge | acc_norm | 55.5% | 54.5% | -1.0% | -1.8% |
| GSM8K | flexible-extract | 71.3% | 69.2% | -2.1% | -2.9% |
精度损失在 1~3% 之间,常识推理(HellaSwag)损失最小,数学推理(GSM8K)和科学知识(ARC-Easy)略高,说明这两类任务对权重精度更敏感。总体在可接受范围内。
4.2 单次性能测试结果
先看 rate=4、conc=16 下的单次测试结果:
| 指标 | BF16 基线 | AWQ INT4 | 变化 |
|---|---|---|---|
| TTFT P50 | 113 ms | 93 ms | -18% |
| TTFT P99 | 293 ms | 293 ms | ≈0% |
| TPOT P50 | 23.9 ms | 9.0 ms | -62% |
| TPOT P99 | 34.4 ms | 21.7 ms | -37% |
| Out_TPS | 558 tok/s | 777 tok/s | +39% |
TPOT P50 直接从 23.9ms 降到 9.0ms,降幅 62%,超出预期的"减半"目标。
4.3 参数扫描结果
实验组 A:固定 conc=32,扫描 request-rate
| Rate | BF16 TPOT P50 | AWQ TPOT P50 | BF16 Out_TPS | AWQ Out_TPS |
|---|---|---|---|---|
| 1 | 20.4 ms | 6.9 ms | 202 | 1222 |
| 2 | 22.1 ms | 7.1 ms | 385 | 1842 |
| 4 | 24.2 ms | 7.5 ms | 695 | 1806 |
| 8 | 26.5 ms | 10.0 ms | 926 | 1451 |
| 16 | 27.0 ms | 13.0 ms | 967 | 1965 |
| inf | 26.9 ms | 12.9 ms | 979 | 2128 |
低负载下 TPOT 降幅更大:rate=1~4 时 AWQ 的 TPOT 稳定在 7ms 左右,是 BF16 的 1/3。吞吐饱和点后移:BF16 在 rate=8 就趋于饱和(926→967,+4.4%),AWQ 在 rate=16 才开始饱和(1965→2128,+8.3%)。原因是模型权重压缩后省出的显存全部给了 KV cache,GPU 能同时处理更多请求。
实验组 B:固定 rate=inf,扫描 max-concurrency
| Conc | BF16 TPOT P50 | AWQ TPOT P50 | BF16 TPOT P99 | AWQ TPOT P99 | BF16 Out_TPS | AWQ Out_TPS |
|---|---|---|---|---|---|---|
| 1 | 19.7 ms | 6.8 ms | 19.8 ms | 6.9 ms | 501 | 4482 |
| 4 | 21.4 ms | 7.5 ms | 27.2 ms | 7.8 ms | 345 | 9941 |
| 8 | 23.4 ms | 8.7 ms | 35.2 ms | 9.2 ms | 595 | 16153 |
| 16 | 26.8 ms | 12.9 ms | 60.2 ms | 14.5 ms | 978 | 21266 |
| 32 | 34.4 ms | 23.3 ms | 165 ms | 30.3 ms | 1341 | 23191 |
| 64 | 51.9 ms | 39.4 ms | 387 ms | 60.9 ms | 1583 | 2509 |
单请求 TPOT 远超物理预期:conc=1 时 TPOT P50 = 6.84ms,已经超过了基线文章中定的 8ms 目标。理论下限重新计算:INT4 权重 ≈ 7B × 0.5 bytes ≈ 3.5 GB,RTX 3090 带宽 936 GB/s,理论 TPOT = 3.5 / 936 ≈ 3.7 ms,实测 6.84 ms,MBU ≈ 54%。MBU 比 BF16(76%)低,差距主要来自 INT4 反量化的额外计算开销——GPU 需要把 INT4 权重实时解压为 FP16 参与计算。
TPOT P99 拐点从 conc=32 后移到 conc=64:BF16 下 conc=32→64,TPOT P99 从 60ms 暴涨至 165ms;AWQ 下 conc=32→64,TPOT P99 从 14.5ms 涨至 30.3ms,翻倍但仍可控;conc=64→128 才开始恶化。拐点后移一档,说明量化后 KV cache 池更大,系统能承受更高的并发压力。生产甜点区从 conc=8~16 扩大到了 conc=16~32。
4.4 SLO 分析:有效吞吐提升 5.5 倍
严格 SLO(TTFT P99 < 300ms,TPOT P99 < 40ms):
| BF16 | AWQ INT4 | |
|---|---|---|
| 达标档位 | 仅 rate=1~2 | 全部达标(rate=1 到 inf) |
| 最佳 Goodput | 826 tok/s(rate=2) | 4547 tok/s(rate=inf) |
| 提升倍数 | — | 5.5× |
这是量化收益最震撼的一个数字:BF16 下只有最轻的两档负载能满足严格 SLO,AWQ 下连 rate=inf 全速压测都轻松达标。有效吞吐提升 5.5 倍。原因是双重收益叠加:TPOT 大幅下降让每个请求处理更快,排队时间缩短,TTFT 也跟着下降,全链路延迟改善。
4.5 完整对比汇总
=== BF16 基线 vs AWQ INT4 完整对比 === 精度(0-shot,不加 chat template): HellaSwag: 80.5% → 79.6% (-0.9%) ARC-Easy: 81.0% → 78.8% (-2.2%) ARC-Challenge: 55.5% → 54.5% (-1.0%) GSM8K: 71.3% → 69.2% (-2.1%) 性能: 单请求 TPOT P50: 19.7 ms → 6.84 ms (-65%) 吞吐天花板(Out_TPS): 978 → 2126 tok/s (+117%) TPOT P99 拐点: conc=32 → conc=64 (后移一档) 严格 SLO Goodput: 826 → 4547 tok/s (+450%) 生产甜点区: conc=8~16 → conc=16~32 模型体积: 权重大小: ~14 GB → ~4 GB (-71%)5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
5.1 401 Unauthorized:API Key 没配对
如果你在评测脚本里通过 TaoToken 调用云端模型做对照,报 401 通常是 Key 没填对或没带上。检查https://taotoken.net/api-keys里生成的 Key 是否复制完整,请求头里是否带了Authorization: Bearer <your-key>。本地 vLLM 服务默认不校验 Key,所以 401 一般出现在云端调用环节。
5.2 local proxy failed:本地代理配置冲突
这个报错通常出现在lm_eval或vllm bench serve请求本地 8000 端口时。原因是环境变量里残留了HTTP_PROXY或HTTPS_PROXY,导致请求被转发到不存在的代理。解决办法是在脚本开头清掉:
unset HTTP_PROXY unset HTTPS_PROXY unset http_proxy unset https_proxy然后重新跑评测。本地服务不需要走任何代理,直连http://localhost:8000即可。
5.3 reading choices:数据集格式不匹配
vllm bench serve报reading choices相关错误,一般是--dataset-name sharegpt和--dataset-path指向的文件格式对不上。ShareGPT 格式要求 JSON 数组,每个元素包含conversations字段,里面是from/value的列表。检查你的sharegpt.json是否符合这个结构,或者改用--dataset-name random先跑通流程。
5.4 OAuth 相关报错:Claude Code 接入配置
如果你用 Claude Code 接入 TaoToken 做辅助开发,遇到 OAuth 报错,通常是 Base URL 或 Model ID 没配对。三件套配置如下:Base URL 填https://taotoken.net/api,Key 从https://taotoken.net/api-keys获取,Model ID 填实际模型名。Claude Code 的接入文档在https://taotoken.net/doc,里面有完整的配置示例。如果用的是 Codex 的auth.json,确保里面的base_url和api_key字段与上面一致。
5.5 量化模型加载失败:config.json 缺字段
如果 vLLM 启动时报quantization method not found或类似错误,检查模型目录下的config.json是否有quantization_config字段。有些第三方量化模型会漏掉这个字段,手动补上即可:
"quantization_config": { "quant_method": "awq", "bits": 4, "group_size": 128, "zero_point": true }补完后重启 vLLM 服务,应该能正常识别。
6. 从量化到生产:下一步优化方向与接入入口
AWQ INT4 已经把 TPOT 压到了 6.84ms,距离理论下限 3.7ms 还有空间,但剩余优化的难度和收益比会逐渐递减。后续可以叠加 FP8 KV Cache(KV cache 减半,吞吐再涨 20~30%,精度损失 < 0.5%)、投机采样(低并发 TPOT 再降 30~50%,输出完全一致),以及组合优化冲击 TPOT < 4ms。
如果你在本地跑量化实验的同时,需要云端模型做对照评测或辅助生成校准数据,TaoToken 的模型对话入口在https://taotoken.net/models,API 地址是https://taotoken.net/api,API Key 在https://taotoken.net/api-keys管理。长期做编码和 Agent 场景的话,Coding Plan 入口在https://taotoken.net/coding-plan,接入文档在https://taotoken.net/doc。
量化收益的本质不是简单的"权重变小了",而是三重复利效应叠加:TPOT 下降(权重搬运量降 71%)、KV cache 扩容(省出 ~10GB 显存全给 KV cache)、SLO 全面达标(TPOT 下降带动 TTFT 下降,Goodput 提升 5.5 倍)。这三重收益互相放大,最终效果远大于单纯的"4 倍压缩"所暗示的线性提升。1~3% 的精度换 5.5 倍的有效吞吐,对绝大多数生产场景来说都值得。