Skip to content

Register parent-reduce rules with the session kernels - #11092

Open
robert3005 wants to merge 10 commits into
rk/deprecate-sessionless-optimizefrom
rk/session-parent-rules
Open

robert3005 wants to merge 10 commits into
rk/deprecate-sessionless-optimizefrom
rk/session-parent-rules

Conversation

@robert3005

@robert3005 robert3005 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Parent-reduce rules move from static PARENT_RULES sets on the vtables into the session kernel registry, so the session controls all rules, as it already does for execute-parent kernels.

Changes

  • Each encoding registers its parent-reduce rules in its initialize function, through ArrayKernels::register_reduce_parent_rule. The registry keys the rules by the parent and child encoding ids. Rules for any scalar function use AnyScalarFn::id.
  • The optimizer reads only the registry. It skips the children of a parent for which no rule is registered.
  • ExecutionCtx takes a snapshot of the parent-reduce kernels when it is created, so optimizing with the context does not read the session again.

@robert3005
robert3005 added this pull request to stack #11093 November 2, 2026 12:06
@robert3005 robert3005 added the changelog/break A breaking API change label Nov 2, 2026
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch 2 times, most recently from 1364ff7 to 99ee78b Compare November 2, 2026 12:28
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch 2 times, most recently from be6bdae to 951db46 Compare November 2, 2026 15:11
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch 2 times, most recently from f6a5894 to a564054 Compare November 2, 2026 19:18
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from a564054 to cc7b45d Compare November 3, 2026 13:16
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from cc7b45d to 1d85ea1 Compare November 4, 2026 14:31
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from 1d85ea1 to d493f1f Compare November 4, 2026 15:29
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from d493f1f to aa27450 Compare November 4, 2026 18:30
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from aa27450 to 0adc708 Compare November 4, 2026 18:42
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from 0adc708 to 3894fcb Compare November 4, 2026 19:36
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from 3894fcb to 5402a3c Compare November 4, 2026 20:06
Constructors such as slice, filter, take and the array builtins now
return lazy arrays. Code that inspects the encoding of the result, stores
it, or reads it row by row lost its fast paths or changed its output.
Optimize the result at those sites, and add a Dict rule that keeps only
the referenced value when the codes reduce to a constant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from 365f883 to d180ffb Compare November 6, 2026 13:35
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from d180ffb to 4752b50 Compare November 6, 2026 13:56
robert3005 and others added 4 commits November 6, 2026 16:48
Signed-off-by: Robert Kruszewski <github@robertk.io>
Signed-off-by: Robert Kruszewski <github@robertk.io>
Each encoding now registers its parent-reduce rules with the session's
ArrayKernels through register_reduce_parent_rule, keyed by the parent and
child encoding ids, in place of a static PARENT_RULES set on its vtable.
Rules for any scalar function are keyed by AnyScalarFn::id. The vtable
reduce_parent hook and ParentRuleSet are removed.

The optimizer consults only the registry and skips the children of a
parent that no rule is registered for. An ExecutionCtx snapshots the
parent-reduce kernels when it is created, so optimizing with the context
does not read the session again.

The shared dictionary values of a chunked array are now pulled up by a
chunked reduce rule instead of a dict parent rule.

The deprecated session-less optimize now uses only the rules vortex-array
registers by default, and it does not mark its result as optimized.

Signed-off-by: Robert Kruszewski <github@robertk.io>
Create one execution context per sample and mark the input arrays as
optimized, so the timed loop measures the slice and its reduction rather
than the session lookup and the walk over the unmarked input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Robert Kruszewski <github@robertk.io>
@robert3005
robert3005 force-pushed the rk/session-parent-rules branch from 4752b50 to 5543bce Compare November 6, 2026 16:51
Comment thread encodings/alp/src/alp/rules.rs Outdated
ParentRuleSet::lift(&CastReduceAdaptor(ALP)),
ParentRuleSet::lift(&MaskReduceAdaptor(ALP)),
]);
/// Registers the parent-reduce rules for [`ALP`] children in the session kernels.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this and other comments please?

/// Registers the parent-reduce rules for [`DecimalByteParts`] children in the session kernels.
pub(crate) fn register_parent_rules(session: &VortexSession) {
let kernels = session.kernels();
kernels.register_reduce_parent_rule(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't have a strong opinion, but I'd prefer kernels.register_parent_rule(Cast, DecimalByteParts, ... so that the function is parametrized on impl ScalarFnVTable instead of an Id.

Comment on lines +40 to +43
/// Rewrites `Chunked<Dict<codes_i, values>>` into `Dict<Chunked<codes_i>, values>` only when
/// every chunk is a dictionary over the exact same values array allocation. The rewrite depends
/// on every chunk at once, so it is a rule of the chunked array rather than a parent rule each
/// chunk evaluates.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this comment?

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
Comment on lines +6 to +11
//! [`ArrayKernels`] stores the parent-reduce rules and parent-execution kernels that participate in
//! array optimization and execution without adding them to an encoding vtable. The optimizer
//! consults it for every parent-reduce rewrite, and the executor consults it for parent execution,
//! so a session can add support for an extension encoding or register a rule ahead of a built-in
//! one. When several entries are registered for the same key and kind, they are tried in
//! registration order until one applies.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're changing the description, can you please rewrite this paragraph altogether? This is a hard read for me

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
#[derive(Clone, Debug, Default)]
pub(crate) struct ParentReduceKernels {
/// Kernels keyed by hashed `(parent, child)` pair.
kernels: Arc<HashMap<ReduceParentFnId, Arc<[ReduceParentKernelRef]>>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an inner Arc?
Do we need an outer Arc?

pub type ReduceParentFn =
fn(child: &ArrayRef, parent: &ArrayRef, child_idx: usize) -> VortexResult<Option<ArrayRef>>;
/// Type-erased parent-reduce kernel stored in the session registry.
pub(crate) trait DynReduceParentKernel: Debug + Send + Sync + 'static {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We likely don't need Debug

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
parent: &ArrayRef,
child_idx: usize,
) -> VortexResult<Option<ArrayRef>> {
let Some(child) = child.as_opt::<V>() else {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we match a child and a parent in one match instead of doing this separately?

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
/// Registry of [`ReduceParentFn`]s, keyed by hashed `(parent, child)` pair.
type ReduceParentRegistry = ArcSwapMap<ReduceParentFnId, Arc<[ReduceParentFn]>>;
/// Registry of [`ReduceParentKernelRef`]s, keyed by hashed `(parent, child)` pair.
type ReduceParentRegistry = ArcSwapMap<ReduceParentFnId, Arc<[ReduceParentKernelRef]>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have an ArcSwapMap, then an Arc of slices, and each slice element is an Arc itself.
Can we remove two inner Arcs? We can use a Box of a slice of &'static dyn DynRule trait

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
Comment on lines +253 to +254
/// many threads at once. Borrowing this published value does not write to a shared reference
/// count, unlike taking a new snapshot of each map.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't document the implementation in the public contract

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
execute_parent: ExecuteParentRegistry,
}

#[derive(Debug, Default)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have here an Arc of a pair of an ArcSwap of a structure holding Arcs themselves, and also a Mutex.

Can we get rid of Arcs? Can we get rid of mutex and use a semaphore instead?

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
/// [`AnyScalarFn::id`].
///
/// Use [`Self::register_scalar_fn_reduce_parent_rule`] for a rule on a scalar function child.
pub fn register_reduce_parent_rule<V, R>(&self, parent: Id, child: V, rule: R)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it taking an impl ScalarFnVTable as first argument

/// function array. Rules for the exact ids are tried before those registered under
/// [`AnyScalarFn::id`].
///
/// Use [`Self::register_scalar_fn_reduce_parent_rule`] for a rule on a scalar function child.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rewrite this comment and/or trim it?

Comment thread vortex-array/src/optimizer/mod.rs Outdated
//! after a child is decoded, new `reduce_parent` rules may match that were previously blocked.
//!
//! There are three public entry points on [`ArrayOptimizer`]:
//! Parent-reduce rules live in the session's [`kernels::ArrayKernels`] registry, keyed by the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please trim this comment?

- register_reduce_parent_rule and register_scalar_fn_reduce_parent_rule
  take the parent vtable, such as Cast or Filter, instead of its Id.
- Remove the doc comments on the register_parent_rules functions.
- Keep the parent-reduce and execute-parent kernels each in one
  ArcSwap of a HashMap with boxed slices. Registration replaces the
  value with rcu.

Signed-off-by: Robert Kruszewski <github@robertk.io>
Signed-off-by: Robert Kruszewski <github@robertk.io>
let any_scalar_fn = AnyScalarFn.id();
let parent_ids = [
Some(parent.encoding_id()),
parent.is::<ScalarFn>().then_some(any_scalar_fn),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the future work: any-any case is imo the wrong path

if let Some(new_array) = reduce_parent(child, parent, slot_idx)? {
for (_kernel_idx, kernel) in reduce_parent_kernels.iter().enumerate() {
if let Some(new_array) = kernel.reduce_parent(child, parent, slot_idx)? {
vortex_ensure_eq!(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to a special #[cold] handler, this will be slow?

@myrrc myrrc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Can we please remove extra comments and make the function take an impl ScalarFnVtable instead of an Id so we'd have typecheck

Comment thread vortex-array/src/optimizer/kernels.rs Outdated
) -> VortexResult<Option<ArrayRef>>;
}

pub(crate) type ReduceParentKernelRef = Arc<dyn DynReduceParentKernel>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we need an Arc here. Yes, we have adapters, but given rules can have 'static lifetime, you can have a &'static dyn DynReduceParentKernel, in which case the adapter can be put in a const {} block which will also propagate 'static.
So a ref will also be a &'static dyn DynReduceParentKernel

Callers pass each rule as a const { &rule } block, and the registry keeps a &'static dyn reference to it instead of an Arc.

Signed-off-by: Robert Kruszewski <github@robertk.io>

This branch has not been deployed

No deployments
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