Repository navigation
[memory] layout: exceptions take 6 bytes, with 16-bit feature-relative slots and a 32-bit fallback - #67
Merged
Conversation
This was referenced Nov 6, 2026
victor-ab
force-pushed
the
victor-ab/exception-slots
branch
from
November 6, 2026 14:26
9183b84 to
ec3b280
Compare
victor-ab
force-pushed
the
victor-ab/exception-slots
branch
from
November 6, 2026 23:40
ec3b280 to
70ef0dd
Compare
ukaratay
approved these changes
Nov 7, 2026
ukaratay
left a comment
Collaborator
There was a problem hiding this comment.
Approving. I checked the width boundary (65,536 slots stay 16-bit, 65,537 widen) and that the cap counts a widening before the tables grow. The +1.3% at 1 candidate shows up in every round, so it's a real cost. That's fine for 14% of the loaded heap, but it adds to the x86 question on #74.
victor-ab
force-pushed
the
victor-ab/exception-slots
branch
from
November 7, 2026 18:29
70ef0dd to
99ab9b1
Compare
Collaborator
Author
|
On AVX-512 this PR is faster at every size: −5.5%, −5.7% and −6.4% at 1, 32 and 512 candidates, and −1.2% to −3.8% on groups of 128 to 1,024 rows (GCP c4, six paired rounds). Details are on #74. |
The rank tables kept each exception's absolute slot beside its word. Keep parallel arrays instead, with slots relative to their feature, so that restores read only the slots and the usual entries take six bytes rather than eight. This reduces the loaded memory of a large model with many categorical splits without changing the chains, packed fields or outputs. Slots start as u16. The first feature with more than 65,536 categorical slots widens the preceding slots to u32 once, and later features keep that width. Offsets and words do not change. A model-wide width avoids a format per feature and lets the fill choose its width once before both loops, rather than branch per exception. Wide tables still take eight bytes per entry, so the narrower format adds no load limit. Count the selected width before growing the tables, including the cost of widening earlier entries. Update the cap's documentation and byte checks. Tests cover the width boundary, widening after a narrow feature, repeated fills across the boundary, and wide single-row and grouped predictions, with constant and varying features, against the packed model and oracle.
victor-ab
force-pushed
the
victor-ab/exception-slots
branch
from
November 7, 2026 20:49
99ab9b1 to
da5d340
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.
Motivation
After the stack's first PR, the rank tables' exceptions are the largest part of a categorical model's loaded heap: 8 bytes each,
[slot, word]with an absolute 32-bit slot. A feature's slots are its own contiguous range, almost always far fewer than 65,536, and the fill's restore loop reads only the slots.Design
layout: separate exception words from feature-relative slotsBinMapkeeps the exceptions' words and their slots in separate arrays, the slots relative to their feature's first slot.fill_withdispatches on the width once per feature, then runs a monomorphic loop; the restore loop reads 2 bytes per exception rather than 8.rank_table_bytescounts 6 bytes per exception (8 in the 32-bit fallback), and the cap is checked on the same count, including a widening, before the tables grow; soLoadOptions::max_rank_table_bytescounts what is held, as the cap table indocs/treelite-loading.mdsays. A model near the cap that failed before can now load; none that loaded fails.Evidence
On the private model, the loaded model's heap is 14% lower than with #66 (41% of 3.2.2's), and outputs are bitwise equal to 3.2.2's on synthetic rows and on the generated requests.
Latency,
predict_fixedper request on the generated requests, one thread, Apple M4 Pro: p50 change from #66, median of four paired rounds (A B B A, 1 s per cell), with the rounds' range:On AVX-512 (GCP c4-standard-8, Intel Xeon Platinum 8581C, SMT off, all-core turbo, one pinned core; six paired rounds), for the served requests and for groups of 128 to 1,024 rows (
max_group_width1024): like any group of 65 to 1,024 rows, these takeHandlecarriers and reachlist_avx512:The M4's 1-candidate cost doesn't carry over: on AVX-512 the narrower slots are faster at every size.
Where the rounds disagree in sign, only their range is given.
tests/import.rs,tests/limits.rsandtests/research.rsare recomputed for 6-byte entries.cargo test --release --features researchpasses; clippy (workspace, research, library alone) and fmt.