模型量化几乎是大模型推理上线前的必经环节。团队第一次接触量化时,关注点几乎都集中在一件事上:显存够不够用。FP16 模型放不下,量化到 INT8 能放下;INT8 还是紧张,继续量化到 INT4。这种"缺多少显存就量化到哪一档"的思路在早期没什么问题,但当选项从"量化不量化"变成"FP8、NVFP4、INT4 该选哪个"时,只看显存会直接导致选型错误。
问题出现的典型场景是这样的:某团队把一个 32B 模型从 FP16 换成 INT4 权重量化,显存确实降下来了,上线后却发现在高并发场景下吞吐反而没有提升,P99 延迟还变差了;换了一批新到的 H100 机器后,团队又听说 FP8 是"官方推荐"的量化方式,直接切换过去,结果发现某些层的数值溢出导致输出乱码;再往后采购了 Blackwell 架构的 GPU,宣传材料里提到 NVFP4 能进一步压缩显存,团队不清楚这跟之前的 INT4 是不是一回事,也不知道旧的量化模型能不能直接搬过来用。
这三种量化方式对应的不是同一个技术路线的三个档位,而是三种不同的数值表示方式加上三种不同的硬件依赖关系。FP8 是浮点量化,NVFP4 是带微缩放(micro-scaling)的 4-bit 浮点量化,INT4 通常指整数权重量化。它们在精度损失曲线、硬件兼容性、框架支持成熟度、计算吞吐收益上完全不同。选型选错,代价不只是"没省到显存",可能是线上输出质量下降、GPU 利用率不升反降、甚至因为硬件不支持直接跑不起来。
这篇文章面向已经具备一定大模型部署经验的运维工程师和 AI 平台工程师,目标是把"选型"这件事讲清楚:三种量化方式各自的原理、各自依赖什么硬件、各自在主流推理框架里的支持现状,以及一套可以照着执行的选型验证流程。文章不会停留在"哪个更好"的空泛结论上,而是给出可以直接在自己的环境里跑一遍的命令、配置和判断标准。
不适用场景:如果只是想验证"能不能跑起来"的实验性部署,不涉及生产吞吐和精度要求,可以直接选择当前框架默认支持最成熟的量化方式,不必纠结选型细节。
讨论 FP8、NVFP4、INT4 之前,先厘清一个经常被混淆的前提:量化可以作用在模型的三个不同部分上,而这三个部分的量化收益和风险完全不同。
第一个部分是权重(Weights)。权重在模型加载后基本不变,量化权重最直接的收益是降低模型静态占用的显存,这也是大多数团队最先接触的量化形式,例如 GPTQ、AWQ 都属于权重量化。
第二个部分是激活值(Activations)。激活值是推理过程中每一层的中间输出,数值范围随输入动态变化,量化激活值比量化权重难度更高,因为需要在运行时动态确定缩放因子,或者依赖校准数据集提前估计。FP8 之所以比传统 INT8 权重量化复杂,很大一部分原因就是它同时对权重和激活做量化。
第三个部分是KV Cache。长上下文和高并发场景下,KV Cache 占用的显存会超过模型权重本身,KV Cache 量化(比如 FP8 KV Cache)是另一个独立的优化维度,跟权重量化可以叠加使用,也可以单独使用。
理解这三者的区别很重要,因为"我们上线了 FP8"这句话本身信息量不够,需要追问清楚:量化的是权重、激活,还是 KV Cache,还是三者都量化了。
FP8 是 8-bit 浮点数格式,业界主要使用两种子格式:
推理场景以 E4M3 为主。FP8 的核心优势是它仍然是浮点格式,天然具备比整数量化更宽的动态范围,不需要像 INT8/INT4 那样把一个大范围的浮点数强行映射到有限的整数格子里,因此在同等比特数下,FP8 通常比 INT8 更容易保持精度。
FP8 的硬件依赖非常明确:需要 GPU Tensor Core 原生支持 FP8 矩阵乘法,这在 NVIDIA 产品线里意味着 Hopper 架构(H100、H800、H200)及以后的型号才有意义。在 Ampere(A100、A10)等更早架构的卡上,即使框架层面允许你"配置"FP8,底层也是通过软件模拟或退化到其他精度实现的,拿不到真实的算力收益,这是很多团队踩过的坑:配置文件写了 --quantization fp8,服务确实启动了,但吞吐没有任何提升,因为硬件根本没有走 FP8 Tensor Core 路径。
FP8 量化通常需要处理**缩放因子(scale factor)**的问题。因为 FP8 的动态范围有限,直接把 FP16 数值截断成 FP8 会造成明显的精度损失,实践中普遍采用按 tensor 或按 channel 计算缩放因子,配合校准数据集(少量代表性输入)预先统计激活值分布,或者使用运行时动态计算缩放因子(dynamic scaling)。静态缩放的推理速度更快但需要校准过程,动态缩放不需要提前校准但会引入运行时开销。
NVFP4 是 NVIDIA 在 Blackwell 架构(B100、B200、GB200 等)上引入的 4-bit 浮点格式,属于微缩放(micro-scaling,业界也称 MX 格式家族)量化方案的一种具体实现。理解 NVFP4 要抓住两个关键点:
第一,它是浮点格式,不是整数格式。4-bit 浮点通常采用 E2M1 这样的指数尾数划分(1 位符号 + 2 位指数 + 1 位尾数),跟传统的 INT4 整数量化在数学表示上完全不同。
第二,它是分块缩放(block-wise scaling),不是整个 tensor 共享一个缩放因子,也不是按 channel 共享。NVFP4 把一组连续的元素(例如每 16 个或 32 个元素,具体分组大小以官方实现为准)划分为一个微块,每个微块单独维护一个缩放因子。这种细粒度的缩放机制是 NVFP4 能在 4-bit 精度下仍然保持可用精度的关键,因为它把量化误差的影响范围限制在很小的局部窗口内,而不是让一个异常大的数值拉低整个 tensor 的量化精度。
NVFP4 的硬件依赖比 FP8 更严格:必须是 Blackwell 及以后架构的 GPU,Hopper 架构不支持 NVFP4 的原生 Tensor Core 运算。这意味着如果团队的 GPU 是 H100/H800,NVFP4 这个选项直接不存在,不用花时间评估。
在框架支持上,NVFP4 是三种方案里成熟度最低的一个,目前主要由 TensorRT-LLM 提供较完整的支持,vLLM 和 SGLang 的支持随版本迭代很快,落地前必须以当前安装的框架版本文档为准,不要假设某个版本能用的参数在另一个版本上依然存在。
INT4 权重量化是三者里历史最久、生态最成熟的方案,常见实现包括:
INT4 权重量化的显存收益非常直接:权重从 16-bit 降到 4-bit,理论上降到四分之一(实际因为分组量化需要额外存储缩放因子和零点,比例会略高于四分之一)。硬件门槛是三者里最低的,Ampere 架构的卡就能跑,因为 INT4 权重量化的核心计算路径是:加载时权重以 4-bit 存储,计算时先反量化(dequantize)成 FP16 再做矩阵乘法,矩阵乘法本身仍然走的是标准 FP16 Tensor Core,不需要专门的 INT4 Tensor Core 支持。
这也正是 INT4 权重量化的性能短板所在:反量化这一步是额外开销。如果反量化 kernel 没有做好融合优化,INT4 权重量化在小批量、低并发场景下可能比 FP16 更快(因为显存带宽是瓶颈,读取的数据量小了),但在大批量、计算密集场景下可能出现"显存降了,速度却没提升甚至变慢"的情况,因为计算瓶颈从显存带宽转移到了反量化计算本身。这一点是团队最容易忽略、也是本文标题强调"不能只看显存节省"的核心原因之一。
INT4 和 NVFP4 都涉及分组量化的概念,分组大小是一个需要工程判断的参数,不是越小越好或越大越好:
常见默认值是 128,这是大多数量化工具(AWQ、GPTQ)在精度和显存开销之间取得平衡后的经验值,调整这个参数前建议先用默认值跑通整个验证流程,再根据实际精度评估结果决定是否需要调整,不要在没有基线对比的情况下直接改动。
FP8 静态缩放和 INT4/GPTQ/AWQ 量化都依赖校准数据集来估计激活值或权重的数值分布。校准数据集的代表性直接影响量化质量:
工程决策不能只算显存这一笔账,量化引入的隐性成本同样需要计入:
量化过程本身的资源消耗:GPTQ 和 AWQ 量化过程需要占用 GPU 显存和较长时间(大模型可能需要数十分钟到数小时),这个过程本身需要机器资源,如果团队需要频繁重新量化(例如模型经常更新),这部分成本需要计入运维排期。
运维复杂度的增加:量化后需要维护多套模型文件(原始 FP16、各种量化版本),需要维护多套部署配置,需要在监控和告警中区分不同量化方案的指标,团队的运维复杂度会明显上升,尤其是在同时有多个模型、多种量化方案并存的场景下。
排查难度的增加:量化引入了额外的数值处理环节,当输出质量出现问题时,排查链路会比 FP16 基线更长(需要额外排除是否是量化引入的数值问题),对团队的调试经验要求更高。
这三项隐性成本不会直接体现在显存或吞吐数字上,但会实际影响团队的长期运维负担,在做选型决策时应该作为参考因素之一,尤其是团队规模较小、运维资源有限的情况下,有时候"显存够用就不做过度激进的量化"反而是更稳妥的工程选择。
| 量化方式 | 数值格式 | 最低硬件架构要求 | 典型显存降幅(权重) | 计算路径 |
|---|---|---|---|---|
| FP8 | 8-bit 浮点(E4M3/E5M2) | Hopper(H100/H800/H200) | 约 50%(相对 FP16) | 原生 FP8 Tensor Core |
| NVFP4 | 4-bit 浮点,微缩放 | Blackwell(B100/B200/GB200) | 约 75%(相对 FP16) | 原生 NVFP4 Tensor Core |
| INT4(权重量化) | 4-bit 整数 | Ampere 及以上 | 约 75%(相对 FP16,含缩放因子开销略低于此) | 反量化 + FP16 Tensor Core |
这张表只是方向性参考,具体显存降幅和吞吐表现跟模型结构、量化分组大小、框架实现细节都有关系,不同版本的框架对同一种量化方式的 kernel 优化程度也会有明显差异,实际数值必须以自己环境里的实测结果为准,不能直接套用这张表或任何第三方评测的绝对数字。
以下信息基于当前主流版本的一般性支持情况,不同版本之间的参数名称、支持范围可能存在差异,具体以你正在使用的框架版本官方文档为准:
--quantization fp8 或类似参数启用;NVFP4 支持随版本快速演进,需要确认自己安装的版本是否已经合入相关 kernel判断一个框架版本是否真正支持某种量化方式,不要只看文档里"支持列表"提到的名字,要实际跑一次加载和推理,并观察日志和显存占用是否符合预期,这一点后面的实战步骤会具体展开。
误区一:量化比特数越低,显存节省一定越多,收益一定越大
比特数降低确实带来显存降幅增大,但收益要综合看吞吐和精度。前面提到的 INT4 反量化开销就是典型反例:显存降幅最大,吞吐未必最优。
误区二:FP8 和 INT8 是同一件事,只是叫法不同
FP8 是浮点表示,INT8 是整数表示,两者的数值分布特性完全不同。FP8 的非均匀分布(浮点数在数值范围内的密度不均匀,接近零的地方更密集)更适合神经网络权重和激活值本身就呈现的非均匀分布特性,这也是 FP8 在同等比特数下通常比 INT8 精度损失更小的原因之一。不能把两者的量化经验直接互相套用。
误区三:只要框架文档写了"支持 FP8",直接上生产就行
框架层面支持和某个具体模型结构在某个具体硬件上跑起来是两件事。同一个框架版本,对不同模型架构(例如 MoE 结构和稠密结构)的 FP8 支持成熟度可能不同,MoE 模型的专家路由层量化通常比稠密层更复杂,容易出现某些框架版本对 MoE 层的量化支持还不完善的情况。上生产前必须针对自己实际使用的模型结构做验证,不能只看框架层面的通用声明。
误区四:NVFP4 是 INT4 的升级版,可以直接替换
两者数值格式完全不同(浮点 vs 整数),量化工具链、模型 checkpoint 格式、推理引擎构建流程都不同,不存在"直接替换"的路径,需要重新走一遍量化和验证流程。
误区五:量化后模型变小了,随便换个机型都能跑
量化模型的运行仍然依赖对应的 Tensor Core 特性,即便模型文件本身变小了、理论上能塞进更小显存的卡里,如果目标硬件不支持对应的量化计算路径,要么无法加载,要么退化为效率很差的模拟计算路径。显存够用不代表硬件架构匹配。
选型不是拍脑袋决定,而是一套可以走完的验证闭环:
明确约束条件 ├── 硬件架构(Ampere / Hopper / Blackwell) ├── 显存缺口(当前占用 vs 可用显存) ├── 延迟和吞吐目标(SLA) └── 精度容忍度(业务对输出质量的要求) ↓ 根据硬件架构排除不可用选项 ↓ 硬件支持多个选项时,逐个搭建测试环境 ↓ 对每个候选方案测量四组指标 ├── 显存占用(加载 + 峰值 + 不同并发下) ├── 吞吐(QPS / TPS,不同批大小) ├── 延迟(TTFT / TPOT / P95 / P99) └── 精度(困惑度 / 任务准确率 / 人工抽样) ↓ 横向对比,排除不满足硬件或精度红线的方案 ↓ 在剩余方案里选吞吐和延迟综合最优的一个 ↓ 小流量灰度验证 ↓ 全量上线并建立长期监控 这套流程的核心原则是:先用硬件架构做硬性筛选,再用实测数据做软性选型,不要跳过硬件确认这一步直接进入性能对比,否则会在不支持的硬件上做无意义的测试。
目的:避免在不支持的硬件上浪费时间测试根本跑不出真实收益的量化方式。
查看 GPU 型号和架构:
bash
nvidia-smi --query-gpu=name,compute_cap --format=csv 预期输出:
name, compute_cap NVIDIA H100 80GB HBM3, 9.0 判断逻辑:
下一步动作:如果硬件是 Ampere 及更早架构,直接跳过 FP8 和 NVFP4 的评估,只测试 INT4/INT8 权重量化方案;如果是 Hopper,可以同时评估 FP8 和 INT4;如果是 Blackwell,三个选项都在候选范围内。
目的:文档说"支持"不等于当前安装的版本真正支持,需要用最小化流程实测确认。
查看当前框架版本:
bash
pip show vllm | grep -E "^(Name|Version)" 预期输出:
Name: vllm Version: 0.6.3 查看该版本是否包含目标量化方式的 kernel:
bash
python -c "from vllm.model_executor.layers.quantization import QUANTIZATION_METHODS; print(list(QUANTIZATION_METHODS.keys()))" 预期输出类似(不同版本内容会不同):
['aqlm', 'awq', 'deepspeedfp', 'fp8', 'gguf', 'gptq', 'gptq_marlin', 'marlin', 'squeezellm', ...]
判断逻辑:如果列表里没有目标量化方式的关键字(例如没有 fp8 或没有 nvfp4/modelopt 相关字样),说明当前版本不支持,需要升级框架版本,而不是继续排查配置问题。
下一步动作:确认支持后,进入下一步的小规模加载测试;如果不支持,先完成框架升级,升级后重新执行本步骤确认。
目的:任何量化方案的评估都需要一个未量化的基线作为参照,没有基线的对比是没有意义的。
启动 FP16 基线服务(以 vLLM 为例):
bash
python -m vllm.entrypoints.openai.api_server --model /data/models/qwen2.5-32b --dtype bfloat16 --tensor-parallel-size 2 --max-model-len 8192 --gpu-memory-utilization 0.85 --port 8000 2>&1 | tee baseline_fp16.log 等待日志出现类似字样,确认服务已启动:
INFO: Uvicorn running on http://0.0.0.0:8000 记录基线显存占用:
bash
nvidia-smi --query-gpu=index,memory.used,memory.total --format=csv 预期输出示例:
index, memory.used [MiB], memory.total [MiB] 0, 62000 MiB, 80000 MiB 1, 61500 MiB, 80000 MiB 判断逻辑:记录这个数字作为后续所有量化方案对比的分母,同时确认当前显存占用是否已经接近上限(超过 90% 使用率意味着基线本身已经处于临界状态,量化收益会立刻在可用并发数上体现出来)。
目的:确认 FP8 不是"配置了但没生效"的假启用状态。
启动 FP8 量化服务:
bash
python -m vllm.entrypoints.openai.api_server --model /data/models/qwen2.5-32b --quantization fp8 --kv-cache-dtype fp8 --tensor-parallel-size 2 --max-model-len 8192 --gpu-memory-utilization 0.85 --port 8001 2>&1 | tee fp8_deploy.log 检查启动日志中是否出现量化相关的提示:
bash
grep -i "quant" fp8_deploy.log 预期输出应该包含类似字样:
INFO ...quantization method: fp8, using dynamic per-tensor scaling
异常表现:如果日志中没有任何 quant 相关字样,或者出现 falling back to 之类的降级提示,说明当前硬件或模型层不支持 FP8,实际走的是其他精度路径。
记录 FP8 方案显存占用:
bash
nvidia-smi --query-gpu=index,memory.used,memory.total --format=csv 判断逻辑:将这个数值与第三步的基线对比,正常情况下应该有明显降幅(权重部分降幅通常接近 50%,如果同时启用 FP8 KV Cache,长上下文场景下降幅会更明显)。如果显存占用几乎没变化,说明量化没有真正生效,需要回到框架版本和硬件架构两项重新确认。
下一步动作:显存验证通过后,进入吞吐和延迟测试;如果验证不通过,先排查框架和硬件问题,不要继续往下测吞吐。
目的:验证 INT4 权重量化在当前模型和硬件上的实际收益,特别关注反量化开销是否抵消了显存收益带来的吞吐提升。
如果模型还没有量化为 AWQ 格式,先执行量化(此步骤耗时较长,建议在非生产环境的机器上执行,且需要预留足够磁盘空间存放量化后的模型文件):
bash
pip install autoawq==0.2.6 python
from awq import AutoAWQForCausalLM from transformers import AutoTokenizer model_path = "/data/models/qwen2.5-32b" quant_path = "/data/models/qwen2.5-32b-awq-int4" model = AutoAWQForCausalLM.from_pretrained(model_path, safetensors=True) tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) quant_config = { "zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM" } model.quantize(tokenizer, quant_config=quant_config) model.save_quantized(quant_path) tokenizer.save_pretrained(quant_path) 量化过程会占用较多显存和 CPU 内存,量化 32B 级别模型通常需要 30-90 分钟,具体耗时和校准数据集大小、GPU 型号相关。
启动 INT4 量化服务:
bash
python -m vllm.entrypoints.openai.api_server --model /data/models/qwen2.5-32b-awq-int4 --quantization awq --tensor-parallel-size 1 --max-model-len 8192 --gpu-memory-utilization 0.85 --port 8002 2>&1 | tee int4_deploy.log
注意这里 --tensor-parallel-size 从基线的 2 降为 1,因为 INT4 量化后单卡显存应该已经足够容纳模型,这本身也是量化收益的直接体现:从需要 2 卡变为 1 卡即可部署。
记录显存占用:
bash
nvidia-smi --query-gpu=index,memory.used,memory.total --format=csv
判断逻辑:INT4 权重量化的显存降幅预期是三者中最大的,但如果发现单卡显存占用仍然很高(远超模型参数量本身对应的理论值),需要检查是否 q_group_size 设置不合理导致缩放因子存储开销过大,或者 KV Cache 占用了大量额外显存(KV Cache 大小与 --max-model-len 和并发数直接相关,不会因为权重量化而减少,除非同时启用 KV Cache 量化)。
目的:拿到可比的性能数字,避免"感觉上量化更快/更慢"这种主观判断。
准备统一的压测脚本,对三个端口(基线 8000、FP8 8001、INT4 8002)分别执行相同的压测参数:
bash
#!/bin/bash # bench_quant.sh # 用法: ./bench_quant.sh set -euo pipefail PORT="$1" LABEL="$2" CONCURRENCY_LIST="1 4 8 16 32" OUTPUT_DIR="./bench_results" mkdir -p "${OUTPUT_DIR}" for c in ${CONCURRENCY_LIST}; do echo "=== ${LABEL} concurrency=${c} ===" hey -n 200 -c "${c}" -m POST -H "Content-Type: application/json" -d '{"model": "qwen2.5-32b", "prompt": "请用两百字介绍一下容器编排的基本原理", "max_tokens": 200}' "http://localhost:${PORT}/v1/completions" | tee "${OUTPUT_DIR}/${LABEL}_c${c}.log" done 依次执行三次:
bash
chmod +x bench_quant.sh ./bench_quant.sh 8000 baseline_fp16 ./bench_quant.sh 8001 fp8 ./bench_quant.sh 8002 int4_awq
从每次输出中提取关键字段(hey 工具输出中通常包含以下内容):
Summary: Total: 18.2341 secs Slowest: 2.1053 secs Fastest: 0.4321 secs Average: 0.9102 secs Requests/sec: 21.9581 Latency distribution: 50% in 0.8801 secs 95% in 1.6532 secs 99% in 1.9887 secs 汇总成对比表格(示例数据,实际数值以自己环境压测结果为准):
| 方案 | 并发数 | Requests/sec | P50 延迟 | P95 延迟 | P99 延迟 |
|---|---|---|---|---|---|
| FP16 基线 | 16 | 18.2 | 0.75s | 1.42s | 1.68s |
| FP8 | 16 | 24.6 | 0.58s | 1.05s | 1.21s |
| INT4 AWQ | 16 | 15.9 | 0.82s | 1.61s | 1.95s |
| FP16 基线 | 32 | 21.1 | 1.20s | 2.35s | 2.80s |
| FP8 | 32 | 31.4 | 0.85s | 1.68s | 1.95s |
| INT4 AWQ | 32 | 19.8 | 1.35s | 2.58s | 3.02s |
判断逻辑:这组示例数据反映了一个常见但容易被忽视的现象——INT4 权重量化虽然显存降幅最大,但在计算密集的高并发场景下,反量化开销可能导致吞吐反而低于 FP16 基线;FP8 因为是原生 Tensor Core 支持,没有反量化开销,往往能同时兼顾显存和吞吐收益。这正是"选型不能只看显存节省"的核心证据:如果只看显存数字,INT4 看起来最优,但实际吞吐测试会推翻这个结论。
下一步动作:如果 GPU 是 Ampere 架构(不支持 FP8),上面的对比就不存在 FP8 选项,此时需要更细致地评估 INT4 方案在自己实际业务并发范围内的表现,而不是简单否定 INT4;如果 GPU 是 Hopper 或更新架构,优先看 FP8 是否能满足精度要求。
目的:吞吐和延迟达标不代表可以上线,必须确认量化后的输出质量在业务可接受范围内。
使用固定测试集分别请求三个端口,收集输出并做人工或自动化对比:
bash
#!/bin/bash # collect_outputs.sh set -euo pipefail PORTS="8000:fp16 8001:fp8 8002:int4" TEST_FILE="test_prompts.jsonl" for entry in ${PORTS}; do port="${entry%%:*}" label="${entry##*:}" outfile="outputs_${label}.jsonl" : > "${outfile}" while IFS= read -r line; do prompt=$(echo "${line}" | jq -r '.prompt') response=$(curl -s -X POST "http://localhost:${port}/v1/completions" -H "Content-Type: application/json" -d "$(jq -n --arg p "${prompt}" '{model:"qwen2.5-32b", prompt:$p, max_tokens:200, temperature:0}')") echo "${response}" >> "${outfile}" done < "${TEST_FILE}" echo "Done: ${outfile}" done 如果业务场景有客观评估指标(分类准确率、结构化输出的字段完整率等),优先用自动化脚本对比三份输出与标准答案的一致率;如果场景偏生成式、难以自动评估,安排至少两名熟悉业务的人员对三份输出做盲评打分(不告知评审人员每份输出对应哪个方案,避免主观偏向)。
判断逻辑:设定一个业务能接受的精度损失阈值(例如相对基线准确率下降不超过 2 个百分点),任何超过阈值的方案直接从候选列表中排除,即便它在吞吐测试中表现最好。
下一步动作:精度和性能都满足要求的方案进入灰度发布环节;如果所有候选方案都不满足精度要求,考虑退回到 FP16 基线,或评估混合精度方案(部分层量化、部分层保持高精度)。
bash
# 查看 GPU 架构和 Compute Capability nvidia-smi --query-gpu=name,compute_cap,driver_version --format=csv # 查看 CUDA 版本 nvcc --version # 查看 vLLM 版本和支持的量化方法 pip show vllm python -c "from vllm.model_executor.layers.quantization import QUANTIZATION_METHODS; print(list(QUANTIZATION_METHODS.keys()))" bash
# 单次快照 nvidia-smi --query-gpu=index,name,memory.used,memory.total,utilization.gpu --format=csv # 持续监控并落盘(Ctrl+C 停止) nvidia-smi --query-gpu=timestamp,index,memory.used,utilization.gpu --format=csv -l 1 >> gpu_watch.csv # 查看是否有僵尸进程占用显存未释放 nvidia-smi --query-compute-apps=pid,used_memory --format=csv bash
# 基础压测 hey -n 200 -c 16 -m POST -H "Content-Type: application/json" -d '{"model":"qwen2.5-32b","prompt":"test","max_tokens":100}' http://localhost:8000/v1/completions # 流式请求验证 TTFT curl -N -X POST http://localhost:8000/v1/completions -H "Content-Type: application/json" -d '{"model":"qwen2.5-32b","prompt":"test","max_tokens":100,"stream":true}' bash
# 查找占用端口的进程 lsof -i :8000 # 优雅停止 vLLM 服务进程(先 SIGTERM,给出清理时间) kill -TERM $(pgrep -f "vllm.entrypoints.openai.api_server.*port 8001") # 确认进程已退出 pgrep -f "vllm.entrypoints.openai.api_server" || echo "no matching process"
配置文件路径:无固定配置文件,以下为启动脚本 /opt/llm-serving/start_fp8.sh:
bash
#!/bin/bash set -euo pipefail MODEL_PATH="/data/models/qwen2.5-32b" LOG_DIR="/var/log/llm-serving" mkdir -p "${LOG_DIR}" exec python -m vllm.entrypoints.openai.api_server --model "${MODEL_PATH}" --quantization fp8 --kv-cache-dtype fp8 --tensor-parallel-size 2 --max-model-len 8192 --gpu-memory-utilization 0.85 --max-num-seqs 64 --port 8000 --host 0.0.0.0 --served-model-name qwen2.5-32b --disable-log-requests >> "${LOG_DIR}/fp8_server.log" 2>&1 参数说明:
--quantization fp8:启用 FP8 权重和激活量化,仅在 Hopper 及以后架构生效--kv-cache-dtype fp8:KV Cache 也使用 FP8 存储,长上下文场景显存收益更明显,需要框架版本支持--gpu-memory-utilization:显存使用上限比例,量化后显存余量增大,可以适当调高此值换取更大的 KV Cache 空间,但要保留一定余量应对突发峰值,不建议设置超过 0.9是否需要重启:修改量化相关参数属于启动参数,必须重启服务进程生效,不支持热更新。
验证是否生效:参照第四步的日志检查方法,确认启动日志中出现 FP8 相关字样,并对比显存占用。
启动脚本 /opt/llm-serving/start_int4.sh:
bash
#!/bin/bash set -euo pipefail MODEL_PATH="/data/models/qwen2.5-32b-awq-int4" LOG_DIR="/var/log/llm-serving" mkdir -p "${LOG_DIR}" exec python -m vllm.entrypoints.openai.api_server --model "${MODEL_PATH}" --quantization awq --tensor-parallel-size 1 --max-model-len 8192 --gpu-memory-utilization 0.85 --max-num-seqs 32 --port 8000 --host 0.0.0.0 --served-model-name qwen2.5-32b --disable-log-requests >> "${LOG_DIR}/int4_server.log" 2>&1 参数说明:
--quantization awq:指定量化格式为 AWQ,模型目录本身必须是已经量化好的 AWQ 格式--max-num-seqs:相比 FP8 配置调低,是因为反量化计算开销在高并发下会显著增加延迟,需要根据第六步压测结果调整到合理值,不是固定不变的经验值是否需要重启:同样是启动参数,修改后需要重启服务。
验证是否生效:查看启动日志中是否出现权重加载相关提示,并使用 nvidia-smi 确认显存占用符合 INT4 权重量化的预期降幅。
配置文件路径:无固定配置文件,以下为启动脚本 /opt/llm-serving/start_sglang_fp8.sh:
bash
#!/bin/bash set -euo pipefail MODEL_PATH="/data/models/qwen2.5-32b" LOG_DIR="/var/log/llm-serving" mkdir -p "${LOG_DIR}" exec python -m sglang.launch_server --model-path "${MODEL_PATH}" --quantization fp8 --tp 2 --context-length 8192 --mem-fraction-static 0.85 --port 8000 --host 0.0.0.0 >> "${LOG_DIR}/sglang_fp8_server.log" 2>&1 参数说明:
--quantization fp8:与 vLLM 类似的量化开关,具体支持的取值范围以当前安装的 SGLang 版本 --help 输出为准--mem-fraction-static:SGLang 中控制静态显存占用比例的参数,作用类似 vLLM 的 --gpu-memory-utilization,命名不同,不要混用两个框架的参数名是否需要重启:启动参数变更需要重启进程生效。
验证是否生效:查看启动日志中的量化相关字样,并结合 nvidia-smi 确认显存占用。
NVFP4 的使用流程与 vLLM/SGLang 的动态量化不同,通常需要先用 TensorRT-LLM 提供的转换工具生成量化后的 checkpoint,再构建推理引擎。以下为示意流程(不同版本的 TensorRT-LLM 命令行参数可能存在差异,具体参数名以当前安装版本的官方文档和 --help 输出为准):
bash
# 步骤一:将 HuggingFace 模型转换为量化 checkpoint(示例思路,具体子命令以实际版本文档为准) python convert_checkpoint.py --model_dir /data/models/qwen2.5-32b --output_dir /data/models/qwen2.5-32b-nvfp4-ckpt --qformat nvfp4 --calib_size 512 # 步骤二:基于量化 checkpoint 构建 TensorRT 引擎 trtllm-build --checkpoint_dir /data/models/qwen2.5-32b-nvfp4-ckpt --output_dir /data/models/qwen2.5-32b-nvfp4-engine --gemm_plugin nvfp4 --max_batch_size 32 --max_input_len 4096 --max_output_len 2048 风险提醒:NVFP4 相关工具链和参数名称在不同 TensorRT-LLM 版本之间变化较快,上面的命令是示例思路,用于说明整体流程分为"转换量化 checkpoint"和"构建引擎"两个阶段,实际执行前必须查阅当前安装版本对应的官方文档确认准确的子命令和参数名,直接照抄可能因为版本差异导致参数不存在而报错。
是否需要重启:引擎构建完成后需要重新启动 TensorRT-LLM 的服务进程加载新引擎,旧引擎不支持热替换。
验证是否生效:查看引擎构建日志中是否有 NVFP4 相关的层替换记录,加载服务后通过 nvidia-smi 确认显存占用,并执行小规模推理请求确认输出正常(无乱码、无重复)。
配置文件路径:k8s/llm-fp8-deployment.yaml
yaml
apiVersion: apps/v1 kind: Deployment metadata: name: qwen32b-fp8 namespace: llm-serving labels: app: qwen32b-fp8 spec: replicas: 2 selector: matchLabels: app: qwen32b-fp8 template: metadata: labels: app: qwen32b-fp8 spec: containers: - name: vllm image: vllm/vllm-openai:v0.6.3 args: - --model - /models/qwen2.5-32b - --quantization - fp8 - --kv-cache-dtype - fp8 - --tensor-parallel-size - "2" - --max-model-len - "8192" - --gpu-memory-utilization - "0.85" - --port - "8000" resources: requests: cpu: 8 memory: 32Gi nvidia.com/gpu: 2 limits: cpu: 16 memory: 64Gi nvidia.com/gpu: 2 ports: - containerPort: 8000 name: http readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 30 volumeMounts: - name: models mountPath: /models readOnly: true nodeSelector: gpu-arch: hopper volumes: - name: models persistentVolumeClaim: claimName: model-storage-pvc --- apiVersion: v1 kind: Service metadata: name: qwen32b-fp8-svc namespace: llm-serving spec: selector: app: qwen32b-fp8 ports: - port: 8000 targetPort: 8000 type: ClusterIP 关键字段说明:
nodeSelector: gpu-arch: hopper:确保 Pod 只会调度到打了 Hopper 架构标签的节点上,避免误调度到 Ampere 节点后 FP8 无法生效,这个标签需要提前在节点上打好(kubectl label node gpu-arch=hopper )readinessProbe/livenessProbe 的 initialDelaySeconds 设置得比默认值更长,因为大模型加载耗时较长,过短的探测延迟会导致 Pod 反复被判定为未就绪甚至被重启应用配置:
bash
kubectl apply -f k8s/llm-fp8-deployment.yaml -n llm-serving 验证是否生效:
bash
kubectl get pods -n llm-serving -l app=qwen32b-fp8 kubectl logs -n llm-serving | grep -i quant 持续采集显存占用,观察是否存在缓慢增长(可能是显存泄漏或 KV Cache 管理异常):
bash
nvidia-smi --query-gpu=timestamp,memory.used,memory.total,utilization.gpu --format=csv,noheader -l 5 >> gpu_metrics.csv 正常表现:显存占用在服务启动完成、达到稳定并发后应该趋于平稳,围绕一个中心值上下波动(波动幅度取决于请求长度和并发变化)。
异常表现:显存占用持续单向增长且不回落,通常提示 KV Cache 没有被正确释放,或者存在请求堆积导致等待队列越来越长。
vLLM 等框架通常在标准输出日志中周期性打印运行时统计信息,关注以下类型的字段(不同版本框架输出的具体字段名可能有差异,重点关注这几类指标):
如果启用了 Prometheus 集成,可以通过 metrics 端点采集:
bash
curl http://localhost:8000/metrics | grep -E "vllm:(num_requests|gpu_cache_usage|time_to_first_token)" 以实际部署框架和版本导出的指标名称为准,不同版本的指标命名可能发生变化,Grafana 面板配置前先用上面的命令确认当前版本实际暴露了哪些指标。
在网关或应用层记录每个请求的关键信息,便于后续按量化方案分组统计:
python
import logging import time logger = logging.getLogger("llm_request") def log_inference_call(quant_method, prompt_tokens, completion_tokens, latency_s, status): logger.info( "quant=%s prompt_tokens=%d completion_tokens=%d latency=%.3f status=%s", quant_method, prompt_tokens, completion_tokens, latency_s, status )
日志落盘后可以用简单的统计脚本按 quant 字段分组计算平均延迟和成功率,避免混在一起分析导致数据失真。
量化上线后出现问题时,按以下路径排查:
上线后出现异常 ↓ 异常类型判断 ├── 显存未按预期下降 → 检查框架版本是否真正支持该量化方式(第二步方法) │ 检查启动日志是否出现降级提示 │ 确认硬件架构是否满足要求 │ ├── 吞吐低于基线甚至更差 → 检查是否为 INT4 反量化开销主导的场景 │ 检查并发数和批处理参数是否合理 │ 对比不同并发梯度下的吞吐曲线,确认瓶颈点 │ ├── 输出质量下降、乱码、重复 → 检查是否为激活量化的数值溢出(常见于 FP8 未正确处理异常值) │ 检查量化校准数据集是否与实际业务分布匹配 │ 尝试切换为更保守的量化方案(如 FP8 换 INT8,或 4-bit 换 8-bit) │ └── 服务启动失败或报错 → 检查 CUDA 版本、驱动版本与框架版本兼容性 检查量化库版本是否匹配(autoawq、auto-gptq 等) 查看完整报错堆栈,定位是加载阶段还是推理阶段失败 ↓ 定位到具体环节后,回退到该环节对应的验证步骤重新执行 ↓ 记录问题现象、根因和处理方式,形成排查记录 某些框架版本对不支持的硬件架构会静默降级或抛出不明确的警告,而不是直接报错拒绝启动。这意味着服务可能正常运行,但完全没有获得量化的性能收益,团队却误以为量化已经生效。
预防措施:上线前务必执行第一步和第二步的硬件与框架确认流程,不要仅凭配置参数是否被接受来判断量化是否生效,必须结合显存实测数据交叉验证。
如第六步压测结果所示,INT4 权重量化在计算密集场景下可能出现吞吐不升反降的情况。这是一个容易被忽视的风险点,因为大多数团队评估量化时只做了低并发或单请求测试,没有覆盖到生产环境真实的并发范围。
预防措施:压测并发梯度必须覆盖生产环境实际可能出现的峰值并发,不能只测 1-4 并发就下结论。
动态缩放因子是根据实际输入数据实时计算的,这意味着相同的 prompt 在不同批次、不同并发条件下,理论上可能因为批内其他请求的数值分布不同而产生细微的数值差异。对大多数业务场景这种差异不可感知,但对要求输出完全确定性(deterministic)的场景(例如某些金融计算或需要严格复现的测试场景)需要额外评估。
预防措施:如果业务对输出确定性有硬性要求,优先测试静态缩放方案,或在验证阶段增加"相同输入多次请求,输出是否一致"的专项测试。
AWQ、GPTQ、NVFP4 等量化后的模型文件(checkpoint)通常与生成它们的工具版本和目标推理框架版本存在隐性绑定关系,跨版本、跨框架加载可能失败或者静默产生错误结果。
预防措施:量化产出的模型目录必须同时记录量化工具版本、目标框架版本、CUDA 版本等元信息,避免团队成员在不同环境复用模型文件时踩坑。
量化后显存空间增大,团队往往会调高 --gpu-memory-utilization 之类的参数追求更大的 KV Cache 空间,但如果设置过于激进(例如设为 0.95 以上),在突发长文本请求或并发峰值时可能触发显存溢出(OOM),导致服务崩溃。
预防措施:显存利用率参数调整应循序渐进,每次调整后观察至少一个完整的业务高峰周期,确认没有 OOM 后再考虑进一步调高;生产环境不建议设置超过 0.9。
完整的验证清单应覆盖以下五个方面,缺一不可:
硬件与框架验证:
bash
nvidia-smi --query-gpu=name,compute_cap --format=csv python -c "from vllm.model_executor.layers.quantization import QUANTIZATION_METHODS; print(list(QUANTIZATION_METHODS.keys()))" 预期:GPU 架构满足目标量化方式的最低要求,框架版本的支持列表包含目标量化方式。
显存验证:
bash
nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader 预期:相较 FP16 基线有符合理论预期的降幅(FP8 约 50%,INT4/NVFP4 约 75%,具体以实测为准)。
吞吐与延迟验证:
bash
hey -n 200 -c 16 -m POST -H "Content-Type: application/json" -d '{"model":"qwen2.5-32b","prompt":"test","max_tokens":100}' http://localhost:8000/v1/completions 预期:在目标并发范围内,QPS 不低于业务 SLA 要求,P99 延迟在可接受范围内。
精度验证:
对比量化前后在固定测试集上的准确率或人工评分差异,预期:差异在预先设定的业务可接受阈值以内。
稳定性验证:
以目标并发持续压测至少 30 分钟到 1 小时,观察显存曲线是否平稳、是否出现请求超时或错误率上升:
bash
hey -z 30m -c 16 -m POST -H "Content-Type: application/json" -d '{"model":"qwen2.5-32b","prompt":"test","max_tokens":100}' http://localhost:8000/v1/completions 预期:持续压测期间错误率保持在 0 或业务可接受的极低水平,显存占用无持续增长趋势。
如果生产环境采用蓝绿部署或多版本并存的方式(推荐做法),回滚只需要将网关或负载均衡的流量切回 FP16 基线服务对应的端点或 Service:
bash
# 以 Kubernetes 场景为例,将 Service 的 selector 切回基线版本的 Deployment 标签 kubectl patch service qwen32b-svc -n llm-serving -p '{"spec":{"selector":{"app":"qwen32b-fp16-baseline"}}}' 执行后验证流量已切换:
bash
kubectl get endpoints qwen32b-svc -n llm-serving 确认返回的 Endpoint IP 对应的是基线版本 Pod,而不是量化版本 Pod。
bash
kubectl scale deployment qwen32b-fp8 -n llm-serving --replicas=0 kubectl scale deployment qwen32b-fp16-baseline -n llm-serving --replicas=2
风险提醒:执行 scale --replicas=0 会终止所有正在处理的请求连接,如果当前有长时间运行的推理请求,建议先将新流量切走(上一步),等待存量请求处理完成(可以通过监控运行中请求数指标判断是否归零)后再执行缩容,避免直接掐断进行中的用户请求。
不建议直接切换全部流量到新的量化方案,建议按以下节奏推进:
量化方案切换属于影响服务可用性的变更,应在业务低峰期执行,并提前通知相关业务方。切换前需要明确记录:
量化模型的生产环境部署权限应与普通配置变更权限区分,涉及生产环境 GPU 资源调度和模型切换的操作,建议要求至少两人复核(一人操作、一人审核)后才能执行,避免单人误操作导致大范围服务不可用。
如果团队同时存在多种 GPU 架构(例如部分节点是 Ampere,部分是 Hopper),需要在调度层面明确标记节点架构标签,并在部署配置中通过 nodeSelector 或 affinity 强制约束量化服务只调度到满足硬件要求的节点,避免因调度到不兼容节点导致量化失效或服务启动失败。
量化方案上线后不是一次性工作,以下场景需要重新走一遍本文的验证流程:
不是。A100 属于 Ampere 架构,FP8 和 NVFP4 的原生 Tensor Core 支持都不可用,但 INT4/INT8 权重量化(GPTQ、AWQ、bitsandbytes)在 Ampere 上完全可用,且生态最成熟。A100 环境下的量化选型应该聚焦在对比不同 INT4/INT8 实现(GPTQ vs AWQ vs bitsandbytes)之间的精度和吞吐差异,而不是纠结要不要上 FP8。
先看业务对延迟和吞吐的敏感度。如果业务是高并发在线服务,且 GPU 是 Hopper 架构,优先测试 FP8,因为它没有反量化开销,通常能同时兼顾显存和吞吐。如果业务并发不高、对显存极限压缩的需求更强(例如需要在单卡上塞尽可能多的模型副本),可以同时评估 INT4,用第六步的压测方法在自己的并发范围内实测决定。
这种情况通常提示量化对某些数值分布特殊的层或某些依赖精细数值区分的任务更敏感。先检查校准数据集是否覆盖了这类问题对应的输入分布,如果没有覆盖,用针对性样本重新做校准量化;如果调整校准数据集后仍无改善,考虑对模型做混合精度处理(问题敏感的层保留更高精度,其余层量化),部分量化工具支持逐层指定精度,具体能力以所用工具版本文档为准。
可以,两者是独立的开关,实践中常见做法是权重量化和 KV Cache 量化同时启用以获得最大显存收益,但引入的数值处理环节也随之增加,精度验证时应该分别测试"只量化权重"和"权重+KV Cache 都量化"两种配置,确认叠加后的精度损失仍在可接受范围。
张量并行场景下,每张卡上只存储模型的一部分权重,量化收益仍然存在,但需要额外确认量化实现是否正确处理了跨卡的通信和聚合逻辑(例如 AllReduce 操作前后的精度转换)。部分量化工具或框架版本可能对张量并行场景的支持不如单卡场景成熟,建议在张量并行环境下单独跑一遍精度和吞吐验证,不能直接假设单卡验证通过的方案在多卡场景下表现一致。
没有固定周期,但以下几个事件应该触发复审:框架大版本升级、模型版本升级、GPU 硬件更新、业务输入分布发生明显变化。如果没有触发事件,建议至少每季度做一次抽样复审,确认精度和性能指标没有随着业务数据分布的自然漂移而出现劣化。
FP8、NVFP4、INT4 三种量化方式解决的不是同一个问题的三个程度选项,而是三条依赖不同硬件、面向不同场景的技术路线:
选型的正确顺序是:先用硬件架构做硬性筛选排除不可用选项,再对硬件允许范围内的候选方案逐一实测显存、吞吐、延迟、精度四组指标,横向对比后选择综合最优方案,最后通过灰度发布验证后再全量上线。任何脱离自己实际硬件条件和业务并发特征、仅凭"官方推荐"或"显存降得最多"做出的选型决策,都存在线上表现不及预期的风险。
量化选型的本质是工程决策,不是追新决策,先把约束条件和验证指标定义清楚,再做选择。
全部0条评论
快来发表一下你的评论吧 !