[feat] tiled fixed size list array (PDX-style) - #9118
Conversation
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
…write Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
Signed-off-by: Will Manning <will@willmanning.io>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4dfc88bfc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let (Some(source_validity), Some(output_validity)) = | ||
| (&source_validity, &mut output_validity) | ||
| { | ||
| output_validity.set_to(physical, source_validity.value(canonical)); |
There was a problem hiding this comment.
Avoid bit-at-a-time validity transposition
When nullable primitive elements resolve to Mask::Values, encoding performs a BitBuffer::value lookup and branch for every scalar; the inverse path repeats the same pattern at line 179. For large vector arrays this makes validity transposition a bit-at-a-time hot loop and discards the bitmap word-level fast paths, so transpose validity by u64 chunks with all-set/all-unset handling (or iterate set indices into an unset destination) and cover the nullable case in the transpose benchmark.
AGENTS.md reference: AGENTS.md:L185-L190
Useful? React with 👍 / 👎.
|
|
||
| pub const UNSTABLE_2026_07_0: EditionId = EditionId::new("unstable", 2026, 7, 0); | ||
|
|
There was a problem hiding this comment.
Document the new public edition declarations
Add API documentation for UNSTABLE_2026_07_0 and DECLARATION, as well as module-level documentation for this publicly exported edition module. Every neighboring unstable-edition module documents these public definitions, while this new module exposes them without any doc comments, contrary to the repository requirement for every new public API definition.
AGENTS.md reference: AGENTS.md:L146-L146
Useful? React with 👍 / 👎.
Signed-off-by: Will Manning <will@willmanning.io>
| } | ||
|
|
||
| #[expect(clippy::result_large_err)] | ||
| fn fuzz<T>(result: VortexResult<T>) -> VortexFuzzResult<T> { |
There was a problem hiding this comment.
this function is terrible, it's just a map_err, inline it everywhere and a lot of weird code will go away imho
| tiled.encoding_id() | ||
| ); | ||
| Ok(()) | ||
| })())?; |
There was a problem hiding this comment.
We run reduce operations so I don't think you will have a slice here always, looks like this sometimes will end up with just the tiled array
| use vortex_fuzz::run_tiled_fsl; | ||
|
|
||
| fuzz_target!(|input: FuzzTiledFsl| -> Corpus { | ||
| match run_tiled_fsl(input) { |
There was a problem hiding this comment.
I think you can unwrap the error here and use vortexresult throughout the module. I only have VortexFuzzResult so that we have the necessary debug information for fuzzing
| .collect::<VortexResult<Buffer<u64>>>()?; | ||
| let row = array | ||
| .elements() | ||
| .take(PrimitiveArray::from_iter(indices).into_array())? |
There was a problem hiding this comment.
This should not need from_iter, you already have a Buffer
| Mask::Values(validity) => values | ||
| .iter() | ||
| .copied() | ||
| .zip(validity.bit_buffer().iter()) |
There was a problem hiding this comment.
bit_buffer().for_each_set_index?
| } | ||
| let elements = elements.finish(); | ||
| let decoded = decode_visible_elements( | ||
| elements.as_::<Primitive>(), |
There was a problem hiding this comment.
how do we know this is primitive?
| let span_elements = array | ||
| .elements() | ||
| .slice(span)? | ||
| .execute::<PrimitiveArray>(ctx)?; |
| let mut starts = Vec::<u64>::with_capacity(dimension_slab_count); | ||
| let mut lengths = Vec::<u64>::with_capacity(dimension_slab_count); | ||
|
|
||
| for span in spans { | ||
| starts.push(u64::try_from(span.start)?); | ||
| lengths.push(u64::try_from(span.len())?); | ||
| } |
| let validity = self | ||
| .as_ref() | ||
| .slots() | ||
| .get(TiledFixedSizeListSlots::VALIDITY) |
There was a problem hiding this comment.
You will get TiledFixedSizeListSlotsExt which will give you a method so you can make this be just self.validity(). See StructArray
Rationale for this change
Many vector workloads, including vector indices, benefit from storing fixed-size primitive lists in a two-dimensional, PDX-style layout so kernels can process tiles directly instead of transposing rows during scoring. This adds an experimental tiled fixed-size-list encoding behind
unstable_encodings; it does not carry a stable wire-format guarantee.What changes are included in this PR?
Adds the
vortex-tiled-fslcrate with configurable row and dimension tiles, checked geometry, scalar access, canonical take fallback, file round trips, benchmarks, and differential fuzzing against canonical fixed-size lists. Full-width slices are O(1) row views at the TiledFSL layer and retain at most the selected rows plus two boundary tiles; a local slice-reduce benchmark measured 42.05 ns at 1,024 rows and 43.04 ns at 1,000,000 rows. Aligned multi-slab slices use onePiecewiseSequenceArrayrun per dimension slab. Unaligned multi-slab slices remain lazy and, when executed, issue a bounded slice to each child slab before canonical assembly; child-internal work remains encoding-dependent.Element validity remains encapsulated by the nullable primitive elements child, while TiledFSL owns only outer-row validity. Serialized row views carry their logical offset and retained backing extent, including nullable FastLanes-bitpacked children. Tensor and TiledFSL registration remain available without the files feature and initialize only once in default sessions.
The default compressor does not select or preserve this encoding automatically. Callers that persist tiled arrays must choose an appropriate layout strategy for the relevant field. Coverage includes canonical conformance, malformed serde inputs, file round trips, deterministic stateful fuzzing, a 10,000-run libFuzzer pass, and structural scaling checks.
What APIs are changed? Are there any user-facing changes?
vortex::encodings::tiled_fslis available behindunstable_encodings, and the unstable edition permitsvortex.tiled_fsl. The experimental API exposes tiled array construction,TileGeometry, tile-bound iteration, encoding from primitive fixed-size lists, and access to the physical child and row-view metadata.🤖 Generated with Codex