Skip to content

Extract scheme-selection policy into a pluggable CostModel#8698

Draft
connortsui20 wants to merge 6 commits into
developfrom
cost-model-r2-costmodel
Draft

Extract scheme-selection policy into a pluggable CostModel#8698
connortsui20 wants to merge 6 commits into
developfrom
cost-model-r2-costmodel

Conversation

@connortsui20

@connortsui20 connortsui20 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Tracking Issue: #8701

Rationale for this change

The compressor's selection objective — maximize estimated compression ratio — is hardcoded into the selection loop, so it cannot be varied per workload (#7697). This PR makes the objective a pluggable CostModel: schemes continue to produce model-independent estimate facts, the model converts those facts into Cost, and the compressor selects the lowest-cost candidate.

The default SizeCost preserves today's ratio ordering and canonical-acceptance threshold. It prices the exact ratio signal (Cost(-ratio), canonical baseline Cost(-1.0)) rather than recomputing bytes, so IEEE rounding cannot turn a strict ratio ordering into a cost tie.

What changes are included in this PR?

Details
  • Candidate: an opaque, borrowed view of the mechanical facts available for each selection option, including its estimated ratio and optional compressed sample. Sampling exposes the compressed sample array to the model only while that candidate is priced; the selector does not retain it or clone cascade history.
  • vortex_compressor::cost: a finite, totally ordered Cost, the CostModel trait, and the default SizeCost. Cost is the only candidate-ranking value; compression ratios remain estimator input and tracing metadata.
  • EstimateScore is removed. Deferred estimate callbacks now receive best_ratio: Option<f64> directly, and sampled estimates represent a zero-byte output as an absent ratio. The Delta and Sequence schemes are updated to the simplified callback contract.
  • The selection loop is wired to the model. Installing a custom model disables compression-ratio early-exit hints for deferred callbacks, since a custom ordering may prefer a lower-ratio candidate.
  • The initial model boundary and the decisions that remain outside it — constant handling, AlwaysUse, the final byte-acceptance gate, and extension-versus-storage selection — are documented explicitly.
  • The winner_compress trace span gains model-defined estimated_cost alongside estimated_ratio.
  • Selector-level tests prove that a custom model can change the winner, candidates must beat canonical_cost, sampled arrays are exposed only for sampled candidates, and custom models are not incorrectly pruned by ratio thresholds. The golden encoding-tree and size snapshots remain unchanged.

This PR does not add model-driven pruning, scheme priors, Delta-specific policy, builder/session plumbing, or a production time-based model.

What APIs are changed? Are there any user-facing changes?

This adds cost::{Cost, CostModel, SizeCost, Candidate} and CascadingCompressor::with_cost_model.

It deliberately changes the scheme-facing deferred callback API: EstimateFn receives Option<f64> (best_ratio) instead of Option<EstimateScore>, and EstimateScore is removed. Scheme::expected_compression_ratio itself retains the same signature. The default SizeCost preserves existing selection ordering, and the golden encoding-tree and array-size snapshots remain unchanged.

@codspeed-hq

codspeed-hq Bot commented Jul 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

⚡ 3 improved benchmarks
❌ 1 regressed benchmark
✅ 1626 untouched benchmarks
⏩ 42 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation compact_sliced[(4096, 90)] 779.7 ns 867.2 ns -10.09%
Simulation bitwise_not_vortex_buffer_mut[128] 273.6 ns 244.4 ns +11.93%
Simulation compact[(4096, 90)] 867.2 ns 779.7 ns +11.22%
Simulation compare_int_constant 298.4 µs 268.4 µs +11.15%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing cost-model-r2-costmodel (0228bc7) with develop (42bea5d)

Open in CodSpeed

Footnotes

  1. 42 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@connortsui20 connortsui20 force-pushed the cost-model-r2-costmodel branch from e39c549 to e17a967 Compare July 9, 2026 17:17
@connortsui20 connortsui20 force-pushed the cost-model-r0-golden branch 3 times, most recently from 00ccec3 to 1c1ad1b Compare July 9, 2026 17:27
@connortsui20 connortsui20 force-pushed the cost-model-r2-costmodel branch from e17a967 to 83988a0 Compare July 10, 2026 09:18
@connortsui20 connortsui20 force-pushed the cost-model-r0-golden branch from 1c1ad1b to 29b6106 Compare July 10, 2026 09:18
@connortsui20 connortsui20 changed the title Extract scheme-selection policy into a pluggable CostModel; SizeCost reproduces today bit-exactly Extract scheme-selection policy into a pluggable CostModel Jul 10, 2026
Base automatically changed from cost-model-r0-golden to develop July 10, 2026 12:42
claude and others added 4 commits July 10, 2026 13:44
Pure refactor of choose_best_scheme and the sampling estimator:

- estimate_compression_ratio_with_sampling now returns a SampledEstimate
  carrying the compressed sample array alongside its score, instead of
  measuring nbytes and dropping the array.
- Selection bookkeeping switches from (scheme, EstimateScore) tuples to a
  crate-private Candidate carrying the mechanical facts about each option
  (scheme, score, input bytes, value count, sampled array, cascade
  ancestry). Candidates are still compared by ratio, ties still break by
  registration order, and the sampled array is dropped at the end of
  selection, so behavior is unchanged: same winners, same estimates, same
  output (golden suite has zero snapshot churn).

The not-yet-read Candidate fields are the facts a pluggable cost model
prices in the follow-up rungs (#7697) with no new Scheme API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
New vortex_compressor::cost module extracting selection policy into a
pluggable model, per the compressor cost-model plan (#7697):

- Cost: opaque finite f64 newtype, lower is better; units are defined
  per model since costs are only compared within one model.
- CostModel: price a Candidate (None = reject) and price the canonical
  baseline every candidate must strictly beat. The module docs state the
  axioms that stay outside the model: AlwaysUse short-circuiting and the
  byte-acceptance gate (including its AnyScalarFn carve-out).
- SizeCost: prices a candidate at Cost(-ratio) over the exact ratio
  signal and canonical at Cost(-1.0), reproducing ratio-argmax selection
  bit-exactly. Pricing off the ratio rather than recomputed bytes
  (input/ratio) avoids IEEE division collapsing strict ratio
  inequalities into ties and flipping winners via registration order.

Candidate becomes public (it is the trait's argument), re-exported via
the cost module. Unit tests pin the validity gate, pairwise ordering and
tie behavior against the historical rules. Nothing consumes the module
yet; the selection loop is wired to it in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
choose_best_scheme now prices every scored candidate through the
compressor's CostModel: the winner is the argmin-cost candidate, and
eligibility is cost strictly below CostModel::canonical_cost. Strict <
comparisons preserve registration-order tie-breaking exactly as before.
The legacy comparators whose logic moved into SizeCost and the selection
loop (is_better_score, EstimateScore::{is_valid, beats}) are deleted.

CascadingCompressor gains with_cost_model(Arc<dyn CostModel>), holding
SizeCost by default, so default behavior is unchanged (bit-exact under
SizeCost by decision 1 of the cost-model plan; golden suite shows zero
snapshot churn). AlwaysUse short-circuiting, the deferred-callback ratio
threshold, and the byte-acceptance gate are untouched.

The winner_compress trace span gains estimated_cost alongside
estimated_ratio, keeping estimator observability at parity for
non-ratio models. WinnerEstimate::Score becomes a struct variant
carrying the winning cost; the choose_best_scheme unit tests' match
patterns are updated mechanically for the new variant shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
@connortsui20 connortsui20 force-pushed the cost-model-r2-costmodel branch from 7672d77 to cf51915 Compare July 10, 2026 12:46
connortsui20 and others added 2 commits July 10, 2026 14:14
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
The module docs' SizeCost link stopped resolving when SizeCost moved
into the public cost::size submodule without a top-level re-export, and
CostModel's module-docs link pointed at the now-private cost_model
module. Point them at size::SizeCost and crate::cost respectively;
rustdoc with -D warnings passes again.

Signed-off-by: Connor Tsui <connor@spiraldb.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MVQ2n8mT9FPCf8cmqRNcw1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants