Add a pluggable CostModel for the compressor#8753
Conversation
314bf6e to
48c8634
Compare
CostModel for the compressor
48c8634 to
1376bf5
Compare
Polar Signals Profiling ResultsLatest Run
Powered by Polar Signals Cloud |
Benchmarks: PolarSignals ProfilingVortex (geomean): 1.122x ❌ How to read Verdict and Engines
datafusion / vortex-file-compressed (1.122x ❌, 0↑ 7↓)
No file size changes detected. |
Benchmarks: FineWeb NVMeVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.032x ➖, 0↑ 2↓)
datafusion / vortex-compact (0.985x ➖, 0↑ 0↓)
datafusion / parquet (0.966x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed (1.018x ➖, 0↑ 1↓)
duckdb / vortex-compact (1.001x ➖, 0↑ 0↓)
duckdb / parquet (0.988x ➖, 0↑ 0↓)
File Size Changes (1 files changed, -0.1% overall, 0↑ 1↓)
Totals:
|
Benchmarks: TPC-H SF=1 on NVMEVerdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.040x ➖, 0↑ 0↓)
datafusion / vortex-compact (1.025x ➖, 0↑ 0↓)
datafusion / parquet (1.053x ➖, 0↑ 4↓)
datafusion / arrow (1.060x ➖, 0↑ 5↓)
duckdb / vortex-file-compressed (1.039x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.038x ➖, 0↑ 2↓)
duckdb / parquet (1.054x ➖, 0↑ 3↓)
duckdb / duckdb (1.034x ➖, 0↑ 1↓)
File Size Changes (9 files changed, +0.1% overall, 7↑ 2↓)
Totals:
|
Benchmarks: TPC-DS SF=1 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.985x ➖, 0↑ 1↓)
datafusion / vortex-compact (0.972x ➖, 3↑ 0↓)
datafusion / parquet (0.993x ➖, 3↑ 1↓)
duckdb / vortex-file-compressed (1.001x ➖, 0↑ 1↓)
duckdb / vortex-compact (1.000x ➖, 0↑ 1↓)
duckdb / parquet (0.997x ➖, 0↑ 1↓)
duckdb / duckdb (0.982x ➖, 1↑ 0↓)
File Size Changes (7 files changed, +0.0% overall, 4↑ 3↓)
Totals:
|
Benchmarks: FineWeb S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.191x ➖, 0↑ 3↓)
datafusion / vortex-compact (0.986x ➖, 0↑ 0↓)
datafusion / parquet (0.951x ➖, 1↑ 0↓)
duckdb / vortex-file-compressed (1.038x ➖, 0↑ 1↓)
duckdb / vortex-compact (1.024x ➖, 0↑ 0↓)
duckdb / parquet (1.000x ➖, 0↑ 0↓)
|
Benchmarks: Statistical and Population GeneticsVerdict: No clear signal (low confidence) How to read Verdict and Engines
duckdb / vortex-file-compressed (0.998x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.997x ➖, 0↑ 0↓)
duckdb / parquet (0.997x ➖, 0↑ 0↓)
File Size Changes (1 files changed, +0.0% overall, 1↑ 0↓)
Totals:
|
Benchmarks: Clickbench Sorted on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (0.991x ➖, 1↑ 1↓)
datafusion / parquet (1.001x ➖, 0↑ 0↓)
duckdb / vortex-file-compressed (1.175x ❌, 0↑ 7↓)
duckdb / parquet (1.024x ➖, 0↑ 0↓)
duckdb / duckdb (1.059x ➖, 0↑ 2↓)
File Size Changes (201 files changed, -0.0% overall, 95↑ 106↓)
Totals:
|
Benchmarks: TPC-H SF=10 on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.055x ➖, 0↑ 6↓)
datafusion / vortex-compact (0.988x ➖, 0↑ 1↓)
datafusion / parquet (1.013x ➖, 0↑ 2↓)
datafusion / arrow (1.095x ➖, 0↑ 10↓)
duckdb / vortex-file-compressed (1.109x ❌, 0↑ 10↓)
duckdb / vortex-compact (1.066x ➖, 0↑ 6↓)
duckdb / parquet (1.038x ➖, 0↑ 5↓)
duckdb / duckdb (1.047x ➖, 0↑ 4↓)
File Size Changes (27 files changed, +0.1% overall, 19↑ 8↓)
Totals:
|
Benchmarks: Clickbench on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.046x ➖, 1↑ 10↓)
datafusion / parquet (1.022x ➖, 2↑ 5↓)
duckdb / vortex-file-compressed (1.093x ➖, 0↑ 19↓)
duckdb / parquet (1.031x ➖, 0↑ 8↓)
duckdb / duckdb (1.015x ➖, 0↑ 3↓)
File Size Changes (101 files changed, +0.0% overall, 50↑ 51↓)
Totals:
|
Benchmarks: TPC-H SF=1 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.103x ➖, 0↑ 2↓)
datafusion / vortex-compact (1.204x ➖, 0↑ 7↓)
datafusion / parquet (1.125x ➖, 0↑ 4↓)
duckdb / vortex-file-compressed (1.053x ➖, 0↑ 0↓)
duckdb / vortex-compact (1.053x ➖, 0↑ 0↓)
duckdb / parquet (1.007x ➖, 1↑ 0↓)
|
Benchmarks: Random AccessVortex (geomean): 1.002x ➖ How to read Verdict and Engines
unknown / unknown (1.006x ➖, 1↑ 0↓)
|
Benchmarks: Appian on NVMEVerdict: No clear signal (low confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.047x ➖, 0↑ 0↓)
datafusion / parquet (1.060x ➖, 0↑ 3↓)
duckdb / vortex-file-compressed (0.981x ➖, 0↑ 0↓)
duckdb / parquet (1.006x ➖, 0↑ 0↓)
duckdb / duckdb (1.024x ➖, 0↑ 0↓)
File Size Changes (4 files changed, -0.0% overall, 2↑ 2↓)
Totals:
|
Benchmarks: TPC-H SF=10 on S3Verdict: No clear signal (environment too noisy confidence) How to read Verdict and Engines
datafusion / vortex-file-compressed (1.061x ➖, 0↑ 0↓)
datafusion / vortex-compact (0.863x ➖, 2↑ 2↓)
datafusion / parquet (1.039x ➖, 0↑ 1↓)
duckdb / vortex-file-compressed (0.999x ➖, 0↑ 0↓)
duckdb / vortex-compact (0.988x ➖, 0↑ 0↓)
duckdb / parquet (0.990x ➖, 0↑ 0↓)
|
Benchmarks: CompressionVortex (geomean): 1.002x ➖ How to read Verdict and Engines
unknown / unknown (0.998x ➖, 2↑ 0↓)
|
<!-- ccr-slack-attribution --> _Requested by **Connor Tsui** · [Slack thread](https://spiraldb.slack.com/archives/C09QYV51VL6/p1784056443357189?thread_ts=1784055867.604669&cid=C09QYV51VL6)_ Pins OnPair's dictionary-training seed to `42` so compression is deterministic run-to-run. OnPair's default config left `seed: None`, which seeds the dictionary trainer's RNG from OS entropy — so the trained dictionary, and therefore compressed file sizes, varied between runs. That noise showed up in the file-size benchmark comments (e.g. #8753). This sets `DEFAULT_DICT12_CONFIG` (the only OnPair `Config` in the repo) to `seed: Some(42)`, making the production path, benches, and tests reproducible. Published `onpair` 0.0.4 already exposes the `seed` field, so no upstream release or version bump is needed. Co-authored-by: Claude <noreply@anthropic.com>
Separate candidate evaluation from selection policy in the cascading compressor (#7697). Schemes produce model-independent candidate evidence via Scheme::evaluate (replacing expected_compression_ratio); the compressor combines each CandidateEstimate with selection context into a borrowed Candidate; and the configured CostModel prices candidates, with selection becoming argmin cost against a canonical_cost baseline. The new vortex_compressor::cost module contains a finite, totally ordered Cost, the CostModel trait, the model-facing Candidate, and the default SizeCost, which preserves the historical ratio ordering, canonical acceptance, encoding trees, and array sizes. The evaluation types live in scheme/evaluation.rs and are re-exported from vortex_compressor::scheme. Deferred callbacks no longer observe the current winner or a ratio threshold, so Delta and Sequence always finish their evaluation before the active model decides whether their candidates are competitive. The winner_compress trace span gains a model-defined estimated_cost and drops the estimated-ratio field. This commit contains the core abstraction and the two scheme callbacks (Delta, Sequence) whose selection logic changed. The mechanical call-site migrations to the new evaluate API and the compressor test updates follow in separate commits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Aqw9S8Q1wSpNjaRhdFQ1uf Signed-off-by: Connor Tsui <connor.tsui20@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENnFv2a1HrjTg4j6syybux
Mechanical follow-up to the CostModel extraction: update every btrblocks
scheme, the built-in dictionary schemes, and the tensor L2 denorm
encoding to implement Scheme::evaluate instead of
expected_compression_ratio.
Each call site is a direct translation of the reshaped evaluation enum
and carries no selection-policy change:
- CompressionEstimate::Verdict(EstimateVerdict::Skip) -> SchemeEvaluation::Skip
- CompressionEstimate::Verdict(EstimateVerdict::AlwaysUse) -> SchemeEvaluation::AlwaysUse
- CompressionEstimate::Verdict(EstimateVerdict::Ratio(r))
-> SchemeEvaluation::Candidate(CandidateEstimate::from_compression_ratio(r))
- CompressionEstimate::Deferred(DeferredEstimate::*) -> SchemeEvaluation::Deferred(DeferredEvaluation::*)
along with the corresponding import renames. The two schemes whose
deferred callbacks changed behavior (Delta and Sequence) are in the
preceding commit.
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ENnFv2a1HrjTg4j6syybux
Adapt the cascading-compressor tests to the evaluate/SchemeEvaluation API and the new cost-model selection: candidates are now priced by the configured CostModel (default SizeCost) rather than compared by ratio in the scheme layer, and deferred callbacks no longer receive a best_so_far threshold. Signed-off-by: Connor Tsui <connor.tsui20@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ENnFv2a1HrjTg4j6syybux
1376bf5 to
cb878e4
Compare
Tracking Issue: #8701
Supersedes #8698, rebased onto the reorganized compressor modules (#8745).
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 separates candidate evaluation from selection policy: schemes produce model-independent candidate evidence, the configured
CostModelprices eachCandidate, and the compressor picks the lowest-cost candidate.The default
SizeCostpreserves today's behavior exactly: same winners, same canonical-acceptance threshold, same encoding trees and sizes (golden snapshots unchanged).What changes are included in this PR?
Details
Scheme::evaluatereplacesexpected_compression_ratio. It returns aSchemeEvaluation: skip, forceAlwaysUse, produce an opaqueCandidateEstimate, or request aDeferredEvaluation(sampling or a callback).vortex_compressor::costmodule holds the finite, totally orderedCost, theCostModeltrait, the defaultSizeCost, and the borrowedCandidateview (scheme, estimate, input, optional compressed sample, cascade ancestry) handed toCostModel::cost. Selection retains and compares onlyCost; compression ratio is transient evidence consumed bySizeCost.winner_compresstrace span gains a model-definedestimated_costand drops the estimated-ratio field; achieved ratio remains post-compression observability.canonical_cost, and sampled evidence is exposed correctly.Decisions that deliberately remain outside the model boundary: constant handling,
AlwaysUse, the final byte-acceptance gate, and extension-versus-storage selection. Not included here: model-driven pruning, scheme priors, builder/session plumbing, or a production time-based model.What APIs are changed? Are there any user-facing changes?
Breaking for scheme implementors (
changelog/break):Scheme::expected_compression_ratio→Scheme::evaluate.CompressionEstimate,EstimateVerdict,DeferredEstimate, andEstimateFnare replaced bySchemeEvaluation,ResolvedEvaluation,DeferredEvaluation,DeferredEvaluationFn, andCandidateEstimate, all re-exported fromvortex_compressor::scheme. Deferred callbacks lose their best-so-far argument.New API:
cost::{Cost, CostModel, SizeCost, Candidate}andCascadingCompressor::with_cost_model.