feat(compression): update tooling to use DECODE operators - #3647
Draft
rkuester wants to merge 16 commits into
Draft
feat(compression): update tooling to use DECODE operators#3647rkuester wants to merge 16 commits into
rkuester wants to merge 16 commits into
Conversation
rkuester
force-pushed
the
feat-decode/queue
branch
2 times, most recently
from
August 12, 2026 22:26
266a467 to
34de4fa
Compare
rkuester
force-pushed
the
feat-decode/queue
branch
5 times, most recently
from
August 20, 2026 19:19
d3133d9 to
81d096f
Compare
rkuester
force-pushed
the
feat-decode/queue
branch
from
August 24, 2026 21:56
81d096f to
937294a
Compare
Comment the two environment variables set before the tests run. BUG=part of tensorflow#3256
A model built from scratch with the model editor declares schema version 0 and lacks the TFL3 file identifier. Loaders that verify these fields, such as the TfLite interpreter, reject such a model. Set version 3 on a model that declares no version, and finish every built flatbuffer with the file identifier. Leave the version of a model read from a file unchanged.
Reading or building a model sets the index of every tensor and subgraph, but an operator's index is set only when the operator is added imperatively to a subgraph. Operators of a model read from a flatbuffer report no index. Set the operator index in the read and build paths too, and document when the index is valid, matching the tensor and subgraph indexes.
Writing a compression spec by hand means searching the model for the
tensors worth compressing and working out a bitwidth for each. Add a
tool that walks a model and proposes a spec entry for every constant
tensor that LUT compression can encode, with the minimum
index_bitwidth able to enumerate the tensor's unique values. Comment
each entry with the tensor's identity and its size arithmetic, so a
reviewer can read the proposal and delete the entries for tensors
that should stay uncompressed:
# "arith.constant" INT32 [4, 2], input 1 of PAD (operators 170, 2446)
# 8 elements, 2 unique values, 32 -> 25 bytes (7 saved, 22%)
- subgraph: 0
tensor: 36
compression:
- lut:
index_bitwidth: 1
Propose by default only the tensors that compression would shrink,
counting the packed indices, the value tables, and the decode header.
Add a flag that widens the proposal to every encodable constant. In a
footer, list the constants left out and why. Annotate tensors that
share a buffer, where the converter deduplicated identical constants,
so a reviewer keeps or deletes all aliases together. Estimate the
whole model file's size change in a header line.
Give the operator wrapper a property returning its kind as text. Custom operators go by their custom code, builtins by the name of their enumerator, and an unrecognized code by its number. The spec proposal tool derived this from the schema enum itself. Naming an operator is a property of an operator, so it belongs with the wrapper that models one, where every caller can reach it instead of repeating the lookup.
Let a compression spec state whether a tensor gets one value table or one table per channel along a given axis, rather than leaving the choice to inference from the tensor's quantization. Honor an explicit choice in the LUT compressor and record it in a new axis field in the header byte that holds the bitwidth: values 0--14 name the channel axis of the output tensor's shape, and 15 means one table. Reject axes the kernels do not support. Current kernels mask off the axis field and infer the table layout from quantization, so a model whose explicit choice agrees with its quantization runs on them unchanged. A spec without an explicit choice compresses by inference as before and leaves the axis field zero. This path is transitional, kept so the change merges in small reviewable commits. The spec-file parser does not yet accept the choice; a later commit adds parsing, requires the choice, and removes the inference.
Extend the spec builder's fluent API so a lookup-table entry can
state the compression mode, per tensor or per channel along a
given axis:
SpecBuilder().add_tensor(subgraph=0, tensor=2) \
.with_lut(index_bitwidth=4, mode=PerChannel(axis=0)) \
.build()
Omitting the mode leaves the choice to inference from the
tensor's quantization, the transitional default, until the
spec-file parser accepts and requires the choice in an upcoming
commit.
Write the per-tensor or per-channel choice into each entry of a proposed compression spec, drawn from the tensor's quantization: per_channel with the quantized axis for a per-channel-quantized tensor, per_tensor otherwise. The proposal already computed the choice to size the value tables; writing it out shows it to the human reviewing the proposal. The spec-file parser ignores the keys today; a later commit makes them required, and proposals written from now on parse unchanged across that cut.
Require each lut entry of a compression spec file to give exactly one of per_tensor or per_channel. per_channel takes a mapping with a non-negative integer axis; per_tensor takes no value. Parsing fails, naming the two choices, for an entry giving neither or both, and for a malformed value under either key. The compressor still tolerates a spec object built without a mode; the next commit removes that tolerance along with the inference from quantization it falls back on.
Remove the transitional path that inferred a tensor's compression mode from its quantization when a spec object gave none. A spec object without a mode is now an error, matching the spec-file parser, which already requires the choice, so the header's axis field is always written explicitly. State the mode in every test fixture's spec; each states what its tensor's quantization implied, so the compressed bytes are unchanged except the axis field. Move the quantization-based axis inference into the proposal tool, its only remaining caller, where it drafts the proposed mode for human review.
Choose a proposed tensor's per-tensor or per-channel mode by testing both layouts against its values and proposing the one that encodes the tensor smallest. Stop reading the mode from the tensor's quantization. Quantization says nothing about how weights were binned, and an unquantized tensor has none to read. Weights binned per channel hold few distinct values within a channel and many across the whole tensor, so the old rule proposed one value table, found more distinct values than the index can enumerate, and dropped the tensor as unencodable. The weights that compress best were the ones left out. When no layout fits, name the closest one tried, so a rejection shows whether a channel axis would have helped. Skip a channel axis whose slices hold a single element. Every element would go in a value table and the indices would be pure overhead.
Give the per-channel integration model distinct values in each channel, so a decode that used the wrong table or stride no longer produces the right output by accident. Assert the packed value tables and the channel stride byte by content in the compressor's unit tests. Add an end-to-end test of a tensor quantized along its last axis, which nothing ran through the compressor and interpreter together before. Add an end-to-end test of a per-channel choice on an unquantized tensor, which the current kernel decodes wrongly because it derives the channel layout from quantization and ignores the header's axis field. Mark the test as an expected failure until the kernel reads the axis field.
Add the compression mode, now required, to each entry of the example spec file, and describe the two forms: per_channel with the axis that gives the channel count, and a bare per_tensor.
Leave a tensor whose consumer must read it during Prepare out of a proposed spec, naming the kernel and the input position that require it. Compression replaces a constant with the output of a DECODE operator, and that output holds no values until DECODE runs in Invoke, so the model fails to prepare. List the inputs every implementation requires, keyed by operator and input position, in one table. The compressor shares the table in the commit that follows, which refuses such a tensor named in a spec written by hand.
Refuse to compress a tensor whose consumer must read it during Prepare, naming the kernel and the input position that require it. Compression replaces a constant with the output of a DECODE operator, and that output holds no values until DECODE runs in Invoke, so the model fails to prepare. A proposed spec already leaves such a tensor out. A spec written or edited by hand can still name one, and until now the compressor took it.
Take a --min_savings floor in bytes and leave out any tensor whose compression saves less, naming the shortfall in the footer beside the other reasons a tensor is left out. Replace the boolean --require_savings, whose off position becomes --min_savings=0 and whose on position becomes any positive floor. The old rule listed a tensor whenever compression shrank it by a single byte. That undercounts the cost. Compressing a tensor also adds a DECODE operator, an ancillary tensor, and a decoded output tensor, and the byte estimate counts none of them. Measured on a production model, that structure runs 120 to 300 bytes per entry, varying with the length of the tensor names, so an entry saving less than that grows the model it was meant to shrink. Default the floor to 512 bytes, above the measured range, so a proposed entry pays for itself. Nothing is hidden by the default. A spec is a draft for review, and a tensor held back by the floor still appears in the footer with the bytes it would have saved, so a reader who disagrees can lower the floor and get it back.
rkuester
force-pushed
the
feat-decode/queue
branch
from
August 25, 2026 22:28
937294a to
df01570
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is a draft PR for running CI, review, and seeing the commits in
context. The commits along this branch will be individually submitted
for merge.
This replaces #3574, whose head branch was renamed to
feat-decode/queue for a stacked-commits workflow; renaming a fork
branch closes its cross-repo PR, so the draft is reopened here with
the same content under the new head.
See the linked issue for a description of the change.
BUG=implements #3256