ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

coremltools 权重 Palettization API 完整指南:Core ML 与 PyTorch 模型压缩实战

coremltools 权重 Palettization API 完整指南:Core ML 与 PyTorch 模型压缩实战
  • AI 应用
  • 机器学习
  • 模型优化

【免费下载链接】coremltools

Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.

项目地址:https://gitcode.com/gh_mirrors/co/coremltools
点击查看免费下载

本篇技术指南系统讲解 coremltools 中用于**模型权重 Palettization(调色板化/查表压缩)**的全部公开 API。你将掌握一条通用且一致的工作流:先定义压缩配置对象(Config),再调用压缩方法将模型权重量化为 n-bit 查表表示。文中覆盖 Core ML 模型(cto.coreml.palettize_weights)与 PyTorch 模型(PostTrainingPalettizer、SKMPalettizer、DKMPalettizer)两条压缩路线,并深入参数语义、定制粒度与底层实现,读完即可在你的模型上完成从压缩到 Core ML 转换的完整实操。

一、Palettization 与 API 总览

Palettization 是权重压缩技术:把连续浮点权重离散化为一组有限的“调色板”值(Lookup Table,LUT),权重只存储指向 LUT 的索引。一个n-bitLUT 最多有2^n个条目,相比原始 fp16/fp32 权重可显著降低存储占用。运行时由 LUT 与索引重建浮点权重后再参与浮点运算,因此不改变计算精度与中间张量精度,只改变存储形式(见 palettize_weights 源码)。

虽然核心mltools 提供了多条 Palettization API(因输入模型格式——Core ML 还是 PyTorch——以及优化工作流的不同而各异),但所有 API 都遵循相同的两步流程:

  1. 定义配置对象(Config):指定算法参数(如位数n_bits、粒度granularity等)。配置既可在全局层面定义,应用到模型中所有 op/module;也可按op 类型(op-type)或op 名称(op-name)做细粒度定制。配置对象既可在代码中以字典初始化,也可通过 YAML 文件从磁盘加载(from_dict/from_yaml)。
  2. 调用压缩方法:传入配置与模型,执行压缩。

二、Palettizing a Core ML 模型:cto.coreml路线

这条路线针对已经转换为 Core ML(mlprogram 类型)的模型做训练后压缩(Post-Training Palettization)。压缩对模型内所有受支持权重矩阵执行 K-Means 聚类,生成 LUT 并改写权重存储。

2.1 最简示例:全局 6-bit Palettization

以下示例对所有参数个数超过 512 的 op应用6-bitPalettization(通过weight_threshold=512控制):

import coremltools as ct import coremltools.optimize as cto # load model mlmodel = ct.models.MLModel(uncompressed_model_path) # define op config op_config = cto.coreml.OpPalettizerConfig(nbits=6, weight_threshold=512) # define optimization config by applying the op config globally to all ops config = cto.coreml.OptimizationConfig(global_config=op_config) # palettize weights compressed_mlmodel = cto.coreml.palettize_weights(mlmodel, config)

2.2OpPalettizerConfig核心参数

配置类 OpPalettizerConfig 是描述算法参数的载体,其字段与默认值在源码中有完整定义,关键参数如下:

参数说明默认值 / 取值
nbits控制聚类数量,即 LUT 条目数为2^nbits合法值{1, 2, 3, 4, 6, 8};kmeans/uniform模式必填,unique/custom模式禁止填写
weight_threshold元素总数小于该阈值的权重张量不被 palettize2048(源码_check_weight_threshold校验为非负整数)
mode决定 LUT 的构造方式kmeans(默认)、unique、uniform、custom
granularityPalettization 粒度per_tensor(默认)或per_grouped_channel
group_size一个分组内的通道数(仅per_grouped_channel时生效)32
channel_axis形成通道分组的轴(仅per_grouped_channel时生效)None,为None时按 op 类型自动选择
cluster_dim每个 LUT 质心的维度;1为标量 palettization,>1为向量 palettization(沿通道轴每cluster_dim长度的权重向量共享同一个 2-D 质心)1
enable_per_channel_scale为True时,palettize 前按输出通道使用逐通道 scale 对权重归一化False
num_kmeans_workers执行 k-means 的 worker 进程数,多 CPU 时建议大于 1 以并行聚类1

关于mode的底层细节(来自 OpPalettizerConfig 源码):

  • kmeans(默认):通过 k-means 聚类生成 LUT,需提供nbits。
  • uniform:基于线性直方图生成 LUT,取值形如[v_min, v_min + scale, ..., v_max],其中scale = (v_max - v_min) / (1 << nbits - 1)。源码注释示例:nbits=4、weight=[0.11, 0.19, 0.3, 0.08, 0.0, 0.02]时,数据范围[0.0, 0.3]被线性切成 4 份得到 LUT[0.0, 0.1, 0.2, 0.3],权重被舍入为索引[01b, 10b, 11b, 01b, 00b, 00b]。
  • unique:直接从权重中的唯一值构造 LUT(适用于离散网格但以浮点存储的权重)。此时不能传nbits,框架自动在{1, 2, 4, 6, 8}中选取能容纳唯一值数量的最小位数;若唯一值超过 256 个则跳过压缩。例如权重{0.1, 0.2, 0.3, 0.4}会被转换为{00b, 01b, 10b, 11b}并生成 LUT[0.1, 0.2, 0.3, 0.4]。
  • custom:使用自定义函数lut_function(weight)计算 LUT 与索引,此时须提供lut_function,且nbits由函数内部自定义。注意OpPalettizerConfig._from_dict不支持lut_function的字典/ YAML 序列化,需在代码中直接构造对象(源码 L774-L777 有明确约束)。

2.3OptimizationConfig:按 op 类型与 op 名称精细定制

除全局配置外,OptimizationConfig提供三级配置优先级:op name(最高)→op type→global(源码 OptimizationConfig 的_get_op_config/_get_const_op_config实现),层级越具体覆盖越广。

示例:对全部 op 应用6-bit,但所有linearop 用8-bit,并跳过名为conv1、conv3的两个卷积 op:

import coremltools as ct import coremltools.optimize as cto mlmodel = ct.models.MLModel(uncompressed_model_path) global_config = cto.coreml.OpPalettizerConfig(nbits=6) linear_config = cto.coreml.OpPalettizerConfig(nbits=8) config = cto.coreml.OptimizationConfig( global_config=global_config, op_type_configs={"linear": linear_config}, op_name_configs={"conv1": None, "conv3": None}, # None 表示跳过该 op ) compressed_mlmodel = cto.coreml.palettize_weights(mlmodel, config)

要点说明:

  • op_type_configs的键是 op 类型字符串(如"conv"、"linear"),值为对应的 config;值为None表示该类型 op 不压缩。
  • op_name_configs的键是具体常量或 op 实例的名称,值为None表示跳过该实例。若某个常量被多个 op 消费且各 op 配置冲突,框架会抛出compression config conflict detected错误(见_get_const_op_config)。
  • 也可用config.set_global(...)、config.set_op_type(...)、config.set_op_name(...)方法等价地设置各层级(注意:若使用旧的ct.compression_utils兼容路径,set_op_type/set_op_name会被禁用)。
  • 配置对象支持OptimizationConfig.from_dict(...)与OptimizationConfig.from_yaml(...)(源码 L1031-L1182),可从嵌套字典或 YAML 文件构造,YAML 中通过config_type: OpPalettizerConfig指定配置类。

2.4 获取 op 名称:get_weights_metadata()

要定制op_name_configs,必须先知道模型里各权重/op 的名字。使用cto.coreml.get_weights_metadata(mlmodel, weight_threshold=...)工具(见 mlmodel-utilities.md),它返回一个保序字典,键为权重名称,值为CoreMLWeightMetaData(包含val权重数据、sparsity稀疏率、unique_values唯一值数量、child_ops该权重馈入的 op 元信息)。weight_threshold用于过滤:元素总数不大于该阈值的权重不会出现在结果中(源码 get_weights_metadata)。

典型用法——找到最后一个权重并跳过其压缩(最后一层往往对精度更敏感):

import coremltools.optimize as cto from coremltools.models import MLModel from coremltools.optimize.coreml import get_weights_metadata mlmodel = MLModel("SegmentationModel_with_metadata.mlpackage") weight_metadata_dict = get_weights_metadata(mlmodel, weight_threshold=2048) # 遍历权重,按名称筛选要定制/跳过的 op op_name_configs = {name: None for name in weight_metadata_dict.keys() if 需要跳过} config = cto.coreml.OptimizationConfig(global_config=..., op_name_configs=op_name_configs)

2.5 底层原理:从const到constexpr_lut_to_dense

从 palettize_weights 源码 可以看清内部机制:函数遍历mlprogram中所有权重,按mode指定的算法离散化并构造 LUT,随后把存储权重的constop 替换为constexpr_lut_to_denseop。运行时由 LUT 与 n-bit 索引重建浮点权重再参与浮点计算。该函数本质上是对PASS_REGISTRY["compression::palettize_weights"]图变换(graph pass)的封装,通过PassOption("config", ...)把配置注入 pass;它还支持joint_compression=True做联合压缩(如“剪枝 + palettize”:lut(sparse) -> constexpr_lut_to_sparse -> weight(sparse) -> constexpr_sparse_to_dense -> weight(dense))。

三、Palettizing 一个 Torch 模型:ct.optimize.torch.palettization路线

与 Core ML 路线逻辑一致,但直接作用于 PyTorch 模型(在转换前压缩),提供三种算法:训练后 Palettization(PT)、Sensitive K-Means(SKM)与可微 K-Means(DKM)。公共 API 在 palettization/init.py 中统一导出。

3.1 训练后 Palettization:PostTrainingPalettizer

与 Core ML 路线相同的 k-means 思想,只不过对象是 Torch 模型。示例为所有 op应用4-bit,granularity为per_grouped_channel,group_size=4(每 4 个通道共享一个 LUT):

from model_utilities import get_torch_model from coremltools.optimize.torch.palettization import PostTrainingPalettizer, \ PostTrainingPalettizerConfig # load model torch_model = get_torch_model() palettization_config_dict = { "global_config": {"n_bits": 4, "granularity": "per_grouped_channel", "group_size": 4}, } palettization_config = PostTrainingPalettizerConfig.from_dict(palettization_config_dict) palettizer = PostTrainingPalettizer(torch_model, palettization_config) palettized_torch_model = palettizer.compress()

关键参数(定义于 ModulePostTrainingPalettizerConfig):

参数说明默认值
n_bits聚类数量控制位,LUT 条目数为2^n_bits4
lut_dtypeLUT 元素的存储 dtype;为None时不对 LUT 量化None;支持torch.int8、torch.uint8
granularityper_tensor(整个张量共享一个 LUT)或per_grouped_channelper_tensor
group_size一个分组内的通道数;仅per_grouped_channel时必填且需 > 0;per_tensor时须为None(源码有校验)None
channel_axis形成通道分组的轴;仅per_grouped_channel生效,当前支持输入或输出通道轴0
cluster_dim每个 LUT 质心的维度;>1表示 2-D 聚类,LUT 中每个条目长度为cluster_dimNone
enable_per_channel_scale为True时按输出通道逐通道 scale 归一化权重后再 palettize;与cluster_dim > 1不兼容(源码有断言校验)False
enable_fast_kmeans_mode开启时若数据在 fp16 范围内先舍入再聚类(fp32 会先转 fp16),加速 k-meansTrue
rounding_precisionenable_fast_kmeans_mode开启时的舍入小数位数,越小越快但近似更粗糙4

compress()方法支持num_kmeans_workers与inplace参数:当 worker 数 > 1 时走_ParallelKMeans.cluster_weights并行聚类(推荐多 CPU 场景开启),否则走_SequentialKMeans.cluster_weights(见 PostTrainingPalettizer.compress 源码)。压缩后还可调用report()获得压缩前后对比报告。

3.2 Sensitive K-Means:SKMPalettizer(需校准数据)

SKM 实现基于敏感度的加权 k-means(对应算法介绍见 opt-palettization-algos.md)。与普通 k-means 的差异在于:它利用 Fisher 信息矩阵(Hessian 的近似)计算每个权重元素对模型损失的敏感度,加权 k-means 会把聚类中心向更敏感的值靠拢,从而在同等位数下获得更小的精度损失。该算法需校准数据与损失函数来计算敏感度。

配置同样可用 YAML 文件描述,以下palettization_config.yaml实现:全部 op4-bit、所有Linear模块6-bit、跳过conv1/conv3:

global_config: n_bits: 4 module_type_configs: Linear: n_bits: 6 module_name_configs: conv1: null conv3: null calibration_nsamples: 64

Python 脚本加载该 YAML 并执行 SKM Palettization:

from model_utilities import get_torch_model from data_utils import get_calibration_data from coremltools.optimize.torch.palettization import (SKMPalettizer, SKMPalettizerConfig) import torch.nn.functional as F torch_model = get_torch_model() palettization_config = SKMPalettizerConfig.from_yaml('palettization_config.yaml') # create the loss function loss_fn = lambda mod, dat: F.nll_loss(mod(dat[0]), dat[1]) calibration_data = get_calibration_data() palettizer = SKMPalettizer(torch_model, palettization_config) palettized_torch_model = palettizer.compress(dataloader=calibration_data, loss_fn=loss_fn)

要点:

  • 配置参数与训练后 Palettization 相同(n_bits、lut_dtype、granularity、group_size、channel_axis、cluster_dim、enable_per_channel_scale,定义于 ModuleSKMPalettizerConfig),另加顶层calibration_nsamples(默认128,见 SKMPalettizerConfig)。
  • compress()相比 PT 路线多两个必传参数:
    • dataloader:可迭代对象,每个元素是模型的输入,用于计算权重的梯度(源码中通过loss.backward()累计梯度平方得到敏感度)。
    • loss_fn:可调用对象,接收模型与数据,执行前向并计算训练损失。
  • 从源码看,SKM 支持单进程与多进程两种敏感度计算(_compute_sensitivity_impl_single_worker/_compute_sensitivity_impl_multiple_workers),多进程模式用FullyShardedDataParallel分片模型,适合单卡放不下的大模型。

3.3 Differentiable K-Means:DKMPalettizer(训练时压缩)

DKM 是**训练时(training-time)**的 Palettization 算法,基于注意力机制的可微 k-means,直接嵌入训练流水线,通常需要更多数据。核心思路(见 palettizer.py 源码):用可微 k-means 对权重聚类,使 LUT 与索引可以通过 SGD 等梯度优化方法联合学习。

训练时 Palettization 的关键步骤:

  1. 定义DKMPalettizerConfig配置 Palettization 参数;
  2. 用DKMPalettizer初始化 palettizer 对象;
  3. 调用prepare(inplace=True)向模型插入FakePalettize模块(通过给模块挂qconfig实现,见_attach_qconfig源码);
  4. 运行常规训练循环,并额外调用palettizer.step();
  5. 模型收敛后调用finalize()将 LUT 与索引折叠回权重,得到可直接转换 Core ML 的模型。

以下示例对全部 op 应用2-bitDKM(配置字典中n_bits: 4仅为示意默认值),并指定milestone=10,即第 10 次调用palettizer.step()时才真正开始 Palettization:

from model_utilities import get_torch_model from training_utilities import train_step from data_utils import get_dataloader from coremltools.optimize.torch.palettization import (DKMPalettizer, DKMPalettizerConfig) import torch.nn as nn torch_model = get_torch_model() dataloader = get_dataloader() num_palettization_epochs = 2 palettization_config_dict = { "global_config": {"n_bits": 4, "milestone": 10}, } palettization_config = DKMPalettizerConfig.from_dict(palettization_config_dict) palettizer = DKMPalettizer(torch_model, palettization_config) # Call the prepare API to insert palettization friendly modules into the model palettizer.prepare(inplace=True) torch_model.train() for epoch in range(num_palettization_epochs): for batch_idx, (data, target) in enumerate(dataloader): train_step(data, target, torch_model) palettizer.step() palettized_torch_model = palettizer.finalize()

ModuleDKMPalettizerConfig关键参数(定义于 palettization_config.py):

参数说明默认值
n_bits聚类数量控制位,聚类数为2^n_bits;Linear 默认4,其余层默认2(见DEFAULT_PALETTIZATION_SCHEME)按模块类型
weight_threshold权重元素数不超过该值的模块不 palettize;对MultiheadAttention等含多个权重的模块,须所有权重都超过阈值才压缩2048
granularity/group_size/channel_axis同前述;per_grouped_channel时group_size必填,DKM 目前仅支持输出通道轴分组per_tensor/None/0
enable_per_channel_scale为True时沿通道维度使用逐通道 scaleFalse
milestone开始 Palettization 前需调用palettizer.step()的次数;若每步调用一次则为训练步序号,若每 epoch 调用一次则为 epoch 序号;0表示训练一开始即启用0
cluster_dim每个聚类(LUT 条目)的维度按模块类型
quantize_activations为True时同时对激活量化False
quant_min/quant_max权重聚类被量化时每个元素的最小/最大值-128/127
dtype激活量化使用的 dtype(仅quantize_activations=True时生效)torch.qint8(支持 qint8/quint8/fp32)
lut_dtypeLUT 元素的量化 dtype;f32表示不量化聚类'f32'(可选 i8/u8/f16/bf16/f32)

此外还有大量高级选项(DEFAULT_PALETTIZATION_ADVANCED_OPTIONS,源码 L40-L66):如kmeans_max_iter(可微 k-means 最大迭代,默认 3)、kmeans_init(auto/opt1d/cpu.kmeans++/kmeans++)、palett_mode(gsm/dkm/hard)、palett_tau(DKM softmax 温度,默认 0.0001)、enforce_zero(强制将最接近原点的质心固定为 0)、palett_unique/palett_shard/palett_batch_mode(面向大模型 LLM 的 eDKM 高效/分布式选项)、percentage_palett_enable、kmeans_n_init等。源码注释明确说明:大多数情况下无需修改这些高级参数,使用默认值即可;仅在将 DKM 与激活量化联用时才需要调整quant_min/quant_max/dtype/quantize_activations。另外旧参数partition_size、cluster_dtype已被标记弃用,请改用group_size、lut_dtype。

四、将 Palettized 的 PyTorch 模型转换为 Core ML

对使用ct.optimize.torch.*API 完成 Palettization 的 PyTorch 模型,coremltools 8 可以直接转换,无需额外参数。若使用了需要较新系统能力的新特性(如per_grouped_channel粒度,对应 iOS18/macOS15),则须通过minimum_deployment_target指定部署目标:

import torch import coremltools as ct palettized_torch_model.eval() traced_model = torch.jit.trace(palettized_torch_model, example_input) palettized_coreml_model = ct.convert(traced_model, inputs=...) # or if iOS18 features were used in palettization palettized_coreml_model = ct.convert(traced_model, inputs=.., minimum_deployment_target=ct.target.iOS18)

如果你是用其他非 coremltools 的 API 对 Torch 模型做了 Palettization,或使用的是 coremltools 8 之前的版本,则需要走另一条转换流程来保证 Core ML 模型内包含正确的 palettized op,详见 opt-conversion.md。

五、如何选择 Palettization 算法

三条 Torch 路线对应三种工作流(Core ML 路线则是已转换模型的后处理手段):

  • PostTrainingPalettizer:训练后直接压缩,无需重新训练,适合对精度要求不苛刻、追求快速压缩的场景;
  • SKMPalettizer:训练后压缩但需要少量校准数据与损失函数计算敏感度,能显著降低大模型压缩后的精度损失(参考 SqueezeLLM 思想),适合对精度敏感、可提供校准集的场景;
  • DKMPalettizer:训练时压缩,LUT 与索引随训练联合优化,效果通常最好但数据与算力要求更高,适合从零训练或继续微调(fine-tune)的场景。

无论选择哪条路线,通用的最佳实践是:先用weight_threshold跳过小权重张量、用get_weights_metadata()识别对精度敏感的关键层(如最后一层)并通过op_name_configs跳过,再配合mode(kmeans/uniform/unique/custom)与granularity(per_tensor或per_grouped_channel)在压缩率与精度之间做权衡。

  • AI 应用
  • 机器学习
  • 模型优化

【免费下载链接】coremltools

Core ML tools contain supporting tools for Core ML model conversion, editing, and validation.

项目地址:https://gitcode.com/gh_mirrors/co/coremltools
点击查看免费下载
上一篇:解决ESP32-C5开发痛点:噪声层测量异常深度排查与优化方案
下一篇:ESP-IDF中esp_reset_reason()函数在日志级别为NONE时的异常行为深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进