per_tenor_quant_fp8和per_token_quant_fp8算法解读

在大型语言模型(LLM)和高性能计算领域,模型量化技术正变得越来越重要。FP8(8位浮点数)作为一种新兴的数据类型,正在成为AI加速器(如NVIDIA H100)的关键特性。本文将深入剖析两种FP8量化算法:per_tensor_quant_fp8(张量级量化)和per_token_quant_fp8(令牌级量化),通过源代码解读其实现原理、数学本质和工程考量。

源代码参考sglang的实现添加链接描述

E4M3格式解析

在深入量化算法之前,我们需要理解FP8 E4M3格式。E4M3表示:

E4: 4位指数位(exponent)

M3: 3位尾数位(mantissa)

这种格式能表示的最大值为448.0,计算方式如下:

最大值 = 1.111(二进制)× 2^(14) = 1.875 × 16 = 448.0

其中指数最大值为14(指数位全1但排除特殊值),尾数最大值为0.875(3位全1)

FP8相比FP16和BF16:

存储节省: 内存占用减少50%

带宽效率: 数据传输量减半

计算吞吐: 理论上计算密度可提升2倍

per_tensor_quant_fp8

per_tensor_quant_fp8采用全局统一的量化尺度,对输入张量的所有元素使用相同的缩放因子。这种设计的核心思想是:保持张量整体的统计特性,以最简单的形式实现量化。

python代码参考

python 复制代码
FP8_E4M3_MAX = 448.0


def per_tensor_quant_fp8_torch(x, symmetric):
    if symmetric == False:
        return
    else:
        absmax = x.flatten().abs().max()

        if absmax == 0:
            scale = torch.tensor(1.0, device=x.device, dtype=torch.float32)
            q = torch.zeros_like(x, dtype=torch.float8_e4m3fn)
            return q, scale, None

        # 2. scale = absmax / FP8_MAX
        scale = absmax / FP8_E4M3_MAX

        # 3. 量化(注意:CUDA 里用的是 x * (1 / scale))
        inv_scale = 1.0 / scale
        x_scaled = x * inv_scale

        # 4. clip 到 FP8 可表示范围
        x_clamped = torch.clamp(x_scaled, -FP8_E4M3_MAX, FP8_E4M3_MAX)

        # 5. cast to fp8 e4m3
        q = x_clamped.to(torch.float8_e4m3fn)

        return q, scale.float(), None

输入特性:

形状:任意维度(1D、2D、3D、4D等)

数据类型:通常为float32、float16或bfloat16

数值范围:理论上任意实数范围

输出特性:

q形状:与输入x完全相同

q数据类型:torch.float8_e4m3fn(FP8 E4M3格式)

scale形状:标量(0维张量)

scale数据类型:torch.float32

per_tensor量化优缺点

优点:

计算简单,只需一次reduce操作

存储开销小,只需一个scale值

适合整体分布均匀的张量

缺点:

对异常值敏感,可能导致量化分辨率下降

无法适应局部特征的差异性

适合per_tensor的场景:

激活值分布相对均匀

对计算延迟极度敏感

存储带宽受限的部署环境

我针对sglang的代码做了一个实现得到对应的cuda代码,参考
添加链接描述

per_token_quant_fp8

python代码参考

python 复制代码
FP8_E4M3_MAX = 448.0


def per_token_quant_fp8_torch(x, symmetric):
    if symmetric == False:
        return
    else:
        assert x.dim() == 2, "per-token quant expects [num_tokens, hidden_dim]"

        # ------------------------------------------------------------
        # Pass-1: per-token absmax (对齐 CUDA 的 warpReduceMax)
        # ------------------------------------------------------------
        # CUDA: max_value = max_j |x[token_id, j]|
        absmax = x.abs().amax(dim=1)  # [num_tokens]

        # ------------------------------------------------------------
        # scale = absmax / FP8_E4M3_MAX
        # CUDA 中 scale 是 per-token 写入 output_s[token_id]
        # ------------------------------------------------------------
        scale = absmax / FP8_E4M3_MAX  # [num_tokens]

        inv_scale = 1.0 / scale
        inv_scale[scale == 0] = float("inf")  # [num_tokens]

        # ------------------------------------------------------------
        # Pass-2: x * inv_scale
        # CUDA: val = input * scale_inv
        # ------------------------------------------------------------
        x_scaled = x * inv_scale.unsqueeze(1)  # broadcast to [N, H]

        # ------------------------------------------------------------
        # clip 到 FP8 E4M3 可表示范围
        # CUDA: fmaxf(fminf(val, FP8_E4M3_MAX), -FP8_E4M3_MAX)
        # ------------------------------------------------------------
        x_clamped = torch.clamp(x_scaled, -FP8_E4M3_MAX, FP8_E4M3_MAX)

        # ------------------------------------------------------------
        # cast to FP8
        # CUDA: static_cast<DST_DTYPE>(val)
        # ------------------------------------------------------------
        q = x_clamped.to(torch.float8_e4m3fn)

        return q, scale.float(), None

输入形状要求:

必须为2维张量:[num_tokens, hidden_dim]

num_tokens:序列中的token数量

hidden_dim:每个token的特征维度

设计理由:

这种形状要求源于Transformer架构:

第一维(token维度):序列中的不同位置

第二维(特征维度):每个token的嵌入表示

输出特性:

q形状:[num_tokens, hidden_dim](与输入相同)

q数据类型:torch.float8_e4m3fn

scale形状:[num_tokens](每个token一个缩放因子)

scale数据类型:torch.float32

per_token量化优缺点

优点:

更好的适应性,每个token获得最优分辨率

对长尾分布有更好的处理能力

在Transformer类模型中表现优异

缺点:

计算开销大,需要per-token的reduce

存储多个scale值,增加元数据开销

实现复杂度高

适合per_token的场景:

Transformer-based语言模型

激活值存在明显的token间差异

对模型精度要求高

我针对sglang的代码做了一个实现得到对应的cuda代码,参考
添加链接描述

per_tensor_quant_fp8和per_token_quant_fp8代表了两种不同的量化哲学:统一性与适应性。前者追求简单高效,后者强调精度保持。在实际应用中,选择哪种算法需要权衡计算效率、存储开销和模型精度。随着AI硬件对FP8支持的成熟,这些量化技术将在模型部署中发挥越来越重要的作用。

相关推荐
tankeven2 小时前
HJ125 最大最小路
c++·算法
AI-小柒2 小时前
OpenClaw技术深度解析:从智能助手到自动化引擎的范式革命(附DataEyes实战)
大数据·运维·开发语言·人工智能·python·http·自动化
MegaDataFlowers2 小时前
认识复杂度和简单排序算法
java·算法·排序算法
MSTcheng.2 小时前
【算法】前缀和:『560. 和为 K 的子数组 & 1314.矩阵区域和』
线性代数·算法·矩阵
小白自救计划2 小时前
【计算机视觉】学习历程
人工智能·学习·计算机视觉
luckycoding2 小时前
739. 每日温度
算法·leetcode·职场和发展
一只黑鸟2 小时前
基于STM32的罐装水泥成分实时检测系统设计与实现(含有matlab仿真)
stm32·嵌入式硬件·算法·matlab·毕设
无人装备硬件开发爱好者2 小时前
Lockhart-Martinelli(L-M)模型原理、历史由来与嵌入式边缘计算工程化落地(附 MATLAB 伪代码)——1
人工智能
寻见9032 小时前
从“聊天框”到“生产力”:OpenClaw是如何把AI从数字囚笼里放出来的?
人工智能·openai