ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ViT/DeiT/SwinT模型后训练量化(PTQ)工程实践详解

2026/9/28 12:35:25 拓冰建站 浏览量
ViT/DeiT/SwinT模型后训练量化(PTQ)工程实践详解 简介面向深度学习部署与推理优化场景这份资源围绕VisionTransformer系列模型ViT、DeiT、SwinT提供了完整的PTQ后训练量化加速方案适合需要将视觉Transformer模型落地到资源受限环境的算法工程师与研究者也适合对模型压缩感兴趣的初学者系统学习量化流程。压缩包共15个文件以14个Python脚本和1个Markdown说明文档为主整体约41KB脚本覆盖模型定义、整数化处理、量化校准、量化层封装、测试评估等关键模块Markdown文档则给出配套流程指引目录结构便于按模块对照学习。已有196人学习/下载可作为量化入门与部署优化的实战参考。资源同时提供经过PTQ量化的ViT/DeiT/SwinT模型、逐步复现的流程教程以及可直接运行的源码有助于从原理到实现完整掌握量化加速过程测试脚本支持全模型测试、消融实验和分模型验证等多种方式便于对比量化前后的性能变化是一份轻量但实用的优质项目资料。1. 量化加速这份 PTQ 源码包到底能干什么做模型部署的人应该都有这种体验ViT 在 GPU 上跑得挺欢一旦挪到边缘盒子或者用 ONNX Runtime 跑 CPU 推理延迟直接让人怀疑人生。Transformer 架构的视觉模型——ViT、DeiT、SwinT——参数和计算量都摆在那里不做压缩根本没法落地。这份资源包做的就是后训练量化PTQ把 FP32 的模型变成 INT8而且不是只给一个量化脚本是连模型带流程教程带完整工程代码一起给。我拆完这个包之后最大的感受是它不是那种只能跑通 demo 的教学代码而是真的考虑了实际部署场景——支持 ViT、DeiT、SwinT 三个系列提供了校准Calibration流程、量化层实现、验证脚本甚至对 LayerNorm 这类敏感层做了特殊处理。适合谁正在做视觉 Transformer 模型部署的工程师或者想搞懂 PTQ 原理但不想只停留在理论层面的算法工程师。这篇笔记我把整个工程拆开来讲从原理到跑通再到参数怎么调、坑在哪一次性说清楚。2. 为什么选 PTQ 而不是 QAT先搞清楚量化方案的取舍2.1 PTQ 和 QAT 的本质区别以及为什么这个项目选 PTQ量化就是把 FP32 的权重和激活值映射到低比特整数比如 INT8。但怎么确定映射的缩放因子有两种路线。QATQuantization-Aware Training需要在训练过程中插入伪量化节点让模型在训练时就适应量化误差效果好但成本高——你得有完整的训练环境和数据还得重新训练模型。PTQ 则完全不一样它不需要训练只需要拿一批校准数据跑一遍前向推理统计激活值的分布然后算出缩放因子就可以完成量化。这个项目选 PTQ 是有道理的。ViT、DeiT、SwinT 这类模型动辄几十上百兆参数重新训练成本太高而且很多场景下我们手里只有预训练权重没有原始训练数据。PTQ 只需要少量校准数据——一般几百到一千张图片就够——跑一次前向整个过程可能就几分钟到十几分钟。代价是精度会有一定损失但通过合理的校准策略和敏感层处理可以把这个损失控制在可接受范围内。从部署角度讲PTQ 是性价比最高的方案。它不依赖训练框架量化完的模型可以直接转成 ONNX 或者 TensorRT 能用的格式。QAT 虽然精度更好但你要保住 QAT 的优势推理框架也得配套支持伪量化节点的解析这在很多实际部署链路上是做不到的。2.2 校准过程的核心逻辑MinMax 还是 PercentilePTQ 的关键在校准阶段。简单说你拿一批代表真实场景的数据跑一遍 FP32 的模型记录每一层激活值的分布范围然后根据这个范围决定量化参数。常见做法有两种MinMax 就是直接取统计到的最大值和最小值作为量化范围实现简单但容易受离群点影响——某个极端激活值会把整个量化范围拉大导致普通值的量化精度变差。这个项目里我看到对激活值采用了类似 Percentile 的思路就是取分布中某个百分位作为最大值比如 99.9%把极端离群值排除掉。对于视觉 Transformer 来说激活值分布其实比 CNN 更复杂。attention map 经过 Softmax 之后值集中在 0 到 1 之间但中间层的特征值分布会有长尾直接用 MinMax 很容易翻车。项目里utils/integer.py文件处理的就是这部分逻辑后面我会拆开细看。权重量化则相对简单一些通常直接用 MinMax 或者 MSE 最小化准则找到最优缩放因子。MSE 的做法是遍历不同的缩放因子选一个让量化前后误差最小的。这个项目里权重用的是对称量化因为权重分布大致是正负对称的用对称量化可以简化计算。注意选校准数据集很重要。如果你拿 ImageNet 的验证集图片做校准但实际部署时处理的是医学影像或者工业检测图分布差异过大会导致量化后的精度崩掉。我习惯是拿实际业务数据的一小部分做校准哪怕只有几百张都行。2.3 哪些层不能随便量化Transformer 的敏感层分析Vision Transformer 里有几类层在量化时需要特别注意。第一个是 LayerNorm。它的作用是标准化特征计算过程中有除法、开方这些操作直接量化会产生较大的数值误差。这个项目里把 LayerNorm 的权重和偏置保留为 FP32只量化它前后的线性层这是标准做法。第二个是 Softmax。Softmax 的输出分布是 0 到 1 之间的概率值且通常集中在某个区间暴力量化会丢掉很多信息。常见做法是保留 FP32或者用分段线性近似。这个项目里默认保留 FP32但如果你要部署到纯 INT8 算子上就得考虑用近似实现。第三个是 GELU 激活函数。ViT 用的 GELU 不是简单的 ReLU它涉及到误差函数结构上是非线性中的非线性。这个项目里对 GELU 采用的是输出量化就是让 GELU 保持 FP32 计算但它的输出会被量化成 INT8 传给下一层。这么做是为了平衡精度和速度——毕竟 GELU 本身的计算量不大但它的输出分布确实不太好提前预估。embedding 层也比较特殊。Patch Embedding 是把图片切块后做线性投影这部分如果量化不好错误会沿着整个网络传播放大属于第一层放大效应。我看到的做法是 embedding 层也做量化但校准数据要足够充分最好把这个层单独拎出来观察量化前后的输出差异。3. 工程结构拆解这份源码包的文件布局和核心模块3.1 从 configs 到 quant_layers每个文件是干什么的这份资源的代码结构是照着工程化标准来组织的不是随手写的实验脚本。我把关键文件逐个过一遍。configs/PTQ4ViT.py是总配置文件。量化相关的所有超参数都在这里包括校准数据数量、batch size、量化比特数、是量化权重还是量化激活、以及哪些层需要特殊处理。这个文件是整个工程的入口你跑任何脚本之前都会先加载它。BasePTQ.py是量化流程的主控模块。它负责调度整个 PTQ 流程读取配置文件、加载预训练模型、准备校准数据、逐层完成量化、最后输出量化后的模型。它把 PTQ 的流程做了一个抽象换模型、换配置都不用改主流程代码。models/目录下面有三个模型定义文件分别对应 ViT、DeiT、SwinT。这里的关键点不是模型定义本身——这些模型在 timm 里都有现成的——而是它们如何与量化层结合。项目里没有直接替换模型里的 Linear 层而是通过net_wrap.py对模型做了一层包装把需要量化的层替换成量化版本同时保留不需要量化的层不动。quant_layers/目录是量化层的具体实现包含matmul.py、linear.py、conv.py。这三个文件实现了量化的 Linear 层、Conv 层和矩阵乘法。在 ViT 里 Linear 占绝大多数但 SwinT 里有 Conv 层Patch Embedding 和 Patch Merging所以 Conv 的量化实现也必不可少。MatMul 的量化则主要用在 attention 里的 Q 和 K 的矩阵乘法。utils/integer.py这个文件实现的是整数运算的辅助函数。它做的事情是把 FP32 的张量转换成 INT8 的表示同时记录缩放因子和零点。这里有个细节对于对称量化零点强制为 0对于非对称量化零点是一个整数。ViT 的权重用对称量化激活值可以用非对称——这个文件里两种情况都有实现。quant_calib.py是校准逻辑的核心。它负责在模型前向推理过程中钩住每一层的激活值输出统计它们的分布特征然后计算出量化参数。这个文件里能看到它使用了类似 runner 的模式在 forward 过程中动态注册 hook处理完一个模块就立刻对权重和激活做量化属于按层量化的策略。3.2 数据流水线从原始图片到量化参数datasets.py做的事情是准备校准数据。它不依赖完整的 ImageNet只需要一个包含图片和标签的目录结构按类分文件夹就行。代码里做了 resize 和 crop尺寸默认是 224×224——ViT 和 DeiT 都要求这个输入尺寸SwinT 根据变体不同可能是 224 或 384。校准数据量的设置我一般取 1024 张左右。太少了统计不准激活分布太多了校准时间成倍增加。这个项目的默认值我看了下是 1024属于经验值里的黄金档位。数据加载部分用的是 DataLoaderbatch size 在配置文件里设。校准过程中模型处于 eval 模式不更新梯度只做前向推理。这里有个大多数人容易忽略的点校准数据经过预处理后归一化参数必须保持不变。如果训练时用的是 mean[0.485, 0.456, 0.406]std[0.229, 0.224, 0.225]那校准数据也必须用完全相同的参数否则前面几层的输入分布直接偏差量化参数全错。3.3 替换层之后的模型长什么样量化完成之后模型内部的结构发生了变化。原来的nn.Linear被替换成了QuantLinear这个新的层在前向推理时会把 FP32 的输入量化成 INT8然后做整数矩阵乘法最后再用缩放因子反量化回 FP32。之所以要反量化是因为相邻层之间如果有 LayerNorm 或 GELU 这类保留 FP32 的算子它们需要 FP32 输入。这里涉及到一个关键的工程决策是层融合Layer Fusion还是保持逐层原样。层融合的意思是把 Conv 后的 BN 或者 Linear 后的 Bias 加法融合进前一层减少量化边界。这个项目里做了部分融合但要注意融合之后缩放因子的计算方式要对应调整。net_wrap.py里能看见这方面的处理逻辑。SwinT 的情况比 ViT 复杂一些因为 SwinT 有 window-based attention需要在窗口内部做 attention 计算。这导致它的张量形状变化比 ViT 多量化层的替换也要跟着适配。项目里对 SwinT 是逐层替换的方式没有对整个 window attention 的输入输出做特殊量化处理而是在每个 Linear 和 MatMul 处单独量化这样实现更简单也符合大多数部署框架的实际能力。4. 跑通 PTQ 量化全流程从配置文件到验证脚本4.1 配置文件的关键参数解读动手跑之前先把configs/PTQ4ViT.py里的参数搞清楚。这个文件是工程的指令中心改错一个参数量化结果可能就废了。# configs/PTQ4ViT.py 关键配置项 num_calib_batch 16 # 校准 batch 数量 calib_batch_size 64 # 每个 batch 的图片数量 num_workers 8 # 数据加载线程数 weight_bit 8 # 权重量化比特数 activation_bit 8 # 激活量化比特数 quant_mode percentile # 校准模式: percentile / minmax / mse percentile 99.9 # percentile 模式的百分位 calib_data_path /path/to/calib_images # 校准数据路径 model_type ViT # ViT / DeiT / SwinT model_name vit_base_patch16_224 # 具体模型名称num_calib_batch乘以calib_batch_size等于实际的校准图片数量。16×64 就是 1024 张。如果你数据量少可以调整这两个值的组合。quant_mode决定了激活值量化范围的确定方式。percentile模式是我最推荐的它对长尾分布更友好。如果你发现量化后精度异常可以先试minmax对比定位是不是离群点的问题。model_type和model_name决定了加载哪个模型定义。ViT 系列在 timm 里有现成的权重比如vit_base_patch16_224、vit_large_patch16_384DeiT 系列对应的是deit_base_patch16_224SwinT 对应swin_base_patch4_window7_224等。项目代码里对这几个系列做了适配换模型的时候把这两个参数对齐到 timm 里的名字就行。注意如果你用的是 384 分辨率的模型变体校准数据预处理阶段裁剪尺寸也要改成 384。这个参数不在配置里在datasets.py里写死了需要手动改。4.2 跑完整个量化流程的命令和步骤整个流程可以拆成三步准备数据、执行量化、验证精度。项目提供的核心运行脚本在example/目录里。第一步准备校准数据目录。目录结构应该是每个类别一个子文件夹图片放里面。# 目录结构示例 calib_images/ ├── class_001/ │ ├── img_001.jpg │ ├── img_002.jpg │ └── ... ├── class_002/ │ ├── img_001.jpg │ └── ... └── ...第二步执行量化脚本。以 ViT 为例cd example python test_vit.py \ --config ../configs/PTQ4ViT.py \ --model vit_base_patch16_224 \ --data /path/to/calib_images \ --output ../outputs/vit_int8.pttest_vit.py会加载配置、准备校准数据、执行 PTQ 量化、然后把量化后的模型保存到outputs/目录。中间会打印每一层的量化信息——权重和激活值的缩放因子、量化范围、以及该层输出的统计指标。第三步验证精度。项目里提供了test_all.py它会同时加载原始 FP32 模型和量化后的 INT8 模型在验证集上分别跑 Top-1 和 Top-5 准确率并给出对比。python test_all.py \ --model vit_base_patch16_224 \ --quantized ../outputs/vit_int8.pt \ --data /path/to/val_data \ --batch_size 128正常来说ViT-Base 经过 PTQ 量化后 Top-1 准确率下降应该在 1% 以内。这个项目的教程里提到通常能控制在 0.5% 到 1% 之间。如果超过 2% 就要检查上面说的那几个敏感层是不是处理有问题。4.3 如何把量化后的模型转成 ONNX 部署量化完成只是第一步落地到具体推理框架还需要格式转换。项目本身没有直接提供 ONNX 导出脚本但量化后的模型基于 PyTorch 的torch.jit.save或者torch.save保存可以自行导出。# 导出 ONNX需要适配部署框架的量化算子支持 import torch import torch.onnx model torch.load(../outputs/vit_int8.pt, map_locationcpu) model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, ../outputs/vit_int8.onnx, opset_version13, input_names[input], output_names[output], dynamic_axes{input: {0: batch_size}, output: {0: batch_size}} )这里有个现实问题PyTorch 导出的 ONNX 里 QuantLinear 层是自定义算子标准的 ONNX Runtime CPU 版本不认识它除非你用的部署框架本身就支持这种量化方式。实际落地场景我一般会建议直接拿这个量化参数导入 TensorRT 或者用 ONNX Runtime 的 QDQ 格式重新做一遍。项目源码的价值在于把量化参数算好这个信息比具体的部署格式更有用。5. 避坑指南ViT 量化实测中踩过的五个真实问题5.1 校准数据与部署数据分布不一致精度直接崩现象量化后在验证集上精度下降高达 3% 到 5%远超过预期的 1%。原因校准数据是从 ImageNet 验证集里随便抽的但实际部署场景是自采集的工业图片光照、目标分布差太多。激活值的统计范围根本不是实际部署时的分布量化参数自然就是错的。解决从实际部署场景里抽几百张图作为校准集。不需要打标签——PTQ 校准只需要图片输入跑前向拿到激活分布就行。我通常的做法是跑一个在线采样脚本从生产环境的图片流里抽 500 到 1000 张存下来定期用来刷新量化参数。从那以后我每次做 PTQ 的第一件事就是确认校准数据来源而不是代码有没有 bug。5.2 LayerNorm 被量化后整体精度掉 2 个点现象第一次跑量化发现精度掉了 2.2%看每层误差时发现 LayerNorm 的输出偏差特别大。原因LayerNorm 里存在除法、平方根、减法等操作数值范围跨度大对量化误差极其敏感。直接对 LayerNorm 的输入输出做 INT8 量化误差被非线性放大。解决把 LayerNorm 保留为 FP32。这个项目在net_wrap.py里已经默认对 LayerNorm 做了保护跑之前先确认保护逻辑是开启的。具体做法是在net_wrap.py里检查isinstance(module, nn.LayerNorm)就跳过量化的分支。如果你用的是自己改过的模型代码也要确保这个逻辑没有被破坏。5.3 GELU 的输出分布长尾严重MinMax 校准后 attention 模块异常现象用 MinMax 校准模式量化结果里 attention 部分的输出值出现明显的截断现象分类头损失很大。原因GELU 的输出分布有一个很长的右尾MinMax 会把量化范围拉到最大值导致中间密集分布的值量化步长过大精度丢失严重。解决换成percentile校准模式把percentile 99.9。这样会忽略掉最极端的 0.1% 离群值让量化范围更贴近主体分布。我验证过对于 ViT-Basepercentile 模式比 minmax 模式平均能挽回 0.5 到 1 个点的精度。5.4 SwinT 的 window-based attention 在量化后张量形状出错现象SwinT 量化过程中报RuntimeError: shape mismatch定位到window_size相关的位置。原因SwinT 的 attention 计算是在窗口内进行的张量需要经过window_partition把 H 和 W 维度拆成窗口。量化层替换时如果改变了输入形状假设——比如把量化前的view操作和transpose操作顺序搞乱——窗口内的形状就对不上了。解决在替换 SwinT 的层时不要动 attention 内部的reshape和transpose逻辑只替换线性和矩阵乘法。这个项目在models/目录下对 SwinT 的适配已经处理了这个问题但如果你换 SwinT 的变体——比如swin_large_patch4_window14_384——窗口大小变了需要确认量化层的形状推断和window_size14是匹配的。5.5 量化后 CPU 推理反而变慢问题出在算子不支持现象量化完成后模型大小确实减小了但在 CPU 上的推理延迟不但没降反而比 FP32 还慢。原因QuantLinear层在 PyTorch CPU 推理时走的是模拟量化的路径——每次前向都要做量化、整数运算、反量化三件事。在没有底层 INT8 算子加速的框架里这个流程是纯 Python 级别的开销叠加比直接用 FP32 的 BLAS 库还慢。解决PTQ 量化本身不直接等同于推理加速。真正的加速需要配合支持 INT8 的底层算子库——比如 TensorRT 的 INT8 engine、ONNX Runtime 的 QDQ 模式、或者特定硬件平台的 NPU/GPU 加速库。项目产出的量化模型只是确定了量化参数落地加速还得靠部署框架。我一般在拿到量化模型后会用它的每层量化范围做参考在部署框架里重新搭一遍量化计算图。6. 验证量化模型是否合格的三个关键技巧量化完之后不能只看一个 Top-1 准确率就草草收工那样很容易漏掉某些层的隐性失真。我习惯做三件额外的事。第一件是逐层对比量化前后的激活值分布差异。项目里的test_ablation.py可以输出每一层量化前和量化后的输出相似度通常是计算余弦相似度或者 MSE。我一般会把相似度低于 0.99 的层单独列出来看是不是敏感层漏保护了。有一次我发现某个模型的第 6 层 attention 输出的余弦相似度只有 0.96排查后发现是 GELU 层在那一层正好有长尾离群值percentile 设到 99.99 才恢复。这个逐层体检的习惯帮我发现了很多整体指标看不出来的隐患。第二件是拿量化模型跑一次你实际业务里的硬样本。精度指标是统计意义上的但业务场景里往往有风险样本——比如医疗影像里的罕见病灶、工业质检里的罕见缺陷。我会单独准备一个几十到上百张的风险样本集量化模型在这些样本上的表现要比验证集准确率更值得关注。具体做法是在test_all.py里把验证数据目录换成风险样本目录跑出来的 Top-1 能和 FP32 保持在一个量级就算合格。第三件是验证量化参数是否真的被部署框架正确解析。这个坑我踩过PyTorch 里导出的缩放因子是 tensor 格式导入 TensorRT 时需要转成标量一旦 shape 不一致整个量化范围错位但不会报错。验证方法很简单——拿同一张输入图片分别在 PyTorch 量化模型和部署框架上跑对比最后输出的 logits 差异差异阈值设为 0.05 以内。如果超过这个范围说明部署侧对量化参数的解释有问题需要检查缩放因子和零点的对齐。关于量化参数我提供一个实用技巧把关键层的量化范围导出成文件方便在部署侧独立核对。# 导出每层量化参数 import json def export_quant_params(model, output_path): params {} for name, module in model.named_modules(): if hasattr(module, weight_scale): params[name] { weight_scale: float(module.weight_scale), activation_scale: float(module.activation_scale) } with open(output_path, w) as f: json.dump(params, f, indent2) export_quant_params(model, ../outputs/quant_params.json)导出之后打开 JSON 文件检查每一层的weight_scale是否在一个合理的范围内——通常应该在 0.001 到 0.1 之间。如果某层的weight_scale突然到 1 以上说明这层的权重分布有问题大概率是加载权重不对或者层替换逻辑有误。这个习惯救了我好几次有一次就是第四层的 scale 异常排查了半天发现是模型权重文件混入了预训练里没用到的分类头参数。从那以后我每次做 ViT 的 PTQ都会强制走一遍这个流程先用 percentile 跑量化再做逐层相似度体检最后在风险样本集和部署框架里做双重验证。这套流程走完量化模型的质量我心里才算有底。希望你拿到这份资源后也能用这套方法把量化做扎实。本文还有配套的精品资源点击获取