来源:https://openzl.org/blog/2026-09-24-compression-transformer/
压缩变换器
Yann Collet
OpenZL 团队
元数据
2026年9月24日
新闻
10 分钟阅读
压缩变换器
让神经网络构建压缩图
OpenZL 通过以任意顺序链接处理层来压缩数据。这很强大,但需要仔细配置:为给定输入选择合适的编解码器组合一直是其最大的挑战。压缩变换器现在可以自动做出这一选择,在压缩过程中逐个决策地动态构建压缩图。它无需针对数据源进行训练,无需手动调优,解压端也无需任何改动。它可从 API 和 CLI 中使用。
图压缩的强大之处------以及复杂性
OpenZL 使用一组可以自由组合成图的编解码器来无损压缩数据。一个图可以将一个输入流推过多个阶段,将其拆分为子流,并为每个子流选择不同的编解码器。这种灵活性很强大:理论上,搜索所有可能的图空间可以为每个输入找到最佳压缩策略。但在实践中,这种搜索在数据压缩过程中执行的成本太高,因此生产系统只能满足于更简单、广泛有效的策略。
可以手动调优图,但这很耗时且难以扩展。这就是 OpenZL 附带训练器 zli train 的原因,它分析数据源的代表性样本,并为该流量生成优化的图。ACE 会生成多个图,涵盖不同的压缩率和速度权衡,让用户选择最符合其约束的那个。
这种方法依赖于两个假设:样本能代表未来的流量,并且流量相当同质。这些假设通常成立。当它们不成立时,训练器不得不折中------一个在样本中不同形态上都能勉强工作的图,但对任何一种形态都不是最优的。
自然的下一步是实时地让图适应每个单独的输入。为每个输入选择图可以处理异构流量、响应异常值,并吸收数据中未宣告的变化,所有这些都无需人工干预或针对数据源的训练。这就是压缩变换器为 OpenZL 带来的能力。
压缩变换器如何工作
压缩变换器从数据流中生成压缩图,一次做一个决策。这里有一个有用的类比:大型语言模型生成文本,因此得名:在每一步,模型根据其当前上下文发出一个标记。在 OpenZL 中,该标记决定一个编解码器及其参数集,即图中的下一个节点。然而,产生的输出并非线性序列。多阶段编解码器会产生子流,模型为每个子流生成决策,递归地构建一个图。
对于每个新流,一个专门评分器------一个小型多层感知机(MLP),每个上下文一个(目前,每个数值元素宽度一个)------评估每个候选编解码器。选择器选出得分最高的有效编解码器并应用它。如果该编解码器产生子流,它们会被反馈给选择器。这样,压缩图就一个节点一个节点地自我组装。
决策循环
一些设计选择使其切实可行且稳健:
- 跳过阶段:平凡输入,例如空流或常量流,直接处理,不调用神经网络。
- 评分守卫:确定性规则在评分器对候选排序后拒绝不可能或无意义的操作。它们还会丢弃请求的格式版本中不可用的编解码器,因此生成的图对目标解压器有效。
- 深度守卫与静态回退:递归有界,低置信度的决策回退到确定性决策树。
- 标准集成:变换器作为常规选择器插入 OpenZL。它不需要特殊的执行框架。
推理流水线
模型仅在压缩时运行。产生的结果是普通的 OpenZL 帧,由通常的通用解压器读回。
在其第一个实现中,变换器专注于数值类型,并附带四个评分器,每个数值元素宽度(1、2、4 和 8 字节)一个。
结果概览
我们针对 868 个数值流族(总计 34,737 个文件、17.9 GB,每个族代表不同的数据形态)评估了数值变换器。在下面呈现的结果中,变换器与 OpenZL(未训练和已训练)以及两个广泛使用的通用压缩器的最强设置(zstd -19 和 xz -9)进行比较。这些是压缩率参考,不是速度参考:变换器的压缩速度远快于 zstd -19,速度与 zstd -8 到 -12 相当,具体取决于数据宽度。
数字是压缩率的加权几何平均值。"OpenZL 未训练"是 OpenZL 的默认数值路径在默认压缩级别(6)下的表现,使用 Field LZ 压缩;"OpenZL 已训练"使用由 ACE 为每个族单独训练的图。
| 数值宽度 | zstd -19 | xz -9 | OpenZL 未训练 | OpenZL 已训练 | 变换器 | 对比 zstd -19 |
|---|---|---|---|---|---|---|
| num8 | 12.26x | 10.19x | 9.867x | 12.23x | 12.42x | +1.3% |
| num16 | 6.772x | 6.467x | 6.515x | 8.415x | 8.429x | +24.5% |
| num32 | 3.056x | 3.483x | 3.516x | 4.390x | 4.255x | +39.2% |
| num64 | 4.578x | 5.586x | 6.114x | 8.723x | 8.477x | +85.2% |
| 全部 | 5.752x | 5.920x | 6.036x | 7.846x | 7.760x | +34.9% |
变换器总体与 OpenZL 已训练版本相差在 1.1% 以内,且无需任何按族训练。
有关每种数据类型的详细信息,请参阅以下表格:
- num8 --- 151 个族中的最佳、中位数和最差
- num16 --- 226 个族中的最佳、中位数和最差
- num32 --- 249 个族中的最佳、中位数和最差
- num64 --- 242 个族中的最佳、中位数和最差
在撰写本文时,变换器在广泛的数值数据形态上表现良好,但其增益并不均匀,而且仍会犯错:在 868 个族中有 95 个落后于 zstd -19,最差情况下落后达 2 倍。这仍处于早期阶段,我们预计随着模型改进,这些差距会缩小。
试用
变换器随 OpenZL v0.3.0 发布,目前为可选启用。从 CLI 中,你可以通过请求压缩级别 7 或更高来查看变换器的运行情况。它适用于直接数值流,或数值子流,例如从解析操作(如 SDDL)中提取的子流。
bash
./zli compress --profile le-i32 --level 7 examples/getting_started/sample_inputs/era5_ints.bin --output era5_ints.zl
在此样本上,压缩率从默认级别的 18.95x 提升到级别 7 的 29.41x。
从 C API 中,选择 ZL_GRAPH_TRANSFORMER_NUMERIC 作为起始图,或使用压缩级别 7 或以上的 ZL_GRAPH_NUMERIC:
c
#include "openzl/codecs/zl_transformer.h"
ZL_Report r = ZL_Compressor_selectStartingGraphID(
compressor, ZL_GRAPH_TRANSFORMER_NUMERIC);
从 C++ 中,同一图可作为 openzl::graphs::TransformerNumeric 使用:
cpp
#include "openzl/cpp/codecs/Transformer.hpp"
compressor.selectStartingGraph(openzl::graphs::TransformerNumeric::graph);
它接受一个或多个宽度为 1、2、4 或 8 字节的数值流。
如果你想查看模型决定了什么,请在上面的命令中添加 --trace era5_ints.cbor,并在图可视化器中打开该文件------变换器的选择会显示为普通的图节点,因为它们本来就是。
下一步
两个方向已经明确。
首先,我们希望将变换器扩展到数值流之外。其他流类型,如字符串,将在未来的扩展中涵盖。
其次,我们希望将速度作为明确目标,以便选择器能够在规定的性能预算内找到最佳压缩率,而不仅仅是瞄准最强选项。
这些是未来文章的主题。更大的要点已经在这里:压缩图不再需要提前设计或训练。它可以由模型在读取数据的过程中为每个输入动态构建,因此当数据变化时,图也随之变化。而且由于结果是普通的 OpenZL 帧,该模型可以随时间不断变得更智能,而解压器无需更改或重新部署。