Conversation
d068a7a to
2fb17f9
Compare
Merging this PR will not alter performance
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| Simulation | fixed_16_advancing_ptr_safe[100] |
< 1 ns | < 1 ns | N/A | |
| Simulation | preverify_advancing_ptr_unchecked[1000] |
< 1 ns | < 1 ns | N/A | |
| Simulation | preverify_advancing_ptr_unchecked[10000] |
< 1 ns | < 1 ns | N/A |
Comparing mk/scheme-registry (c6fc22e) with develop (38fa7e3)
Footnotes
-
343 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. ↩
-
1 benchmark was run, but is now archived. If it was deleted in another branch, consider rebasing to remove it from the report. Instead if it was added back, click here to restore it. ↩
2fb17f9 to
70b0ab1
Compare
70b0ab1 to
830c260
Compare
connortsui20
left a comment
There was a problem hiding this comment.
I feel like we are mixing up too many things in this PR / there is some other work that needs to be done prior to this.
The most obvious thing is that we really should have a cuda family edition instead of forcing the compressor to hardcode this decision.
Additionally, my understanding of the issue with the current system is that we didn't want this BtrBlocksCompressorBuilder instantiation with all of the extra builder methods (like only_cuda_compatible and with_compact) all over the place, and I don't think that this PR solves this (in some cases I think it makes this more unwieldy).
And the additional code smell is that we do not remove that big static list of default schemes, which I thought we wanted to get rid of. Even if we don't do that, the registration, selection, and compatibility checks for all of this seem to still have overlapping owners, which is not ideal.
I am going to write up an issue detailing how I think we should do it, and we should discuss implementation details there.
80af2c5 to
70b0bfe
Compare
| /// Delta, kept out of [`DEFAULT_SCHEMES`](crate::DEFAULT_SCHEMES) because it is slower to | ||
| /// decompress than the schemes that would otherwise win. Callers that want it opt in with | ||
| /// [`with_new_scheme`](BtrBlocksCompressorBuilder::with_new_scheme) and permit `fastlanes.delta`. | ||
| /// | ||
| /// This list is order-sensitive: the builder preserves this order when constructing | ||
| /// the final scheme list, so that tie-breaking is deterministic. | ||
| pub const ALL_SCHEMES: &[&dyn Scheme] = &[ | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| // Integer schemes. | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| // NOTE: FoR must precede BitPacking to avoid unnecessary patches. | ||
| &integer::FoRScheme, | ||
| // NOTE: ZigZag should precede BitPacking because we don't want negative numbers. | ||
| &integer::ZigZagScheme, | ||
| &integer::BitPackingScheme, | ||
| &integer::SparseScheme, | ||
| &integer::IntDictScheme, | ||
| &integer::RunEndScheme, | ||
| &integer::SequenceScheme, | ||
| &integer::IntRLEScheme, | ||
| // Delta is omitted here: see [`DELTA_SCHEME`]. | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| // Float schemes. | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| &float::ALPScheme, | ||
| &float::ALPRDScheme, | ||
| &float::FloatDictScheme, | ||
| &float::NullDominatedSparseScheme, | ||
| &float::FloatRLEScheme, | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| // String schemes. | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| &string::StringDictScheme, | ||
| // Both string-fragmentation schemes are registered; the sample-based | ||
| // selector keeps whichever is smaller per column. | ||
| &string::FSSTScheme, | ||
| &string::OnPairScheme, | ||
| &string::NullDominatedSparseScheme, | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| // Binary schemes. | ||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| &binary::BinaryDictScheme, | ||
| &binary::VarBinScheme, | ||
| // Decimal schemes. | ||
| &decimal::DecimalScheme, | ||
| // Temporal schemes. | ||
| &temporal::TemporalScheme, | ||
| ]; | ||
|
|
||
| /// Delta, kept out of [`ALL_SCHEMES`] because it is slower to decompress than the schemes that | ||
| /// would otherwise win. Callers that want it opt in with | ||
| /// [`with_new_scheme`](BtrBlocksCompressorBuilder::with_new_scheme). | ||
| /// | ||
| /// TODO(robert): Return it to [`ALL_SCHEMES`] once we have scheme filtering. | ||
| /// TODO(robert): Return it to [`DEFAULT_SCHEMES`](crate::DEFAULT_SCHEMES) once we have scheme | ||
| /// filtering. |
There was a problem hiding this comment.
with this change DELTA should come back to default schemes since it will be filtered out
70b0bfe to
b9329d9
Compare
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
b9329d9 to
bca8e15
Compare
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…sion Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
…build time Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
connortsui20
left a comment
There was a problem hiding this comment.
This is much, much better, and at least to me this feels like a more elegant solution (or at the very least it is better than what we had before...)
I think there are a few implementation things that can be improved, as well as some followups that we probably want to get started on immediately
| /// Schemes to leave out. | ||
| pub exclude_schemes: Vec<SchemeId>, |
There was a problem hiding this comment.
Do we really need this? I'm pretty sure we don't want 2 places that decide what schemes we "want" (to use the terminology from #10038), and at least in this PR I don't think this is strictly necessary either?
Basically this is caller convenience, but it creates a leaky abstraction.
There was a problem hiding this comment.
See
vortex/vortex-file/src/strategy.rs
Lines 180 to 185 in 133aacd
There was a problem hiding this comment.
We can just iterate through session.permitted_schemes() and filter out this scheme explicitly instead if preferred
| /// Creates a compressor over the schemes registered on `session` whose serialized IDs the | ||
| /// session's enabled editions permit. | ||
| pub fn from_session(session: &VortexSession) -> Self { | ||
| Self::from_session_with_options(session, &BtrBlocksOptions::default()) | ||
| } |
There was a problem hiding this comment.
I personally would prefer from_session to take options no matter what (and then the caller must provide the options) but you can decide.
Edit: nvm I proposed a different API entirely without the options
| /// Keep only the registered schemes whose serialized IDs the session's enabled editions | ||
| /// permit, which a file writer requires. Off, every registered scheme is used, for in-memory | ||
| /// compression where no edition applies. | ||
| pub enforce_editions: bool, |
There was a problem hiding this comment.
And even this doesn't seem super necessary? (see below)
| /// Creates a compressor over the schemes registered on `session`, per `options`. | ||
| pub fn from_session_with_options(session: &VortexSession, options: &BtrBlocksOptions) -> Self { | ||
| let mut schemes = if options.enforce_editions { | ||
| session.permitted_schemes() | ||
| } else { | ||
| session.registered_schemes() | ||
| }; | ||
| schemes.retain(|scheme| !options.exclude_schemes.contains(&scheme.id())); | ||
| Self(CascadingCompressor::new(schemes)) | ||
| } |
There was a problem hiding this comment.
A potentially more consise API:
impl BtrBlocksCompressor {
/// Registered schemes the enabled editions permit (for writes). This is the
/// "correct" path
pub fn from_session(session: &VortexSession) -> Self;
/// Exactly these schemes like `session.registered_schemes()`.
pub fn new(schemes: Vec<&'static dyn Scheme>) -> Self;
}So that the caller is forced to either use editions (as it is intended) or must provide it themselves (which should be easy via session.registered_schemes() like above), which should work fine for in-memory tests
There was a problem hiding this comment.
Options might be necessary for now so we can support choosing compressor mode after initializing session
There was a problem hiding this comment.
(mode is not in options now but adding it)
There was a problem hiding this comment.
Modes here will essentially be filters, e.g. default will filter out compact.
It would be nice to remove modes in general (and just to rely on what users register in session) but there are some places where we alter compressor state after initializing session:
I'm assuming we can break the first but not sure about the second?
| /// Delta, kept out of the default schemes because it is slower to decompress than the schemes | ||
| /// that would otherwise win. Callers that want it register it and permit `fastlanes.delta`. | ||
| /// | ||
| /// TODO(robert): Return it to the defaults once we have scheme filtering. | ||
| pub static DELTA_SCHEME: integer::DeltaScheme = integer::DeltaScheme::new(1.25); |
There was a problem hiding this comment.
We should be able to move this back now that it will get filtered out by the editions filter?
There was a problem hiding this comment.
yes that can be a followup
| /// The default schemes that CUDA kernels decode, keeping FSST for string compression, plus | ||
| /// Zstd for binary compression when the `zstd` feature is on. | ||
| /// | ||
| /// Both the array-level and the buffer-level Zstd schemes are added. Buffer-level compression | ||
| /// preserves binary arrays' buffer layout for zero-conversion GPU decompression, but belongs | ||
| /// to the opt-in `zstd` edition, so a session's enabled editions decide which of the two a | ||
| /// writer uses. Files written with these schemes may be larger than with the defaults: the | ||
| /// set picks encodings the GPU decodes, not the smallest ones. | ||
| pub fn cuda() -> Self { | ||
| // Keep FSST, which has a CUDA decoder and direct Arrow offset-based export. Other string | ||
| // fragmentation and dictionary schemes still require unsupported decode paths. Delta is | ||
| // not a default either: it has a CUDA decode kernel, but GPU delta decode has not been | ||
| // benchmarked against the schemes it would displace. |
There was a problem hiding this comment.
This really should become a cuda edition, though that can be a separate PR. CC @robert3005
| pub fn compact() -> Self { | ||
| let mut this = Self::default(); | ||
| for scheme in COMPACT_SCHEMES { | ||
| this.register(*scheme); | ||
| } | ||
| this | ||
| } |
There was a problem hiding this comment.
I feel like instead of doing this, we should split the vortex_zstd::initialize(&SESSION) into that PLUS something like vortex_zstd::register_schemes(&SESSION) so that it is easy to split read (initialize) and write (register schemes) concerns. What do you think?
There was a problem hiding this comment.
Oh this would mean that the schemes would need to move to their owning crates, but I think that is fine? And actually I dont think it is actually incorrect for schemes to live next to their encodings UNLESS they have exclusion rules relating each other... Something is weird here but that can be figured out after this PR
There was a problem hiding this comment.
We can do that but would have to figure out ordering as well, could be followup
There was a problem hiding this comment.
I don't think we would need to figure out ordering here, at least for the compact schemes they don't have any ordering constraints iirc, and this would solve the problem you mention at #10034 (comment)
There was a problem hiding this comment.
I mean for all schemes, there are commented ordering constraints
| string::StringDictScheme.id(), | ||
| binary::BinaryDictScheme.id(), |
There was a problem hiding this comment.
Can GPU handle this now? (issue with previous code probably)
| /// | ||
| /// Compressors built with [`BtrBlocksCompressorBuilder::from_session`] start from the schemes | ||
| /// registered on their session. Registration is idempotent, so this may run more than once. | ||
| pub fn initialize(session: &VortexSession) { |
There was a problem hiding this comment.
Move this and ALL_SCHEMES to a session.rs file.
ALL_SCHEMES should be renamed DEFAULT_SCHEMES
| /// Schemes to leave out. | ||
| pub exclude_schemes: Vec<SchemeId>, |
There was a problem hiding this comment.
We can just iterate through session.permitted_schemes() and filter out this scheme explicitly instead if preferred
| /// Creates a compressor over the schemes registered on `session`, per `options`. | ||
| pub fn from_session_with_options(session: &VortexSession, options: &BtrBlocksOptions) -> Self { | ||
| let mut schemes = if options.enforce_editions { | ||
| session.permitted_schemes() | ||
| } else { | ||
| session.registered_schemes() | ||
| }; | ||
| schemes.retain(|scheme| !options.exclude_schemes.contains(&scheme.id())); | ||
| Self(CascadingCompressor::new(schemes)) | ||
| } |
There was a problem hiding this comment.
Options might be necessary for now so we can support choosing compressor mode after initializing session
| /// Creates a compressor over the schemes registered on `session`, per `options`. | ||
| pub fn from_session_with_options(session: &VortexSession, options: &BtrBlocksOptions) -> Self { | ||
| let mut schemes = if options.enforce_editions { | ||
| session.permitted_schemes() | ||
| } else { | ||
| session.registered_schemes() | ||
| }; | ||
| schemes.retain(|scheme| !options.exclude_schemes.contains(&scheme.id())); | ||
| Self(CascadingCompressor::new(schemes)) | ||
| } |
There was a problem hiding this comment.
(mode is not in options now but adding it)
| /// Delta, kept out of the default schemes because it is slower to decompress than the schemes | ||
| /// that would otherwise win. Callers that want it register it and permit `fastlanes.delta`. | ||
| /// | ||
| /// TODO(robert): Return it to the defaults once we have scheme filtering. | ||
| pub static DELTA_SCHEME: integer::DeltaScheme = integer::DeltaScheme::new(1.25); |
There was a problem hiding this comment.
yes that can be a followup
…ressor::new Signed-off-by: Matt Katz <mhkatz97@gmail.com>
| /// the compression schemes the GPU decodes. Separate from vortex-bench's `SESSION`, so the host | ||
| /// backends keep the default schemes. | ||
| static GPU_SESSION: LazyLock<VortexSession> = LazyLock::new(|| { | ||
| let session = VortexSession::default().with_tokio(); |
There was a problem hiding this comment.
This is not correct!
There was a problem hiding this comment.
This needs to be injected no?
There was a problem hiding this comment.
what do you mean injected?
| /// the compression schemes the GPU decodes. Separate from vortex-bench's `SESSION`, so the host | ||
| /// backends keep the default schemes. | ||
| static GPU_SESSION: LazyLock<VortexSession> = LazyLock::new(|| { | ||
| let session = VortexSession::default().with_tokio(); |
There was a problem hiding this comment.
This needs to be injected no?
| pub static COMPACT_SESSION: LazyLock<VortexSession> = LazyLock::new(|| { | ||
| let session = new_session(); | ||
| session.register(CompressionSession::compact()); | ||
| session | ||
| }); |
There was a problem hiding this comment.
should this be static?
| store: Option<AnyVortexStore>, | ||
| ) -> PyVortexResult<()> { | ||
| let session = session(); | ||
| let session = if self.use_compact_encodings { |
There was a problem hiding this comment.
@connortsui20 this is how we have to switch right now
| fn default_compressor() -> BtrBlocksCompressor { | ||
| BtrBlocksCompressor::new(CompressionSession::default().schemes().to_vec()) | ||
| } |
There was a problem hiding this comment.
how did you get this session? this should be passed a session?
Summary
Compression schemes become a session registry, like array encodings and layouts, and every BtrBlocks compressor is built from a session. The compressor keeps the registered schemes whose serialized IDs the session's enabled editions permit, so a default compressor can no longer emit an encoding the edition-enforcing writer rejects.
BtrBlocksCompressorBuilderand the session-lessBtrBlocksCompressor::default()are removed; nothing takes a scheme list any more.Model
CompressionSessionholds the schemes a session can compress with, in registration order. It is add-only.produced_encodings(): every encoding must have a registered array plugin and, when editions are enforced, be permitted by the enabled editions. This is the same rule the file writer applies to the arrays it serializes.default,compact,cuda,empty), register more schemes on it, or build a compressor withBtrBlocksOptions.Scheme registry (
vortex-btrblocks)session::CompressionSessionis a session variable, likeLayoutSession. ItsDefaultcarries the built-in scheme set, which wasALL_SCHEMES.CompressionSession::default()CompressionSession::compact()CompressionSession::cuda()CompressionSession::empty()register(&mut self, &'static dyn Scheme)deduplicates bySchemeId;schemes()reads the list. The scheme lists themselves are private.DELTA_SCHEMEstays public because Delta is opt-in and not part of any edition.CompressionSessionExtgives session access:compression()for the registry andregister_scheme.VortexSession::default()registersCompressionSession; sessions built fromarray_session()get the default registry lazily on first use. A session that already has one swaps it withsession.register(CompressionSession::cuda()), asvortex_arrow::initializereplacesArrowSession.Compressor API (
vortex-btrblocks)BtrBlocksCompressorstays the thin wrapper overCascadingCompressor;vortex-compressoris unchanged.from_sessionkeeps the registered schemes whose encodings the session can serialize and the enabled editions permit.enforce_editions: falsedrops only the edition check, for in-memory compression;exclude_schemesleaves schemes out.newtakes a scheme list as given, for tests and callers that own the list.Removed:
BtrBlocksCompressorBuilder(empty,with_new_scheme,with_compact,only_cuda_compatible,exclude_schemes,retain_allowed_encodings,build),BtrBlocksCompressor::default(),ALL_SCHEMES.File writer (
vortex-file)WriteStrategyBuilder::from_session(&session)replacesdefault(), andfrom_session_with_options(&session, options)replaceswith_btrblocks_builder. The builder holds the session and the options, and atbuildcreates the data compressor withIntDictSchemeadded toexclude_schemes, because the dict layout already dictionary-encodes columns, and the stats compressor with the options as given.with_compressorstill installs an opaqueCompressorPluginused as-is for both.The writer's default strategy is
from_session_with_optionswithenforce_editionsset to whether editions are enforced, sodisable_editions()still writes every registered encoding.CUDA (
vortex-cuda)CompressionSession::cuda()replaces theonly_cuda_compatiblepreset. Sessions that write CUDA files install it next toregister_cuda_layout: the CLI,vx_cuda_session_new, the CUDA sink, the write-strategy tests, and compress-bench's GPU backend, which gets its ownGPU_SESSIONso the host backends keep the default schemes. Read-only CUDA sessions are untouched.cuda_write_strategyisWriteStrategyBuilder::from_session, withBtrBlocksCompressor::from_sessionas the opaque compressor andBtrBlocksCompressor::empty()as the probe in the block-rows path.Callers
CompressionSession::compact(): Python's fork-safecompact_session()behindVortexWriteOptions.compact(), vortex-bench'sCOMPACT_SESSIONviaCompactionStrategy::session(), a compact session built with the caller's runtime handle in the TUI, the fuzzCOMPACT_SESSION, compat-gen, and the tracing example.exclude_schemes: the golden snapshots without OnPair, the varbin and StringDict tests, and string-bench's forced encoder. Tests that want a single scheme start fromCompressionSession::empty().BtrBlocksCompressor::new; the golden snapshots, which pin the edition-filtered set, register the encoding plugins on their session.vortex::compressormodule exportsBtrBlocksCompressor,BtrBlocksOptions,CompressionSession,CompressionSessionExt,Scheme,SchemeId.Behavior
WriteStrategyBuilder::from_session(&session).build()only emits permitted encodings. The edition test that previously asserted such a strategy emitsvortex.sequenceand is rejected now asserts the write succeeds.vx.compressis restricted to the session's enabled editions instead of every scheme.vx_cuda_session_newdoes at creation.build, not atfrom_session.array_session()with the core edition but withoutregister_default_encodings, writes successfully: the compressor skips schemes whose output the writer could not serialize.write_uses_only_registered_encodingspins this.Validation
Run locally on macOS (arm64):
cargo +nightly-2026-09-10 fmton the touched crates;cargo clippy --all-targets --all-features -- -D warningsonvortex-compressor,vortex-btrblocks,vortex-file,vortex-layout,vortex,vortex-cuda,gpu-scan-cli,compress-bench,vortex-python,vortex-tui,vortex-bench,vortex-compat,string-bench,duckdb-bench;vortex-btrblocksalso without features and withpcoandzstdalone;vortexwithzstdbut notfiles;vortex-fuzzwith all features; all clean.cargo nextest run --all-featuresonvortex-btrblocks,vortex-file,vortex-layout,vortex: 543 passed, 1 skipped;vortex-btrblockswithout features: 61 passed;string-bench: 14 passed; the twocuda_write_strategytests pass. Doctests onvortex-btrblocks,vortex-file,vortex: passed.cargo check -p vortex-cuda-ffi(lib): clean.Not run: GPU tests and
wasm-test, which need a device or a wasm target, and the Python test suite, which needs a Maturin rebuild.