DeepSpeed Mixture-of-Quantization(MoQ)量化训练完全指南:从 QAT 渐进式降精度到 GLUE 任务实战
【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed
Mixture-of-Quantization(MoQ)是 DeepSpeed 在 QAT(Quantization-Aware Training,量化感知训练)之上提出的一套模型压缩方案:它不再让模型全程使用单一精度做量化微调,而是在训练过程中按照预定义调度将数据精度从高位宽逐步降低到目标位宽,并借助模型参数的二阶信息为不同网络层分别动态调整量化节奏。读完本文,你将掌握 MoQ 的全部配置项语义与默认值、如何用一份 DeepSpeed JSON 配置 + 一段启动脚本对 GLUE 任务(如 MRPC)开展 8-bit 量化微调、如何用 eigenvalue 做逐层敏感性分析,以及这些机制在当前仓库源码中的落点与注意事项。
本文以官方教程 MoQ-tutorial.md 为核心骨架,并结合deepspeed/compression、deepspeed/runtime下的实现代码交叉印证。
MoQ 是什么:用“渐进式降精度”替代“一步到位”的量化
传统 QAT 在整个训练过程中把模型权重固定在目标位宽(例如直接跳到 8-bit),被量化层从第一轮起就在低精度下“挣扎”。MoQ 的核心不同在于:
- 以 QAT 为底座:量化在训练中发生(而非训练后 PTQ),梯度仍然流过伪量化算子,让模型适应量化误差;
- 精度随训练步进递减:训练从高精度(FP16 或 16-bit 量化)起步,经过一段稳定期后按预设周期逐步降低位宽,直到触达
target_bits(如 8-bit); - 逐层差异化调度:利用模型参数的二阶信息(eigenvalue,特征值)评估各层的敏感度,为网络的不同层单独调节量化调度,让更“敏感”的层在低精度前停留更久、获得更多适应迭代。
实验表明,加入这种调度并在训练过程中混合多种数据精度后,量化模型的质量与最终精度保持能力优于一步到位的基本 QAT(见下文“微调结果”表)。MoQ 方法的深入原理可参见官方发布的 MoQ 深度解读博客。
使用 MoQ 的前提条件
要在自己的训练脚本里启用 MoQ 量化训练,需要满足两条硬性要求:
- 将 DeepSpeed 集成进训练脚本:参见 Getting Started 快速上手(在训练脚本中通过
--deepspeed与--deepspeed_config参数或deepspeed.initialize()初始化引擎)。 - 在 DeepSpeed 配置 JSON 中补充模型量化相关参数:也就是下文逐一说明的 MoQ 参数。
额外说明:MoQ 需要与 FP16 混合精度训练配合使用。在 deepspeed/runtime/engine.py 中可以看到明确约束:
MoQ (quantize in optimization step) weight quantization is only supported for FP16——量化发生在优化器步骤中的 MoQ 路径目前仅支持 FP16 训练模式,因此教程中的配置文件都开启了"fp16"。
MoQ 参数全解:从开关到算法逐项释义
MoQ 的量化调度由一组配置参数定义,用户可以通过不同组合探索多样的量化策略。教程中记录的参数清单与默认值整理如下。
MoQ 核心参数
| 配置项 | 含义 | 默认值 |
|---|---|---|
enabled | 是否启用量化训练 | False |
quantize_verbose | 是否打印量化的详细信息 | False |
quantizer_kernel | 是否启用专门的量化算子内核 | False |
quantize_type | 量化类型,"symmetric"或"asymmetric" | "symmetric" |
quantize_groups | 量化分组数,表示量化一个模型张量时使用的 scale 个数 | 1 |
quantize_bits:位宽转换区间
控制数据精度从起始位宽向最终目标位宽的过渡过程(例如从 16-bit 一路降到 8-bit):
start_bits:量化训练中的起始位宽,默认16;target_bits:量化训练的目标位宽,默认16。
quantize_schedule:每个精度档位的训练步数调度
决定在每个精度级别上如何安排训练步数:
quantize_period:精度(位宽数)降低一次的周期。默认每100个训练步降一次精度,且每降低 1 bit,周期会翻倍(即第二次降精度发生在 200 步后、第三次在 400 步后,依此类推);schedule_offset:量化开始生效的时间点。在该 offset 之前,模型保持普通训练精度(FP32/FP16)。默认100步。
quantize_algo:量化算法
q_type:目前支持对称量化与非对称量化,分别产生有符号与无符号整数;默认symmetric;rounding:量化值取整策略,可选择就近取整nearest或随机取整stochastic;默认nearest。
补充说明:教程早期版本将上述参数统一书写在顶层
"quantize_training"段下。从当前仓库的 deepspeed/compression/constants.py 看,同一套语义在新的weight_quantization框架中以enabled / quantizer_kernel / schedule_offset / quantize_groups / quantize_verbose / quantization_type / rounding(共享参数)加different_groups(分组模块作用域)的形式组织,位宽切换所需的start_bits / target_bits / quantization_period被放入每个分组(group)的params中,且schedule_offset的默认值为0。若你的安装版本以compression_training为解析入口(见 deepspeed/runtime/engine.py 的quantize_training()),建议参考 docs/_pages/config-json.md 中compression_training一节的最新字段布局。
Eigenvalue(特征值)参数
用于基于二阶信息的动态逐层量化调度,全部收在"eigenvalue"子段中:
| 配置项 | 含义 | 默认值 |
|---|---|---|
enabled | 是否启用带 eigenvalue 调度的量化训练 | False |
verbose | 是否打印 eigenvalue 计算的详细信息 | False |
max_iter | 计算特征值的最大迭代次数(幂迭代上限) | 100 |
tol | 计算特征值的收敛容忍误差 | 1e-2 |
stability | 方差稳定因子,用于归一化时防止除零 | 1e-6 |
gas_boundary_resolution | 每经过 N 个 gradient accumulation step(gas)边界计算一次特征值 | 1 |
layer_name | 指向需要计算特征值的所有层的模型作用域名 | "bert.encoder.layer" |
layer_num | 需要计算特征值的层数 | 无默认(须显式指定) |
这些键名与默认值与 deepspeed/runtime/constants.py 中的定义一一对应;解析逻辑位于 deepspeed/runtime/config.py 的get_eigenvalue_config()。
实战:对 GLUE 任务做 MoQ 量化微调
教程以 GLUE 任务微调作为 MoQ 的完整示范。动手前你需要:
- 安装 DeepSpeed;
- checkout Hugging Face
transformers分支并安装其全部依赖(GLUE 示例使用其text-classification/run_glue.py)。
第一步:准备 DeepSpeed 量化配置文件
以test.json为例,以下是对量化训练至关重要的参数布局:
{ "optimizer": { "type": "AdamW", "params": { "lr": 2e-5, "weight_decay": 0.0, "bias_correction": true } }, "gradient_clipping": 1.0, "fp16": { "initial_scale_power": 16, "enabled": true }, "quantize_training": { "enabled": true, "quantize_verbose": true, "quantizer_kernel": true, "quantize-algo": { "q_type": "symmetric" }, "quantize_bits": { "start_bits": 16, "target_bits": 8 }, "quantize_schedule": { "quantize_period": 400, "schedule_offset": 0 }, "quantize_groups": 8, } }要点解读:
fp16必须开启:如前面所述,量化在优化步骤中进行的 MoQ 仅支持 FP16 引擎;quantize_bits的 16→8 表示从 16-bit 出发逐步向 8-bit 收敛;quantize_period: 400配合教程“每降 1 bit 周期翻倍”的规则,总训练步数需要覆盖全部降级阶段,否则无法在训练结束前达到target_bits;quantize_groups: 8表示对每个被量化张量使用 8 个 scale 的分组对称量化。
第二步:编写启动脚本(以 MRPC 为例)
将脚本放到 Hugging Face 示例目录(如transformers/examples下),通过--deepspeed test.json让 DeepSpeed 读取上述 JSON。以 MRPC 任务为例:
TSK=mrpc TEST_JSON=test.json python text-classification/run_glue.py \ --model_name_or_path bert-base-cased \ --task_name $TSK \ --do_train \ --do_eval \ --max_seq_length 128 \ --per_device_train_batch_size 32 \ --learning_rate 2e-5 \ --num_train_epochs 3 \ --output_dir /tmp/$TSK/ \ --fp16 \ --warmup_steps 2 \ --deepspeed test.json运行该脚本即会得到带 MoQ 量化的 MRPC 精度(accuracy)与 F1 指标。可调项均可替换:将TSK换成cola/sst2/rte/qnli/qqp/mnli/wnli/stsb即可在其余 GLUE 任务上复现。
基于二阶信息的动态调度:Eigenvalue 机制
Eigenvalue 在训练中扮演“层敏感度代理”的角色:开启后,DeepSpeed 会在每个gas_boundary_resolution边界为每个指定层计算特征值,并根据层敏感度将quantize_period提升最多 5 倍(该倍数来自启发式选择),让敏感层在下一轮降精度之前有足够的迭代步数来适应。
其计算原理在 deepspeed/runtime/eigenvalue.py 的Eigenvalue.compute_eigenvalue()中可见:对每个 block 使用幂迭代(power iteration)逼近海森-向量积对应的最大特征值——先随机初始化向量v并归一化,然后循环执行Hv = torch.autograd.grad(grads, params, grad_outputs=v, ...)、用内积inner_product(Hv, v)更新特征值估计,直到满足max_iter上限或相邻两次估计的相对误差低于tol。post_process()会把各层特征值映射到[0, 1.0]区间用于调度比较。
使用 eigenvalue 的注意事项(沿用教程要点)
- 训练会明显变慢:每个指定层都要迭代计算特征值,开销显著;
- FP16 下的 NaN/Inf 处理:受限于 FP16 表示范围,部分层的特征值可能变成 NaN/Inf。对这些层,DeepSpeed 会返回所有非 NaN/Inf 层特征值中的最大值;若所有层都是 NaN,则统一返回
1.0。这一回退逻辑与 deepspeed/runtime/eigenvalue.py 的post_process()行为一致; quantize_period可能被显著放大:特征值最多把周期提升 5 倍,叠加“每降 1 bit 周期翻倍”,若初始quantize_period已很大,最终周期可能超出训练总步数。因此使用 eigenvalue 时应从相对较小的quantize_period起步,保证训练能在结束前走完所有精度过渡阶段;- 不保证精度更好:开启 eigenvalue 通常需要与
start_bits、quantize_period、quantize_groups等设置联合调优。
带有 eigenvalue 的完整配置示例(12→8-bit,BERT 12 层逐层调度):
{ ...... "quantize_training": { "enabled": true, "quantize_verbose": true, "quantizer_kernel": true, "quantize_type": "symmetric", "quantize_bits": { "start_bits": 12, "target_bits": 8 }, "quantize_schedule": { "quantize_period": 10, "schedule_offset": 0 }, "quantize_groups": 8, "fp16_mixed_quantize": { "enabled": false, "quantize_change_ratio": 0.001 }, "eigenvalue": { "enabled": true, "verbose": true, "max_iter": 50, "tol": 1e-2, "stability": 0, "gas_boundary_resolution": 1, "layer_name": "bert.encoder.layer", "layer_num": 12 } } }当前仓库实现现状(务必以实际版本为准):教程所描述的 eigenvalue 动态调度在早期版本为可用特性,但当前 deepspeed/runtime/config.py 的
get_eigenvalue_config()中带有一条assert not get_eigenvalue_enabled(...), "Eigenvalue based MoQ is temporarily disabled"断言——即在当前代码路径下开启 eigenvalue 会直接触发“暂时停用”的断言失败。同时,本仓库的量化主入口已迁移至compression_training/weight_quantization框架,教程中的顶层quantize_training段属于历史配置路径。因此:在较新版本中运行 eigenvalue 示例前,请先核对所安装 DeepSpeed 的版本与配置解析行为;若不支持,应使用纯quantize_schedule的固定周期调度,或按 deepspeed/compression/constants.py 中的新框架字段重组配置。
GLUE 微调结果与调度策略对照
教程在下表中给出每个 GLUE 任务达到报告精度所用的调度参数;所有实验统一采用 8 组的分组对称量化(symmetric grouped quantization with 8 groups)。
| 任务 | STSB | MRPC | COLA | WNLI | SST2 | RTE | QNLI | QQP | MNLI |
|---|---|---|---|---|---|---|---|---|---|
| start-bits | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 12 | 14 |
| period | 10 | 10 | 8 | 8 | 400 | 8 | 64 | 18 | 12 |
| 启用 Eigenvalue | False | True | True | True | False | True | False | True | True |
可以看到不同任务对起始位宽(12 或 14)、周期长短(8~400)以及是否启用 eigenvalue 的选择差异很大——这正体现了 MoQ 参数空间的自由度:SST2/QNLI 使用大周期且关闭/开启 eigenvalue 的不同组合,而绝大多数任务采用了较小周期并开启 eigenvalue。
各方案在 GLUE + SQuAD 上的精度保持结果:
| 任务 | STSB | MRPC | COLA | WNLI | SST2 | RTE | QNLI | QQP | MNLI | SQuAD | ACC+ |
|---|---|---|---|---|---|---|---|---|---|---|---|
| w/o QAT(FP16) | 88.71 | 88.12 | 56.78 | 56.34 | 91.74 | 65.3 | 90.96 | 90.67 | 84.04 | 90.56 | 0 |
| Basic QAT | 88.9 | 88.35 | 52.78 | 55.3 | 91.5 | 64.2 | 90.92 | 90.59 | 84.01 | 90.39 | -0.87 |
| MoQ | 88.93 | 89 | 59.33 | 56.34 | 92.09 | 67.15 | 90.63 | 90.94 | 84.55 | 90.71 | 0.75 |
解读要点:
- 以最后一列
ACC+(相对未量化 FP16 基线的平均精度增量)衡量,Basic QAT 平均下降约 0.87 个百分点,而 MoQ 在绝大多数任务上不仅没有掉点,平均还高出约 0.75 个百分点; - MoQ 在 COLA、SST2、RTE、QQP、MNLI 上均超越了 FP16 基线,说明渐进式降精度能有效弥补量化误差带来的精度损失。
工程细节:量化在训练管线中的实际执行路径
把教程放到仓库源码中“对号入座”,可以帮助你理解 MoQ 的工程实现:
- 调度器:配置解析后,deepspeed/compression/scheduler.py 中的
compression_scheduler负责在引擎每次step()时推进training_steps并轮询各压缩方法,在训练步数越过schedule_offset后把目标模块的weight_quantization_enabled置为True; - 量化层载体:deepspeed/compression/basic_layer.py 提供可替换的量化基础模块(如
Linear_Compress、Embedding_Compress等),它们接收start_bits / target_bits / quantization_period,并依据目标位宽选择对称/非对称、三元、二值等不同量化器; - 引擎侧开关:deepspeed/runtime/engine.py 的
quantize_training()将配置汇总为quantize_enabled / quantize_groups / quantization_type / rounding / verbose / quantizer_kernel等运行时标志;当启用且未采用quantize_weight_in_forward时,引擎会从 deepspeed/runtime/quantize.py 构造Quantizer,在FP16 优化器步骤内完成权重位宽随训练步的切换——这正是 MoQ “量化发生在训练过程”的底层实现。
调参 Tips 与总结
围绕 MoQ 的使用,教程给出了两条关键建议,这里结合参数语义做进一步展开:
- 调度必须匹配总迭代数:设置
quantize_period与schedule_offset前,先估算任务的总样本数与训练迭代数,确保模型在训练结束前确实降到了目标精度。由于周期会随每次降 1 bit 而翻倍,可粗略估算“所有精度阶段累计所需步数 ≈ offset + period × (2^降级次数 − 1)”级别的量级,再反推period的取值上限。若启用了 eigenvalue,还需考虑其最多 5 倍的周期放大效应。 - Eigenvalue 是“自动寻优器”而非“精度保险丝”:它对网络不同部分动态调整量化周期,带来两个正面效果:其一,相比所有层共用同一
quantize_period,逐层差异化调度有望获得更高精度;其二,它基于敏感度自动为每层找到一个合适的量化节奏,省去人工为每层手调周期的工作。但它并不保证结果更好,实际使用时应与其他设置(start_bits、quantize_period、quantize_groups)一起纳入搜索空间,并结合上表任务级的参数先例快速定位可行区间。
综上,MoQ 提供了一条“从 FP16 出发、逐步量化到目标位宽、可选 eigenvalue 逐层加速”的完整训练路径。在 DeepSpeed 中落地时,建议以 MoQ-tutorial.md 中的 GLUE 配置为模板,先复现 MRPC 基线,再按任务特性调整start_bits、quantize_period、quantize_groups与 eigenvalue 开关;同时注意你安装的 DeepSpeed 版本对quantize_training(历史)与compression_training(现行框架)两组配置字段的兼容情况。更进一步,可将该量化能力与其他压缩技术(如 模型压缩教程 中讲解的剪枝、层裁剪等)组合,构建完整的端到端模型瘦身方案。
【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考