同一款192GB显卡,不同权重格式性能差异达7.8倍
在同一款AMD Instinct MI300X显卡上,使用相同的vLLM镜像,经过一天的测试,针对Gemma 4 E2B的十种不同权重格式进行性能评估,发现最快的格式与最慢的格式之间存在着约7.8倍的差异。所有的测试日志、报告和脚本均已整理并提交。在MI300X上,fp8是唯一能够与bf16格式相竞争的:在单请求情况下,fp8的性能为bf16的0.75倍,而在8个或64个并发请求时,其性能最高可达1.09倍。相比之下,int8 W8A8的性能在0.29倍到0.87倍之间波动,而4-bit的W4A16格式则在0.14倍至0.63倍之间。值得注意的是,速度最快的格式几乎是对Google训练时的权重进行了最大程度的舍入,这使得速度和保真度之间形成了选择的矛盾。在这款配备192GB内存的显卡上,内存容量并未成为一个限制因素,因为Gemma 4 E2B在bf16格式下只需占用9.42 GiB存储,而MI300X的192 GB HBM足以容纳917万个token的KV缓存,即使是最小构建也只会略微增加到947万的容量。因此,关键在于速度与精度的选择。这款显卡对于某些数字格式进行的是原生乘法运算,而对于另外一些格式则是通过模拟执行实现。每种格式在保存Google的量化感知训练(QAT)权重时,采用了不同的舍入策略。
所有十种构建均以Google提供的gemma-4-E2B-it-qat-q4_0-unquantized版本为基础,其权重自然落在4-bit网格上,且每32个值共享一个scale。九种量化变体全部为纯文本模型,已经上传至Hugging Face,包括fp8、fp8fnuz、fp8emb4、fp8fnuzemb4、w8a8、w8a8emb4、q4w4a16、q4w4a16ple4、q4w4a16emb4等。而int4则聚焦于词表部分,包括embed_tokens、lm_head和逐层嵌入,这些均依照QAT训练时的32值网格进行打包。
在动手进行测试之前,需要准备一套工具和环境:一个包含MI300X实例(gpu-mi300x1-192gb)的AMD Developer Cloud账号及其SSH密钥、一个Hugging Face的token、克隆好的代码仓库,以及使用amd-gputools和make scaffold准备好的实例——这个过程将配置好Docker,连接到GPU组,并拉取vLLM镜像。
测试步骤之一是构建格式,每个版本都从Google的QAT检查点中读取数据,生成特定格式。FP8构建会从4-bit重打包版本中提取纯文本模型的设置和张量列表,以保证每个构建所量化的都是同样的276个线性层。通过verify步骤,会重新读取两个检查点,将每个量化值与对应的QAT原值进行比较,结果表明276个模块中,有1,876,819,968个值,最大相对误差为0.0333,其中264个值完全一致,相对RMS误差为0.0264。build-on过程则通过将已有int4词表的线性层替换为FP8构建,而词表保持不变,从而生成emb4变体。int8构建则源自w8a8_from_qat.py,而4-bit重打包则来自repack_q4_0.py,后者以int4格式恢复训练过程中的步长,避免重新舍入。
第二步骤是对矩阵乘法进行计时。在服务任何模型之前,gemm_decode_shapes.py会首先记录E2B每个token运行过程中的每一次矩阵乘法所需的时间,测量1、8、64行的表现,并通过HIP graph排除启动开销的影响。测试结果显示,在1行时,bf16耗时为1966.2微秒/token,而fp8为1304.2微秒,fp8表现优于bf16,达到1.51倍;在64行时,bf16为2383.6微秒,而fp8则为1589.4微秒,依然维持在1.50倍的优势。而int8在64行时则耗时12146.4微秒,仅为bf16的0.20倍,fp8所需时间仅为bf16的三分之二,性能优越,同时在17行以下,PyTorch对int8乘法的支持甚至出现了运行错误:RuntimeError: self.size(0) needs to be greater than 16, but got 1。在MI300X上并不支持int4乘法,因此4-bit构建在每次乘法运算前必须将权重解包成bf16格式。
最后一步是在同一固定镜像上服务每一构建。dtype_sweep.py依次从各自的rig目录中服务每一种构建,使用相同的镜像digest和完全一致的服务设置:--max-model-len 32768、--gpu-memory-utilization 0.90、启用前缀缓存,并且不添加--quantization标志,使vLLM能够从每个检查点自行读取格式。在每个构建启动时,都会检查vLLM所采用的kernel和加载的内存情况,例如w8a8构建加载了7.07 GiB的内存,KV部分为9025606,所用kernel为TritonInt8ScaledMMLinearKernel;而fp8则也加载了7.07 GiB的内存,KV为9017531,此时所用的kernel为RowWiseTorchFP8ScaledMMLinearKernel。