- 文档
- 教程
- 人工智能
- 大模型
- RLHF
【免费下载链接】Awesome-ML-SYS-Tutorial
My learning notes for ML SYS.
导读
本文以 W4AFp8(INT4 权重 + FP8 激活)量化方案为切入点,系统拆解 SGLang 推理引擎的量化设计与实现思路。SGLang 将全部量化实现收纳在python/sglang/srt/layers/quantization/目录中,用一套"恰到好处"的抽象基类与钩子函数(Hook Function),把模型构建、权重加载、推理执行三个生命周期阶段有机连接起来。读完本文,你将掌握 SGLang 量化的核心抽象模型(配置类与量化方法类的继承层次)、三条关键调用链路的完整走向、W4AFp8 在 MoE 层与线性层的落地细节,以及如何不改动框架核心代码、仅通过三步扩展接入一种全新的量化方案(如 W2A8)。
一、量化三阶段抽象:一条贯穿模型全生命周期的"水管"
SGLang 把量化拆解为三个核心阶段,对应量化方法类上的三个钩子函数:
| 阶段 | 钩子函数 | 职责 | 类比 |
|---|---|---|---|
| 模型构建 | create_weights | 预分配量化权重、Scale 因子等参数张量的内存 | 铺设水管,尚未通水 |
| 权重加载完成后 | process_weights_after_loading | 把权重和 Scale 转换成计算内核(如 CUTLASS)最优的格式与布局(重排、降精度等) | 数据转换、蓄水 |
| 推理执行 | apply | 指挥底层计算内核(如 FP8 GEMM),让激活与权重真正流动完成计算 | 开水放行 |
这套抽象的精妙之处在于:模型代码完全不需要感知量化细节。线性层与 MoE 层在初始化时只负责调用self.quant_method上的钩子,而quant_method由配置类按层类型动态分发。对于不量化的模型,quant_method会被设置为UnquantizedLinearMethod,其create_weights直接创建指定形状和数据类型的普通权重参数,apply退化为常规 GEMM——这保证了量化路径与非量化路径的代码完全统一。
从仓库笔记 SGLang 模型加载过程 可以印证这一抽象在实际代码中的位置:Linear层(如ColumnParallelLinear等)在__init__中调用self.quant_method.create_weights创建权重参数,在forward函数中调用quant_method.apply完成实际计算;权重加载完成后,模型加载器会遍历每个子模块,执行量化方法中定义的process_weights_after_loading,且该阶段要求参数位于全局目标设备上(在 CPU 卸载场景下,SGLang 会将参数先移到设备、执行后处理、再移回原位置)。
二、整体流程:从配置解析到量化 GEMM 的完整链路
SGLang 量化启动的完整调用链如下:
ModelConfig._parse_quant_hf_config → 判定 quant_method(如 w4afp8) ↓ weight_utils.get_quant_config → 构造对应 QuantizationConfig 实例 ↓ _initialize_model(...) → 把 quant_config 传入模型/各层 ↓ LinearBase.quant_method.create_weights → 注册量化权重占位 ↓ DefaultModelLoader.load_weights_and_postprocess → 先 load_weights 加载权重, 再逐层调用 quant_method.process_weights_after_loading ↓ 推理时 LinearBase.forward → quant_method.apply 执行量化 GEMM链路两端各有一个关键的"桥":
- 配置侧:
ModelConfig._parse_quant_hf_config从模型目录的量化配置(如hf_quant_config.json)中判定出字符串标识(quant_method),随后weight_utils.get_quant_config依据注册表把字符串解析为具体的QuantizationConfig实例,并在_initialize_model阶段随模型初始化传入各层。 - 执行侧:权重加载由
DefaultModelLoader统一负责——先通过load_weights把 checkpoint 数据写入create_weights注册的参数占位符,再逐层触发process_weights_after_loading做格式转换;推理时则由各层forward中的quant_method.apply完成最终计算。
三、类继承层次:配置类与量化方法类的"双向解耦"
SGLang 的量化抽象由两条继承链组成,职责划分清晰:
配置类继承关系(定义于 base_config.py): QuantizationConfig(抽象基类) 职责:解析量化配置、硬件校验与激活数据类型校验,并按层返回正确的量化方法实例 ├─ from_config():从配置字典解析并实例化 ├─ get_quant_method():根据层类型返回量化方法 ├─ get_min_capability():校验硬件兼容性 │ 硬件校验包括: │ • NVIDIA GPU:通过 CUDA capability(计算能力)检查, │ 如 70 (Volta)、75 (Turing)、80 (Ampere)、90 (Hopper) 等 │ • AMD GPU:通过 ROCm/HIP 平台检测,某些方案会检查特定 GCN 架构(如 gfx94) └─ get_supported_act_dtypes():返回支持的激活数据类型 ↓ W4AFp8Config(具体配置类) └─→ get_quant_method() 根据层类型返回: ├─→ LinearBase → Fp8LinearMethod └─→ FusedMoE → W4AFp8MoEMethod 量化方法类继承关系(定义于 base_config.py): QuantizeMethodBase(抽象基类) 职责:权重注册、权重加载完成后的处理以及前向执行 ├─ create_weights():注册量化权重占位符(模型构建阶段) ├─ process_weights_after_loading():权重后处理(权重加载完成后) └─ apply():前向传播执行量化计算(推理阶段) ├─→ LinearMethodBase │ └─→ Fp8LinearMethod(用于普通线性层) └─→ FusedMoEMethodBase └─→ W4AFp8MoEMethod(用于 MoE 层) 量化方法注册表(位于 __init__.py): 将原生方案(AWQ、GPTQ、FP8、W4AFp8、ModelOpt 等)映射成字符串标识从源码结构可以清晰看出两条设计原则:
- 配置与实现分离:
QuantizationConfig只负责"配置解析 + 硬件校验 + 方法分发",不触碰任何张量逻辑;QuantizeMethodBase只负责"建参、转格式、算前向",不关心配置从哪来。 - 同方案、异实现:一个配置类可以通过
get_quant_method按层类型分发到不同的方法实例(如 W4AFp8 对普通线性层用Fp8LinearMethod、对 MoE 层用W4AFp8MoEMethod),复用同一份配置语义。
四、三条调用链路的完整走向
4.1 create_weights:模型构建阶段
以 DeepSeekV2 的 MLA 注意力 + MoE 结构为例:
DeepseekV2DecoderLayer.__init__() ↓ DeepseekV2AttentionMLA.__init__() ↓ RowParallelLinear.__init__() ↓ LinearBase.__init__() (super().__init__()) # self.quant_method 在父类初始化函数里赋值 ↓ quant_config.get_quant_method() → 返回 Fp8LinearMethod ↓ RowParallelLinear.__init__() 继续执行 ↓ Fp8LinearMethod.create_weights() ↓ 注册 weight、weight_scale、input_scale 等参数占位符关键点在于self.quant_method是在LinearBase的父类初始化函数中被赋值的,各具体线性层无需关心量化方案的差异。
4.2 process_weights_after_loading:权重加载完成后
Scheduler.__init__() ↓ TpModelWorker.__init__() ↓ ModelRunner.__init__() ↓ ModelRunner.initialize() ↓ get_model() ↓ DefaultModelLoader.load_model() ↓ DefaultModelLoader.load_weights_and_postprocess() ↓ model.load_weights() → 加载权重数据 ↓ 逐层遍历,调用 quant_method.process_weights_after_loading() ↓ Fp8LinearMethod.process_weights_after_loading() └─→ 或 W4AFp8MoEMethod.process_weights_after_loading()这一阶段在仓库笔记中同样有据可查:SGLang 模型加载过程 明确记载了权重加载完成后遍历子模块、执行process_weights_after_loading的行为;并且该钩子在 在线权重更新 场景下也被复用——在线更新流程在model.load_weights(iter)之后,会遍历self.model.named_modules(),对每个带quant_method的模块在device_loading_context作用域内重新调用process_weights_after_loading(module)。这说明三阶段抽象不仅是模型冷启动的流程,也是热更新(权重替换后重新做格式转换)的公共基础设施。
4.3 apply:推理阶段
DeepseekV2DecoderLayer.forward() ↓ DeepseekV2AttentionMLA.forward() ↓ RowParallelLinear.forward() ↓ self.quant_method.apply() ↓ Fp8LinearMethod.apply() └─→ 或 W4AFp8MoEMethod.apply() ↓ 调用底层内核(CUTLASS/Marlin/torch)执行量化 GEMM五、W4AFp8 量化方案深入解析
W4AFp8 即"权重 INT4 + 激活 FP8"的混合精度方案,SGLang 对其的实现包含配置类W4AFp8Config与两个量化方法类W4AFp8MoEMethod、Fp8LinearMethod。
5.1 W4AFp8Config:配置 → 具体量化方法的映射
W4AFp8Config继承自QuantizationConfig,负责描述清楚"配置 → 具体量化方法"这一映射关系。
配置识别:当hf_quant_config.json中quant_algo == "MIXED_PRECISION"时,ModelConfig会把量化方案映射为w4afp8并校验硬件兼容性:
# ModelConfig._parse_modelopt_quant_config if quant_algo == "MIXED_PRECISION": return {"quant_method": "w4afp8"}对象构造:weight_utils.get_quant_config获取W4AFp8Config类,然后调用from_config方法进行实例化。
关键方法:
W4AFp8Config.from_config():从配置字典解析并实例化配置对象。W4AFp8Config.get_quant_method(layer, prefix):核心方法,根据层类型返回对应的量化方法实例:
if isinstance(layer, LinearBase): return Fp8LinearMethod(self) # 普通层用 Fp8LinearMethod elif isinstance(layer, FusedMoE): return W4AFp8MoEMethod(self) # MoE 层用 W4AFp8MoEMethod5.2 W4AFp8MoEMethod:MoE 层的三步落地
W4AFp8MoEMethod是 W4AFp8 在 MoE 层上的具体实现,严格遵循三阶段抽象。
第一步 create_weights:参数预分配
在FusedMoE模块初始化时,为 MoE 层创建量化所需的参数容器。主要工作包括:
- 创建量化权重张量:
w13_weight(gate 和 up projection)和w2_weight(down projection),注意类型是int8; - 分配权重缩放因子:
w13_weight_scale_inv和w2_weight_scale_inv,每组 128 个元素共享一个 scale(group-wise 量化); - 准备激活缩放因子:
w13_input_scale和w2_input_scale; - 初始化计算所需的元数据:如 stride、expert offsets 等。
注意,此时参数为空(使用torch.empty创建),仅完成内存布局的初始化,尚未填充实际数据:
def create_weights(self, layer, num_experts, hidden_size, ...): # 创建量化权重容器(INT8 类型) layer.register_parameter("w13_weight", torch.empty(..., dtype=torch.int8)) layer.register_parameter("w2_weight", torch.empty(..., dtype=torch.int8)) # 创建权重缩放因子(group-wise,每组 128 元素) layer.register_parameter("w13_weight_scale_inv", torch.zeros(...)) layer.register_parameter("w2_weight_scale_inv", torch.zeros(...)) # 创建输入缩放因子(静态量化时使用) layer.register_parameter("w13_input_scale", torch.ones(..., dtype=torch.bfloat16)) # 初始化 stride 等计算元数据 self.a_strides1 = torch.full((num_experts, 3), hidden_size, ...)第二步 process_weights_after_loading:格式转换与布局优化
权重数据从 Checkpoint 加载后,需要进行格式转换以适配底层计算内核:
- 权重 scale 的格式优化:将 float32 格式的 scale 转换为 bfloat16(减少 50% 内存占用),并调用
interleave_scales函数对 scale 进行交错重排。重排的目的是匹配 CUTLASS 内核的内存访问模式(参考了 TRT-LLM 的实现),重排后内核在计算时能够更高效地访问数据,提升缓存命中率; - 输入 scale 的聚合:在静态量化模式下,把每个专家的输入 scale 聚合为单一标量,减少推理计算量。
def process_weights_after_loading(self, layer: Module) -> None: # 将权重 scale 转换为 bfloat16 并重新排列以匹配 CUTLASS 布局 w13_weight_scale = layer.w13_weight_scale_inv.to(torch.bfloat16) w13_weight_scale = interleave_scales(w13_weight_scale) layer.w13_weight_scale_inv = Parameter(w13_weight_scale, requires_grad=False) # 将输入 scale 聚合为单一标量(静态量化模式) w13_input_scale_max = layer.w13_input_scale.max().to(torch.bfloat16).item() layer.w13_input_scale = Parameter(torch.tensor([w13_input_scale_max], dtype=torch.bfloat16), requires_grad=False)第三步 apply:混合精度 GEMM 执行
在前向传播阶段,apply收集所有预处理完成的数据(激活、重排后的权重和 scale、路由结果等),然后调用cutlass_w4a8_moe底层内核执行两个 GEMM 操作:
- GEMM1:
w13_weight(gate 和 up); - GEMM2:
w2_weight(down)。
cutlass_w4a8_moe是封装了 CUTLASS 库的底层函数,实现了 INT4 权重与 FP8 激活的混合精度矩阵乘法,充分利用硬件的量化计算能力:
def apply(self, layer, dispatch_output) -> CombineInput: from sglang.srt.layers.moe.cutlass_w4a8_moe import cutlass_w4a8_moe x = dispatch_output.hidden_states topk_weights, topk_ids, _ = dispatch_output.topk_output # 调用 CUTLASS 内核执行混合精度 MoE 计算 output = cutlass_w4a8_moe( x, layer.w13_weight, layer.w2_weight, layer.w13_weight_scale_inv, layer.w2_weight_scale_inv, topk_weights, topk_ids, self.a_strides1, self.b_strides1, self.c_strides1, # GEMM1 的 stride self.a_strides2, self.b_strides2, self.c_strides2, # GEMM2 的 stride self.s_strides13, self.s_strides2, # Scale 的 stride self.expert_offsets, self.problem_sizes1, self.problem_sizes2, layer.w13_input_scale, layer.w2_input_scale, ) # 应用路由缩放因子 if self.moe_runner_config.routed_scaling_factor is not None: output *= self.moe_runner_config.routed_scaling_factor return StandardCombineInput(hidden_states=output)5.3 Fp8LinearMethod:线性层的简化实现
对于普通线性层,W4AFp8Config分配Fp8LinearMethod,逻辑与 MoE 版本类似但更简单:
create_weights:注册weight、weight_scale和input_scale占位符;process_weights_after_loading:根据硬件(Marlin、CUTLASS 等)要求,对权重和 scale 进行格式转换;apply:调用合适的内核(Marlin、CUTLASS 等)执行 FP8 GEMM 计算。
这里也体现了 SGLang 量化后端的多内核策略:同一个 FP8 线性层,可根据硬件能力和部署偏好选择 CUTLASS 或 Marlin 内核,内核选择与量化语义(三阶段钩子)解耦。
六、如何扩展一种全新的量化方案?
SGLang 的可扩展性在于"恰到好处、不多不少"的抽象。要接入新的量化方案(例如 W2A8),无需修改框架核心代码,只需按照以下三步实现:
- 实现配置类:继承
QuantizationConfig,解析自定义参数并实现get_quant_method方法(以及必要的get_min_capability、get_supported_act_dtypes校验)。 - 实现量化方法类:继承
LinearMethodBase、FusedMoEMethodBase,实现create_weights、process_weights_after_loading和apply三个方法。 - 注册方案:在
__init__.py的BASE_QUANTIZATION_METHODS中注册,建立字符串标识与配置类的映射关系。
接入之后,模型构建、权重加载、推理执行的既有链路会自动"复用"新方案——get_quant_method的分发、DefaultModelLoader的逐层后处理、各层forward的apply调用都不需要任何改动。这也是 在线权重更新 能直接复用process_weights_after_loading的原因:钩子抽象天然支持"换权重后重新转换格式"的场景。
七、附录:SGLang 已支持的量化方法一览
以下表格汇总了 SGLang 量化体系中已覆盖的方案类别,便于对照选择与扩展:
| Category | Representative Configurations | Description |
|---|---|---|
| FP8 Series | fp8,w8a8_fp8,modelopt_fp8,fbgemm_fp8 | Native FP8、W8A8-FP8 混合、ModelOpt/FBGEMM 扩展 |
| INT8 Series | w8a8_int8,blockwise_int8 | 经典 8bit 权重/激活、blockwise INT8 |
| INT4/Mixed Precision | w4afp8,qoq,moe_wna16 | 4bit 权重 + FP8 激活、QoQ、WNA16(W4A16/W8A16) |
| FP4 / MXFP4 | modelopt_fp4,petit_nvfp4,mxfp4,quark | FP4 / MXFP4 方案,quark为 ROCm 专属 |
| Pre-quantized Formats | awq,awq_marlin,gptq,gptq_marlin,gguf,compressed-tensors,auto-round,modelopt | 与外部工具链或压缩张量框架的集成,modelopt可自动识别 FP8/FP4 |
| KV Cache Quantization | BaseKVCacheMethod及其在kv_cache.py中的子类 | 为注意力缓存提供 scale 与 zero-point 管理 |
从该表格可以看出,SGLang 量化体系的覆盖面分为"原生计算方案"(FP8/INT8/INT4/FP4,直接驱动 GEMM 内核)与"预量化格式兼容层"(AWQ/GPTQ/GGUF 等,负责解析外部工具链产出的权重)两类,而它们最终都收敛到本文所述的同一套三阶段抽象之上。仓库中还提供了配套的量化背景资料(见 general_background.md)与 BNB/Unsloth 量化模型接入案例(见 unsloth.md),可以作为理解 SGLang 量化生态的延伸阅读。
结语
SGLang 的量化设计可以概括为一句话:用一对抽象基类(配置类 + 方法类)和三个钩子(create_weights / process_weights_after_loading / apply)解耦了"量化语义"与"计算内核"。模型代码只认quant_method接口,配置解析只负责分发,格式转换与内核调用完全收敛在量化方法类内部。这种设计既让 W4AFp8 这类混合精度方案可以优雅落地,也让社区扩展新量化方案的成本降到了"新增两个文件、注册一行映射"的量级,是值得借鉴的推理框架插件化架构范本。
- 文档
- 教程
- 人工智能
- 大模型
- RLHF
【免费下载链接】Awesome-ML-SYS-Tutorial
My learning notes for ML SYS.
相关推荐
Soldier76安全使用指南:避免检测的终极策略
Soldier76安全使用指南:避免检测的终极策略 Soldier76是一款功能强大的PUBG罗技鼠标宏工具,能够帮助玩家实现自动压枪,支持12种枪械无后坐力效
游戏开发如何构建C语言解释器:深入理解编译器三阶段处理模型
如何构建C语言解释器:深入理解编译器三阶段处理模型 想要学习编译原理却不知从何入手?今天我将为你揭秘C语言解释器的 架构设计 ,让你轻松掌握 编译器三阶段处理模
编译器/解释器文档/教程SGLang 多模态请求生命周期深度解析:以 Qwen2.5-VL 为例的端到端架构详解
SGLang 多模态请求生命周期深度解析:以 Qwen2.5 VL 为例的端到端架构详解 导读:本文以 Qwen2.5 VL 为参考模型,逐层拆解 SGLang
文档教程人工智能大模型RLHF
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考