Skip to content

Move FoR serde logic from VTable to Plugin - #10104

Merged
mhk197 merged 2 commits into
developfrom
mk/for-chunked-01-plugin
Sep 28, 2026
Merged

mhk197 merged 2 commits into
developfrom
mk/for-chunked-01-plugin

Conversation

@mhk197

@mhk197 mhk197 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

First PR of a stack prototyping a FoR encoding with one reference per 1024-element chunk, following the plugin approach used for DecimalByteParts and #9937.

Summary

Move metadata, serialization and deserialization of fastlanes.for into a new FoRPlugin. The VTable keeps only the required serde methods, which now return an error, plus in-memory validation. vortex_fastlanes::initialize registers FoRPlugin instead of the vtable-derived plugin.

The wire format is unchanged: the metadata is still just the reference ScalarValue proto bytes, with a single encoded child.

Users who register FoR directly on a session must now register FoRPlugin to get serde.

Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Signed-off-by: Matt Katz <mhkatz97@gmail.com>
@mhk197
mhk197 marked this pull request as ready for review September 28, 2026 13:54
@mhk197 mhk197 added the changelog/break A breaking API change label Sep 28, 2026
@mhk197
mhk197 added this pull request to stack #10109 September 28, 2026 16:50
@mhk197
mhk197 merged commit 709eaa1 into develop Sep 28, 2026
116 of 117 checks passed
@mhk197
mhk197 deleted the mk/for-chunked-01-plugin branch September 28, 2026 20:53
mhk197 added a commit that referenced this pull request Sep 29, 2026
Stacked on #10104.

## Summary

The in-memory `FoR` array now allows one reference per 1024-element
chunk instead of a single global reference per array.

This is supported by adding a `references` child that records each
reference. Element `i` decodes as `encoded[i] + references[(offset + i)
/ 1024]` with wrapping arithmetic.

This also requires adding an `offset` pointer. This is the position of
the first element within the first chunk and only matters for sliced
arrays.

The wire format is unchanged. `fastlanes.for` reads its single reference
as a `ConstantArray` child, and `FoRPlugin` writes `fastlanes.for` only
when the references are constant.

Note that FoR encoding always uses a constant reference per array still
and serialization refuses arrays that have non-constant references, so
this change will not lead to breaks.

## Example

### References

A 3000-row array spans three chunks, each with its own reference.
`encoded` holds each value minus its chunk's reference.

```text
rows         0 ─────────── 1023 │ 1024 ────────── 2047 │ 2048 ──────── 2999
references   [    1_000_000     │      5_000_000       │     9_000_000     ]
encoded      [  small values    │    small values      │   small values    ]

value[1500] = encoded[1500] + references[1500 / 1024]
            = encoded[1500] + references[1]
            = encoded[1500] + 5_000_000
```

### Offset

Slicing is zero-copy, so a slice can start partway through a chunk.
`offset` records how far into its first chunk the slice starts.

```text
             │◄──── chunk 0 ────►│◄──── chunk 1 ────►│◄──── chunk 2 ────►│
rows         0                 1024      1500      2048      2500      3000
slice                                     [═══════════════════)
                                   │◄ 476 ►│

slice(1500..2500):
  encoded    = encoded.slice(1500..2500)          1000 rows
  references = references.slice(1..3)             [5_000_000, 9_000_000]
  offset     = 1500 % 1024                        476

slice row i uses references[(476 + i) / 1024]:
  i = 0..=547    →  references[0] = 5_000_000     (original chunk 1)
  i = 548..=999  →  references[1] = 9_000_000     (original chunk 2)
```

The new offset is always below 1024. Slicing a slice adds the offsets
and slices the references again.

### Constant references (existing files)

```text
fastlanes.for on disk      metadata = reference 42, children = [encoded]
        │ read                                  ▲ write (only when references are constant)
        ▼                                       │
in memory                  references = ConstantArray(42, len = num_chunks), offset = 0
```

## API

- `FoRSlots` gains `references`.
- `FoRData` holds only `offset: u16`. The stored reference scalar is
gone.
- `FoRArrayExt::reference_scalar() -> &Scalar` is replaced by
`constant_reference() -> Option<Scalar>`, which is `Some` when
`references` is a `ConstantArray`. `offset()` and `ptype()` move onto
the extension trait.
- `FoR::try_new(encoded, reference)` keeps its signature and builds the
constant child. `FoR::try_new_chunked(encoded, references, offset)`
accepts any references.

## Kernels

With constant references, every kernel takes its existing path. That
includes Eq/NotEq compare pushdown, is_constant, is_sorted, take,
filter, the fused BitPacked decode, and CUDA FFOR and dynamic dispatch.

With varying references:
- Decoding adds each chunk's reference in place. `scalar_at` looks up
the element's chunk reference. `slice` slices the references by chunk
and carries the new offset. `cast` casts the references.
- Compare, is_constant, is_sorted, take and filter return `None` and
fall back to decoding.
- CUDA decodes these arrays on the CPU and never fuses them into a
dispatch plan.

Callers that rewrap a FoR's encoded child (the btrblocks FoR scheme,
benches, CUDA tests) now pass through `references()` and `offset()` via
`try_new_chunked`.

---------

Signed-off-by: Matt Katz <mhkatz97@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/break A breaking API change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants