Skip to content

Make L2Denorm an array encoding instead of a scalar function - #9138

Draft
connortsui20 wants to merge 1 commit into
developfrom
ct/l2-denorm-encoding
Draft

Make L2Denorm an array encoding instead of a scalar function#9138
connortsui20 wants to merge 1 commit into
developfrom
ct/l2-denorm-encoding

Conversation

@connortsui20

@connortsui20 connortsui20 commented Aug 2, 2026

Copy link
Copy Markdown
Member

Rationale for this change

(semi-related) Tracking Issue: #9129

Proposed idiom: encodings may hold invariants over their children, scalar functions may not. A scalar function is an operation over any well-typed input. An encoding owns a physical decomposition, so it gets VTable::validate and its own constructors to enforce whatever has to hold between its children.

FoR is the one edge case that comes to mind. Its child is an offset from the reference, which is an invariant, but nothing enforces it and encoded + reference would work as a plain scalar function over any integer column.

L2Denorm was on the wrong side of the line. Its constructor took an ExecutionCtx and scanned both children for the unit-norm invariant, which ScalarFnFactoryExt cannot express. L2Norm reads its stored norms instead of recomputing, CosineSimilarity and InnerProduct reach into its children, L2DenormScheme already listed it in produced_encodings, and persisting it needed a ScalarFnArrayPlugin behind an env var.

What changes are included in this PR?

L2Denorm becomes a VTable in vortex-tensor/src/encodings/l2_denorm/ with slots normalized and norms.

  • try_new scans for the exact invariants, try_new_trusted skips the scan for lossy normalized children. Neither is unsafe, since breaking the contract gives wrong answers rather than undefined behavior.
  • Slice and filter push down into both children. Filter is new: the generic ScalarFnArray rule only fires when at most one child is non-constant.
  • L2DenormScheme cascades its children like TemporalScheme, which removes both HACK TO SUPPORT L2 DENORMALIZATION cases from CascadingCompressor.

What APIs are changed? Are there any user-facing changes?

Before After
scalar_fns::l2_denorm::* encodings::l2_denorm::*
L2Denorm::try_new_array(n, s, ctx) L2Denorm::try_new(n, s, ctx)
unsafe L2Denorm::new_array_unchecked(n, s) L2Denorm::try_new_trusted(n, s)
L2Denorm::new() removed
session.scalar_fns().register(L2Denorm) session.arrays().register(L2Denorm), unconditional
ScalarFnArrayPlugin::new(L2Denorm) removed

VX_SCALAR_FN_ARRAY_TENSOR_PLUGIN no longer gates L2Denorm, since the compressor can emit it. The array ID and L2DenormMetadata are unchanged, so the on-disk bytes match. L2DenormScheme stays out of ALL_SCHEMES.

`L2Denorm` was registered as a `ScalarFnVTable`, but it never behaved like
one. Its constructor took an `ExecutionCtx` and scanned both children to
enforce a unit-norm invariant, `L2Norm` read its stored norms instead of
recomputing, `CosineSimilarity` and `InnerProduct` reached into its physical
children, and the compressor scheme named it as a produced encoding. Those
are all properties of a physical decomposition, not of an operation over
arbitrary well-typed values.

Moves it to `vortex-tensor/src/encodings/l2_denorm/` as a real `VTable` with
two slots (`normalized`, `norms`). Structural validation runs on construction
and on deserialization, `try_new` additionally scans for the exact unit-norm
invariant, and `try_new_trusted` skips that scan for lossy normalized
children whose stored norms stay authoritative. Neither constructor is
`unsafe`, since violating the contract produces wrong answers rather than
undefined behavior.

The encoding keeps the `vortex.tensor.l2_denorm` array ID and the same
two-field metadata message, so the wire format is unchanged.

Slice and filter now push down into both children through `reduce_parent`.
The generic `ScalarFnArray` filter rule only fired when at most one child was
non-constant, which for this encoding was almost never.

Also makes `L2DenormScheme` cascade its two children like `TemporalScheme`
does, which lets both `HACK TO SUPPORT L2 DENORMALIZATION` special cases come
out of `CascadingCompressor`. The scheme now competes on measured size like
every other scheme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XEo4wnqKfAf1QLJbdrz76j
Signed-off-by: Claude <noreply@anthropic.com>
@connortsui20
connortsui20 force-pushed the ct/l2-denorm-encoding branch from 80daa53 to 63e459b Compare August 2, 2026 18:03
@claude claude Bot added the changelog/break A breaking API change label Aug 2, 2026
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