ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PaddleNLP experimental.model_utils 模块深度解析:FasterPretrainedModel 加速推理基类与量化 Scale 加载器

PaddleNLP experimental.model_utils 模块深度解析:FasterPretrainedModel 加速推理基类与量化 Scale 加载器 PaddleNLP experimental.model_utils 模块深度解析FasterPretrainedModel 加速推理基类与量化 Scale 加载器【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP本篇文章以 docs/zh/source/paddlenlp.experimental.model_utils.rst 所对应的 API 文档页为主线结合 paddlenlp/experimental/model_utils.py 的完整源码实现系统讲解 PaddleNLP 实验性experimental模型工具模块包括面向推理加速的FasterPretrainedModel基类、服务于 INT8/FP8 量化推理的激活与权重 Scale 加载器以及 FP8 block 量化相关的底层辅助函数。读完本文你将掌握这些工具类在 llama、qwen2、deepseek_v2 等加速模型量化推理流程中的定位、核心参数语义与调用方式。一、模块定位为加速推理 量化部署服务的实验性工具集在 PaddleNLP 中paddlenlp.experimental目录承载的是面向 LLM 推理优化的实验性实现它通过 paddlenlp/experimental/init.py 中的from .model_utils import *将工具类直接暴露为paddlenlp.experimental.model_utils命名空间。对应 API 文档页通过 Sphinx 的automodule指令自动生成.. automodule:: paddlenlp.experimental.model_utils :members: :no-undoc-members:也就是说文档页所列出的全部公开成员即模块源码中__all__声明的四个导出符号__all__ [FasterPretrainedModel, ActScalesLoader, WeightScalesLoader, PerTensorWeightScalesLoader]除此之外模块内还定义了CacheScaleLoader、get_dequant_weight、block_quant_to_fp8等内部工具被多个加速模型实现直接 import 使用见后文仓库内实际使用场景。从功能上可以把整个模块划分为三块功能块关键符号用途加速推理模型基类FasterPretrainedModel继承标准PretrainedModel提供静态图导出to_static与权重/资源配置加载能力量化 Scale 加载器ActScalesLoader、WeightScalesLoader、PerTensorWeightScalesLoader、CacheScaleLoader将 PTQ 产出的 JSON scale 文件装载为模型推理所需的 NumPy scale 数组量化底层辅助函数get_dequant_weight、block_quant_to_fp8、load_vocabularyFP8 反量化、block 量化与词表加载二、FasterPretrainedModel面向推理的预训练模型基类FasterPretrainedModel继承自 paddlenlp/transformers 中的PretrainedModel见from paddlenlp.transformers import PretrainedModel在保留标准模型加载/保存能力的同时新增了面向推理加速的静态图导出能力。2.1 to_static将动态图模型导出为静态图def to_static(self, output_path): self.eval() # Convert to static graph with specific input description model paddle.jit.to_static( self, input_spec[paddle.static.InputSpec(shape[None, None], dtypecore.VarDesc.VarType.STRINGS)] ) paddle.jit.save(model, output_path) logger.info(Already save the static model to the path %s % output_path)关键点说明先切到 eval 模式self.eval()确保导出时关闭 dropout、BN 等训练期行为保证导出模型与推理语义一致paddle.jit.to_static与InputSpec通过shape[None, None]、dtypecore.VarDesc.VarType.STRINGS的输入描述把模型转换为静态图。这里输入被声明为两维字符串张量对应推理引擎如自研的 Paddle Inference 加速算子约定的原始 token 输入格式paddle.jit.save(model, output_path)将静态图模型保存到指定路径便于后续用 C/Inference 引擎加载执行避开动态图逐算子调度的开销这正是Faster推理的关键一步。2.2 from_pretrained支持内置/社区/本地三种加载来源该基类重写了类方法from_pretrained完整兼容PretrainedModel.from_pretrained的语义pretrained_model_name_or_path参数可接受三类取值内置预训练模型名如bert-base-uncased从cls.pretrained_init_configuration与cls.pretrained_resource_files_map中查表获取配置与资源文件社区贡献模型名如yingyibiao/bert-base-uncased-sst-2-finetuned走resolve_file_path下载解析流程本地目录如./my_bert/要求目录内包含权重文件model_state.pdparams与配置文件model_config.json。加载流程还支持若干扩展关键字参数参数默认值说明cache_dirNone模型文件缓存目录from_hf_hubFalse是否从 Hugging Face Hub 拉取资源from_aistudioFalse是否从 AI Studio 拉取资源subfolder资源文件所在子目录其内部实现还有几处值得注意的细节配置与类签名匹配检查读取model_config.json后通过init_class字段判断配置属于基类还是派生类带 head 的模型并据此拆分配置给base_model_class与派生模型构造词表文件兜底校验无论哪种加载路径都会弹出vocab_file并断言非空否则报错The vocab file is None. Please reload the class ... with pretrained_name.权重前缀自适应加载model_state.pdparams时依据base_model_prefix自动剥离或补齐前缀使得基类权重与派生类带 head模型都能正确装载同时记录missing_keys/unexpected_keys并通过logger.info输出动态/静态模式分支动态图模式下调用set_state_dict并返回模型实例静态图模式下返回(model, state_to_load)元组供上层继续处理。2.3 save_pretrained 与 save_resources一键保存完整模型def save_pretrained(self, save_dir): assert not os.path.isfile(save_dir), Saving directory ({}) should be a directory, not a file os.makedirs(save_dir, exist_okTrue) self.save_model_config(save_dir) # 保存 model_config.json if paddle.in_dynamic_mode(): file_name os.path.join(save_dir, list(self.resource_files_names.values())[0]) paddle.save(self.state_dict(), file_name) # 保存 model_state.pdparams else: logger.warning(Save pretrained model only supported dygraph mode for now!) self.save_resources(save_dir)save_pretrained会依次保存模型配置model_config.json、模型状态model_state.pdparams以及 tokenizer 等附属资源产出的目录可直接作为后续from_pretrained的pretrained_model_name_or_path使用形成保存 → 重新加载闭环save_resources遍历resource_files_names将初始化时传入的资源文件如词表从源路径拷贝到保存目录通过os.path.abspath比较避免源目录与目标目录相同造成冗余拷贝注意静态图模式下保存仅输出告警日志logger.warning因此建议在动态图dygraph模式下调用保存接口。2.4 load_vocabulary词表文件加载模块级函数load_vocabulary(filepath)以及FasterPretrainedModel上的同名静态方法均以每行一个 token的格式解析词表文件构建token - 行号索引的字典供加速推理场景快速构造 tokenizer 词表映射。三、量化 Scale 加载器把 PTQ 产物装进推理引擎大模型量化部署如 W8A8、FP8、KV Cache INT8通常先离线做 PTQ后训练量化得到各张量的 scale再在推理时加载这些 scale 参与反量化计算。model_utils提供的四个 Loader 类统一承担了这一职责。它们有共同的构造形态Loader(scale_json_file_pathxxx_scales.json, key_map_dict..., num_of_layers...)其中key_map_dict描述逻辑 scale 名称 → JSON 文件中的 key 模板的映射模板中的#会被替换为具体层号i从而把 JSON 中按层组织的 scale 提取为[num_of_layers, ...]形状的 NumPy 数组某层缺失时填充-1.0作为跳过/无效层标记。3.1 ActScalesLoader激活 Scale 加载器class ActScalesLoader: def __init__(self, scale_json_file_pathact_scales.json, key_map_dictNone, num_of_layersNone): ... for scale_type, key_template in self.key_map.items(): self.scale[scale_type] np.full([num_of_layers], fill_value-1.0, dtypefloat32) for i in range(num_of_layers): if key_template.replace(#, str(i)) in self.scale_dict.keys(): self.scale[scale_type][i] 1 / self.scale_dict[key_template.replace(#, str(i))]要点默认读取act_scales.json输出形状为[num_of_layers]的一维数组对 JSON 中存储的激活最大值做了取倒数处理1 / scale_dict[...]即直接得到推理计算所需的反量化因子避免推理侧再做除法。3.2 WeightScalesLoader支持 QKV/FFN1 拼接的权重 Scale 加载器class WeightScalesLoader: def __init__(self, scale_json_file_pathweight_scales.json, key_map_dictNone, num_of_layersNone, concat_qkvFalse, concat_ffn1False):相比ActScalesLoader它输出二维数组[num_of_layers, n]其中n由 JSON 中对应层 scale 的长度决定自适应 per-channel 粒度并新增两个拼接开关参数默认值行为concat_qkvFalse为 True 时生成qkv_weight_scale即按[q, k, v]顺序用np.concatenate拼出 QKV 融合权重对应融合了 Q/K/V 三个投影的矩阵的 scaleconcat_ffn1False为 True 时生成ffn1_weight_scale拼接ffn1_1_weight_scalegate与ffn1_2_weight_scaleup两段 scale这两个开关对应推理侧常见的权重融合优化把多头注意力的 Q/K/V 三个权重拼接为一个大 GEMM 的 QKV 权重、把 FFN 的 gate/up 拼接为大 GEMM 权重以提升算子吞吐scale 必须同步拼接才能与融合权重对齐。3.3 PerTensorWeightScalesLoaderper-tensor 粒度的权重 Scale 加载器class PerTensorWeightScalesLoader: def __init__(self, scale_json_file_pathweight_scales.json, key_map_dictNone, num_of_layersNone):与WeightScalesLoader的区别在于形状推断更加通用它从 JSON 中第一个有效层的 scale 值推断scale_shapenp.array(...).shape输出形状为(num_of_layers,) scale_shape因此既能容纳 per-channel[hidden]也能容纳 per-tensor[1]等不同粒度的 scale。其最重要的行为是自动推导 QKV scale当qkv_weight_scale不在 JSON 中时自动按如下规则计算self.scale[qkv_weight_scale][i] max( abs(self.scale[q_weight_scale][i]), abs(self.scale[k_weight_scale][i]), abs(self.scale[v_weight_scale][i]), )即取 q、k、v 三者 scale 绝对值的最大值作为融合 QKV 权重的统一 scale保证融合后反量化不溢出同时省去离线合并 JSON 的步骤。该 Loader 正是 FP8 量化路径quant_type含fp8使用的加载器。3.4 CacheScaleLoaderKV Cache INT8 的 Scale 加载器class CacheScaleLoader: def __init__(self, scale_json_file_pathcache_scales.json, key_map_dictNone, num_of_layersNone, num_headsNone, num_key_value_headsNone):针对 KV Cache 静态 INT8 量化cachekv_int8_type static场景依据 key 名中的cache_k/cache_v区分 K、V并额外生成对应的cache_k_out_scale/cache_v_out_scale输出反量化 scale对 GQAGrouped-Query Attentionnum_heads ! num_key_value_heads做了处理按range(0, num_heads, num_heads // num_key_value_heads)步长采样头部即对每组 Query 头共享同一个 KV 头 scale 做对应否则直接遍历num_key_value_heads内部使用127.0 / scale得到输入 scale、1.0 / scale得到输出 scale符合 INT8 对称量化x_q x / scale、反量化x x_q * scale的约定。四、Scale 文件与 key 模板的约定以 Qwen2 为例Loader 依赖key_map_dict把逻辑名映射到 PTQ 产物 JSON 的真实 key。仓库为不同模型维护了对应映射文件例如 paddlenlp/experimental/transformers/qwen2/ptq_scales_map.jsonW8A8与 paddlenlp/experimental/transformers/qwen2/ptq_fp8_scales_map.jsonFP8{ act_scale: { qkv_in_scale: qwen2.layers.#.self_attn.q_proj.activation_quanter, out_linear_in_scale: qwen2.layers.#.self_attn.o_proj.activation_quanter, ffn1_in_scale: qwen2.layers.#.mlp.gate_proj.activation_quanter, ffn2_in_scale: qwen2.layers.#.mlp.down_proj.activation_quanter }, weight_scale: { q_weight_scale: qwen2.layers.#.self_attn.q_proj.weight_quanter, k_weight_scale: qwen2.layers.#.self_attn.k_proj.weight_quanter, v_weight_scale: qwen2.layers.#.self_attn.v_proj.weight_quanter, out_linear_weight_scale: qwen2.layers.#.self_attn.o_proj.weight_quanter, ffn1_1_weight_scale: qwen2.layers.#.mlp.gate_proj.weight_quanter, ffn1_2_weight_scale: qwen2.layers.#.mlp.up_proj.weight_quanter, ffn2_weight_scale: qwen2.layers.#.mlp.down_proj.weight_quanter }, cachekv_scale: { cache_k_scale: qwen2.layers.#.self_attn.cachek_matmul.activation_quanter, cache_v_scale: qwen2.layers.#.self_attn.cachev_matmul.activation_quanter } }可观察到#即层号占位符qwen2.layers.0.self_attn.q_proj.weight_quanter这样的 key 会被提取为第 0 层的 q 权重 scaleact_scale的 value 指向各线性层输入侧的activation_quanter激活量化器weight_scale指向weight_quanter权重量化器W8A8 映射中 FFN1 拆为ffn1_1/ffn1_2gate/up两个 key正好配合WeightScalesLoader(concat_ffn1True)做拼接FP8 映射则使用ffn1_0_weight_scale等命名配合PerTensorWeightScalesLoader使用。注意ptq_fp8_scales_map.json中weight_scale不含ffn1_1/ffn1_2而含ffn1_0两套映射命名差异体现了 W8A8 与 FP8 两条量化路径各自独立的 scale 布局。五、在仓库中的实际调用链量化 Scale 如何进入推理模型上述 Loader 在实验性加速模型中被系统化调用典型的入口是各模型modeling.py中的set_quant_scale()。以 paddlenlp/experimental/transformers/qwen2/modeling.py 为例其流程为读取映射文件根据self.quant_type选择ptq_fp8_scales_map.json或ptq_scales_map.jsonshift-smooth 变体为ptq_scales_map_shift_smooth.json并解析出 act/weight/cachekv 三组 key 映射定位 scale 文件通过resolve_file_path(self.quant_model_path, act_scales.json)等找到 PTQ 产物当tensor_parallel_degree 1且非single_card_ptq时改用带 rank 后缀的文件如act_scales_{tensor_parallel_rank}.json保证每个张量并行 rank 只加载自己分片对应的 scale构造 Loader 并注入模型FP8 路径ActScalesLoaderPerTensorWeightScalesLoader将结果转 float32 后赋给self.transformer_block.weight_scales/act_scalesW8A8 路径ActScalesLoaderWeightScalesLoader(concat_qkvTrue, concat_ffn1True)随后在注入时对qkv_、out_linear_、ffn1_weight_scale、ffn2各类 scale 除以127.0 * 127.0 * act_scale组合归一化并写入qkv_out_scales、linear_out_scales、ffn1_out_scales、ffn2_out_scales等逐层缓冲区同时处理张量并行切分与重排KV Cache 静态 INT8CacheScaleLoader读取cachekv_scales.json含 rank 后缀变体将cache_k_scale/cache_v_scale/cache_k_out_scale/cache_v_out_scale写入cache_k_scales等缓冲区一致性处理set_quant_scale整体包在paddle.no_grad()下且对缺失层scale 为-1跳过拷贝保持与离线量化时跳过的层一致。同样的调用模式也出现在 paddlenlp/experimental/transformers/llama/modeling.py约 L863-L991 区域FP8 用ActScalesLoaderPerTensorWeightScalesLoaderW8A8 用ActScalesLoaderWeightScalesLoader缓存用CacheScaleLoader以及 paddlenlp/experimental/transformers/mistral/modeling.py、paddlenlp/experimental/transformers/mixtral/modeling.py 中说明这套加载器是各加速模型共享的通用量化装载基础设施。六、FP8 底层辅助函数get_dequant_weight 与 block_quant_to_fp8除 Loader 外模块还提供两个 FP8 相关的底层函数。6.1 get_dequant_weight利用 FP8 GEMM 实现反量化def get_dequant_weight(w, w_sNone, dtypeNone, weight_block_size[128, 128]): if w_s is None: return w assert weight_block_size [128, 128] from paddlenlp_ops import per_token_group_quant try: from paddlenlp_ops import cutlass_fp8_fp8_half_block_gemm_fused as fp8_block_gemm_fused except: assert False, fp8_block_gemm_fused only supported on sm90 eye paddle.eye(w.shape[0], dtypepaddle.float32) x_q, x_s per_token_group_quant(eye, group_sizeweight_block_size[1], transpose_scaleTrue, quant_max_bound448.0, quant_min_bound-448.0) out fp8_block_gemm_fused(x_q, w.t(), x_s, w_s.t(), biasNone, transpose_xFalse, transpose_yTrue, output_dtypedtype, actidentity) return out其设计思路是以 GEMM 代反量化当w_s为 None 时直接返回原始权重否则构造单位矩阵并做 per-token-group 量化得到(x_q, x_s)再调用 CUTLASS FP8 block GEMM 算子cutlass_fp8_fp8_half_block_gemm_fused与量化权重相乘利用 FP8 Tensor Core 硬件加速完成解码从而复用 FP8 算子的高性能路径。同时代码通过assert显式声明该算子仅支持weight_block_size [128, 128]且cutlass_fp8_fp8_half_block_gemm_fused仅支持 SM90 架构Hopper在其他平台上会直接断言失败。该函数在 paddlenlp/experimental/transformers/deepseek_v2/modeling.py 中被调用约 L775 对 KV 投影、L1020-L1033 对 gate/up/ffn2 投影用于在推理前把 FP8 权重复原成目标 dtype。6.2 block_quant_to_fp8128×128 分块量化到 FP8def block_quant_to_fp8(x: paddle.Tensor, weight_block_size[128, 128], eps1e-6): assert weight_block_size [128, 128] assert x.ndim 2 m, n x.shape x_padded paddle.zeros((cell_div(m, 128) * 128, cell_div(n, 128) * 128), dtypex.dtype) x_padded[:m, :n] x x_view x_padded.view([-1, 128, x_padded.shape[1] // 128, 128]) x_amax x_view.cast(paddle.float32).abs().max(axis[1, 3], keepdimTrue).clip(mineps) x_scaled (x_view * (448.0 / x_amax)).to(paddle.float8_e4m3fn) x_q x_scaled.view_as(x_padded)[:m, :n].contiguous() x_s (x_amax / 448.0).view([x_view.shape[0], x_view.shape[2]]) return x_q.cast(paddle.float8_e4m3fn), x_s实现要点将输入按 128×128 分块不足部分先paddle.zeros补齐cell_div即向上取整除法(x y - 1) // y量化后再裁回原始尺寸每个 block 用其绝对最大值abs().max下限eps1e-6防除零作为 amax按 FP8 E4M3 的量化上界 448 缩放后转paddle.float8_e4m3fn返回量化张量x_q与对应 scalex_s形状为[块行数, 块列数]。这正是 FP8 block 量化推理算子所需的标准输入格式。七、总结与实践建议paddlenlp.experimental.model_utils是 PaddleNLP 加速推理与量化部署链路的公共基础设施其价值可归纳为FasterPretrainedModel打通了训练/微调动态图模型 → 静态图导出 → Inference 引擎推理的加速路径同时完整继承标准PretrainedModel的加载保存语义可直接替换使用四个 Scale Loader把离线 PTQ 产出的act_scales.json、weight_scales.json、cachekv_scales.json等标准化装载为模型可用的 NumPy scale 数组并内置 GQA 头映射、QKV/FFN1 拼接、per-tensor 兜底、张量并行 rank 分片等推理侧必需的处理逻辑FP8 辅助函数get_dequant_weight、block_quant_to_fp8与paddlenlp_ops中的 CUTLASS 算子配合把 FP8 量化/反量化融入 GEMM 计算实现 SM90 硬件上的高性能 FP8 推理。若要在自己的量化推理流程中复用这些工具建议先确认模型的ptq_scales_map.json或 FP8 变体key 模板与 PTQ 导出格式一致再按量化类型选择WeightScalesLoaderW8A8配合concat_qkv/concat_ffn1或PerTensorWeightScalesLoaderFP8最后参考set_quant_scale的注入方式把 scale 写入模型对应缓冲区。同时注意get_dequant_weight与block_quant_to_fp8目前仅支持[128, 128]分块及 SM90 架构跨平台/跨分块尺寸使用前需确认算子支持情况。【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表