Skip to content

Commit 5239669

Browse files
committed
feat(fastlanes): put per-chunk bit widths behind a preview edition
Declare preview2026.09.0 with fastlanes.bitpacked_v2 and make it the default preview edition. Add BitPackingV2Scheme behind unstable_encodings: it produces BitPacked arrays with a width per chunk and declares that its output needs the v2 format, so a core-only write drops it. While it is present BitPackingScheme defers to it, so FoR residuals and sampled estimates use per-chunk widths too. The CUDA-compatible preset excludes it. Signed-off-by: Matt Katz <mhkatz97@gmail.com>
1 parent ea6c403 commit 5239669

8 files changed

Lines changed: 234 additions & 47 deletions

File tree

‎vortex-btrblocks/src/schemes/integer/bitpacking.rs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use crate::ArrayAndStats;
2525
use crate::CascadingCompressor;
2626
use crate::CompressorContext;
2727
use crate::Scheme;
28+
#[cfg(feature = "unstable_encodings")]
29+
use crate::SchemeExt;
2830
use crate::compress_patches;
2931

3032
/// BitPacking encoding for non-negative integers.
@@ -64,13 +66,21 @@ impl Scheme for BitPackingScheme {
6466
CompressionEstimate::Deferred(DeferredEstimate::Sample)
6567
}
6668

69+
#[cfg_attr(not(feature = "unstable_encodings"), expect(unused_variables))]
6770
fn compress(
6871
&self,
69-
_compressor: &CascadingCompressor,
72+
compressor: &CascadingCompressor,
7073
data: &ArrayAndStats,
71-
_compress_ctx: CompressorContext,
74+
compress_ctx: CompressorContext,
7275
exec_ctx: &mut ExecutionCtx,
7376
) -> VortexResult<ArrayRef> {
77+
// Per-chunk widths are strictly better when the writer may serialize them; the v2 scheme
78+
// is present exactly then.
79+
#[cfg(feature = "unstable_encodings")]
80+
if compressor.has_scheme(super::BitPackingV2Scheme.id()) {
81+
return super::BitPackingV2Scheme.compress(compressor, data, compress_ctx, exec_ctx);
82+
}
83+
7484
let primitive_array = data.array_as_primitive();
7585

7686
let histogram = bit_width_histogram(primitive_array, exec_ctx)?;
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
//! BitPacking with a bit width per FastLanes chunk.
5+
6+
use vortex_array::ArrayId;
7+
use vortex_array::ArrayRef;
8+
use vortex_array::Canonical;
9+
use vortex_array::ExecutionCtx;
10+
use vortex_array::IntoArray;
11+
use vortex_array::VTable;
12+
use vortex_compressor::scheme::CompressionEstimate;
13+
use vortex_compressor::scheme::DeferredEstimate;
14+
use vortex_compressor::scheme::EstimateVerdict;
15+
use vortex_error::VortexResult;
16+
use vortex_fastlanes::BitPacked;
17+
use vortex_fastlanes::BitPackedArrayExt;
18+
use vortex_fastlanes::bitpack_compress::bitpack_to_best_chunk_widths;
19+
use vortex_fastlanes::bitpacked_v2_id;
20+
21+
use crate::ArrayAndStats;
22+
use crate::CascadingCompressor;
23+
use crate::CompressorContext;
24+
use crate::Scheme;
25+
use crate::compress_patches;
26+
27+
/// BitPacking for non-negative integers with a width chosen per 1024-element chunk.
28+
///
29+
/// The output is an ordinary [`BitPacked`] array, but one whose chunks may differ in width, which
30+
/// serializes as `fastlanes.bitpacked_v2`. The writer therefore keeps this scheme only when an
31+
/// enabled edition permits that format; while it is present, [`super::BitPackingScheme`] defers
32+
/// to it so bit-packed residuals get per-chunk widths too.
33+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
34+
pub struct BitPackingV2Scheme;
35+
36+
impl Scheme for BitPackingV2Scheme {
37+
fn scheme_name(&self) -> &'static str {
38+
"vortex.int.bitpacking_v2"
39+
}
40+
41+
fn matches(&self, canonical: &Canonical) -> bool {
42+
canonical.dtype().is_int()
43+
}
44+
45+
fn produced_encodings(&self) -> Vec<ArrayId> {
46+
vec![BitPacked.id()]
47+
}
48+
49+
fn produced_serialized_ids(&self) -> Vec<ArrayId> {
50+
vec![bitpacked_v2_id()]
51+
}
52+
53+
fn expected_compression_ratio(
54+
&self,
55+
data: &ArrayAndStats,
56+
_compress_ctx: CompressorContext,
57+
exec_ctx: &mut ExecutionCtx,
58+
) -> CompressionEstimate {
59+
let stats = data.integer_stats(exec_ctx);
60+
61+
// BitPacking only works for non-negative values.
62+
if stats.erased().min_is_negative() {
63+
return CompressionEstimate::Verdict(EstimateVerdict::Skip);
64+
}
65+
66+
CompressionEstimate::Deferred(DeferredEstimate::Sample)
67+
}
68+
69+
fn compress(
70+
&self,
71+
_compressor: &CascadingCompressor,
72+
data: &ArrayAndStats,
73+
_compress_ctx: CompressorContext,
74+
exec_ctx: &mut ExecutionCtx,
75+
) -> VortexResult<ArrayRef> {
76+
let primitive_array = data.array_as_primitive().into_owned();
77+
let packed = bitpack_to_best_chunk_widths(&primitive_array, exec_ctx)?;
78+
79+
// If every chunk needs the full bit-width, return the original array.
80+
if packed.chunk_widths().uniform_width().map(usize::from)
81+
== Some(primitive_array.ptype().bit_width())
82+
{
83+
return Ok(primitive_array.into_array());
84+
}
85+
86+
let Some(patches) = packed.patches() else {
87+
return Ok(packed.into_array());
88+
};
89+
90+
// Compress the patches and place them back into the array.
91+
let packed_stats = packed.statistics().to_owned();
92+
let ptype = packed.dtype().as_ptype();
93+
let mut parts = BitPacked::into_parts(packed);
94+
parts.patches = Some(compress_patches(patches, exec_ctx)?);
95+
Ok(BitPacked::try_new(
96+
parts.packed,
97+
ptype,
98+
parts.validity,
99+
parts.patches,
100+
parts.widths,
101+
parts.len,
102+
parts.offset,
103+
)?
104+
.with_stats_set(packed_stats)
105+
.into_array())
106+
}
107+
}

‎vortex-btrblocks/src/schemes/integer/mod.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
66
mod bitpacking;
77
#[cfg(feature = "unstable_encodings")]
8+
mod bitpacking_v2;
9+
#[cfg(feature = "unstable_encodings")]
810
mod delta;
911
mod for_;
1012
mod rle;
@@ -18,6 +20,8 @@ mod pco;
1820

1921
pub use bitpacking::BitPackingScheme;
2022
#[cfg(feature = "unstable_encodings")]
23+
pub use bitpacking_v2::BitPackingV2Scheme;
24+
#[cfg(feature = "unstable_encodings")]
2125
pub use delta::DeltaScheme;
2226
pub use for_::FoRScheme;
2327
#[cfg(feature = "pco")]

‎vortex-btrblocks/tests/snapshots/golden__unstable__float_low_cardinality.snap‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ source: vortex-btrblocks/tests/golden.rs
33
expression: rendered
44
---
55
input: f64, len=16384, nbytes=131072
6-
root: vortex.alp(f64, len=16384) nbytes=10233
6+
root: vortex.alp(f64, len=16384) nbytes=10232
77
metadata: exponents: e: 16, f: 12, patch_offset: 0
8-
encoded: vortex.dict(i64, len=16384) nbytes=6200
8+
encoded: vortex.dict(i64, len=16384) nbytes=6199
99
metadata: all_values_referenced: true
1010
codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144
1111
metadata: bit_width: 3, offset: 0
12-
values: vortex.primitive(i64, len=7) nbytes=56
13-
metadata: ptype: i64
12+
values: fastlanes.bitpacked(i64, len=7) nbytes=55
13+
metadata: bit_width: 0, offset: 0
14+
patch_indices: vortex.primitive(u8, len=6) nbytes=6
15+
metadata: ptype: u8
16+
patch_values: vortex.primitive(i64, len=6) nbytes=48
17+
metadata: ptype: i64
18+
patch_chunk_offsets: vortex.primitive(u8, len=1) nbytes=1
19+
metadata: ptype: u8
1420
patch_indices: vortex.primitive(u16, len=1996) nbytes=3992
1521
metadata: ptype: u16
1622
patch_values: vortex.constant(f64, len=1996) nbytes=9

‎vortex-btrblocks/tests/snapshots/golden__unstable__int_low_cardinality.snap‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ source: vortex-btrblocks/tests/golden.rs
33
expression: rendered
44
---
55
input: i64, len=16384, nbytes=131072
6-
root: vortex.dict(i64, len=16384) nbytes=6192
6+
root: vortex.dict(i64, len=16384) nbytes=6190
77
metadata: all_values_referenced: true
88
codes: fastlanes.bitpacked(u8, len=16384) nbytes=6144
99
metadata: bit_width: 3, offset: 0
10-
values: vortex.primitive(i64, len=6) nbytes=48
11-
metadata: ptype: i64
10+
values: fastlanes.bitpacked(i64, len=6) nbytes=46
11+
metadata: bit_width: 0, offset: 0
12+
patch_indices: vortex.primitive(u8, len=5) nbytes=5
13+
metadata: ptype: u8
14+
patch_values: vortex.primitive(i64, len=5) nbytes=40
15+
metadata: ptype: i64
16+
patch_chunk_offsets: vortex.primitive(u8, len=1) nbytes=1
17+
metadata: ptype: u8

‎vortex-btrblocks/tests/snapshots/golden__unstable__string_fsst_structured.snap‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,31 @@ source: vortex-btrblocks/tests/golden.rs
33
expression: rendered
44
---
55
input: utf8, len=16384, nbytes=653785
6-
root: vortex.fsst(utf8, len=16384) nbytes=121766
6+
root: vortex.fsst(utf8, len=16384) nbytes=121314
77
metadata: len: 16384, nsymbols: 223
8-
uncompressed_lengths: vortex.sparse(u8, len=16384) nbytes=1802
8+
uncompressed_lengths: vortex.sparse(u8, len=16384) nbytes=1734
99
metadata: fill_value: 24u8
10-
patch_indices: fastlanes.delta(u16, len=1575) nbytes=1798
10+
patch_indices: fastlanes.delta(u16, len=1575) nbytes=1730
1111
metadata: offset: 0
1212
bases: vortex.primitive(u16, len=128) nbytes=256
1313
metadata: ptype: u16
14-
deltas: fastlanes.bitpacked(u16, len=2048) nbytes=1542
15-
metadata: bit_width: 6, offset: 0
16-
patch_indices: vortex.primitive(u16, len=1) nbytes=2
14+
deltas: fastlanes.bitpacked(u16, len=2048) nbytes=1474
15+
metadata: bit_widths: 2 chunks, max 6, offset: 0
16+
patch_indices: vortex.primitive(u16, len=16) nbytes=32
17+
metadata: ptype: u16
18+
patch_values: vortex.primitive(u16, len=16) nbytes=32
1719
metadata: ptype: u16
18-
patch_values: vortex.constant(u16, len=1) nbytes=2
19-
metadata: scalar: 67u16
2020
patch_chunk_offsets: vortex.primitive(u8, len=2) nbytes=2
2121
metadata: ptype: u8
2222
patch_values: vortex.constant(u8, len=1575) nbytes=2
2323
metadata: scalar: 23u8
24-
codes_offsets: fastlanes.delta(u32, len=16385) nbytes=8728
24+
codes_offsets: fastlanes.delta(u32, len=16385) nbytes=8344
2525
metadata: offset: 0
2626
bases: vortex.primitive(u32, len=544) nbytes=2176
2727
metadata: ptype: u32
28-
deltas: vortex.dict(u32, len=17408) nbytes=6552
28+
deltas: vortex.dict(u32, len=17408) nbytes=6168
2929
metadata: all_values_referenced: true
30-
codes: fastlanes.bitpacked(u8, len=17408) nbytes=6528
31-
metadata: bit_width: 3, offset: 0
30+
codes: fastlanes.bitpacked(u8, len=17408) nbytes=6144
31+
metadata: bit_widths: 17 chunks, max 3, offset: 0
3232
values: vortex.primitive(u32, len=6) nbytes=24
3333
metadata: ptype: u32

‎vortex/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ vortex-zigzag = { workspace = true }
5757
vortex-zstd = { workspace = true, optional = true }
5858

5959
[dev-dependencies]
60+
parking_lot = { workspace = true }
6061
anyhow = { workspace = true }
6162
arrow-array = { workspace = true }
6263
divan = { workspace = true }

0 commit comments

Comments
 (0)