首页/知识库/FP8 推理基础设施(五):从 DeepGEMM 到 vLLM

FP8 推理基础设施(五):从 DeepGEMM 到 vLLM

✍️ AI Master📅 创建 2026-04-26📖 35 min 阅读
💡

文章摘要

2026 年 4 月,DeepSeek 开源 DeepGEMM——专为 FP8 精度设计的高性能 GEMM 内核库。FP8 推理正在从「实验性优化」变为「行业标准」。本文从 FP8 的数学原理、硬件支持、主流推理框架集成到 DeepGEMM 源码级解析,全面梳理 FP8 推理基础设施的技术全景,并附 Python 实战代码。

一、FP8 为什么成为 2026 年推理的事实标准?

2026 年是 FP8(8-bit 浮点数)推理的转折之年。从 NVIDIA 的路线图到各大大模型的部署策略,FP8 已经从「可选优化」变成了「默认配置」。

FP8 的数学本质

FP8 不是一个新的概念——IEEE 754 标准早在 2008 年就定义了 8-bit 浮点格式。但直到 2022 年 NVIDIA Hopper 架构(H100)引入 FP8 Tensor Core,FP8 才真正具备了实用价值。

FP8 有两种主流格式:

格式 符号位 指数位 尾数位 动态范围 精度
E4M3 1 4 3 ±448 ~2 位有效数字
E5M2 1 5 2 ±57344 ~1.5 位有效数字

E4M3 适合权重和激活值——它有更大的尾数位,精度更高,适合需要数值稳定性的场景。
E5M2 适合梯度——它有更大的动态范围,可以容纳更大范围的数值,适合反向传播。

为什么是 FP8 而不是 FP16/INT8?

对比 FP16: FP8 将显存占用和计算量减半,精度损失在 LLM 推理中通常 <1%(perplexity 差异 <0.1)。对于 70B 模型,FP16 需要 140GB 显存,FP8 只需 70GB——从需要 8×H100 降到 4×H100,成本直接减半。

对比 INT8: INT8 是定点数,没有指数位,动态范围有限。对于 LLM 中的激活值(可能跨越多个数量级),INT8 需要复杂的缩放策略(per-token + per-channel 缩放),实现复杂且容易出错。FP8 自带指数位,天然支持大动态范围,实现更简洁。

对比 INT4: INT4 的精度损失在推理中已经比较显著(perplexity 增加 0.5-2.0),而且需要复杂的量化校准流程。FP8 在精度和效率之间取得了更好的平衡。

二、DeepGEMM 源码级解析:FP8 GEMM 的核心技术

GEMM(General Matrix Multiplication)是深度学习中最核心的计算操作。LLM 的每一层 Transformer 都在大量执行矩阵乘法:Q·K^T、注意力输出投影、FFN 层的两个线性变换。可以说,优化了 GEMM 就优化了 LLM 推理的 80%。

DeepSeek 的 DeepGEMM 正是瞄准了这个核心痛点——提供一个专为 FP8 优化的高性能 GEMM 内核库。

DeepGEMM 的核心设计

DeepGEMM 的关键创新在于细粒度缩放(Fine-grained Scaling)。传统 FP8 量化使用 per-tensor 或 per-channel 缩放——整个张量或每个通道共享一个缩放因子。但 LLM 的激活值在不同 token、不同位置上的分布差异巨大,粗粒度缩放会导致精度损失。

DeepGEMM 支持 per-token + per-channel 双维度缩放:

FP8 GEMM: C = (A_fp8 × S_A) × (B_fp8 × S_B)
其中 S_A 是 per-token 缩放因子,S_B 是 per-channel 缩放因子

这种设计让每个 token 和每个输出通道都有独立的缩放因子,最大限度保留精度,同时保持了 FP8 的显存和计算优势。

与 cuBLAS/cuBLASLt 的对比

NVIDIA 的 cuBLAS 库也支持 FP8 GEMM,但 DeepGEMM 在以下方面做了针对性优化:

  1. 缩放因子融合:将 per-token/per-channel 缩放操作融合到 GEMM 内核中,避免额外的 CUDA kernel 启动开销
  2. Tile 策略优化:针对 LLM 推理中常见的矩阵形状(如 batch×seq×hidden)优化了 block tiling 策略
  3. Clean API:提供了比 cuBLAS 更简洁的 Python 接口,易于集成到现有推理框架
python
import torch
import deep_gemm

# 创建 FP8 矩阵(模拟 LLM 推理中的 Q·K^T 操作)
# batch_size=2, seq_len=1024, hidden_dim=4096
batch, seq, dim = 2, 1024, 4096

# 输入矩阵 A (query): [batch, seq, dim]
A_fp32 = torch.randn(batch, seq, dim, device="cuda", dtype=torch.float32)
# 输入矩阵 B (key): [batch, dim, seq]
B_fp32 = torch.randn(batch, dim, seq, device="cuda", dtype=torch.float32)

# 转换为 FP8 (E4M3 格式)
A_fp8 = A_fp32.to(torch.float8_e4m3fn)
B_fp8 = B_fp32.to(torch.float8_e4m3fn)

# DeepGEMM FP8 GEMM 调用
# m=batch*seq (总 token 数), n=seq (输出序列长度), k=dim (hidden 维度)
m, n, k = batch * seq, seq, dim
C_fp8 = deep_gemm.fp8_gemm(
    A=A_fp8,
    B=B_fp8,
    m=m, n=n, k=k,
    # per-token 缩放因子(对 A)
    scale_a=torch.ones(m, device="cuda", dtype=torch.float32),
    # per-channel 缩放因子(对 B)
    scale_b=torch.ones(n, device="cuda", dtype=torch.float32),
)

# 输出转回 FP32 进行后续计算
C_fp32 = C_fp8.to(torch.float32)
print(f"输出形状: {C_fp32.shape}")  # [2048, 1024]
python
import torch
import time
import deep_gemm

def benchmark_gemm(m, n, k, iterations=100):
    """对比 DeepGEMM FP8 与 cuBLAS FP16 的性能"""
    
    # 准备 FP8 数据
    A_fp8 = torch.randn(m, k, device="cuda", dtype=torch.float8_e4m3fn)
    B_fp8 = torch.randn(k, n, device="cuda", dtype=torch.float8_e4m3fn)
    
    # 准备 FP16 数据(cuBLAS 基准)
    A_fp16 = torch.randn(m, k, device="cuda", dtype=torch.float16)
    B_fp16 = torch.randn(k, n, device="cuda", dtype=torch.float16)
    
    # DeepGEMM FP8 GEMM
    torch.cuda.synchronize()
    start = time.perf_counter()
    for _ in range(iterations):
        _ = deep_gemm.fp8_gemm(A_fp8, B_fp8, m, n, k,
                               torch.ones(m, device="cuda"),
                               torch.ones(n, device="cuda"))
    torch.cuda.synchronize()
    fp8_time = (time.perf_counter() - start) / iterations
    
    # cuBLAS FP16 GEMM
    torch.cuda.synchronize()
    start = time.perf_counter()
    for _ in range(iterations):
        _ = torch.mm(A_fp16, B_fp16)
    torch.cuda.synchronize()
    fp16_time = (time.per*counter() - start) / iterations
    
    print(f"矩阵形状: {m}×{k} × {k}×{n}")
    print(f"DeepGEMM FP8: {fp8_time*1000:.2f}ms")
    print(f"cuBLAS FP16:  {fp16_time*1000:.2f}ms")
    print(f"加速比: {fp16_time/fp8_time:.2f}x")
    print(f"显存节省: 50%")

# 模拟 GPT-5.5 推理场景
# Attention: Q·K^T, batch=4, seq=2048, dim=8192
benchmark_gemm(8192, 2048, 8192)

# FFN: 第一个线性层, batch*seq=8192, dim=8192, ff_dim=22016
benchmark_gemm(8192, 22016, 8192)

三、FP8 推理的硬件支持全景

FP8 推理的普及离不开硬件层面的支持。以下是 2026 年主流 GPU 的 FP8 支持情况:

NVIDIA 阵营

GPU 架构 FP8 Tensor Core FP8 GEMM 吞吐量 备注
H100 Hopper 1,979 TFLOPS 首次引入 FP8
H200 Hopper 1,979 TFLOPS H100 + HBM3e
B200 Blackwell ~3,000 TFLOPS FP8 性能翻倍
GB200 Blackwell ~3,000 TFLOPS 双芯片封装
RTX 5090 Blackwell ~1,200 TFLOPS 消费级支持 FP8
A100 Ampere N/A 仅支持 FP16/INT8

其他厂商

厂商 产品 FP8 支持 备注
AMD MI300X CDNA 3 架构
AMD MI350X CDNA 4 架构
Intel Gaudi 3 ⚠️ 部分支持
华为 昇腾 910C ⚠️ 实验性支持

关键结论: H100 及以上(或 B200/GB200)是使用 FP8 推理的最低硬件要求。如果你的部署环境是 A100 或更老的 GPU,FP8 只能退化为软件模拟,反而会变慢。

FP8 推理的显存收益实战

以一个 70B 参数的 LLM 为例:

精度 权重显存 KV Cache (8K seq, batch=32) 总显存需求 H100 数量
FP16 140 GB 64 GB 204 GB 8
FP8 70 GB 32 GB 102 GB 4
INT4 35 GB 32 GB 67 GB 2

FP8 相比 FP16,显存需求减半,所需 GPU 数量减半——这直接转化为 50% 的推理成本节省。

四、主流推理框架的 FP8 支持对比

2026 年,主流 LLM 推理框架都已支持或正在积极支持 FP8。以下是各框架的 FP8 支持状态:

vLLM

vLLM 是最受欢迎的开源 LLM 推理框架,FP8 支持成熟度最高:

  • FP8 量化:支持 E4M3 格式的 per-tensor FP8 量化
  • FP8 Attention:FlashAttention 的 FP8 变体
  • 动态缩放:支持运行时 per-token 缩放
  • 与 DeepGEMM 集成:vLLM 正在推进 DeepGEMM 作为 FP8 GEMM 后端

启用方式

python
from vllm import LLM

llm = LLM(
    model="meta-llama/Llama-3-70B",
    quantization="fp8",  # 一行代码启用 FP8
    tensor_parallel_size=4,
)

TensorRT-LLM

NVIDIA 官方推理框架,FP8 支持最底层、最优化:

  • 硬件级 FP8:直接调用 FP8 Tensor Core
  • Auto FP8:自动为每层选择最优精度(FP8/FP16/INT8 混合)
  • 与 DeepGEMM 兼容:可以作为 DeepGEMM 的宿主框架

SGLang

新兴推理框架,以高吞吐量和低延迟著称:

  • FP8 KV Cache:支持 FP8 格式的 KV Cache 压缩
  • FP8 推理:实验性支持,性能接近 vLLM

TGI (Text Generation Inference)

HuggingFace 官方推理框架:

  • bitsandbytes FP8:通过 bitsandbytes 库间接支持
  • 性能:不如 vLLM/TensorRT-LLM 的 FP8 实现高效
框架FP8 权重FP8 激活FP8 KV CacheDeepGEMM 集成成熟度

vLLM

✅ 成熟

✅ 成熟

✅ 实验性

🔄 推进中

⭐⭐⭐⭐⭐

TensorRT-LLM

✅ 成熟

✅ 成熟

✅ 成熟

✅ 兼容

⭐⭐⭐⭐⭐

SGLang

✅ 实验性

⚠️ 部分

✅ 成熟

❌ 无

⭐⭐⭐⭐

TGI

✅ bitsandbytes

❌ 无

❌ 无

❌ 无

⭐⭐⭐

llama.cpp

❌ 无

❌ 无

❌ 无

❌ 无

⭐⭐

五、FP8 推理的精度-性能权衡实战

FP8 推理不是「开了就行」——你需要根据具体场景做精度和性能的权衡。以下是实战中总结的最佳实践:

1. 混合精度策略

最聪明的做法不是全模型 FP8,而是分层选择精度:

  • Attention 层:FP8(数值稳定,精度损失 <0.5%)
  • FFN 第一层(上投影):FP8(输入范围可控)
  • FFN 第二层(下投影):FP16(输出精度敏感)
  • 输出层(LM Head):FP16(logits 需要高精度)
  • 嵌入层:FP16(低频 token 的嵌入向量对精度敏感)

这种混合策略在几乎不损失精度的前提下,能实现约 40% 的显存节省和 30% 的加速。

2. 校准数据的选择

FP8 量化的精度高度依赖于校准数据的质量:

  • 最少样本数:512 个校准样本通常足够
  • 数据多样性:覆盖不同领域、不同长度、不同语言
  • 避免偏差:校准数据应该与目标推理场景的分布一致

3. 精度监控

在生产环境中持续监控 FP8 推理的精度:

  • Perplexity 监控:定期在验证集上计算 FP8 vs FP16 的 perplexity 差异
  • 输出一致性:抽样对比 FP8 和 FP16 的输出相似度
  • 回退机制:当精度下降超过阈值时,自动切换回 FP16
python
import torch
import torch.nn as nn
from typing import Dict, Optional

class FP8HybridInference:
    """FP8 混合精度推理引擎
    
    策略:
    - Attention 层: FP8 (E4M3)
    - FFN 上投影: FP8
    - FFN 下投影: FP16
    - LM Head: FP16
    - Embedding: FP16
    """
    
    def __init__(self, model: nn.Module, calibration_data: torch.Tensor):
        self.model = model
        self.fp8_layers = self._identify_fp8_layers(model)
        self.scales = self._calibrate_scales(calibration_data)
    
    def _identify_fp8_layers(self, model: nn.Module) -> Dict[str, bool]:
        """识别哪些层可以用 FP8"""
        fp8_config = {}
        for name, module in model.named_modules():
            if isinstance(module, nn.Linear):
                # LM Head 和 Embedding 用 FP16
                if "lm_head" in name or "embed" in name:
                    fp8_config[name] = False
                # FFN 下投影用 FP16
                elif "down_proj" in name:
                    fp8_config[name] = False
                # 其他用 FP8
                else:
                    fp8_config[name] = True
        return fp8_config
    
    def _calibrate_scales(self, data: torch.Tensor) -> Dict[str, float]:
        """基于校准数据计算每层的 FP8 缩放因子"""
        scales = {}
        for name, module in self.model.named_modules():
            if name in self.fp8_layers and self.fp8_layers[name]:
                # 用校准数据的前向传播统计激活值范围
                with torch.no_grad():
                    # 简化版:用输入数据的 max 绝对值作为缩放因子
                    abs_max = data.abs().max().item()
                    scales[name] = abs_max / 448.0  # E4M3 最大值为 448
        return scales
    
    def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
        """混合精度前向传播"""
        x = self.model.embed_tokens(input_ids)  # FP16
        
        for layer in self.model.layers:
            # Attention (FP8)
            attn_out = self._fp8_attention(layer.self_attn, x)
            x = x + attn_out
            
            # FFN 上投影 (FP8) + 下投影 (FP16)
            ff_out = self._hybrid_ffn(layer.mlp, x)
            x = x + ff_out
        
        # LM Head (FP16)
        logits = self.model.lm_head(x)
        return logits
    
    def _fp8_attention(self, attn_module, x: torch.Tensor) -> torch.Tensor:
        """FP8 注意力计算"""
        q = attn_module.q_proj(x).to(torch.float8_e4m3fn)
        k = attn_module.k_proj(x).to(torch.float8_e4m3fn)
        v = attn_module.v_proj(x).to(torch.float8_e4m3fn)
        
        # FP8 GEMM (用 DeepGEMM 或 cuBLASLt)
        # 简化版:torch.mm 自动降级到 FP16
        attn_scores = (q.to(torch.float16) @ k.transpose(-1, -2).to(torch.float16)) / (q.shape[-1] ** 0.5)
        attn_weights = torch.softmax(attn_scores, dim=-1)
        return attn_weights @ v.to(torch.float16)
    
    def _hybrid_ffn(self, mlp_module, x: torch.Tensor) -> torch.Tensor:
        """混合精度 FFN:上投影 FP8 + 下投影 FP16"""
        # 上投影:FP8
        hidden = mlp_module.up_proj(x).to(torch.float8_e4m3fn)
        hidden = mlp_module.act_fn(hidden.to(torch.float16))
        # 下投影:FP16
        return mlp_module.down_proj(hidden.to(torch.float16))


# 使用示例
# 1. 校准
calibration_data = torch.randn(512, 4096, device="cuda")
engine = FP8HybridInference(model, calibration_data)

# 2. 推理
output = engine.forward(input_ids)
print(f"输出形状: {output.shape}")  # [batch, seq, vocab]

六、生产部署指南:从开发到上线

将 FP8 推理部署到生产环境,需要关注以下几个关键环节:

1. 模型准备

bash
# 使用 **vLLM** 一键导出 FP8 量化模型
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3-70B \
    --quantization fp8 \
    --export-model ./llama3-70b-fp8

2. 服务配置

yaml
# docker-compose.yml
services:
  vllm-fp8:
    image: vllm/vllm-openai:latest
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 4
              capabilities: [gpu]
    command: >
      --model ./llama3-70b-fp8
      --tensor-parallel-size 4
      --max-num-seqs 256
      --gpu-memory-utilization 0.9
    ports:
      - "8000:8000"

3. 监控与告警

  • GPU 利用率:FP8 推理应该达到 60%+ 的 GPU 利用率
  • 显存使用:确认 FP8 量化后显存占用减半
  • 延迟分布:P50 < 100ms/token, P99 < 300ms/token
  • 精度监控:每小时抽样 100 个请求,对比 FP8 vs FP16 输出相似度

4. 常见问题排查

问题 原因 解决方案
FP8 推理反而更慢 GPU 不支持 FP8 Tensor Core 降级到 FP16
输出质量显著下降 校准数据不具代表性 增加校准样本多样性
OOM 批处理过大 减小 max_num_seqs
NaN 输出 缩放因子计算错误 检查 per-token/per-channel 缩放

5. 成本收益分析

以月均 1000 万次推理请求为例(每次 2K input + 1K output tokens):

方案 GPU 配置 月成本 单次延迟 P50 备注
FP16 8×H100 $56,000 120ms 基准方案
FP8 4×H100 $28,000 95ms 推荐
INT4 2×H100 $14,000 85ms 精度略低

FP8 方案在成本(节省 50%)和精度(损失 <1%)之间取得了最佳平衡。

七、总结:FP8 推理的 2026 年路线图

2026 年 4 月 DeepSeek 开源 DeepGEMM,标志着 FP8 推理基础设施进入了成熟期。以下是我们对 FP8 推理未来发展的预测:

短期(2026 Q2-Q3):

  • vLLM 完成 DeepGEMM 集成,FP8 成为 vLLM 默认量化选项
  • TensorRT-LLM 推出 Auto-FP8,自动为每层选择最优精度
  • 更多开源模型提供 FP8 量化版本

中期(2026 Q4-2027 Q1):

  • FP4 推理开始实用化(NVIDIA Rubin 架构支持)
  • 端侧 FP8 推理(手机、IoT)成为主流
  • FP8 训练(不仅仅是推理)开始探索

长期(2027+)

  • FP8 成为 LLM 推理的事实标准,FP16 退居次要地位
  • 混合精度推理引擎成熟,自动为每层、每 token 选择最优精度
  • FP8 硬件普及到消费级 GPU(RTX 50 系列已支持)

行动建议

  1. 如果你有 H100/B200 集群,立即开始 FP8 推理迁移——显存成本减半的收益太大了
  2. 如果你只有 A100,关注 INT8/INT4 量化,FP8 暂时不可用
  3. 如果你是推理服务提供商,FP8 是你的成本竞争力核心
  4. 如果你是模型开发者,在模型发布时同时提供 FP8 量化版本

DeepGEMM 的开源降低了 FP8 推理的技术门槛——现在你只需要几行 Python 代码就能享受 FP8 带来的性能提升。2026 年,不会用 FP8 推理的 LLM 工程师,就像 2020 年不会用 CUDA 的深度学习工程师一样——不是不能用,而是效率差了整整一个时代。

继续你的 AI 学习之旅

浏览更多 AI 知识库文章,或者探索 GitHub 上的优质 AI 项目