Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Palette512 / SignedMagnitude9: isolated arithmetic research gate

> **2026-10-27 · EXPERIMENT / NOT ADMITTED**
> Owner for this probe: **ndarray numerical kernels**. This note and `examples/palette512_probe.rs` do **not** approve a production codec, a BGZ codebook expansion, an ONNX/TFLite compatibility claim, a Quack/DT64 change, or a wire migration.

## Hypothesis

Use a separate sign plane with 64 unsigned magnitudes, giving 512 encodings per lane:

- `sign=0, magnitude=0`: valid +0.
- `sign=0, magnitude=1..255`: +1..+255.
- `sign=1, magnitude=1..255`: -1..-255.
- `sign=1, magnitude=0`: one NaN/invalid sentinel (negative zero).

There are **511 numeric values** and one invalid codeword, versus 255 numeric values and one NaN codeword for the existing 8-bit `Sym + NaNAtMin` domain (`-127..127` plus `-128`). At equal physical range, the added levels *could* allow finer quantization; calibrated quality and end-to-end cost are unmeasured.

A **64-lane** block stores **64 bytes of u8 magnitude + one u64 sign mask = 72 bytes**, or exactly 9 bits per lane. This is a packed block representation, **not** a 9-bit signed integer that native hardware can multiply as `i8`. Physical `repr(C)` footprint is *not* a persisted wire format.

### Arithmetic seam

For magnitudes `a,b` and signs `sa,sb`:

```
product = u8(a) * u8(b) // widened temporary for correctness
acc = (sa XOR sb) ? (acc - product) : (acc + product)
```

That is the proposed **sign-aware multiply-accumulate**: sign handling belongs inside the operation, not in a prior full-buffer decode. The scalar probe uses checked `i32` accumulation and fails closed on NaN or overflow, distinctly. SIMD fusion is a *hypothesis*, not an implemented vector kernel or performance result.

**Important:** x86 VNNI `VPDPBUSD` accepts `u8 × i8`, not two arbitrary `u8` magnitudes. Unsigned × unsigned exists elsewhere: AVX-VNNI-INT8 `VPDPBUUD`, NEON `UDOT`, wasm `extmul_*_u8x16`, or zero-extend plus `pmaddwd`. That list is from documentation, not measured. A lowering that splits lanes by `sign_a ^ sign_b` into two unsigned accumulators (`dot = pos - neg`) needs no correction identity. No SIMD efficiency is presumed.

## Semantic separation from current Palette256

**This is not a generic 512-centroid palette.** Existing `bgz-tensor` `WeightPalette` uses `u8` *archetype indices*, with 256 distinct codes and associated pair LUTs. The sign-magnitude encoding instead indexes a *structured signed numeric domain*. It cannot reinterpret arbitrary Palette256 identifiers by declaring half of their coordinates negative, and it cannot steal an independent presence bit without proving the original presence contract.

An actual **512-archetype** codebook would need a 9-bit index and new pair-LUT addressing: a flat 512×512×u16 table is 512 KiB, compared with 128 KiB at 256²; a 512×512×u16 compose table adds another 512 KiB rather than the existing 64 KiB u8 table. Any sign-symmetric factorization must be individually proven for the relevant distance/compose algebra. The present PR adds **none** of these tables.

**NaN = `-0` = presence clear over a stored 0** (settled in #381). Where the column has a presence plane, NaN is a cleared presence bit with magnitude 0 stored under it, and "sign set, magnitude 0" is the same NaN written without the plane. All of −255..=+255 then stay values, and NaN never reaches a multiply: lanes are selected by `presence_a & presence_b & filter` first. A codec that needs *several* distinguishable invalid states (NULL, unknown, denied) still needs more than one codeword. Quack's predicate/null semantics remain its own contract.

## First executable evidence (this PR)

`cargo test --example palette512_probe`

Five tests:

1. Enumerate all 512 codewords and prove round-trip, **511 numeric + 1 NaN**.
2. Enumerate all **262,144 ordered pairs** of codewords; compare sign-aware `mul_add` against an independently decoded signed multiplication plus a nonzero accumulator.
3. Check a 64-lane sign-plane block against an independent wide `i64` dot-product reference and assert its 72-byte in-memory footprint.
4. Verify NaN propagation even when the other operand is zero.
5. Distinguish overflow from NaN, including positive and negative accumulator boundaries.

This is a reference-quality **scalar example**, not SIMD code, a production API, or a calibrated numerical comparison. All five tests pass (`cargo test --example palette512_probe`, rustc 1.99.0), including the 262,144-pair check.

## Admission and falsification

- **Correctness falsifier:** Any of the 512² pairs disagrees with independently decoded arithmetic; positive zero aliases negative-zero sentinel; NaN silently becomes zero; overflow is misreported.
- **Representation falsifier:** Existing source presence/null meaning is lost, or conversion cannot preserve the independently required missingness states.
- **Numerical falsifier:** A *held-out, fixed dynamic-range* calibration shows no meaningful improvement in endpoint saturation/tail error/task-level accuracy versus current `Codec8<Sym,NaNAtMin>`.
- **Performance falsifier:** After fair end-to-end measurement, packing+sign correction outweighs quality gains or unpacks/widening dominate; evaluate AVX2, AVX-512/VNNI, NEON, WASM, scalar separately, without assuming each ISA offers the same fused instruction.
- **Algebra falsifier:** Any attempted folded sign-aware LUT violates the independently defined distance/compose relation, especially zero/NaN, negation, and multi-hop cases.

**Next gate, separately approved:** implement SIMD lowering and benchmark sign-aware `mul_add` vs established `i8` and `u8 × i8` kernels; measure cycles/value, packing cost, throughput and errors separately. A later **independent** gate may investigate compressed/factored 512-value LUT laws if relevant.

## Related authorities (read-only links)

- [ndarray Codec8 plan](https://github.com/AdaWorldAPI/ndarray/blob/master/.claude/plans/quantized-codec8-v2.md), [#370](https://github.com/AdaWorldAPI/ndarray/pull/370): compile-time numeric codec; **unchanged**.
- [Existing VNNI GEMM](https://github.com/AdaWorldAPI/ndarray/blob/master/src/hpc/vnni_gemm.rs): `u8 × i8 → i32`; **not** a ready-made unsigned-unsigned signed-mask MAC.
- [BGZ palette](https://github.com/AdaWorldAPI/lance-graph/blob/main/crates/bgz-tensor/src/palette.rs), [attention LUT](https://github.com/AdaWorldAPI/lance-graph/blob/main/crates/bgz-tensor/src/attention.rs): **distinct** archetype-index contract.
- [D-NAM-PZL / EXO-152](https://linear.app/exo-red/issue/EXO-152/d-nam-capstone-normalized-native-algebra-onnxtflitemlir-dto-method), [EXO-50 coordination](https://linear.app/exo-red/issue/EXO-50/agent-message-bus-gpt-xgrok-ccsession-id): context only; no production permission implied.
204 changes: 204 additions & 0 deletions examples/palette512_probe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//! Palette512 / SignedMagnitude9 isolated arithmetic probe.
//!
//! This is NOT a 512-centroid codebook. It uses one u8 magnitude per lane
//! and a 64-bit sign plane for 64 lanes: 72 bytes total. Of 512 codewords,
//! sign=1,magnitude=0 is the sole NaN sentinel; the other 511 are numbers.
//! No production codec, public ABI, calibration, or wire format is changed.

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ProbeError {
Nan,
Overflow,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SignedMagnitude9 {
magnitude: u8,
negative: bool,
}

impl SignedMagnitude9 {
/// Bit 8 is the sign; bits 0..7 are the magnitude.
fn from_code(code: u16) -> Self {
assert!(code < 512, "not a 9-bit codeword");
Self {
magnitude: code as u8,
negative: (code & 0x100) != 0,
}
}

fn code(self) -> u16 {
u16::from(self.magnitude) | ((self.negative as u16) << 8)
}

fn value(self) -> Result<i16, ProbeError> {
if self.negative && self.magnitude == 0 {
return Err(ProbeError::Nan);
}
let value = i16::from(self.magnitude);
Ok(if self.negative { -value } else { value })
}

/// Handle sign in the accumulator without a signed-8-bit operand.
fn mul_add(self, rhs: Self, acc: i32) -> Result<i32, ProbeError> {
if (self.negative && self.magnitude == 0) || (rhs.negative && rhs.magnitude == 0) {
return Err(ProbeError::Nan);
}
let product = i32::from(self.magnitude) * i32::from(rhs.magnitude);
if self.negative ^ rhs.negative {
acc.checked_sub(product).ok_or(ProbeError::Overflow)
} else {
acc.checked_add(product).ok_or(ProbeError::Overflow)
}
}
}

/// Bit i of signs belongs to lane i of magnitudes.
/// repr(C) pins the 72-byte in-memory footprint, NOT a persisted wire format.
#[repr(C)]
#[derive(Clone, Debug)]
struct MagnitudeSignBlock64 {
magnitudes: [u8; 64],
signs: u64,
}

impl MagnitudeSignBlock64 {
fn from_codes(codes: &[u16; 64]) -> Self {
let mut magnitudes = [0u8; 64];
let mut signs = 0u64;
for (i, &code) in codes.iter().enumerate() {
let lane = SignedMagnitude9::from_code(code);
magnitudes[i] = lane.magnitude;
if lane.negative {
signs |= 1u64 << i;
}
}
Self { magnitudes, signs }
}

fn lane(&self, i: usize) -> SignedMagnitude9 {
SignedMagnitude9 {
magnitude: self.magnitudes[i],
negative: ((self.signs >> i) & 1) != 0,
}
}

/// Scalar semantic reference for future SIMD lowering, not a speed claim.
fn fused_dot(&self, rhs: &Self) -> Result<i32, ProbeError> {
let mut acc = 0i32;
for i in 0..64 {
acc = self.lane(i).mul_add(rhs.lane(i), acc)?;
}
Ok(acc)
}
}

fn main() {
let plus = SignedMagnitude9::from_code(0x0ff);
let minus = SignedMagnitude9::from_code(0x1ff);
let signed_product = plus.mul_add(minus, 0).expect("valid product");
let lanes = [plus.code(); 64];
let block = MagnitudeSignBlock64::from_codes(&lanes);
let dot = block.fused_dot(&block).expect("valid 64-lane dot");
println!(
"Palette512 SM9: +{} * {} = {}, 64-lane dot = {}, block = {} B",
plus.value().expect("valid value"),
minus.value().expect("valid value"),
signed_product,
dot,
std::mem::size_of::<MagnitudeSignBlock64>()
);
println!("Run cargo test --example palette512_probe for exhaustive evidence");
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn all_512_codewords_are_unique_and_one_is_nan() {
let mut valid = 0;
let mut nan = 0;
for code in 0..512u16 {
let lane = SignedMagnitude9::from_code(code);
assert_eq!(lane.code(), code);
match lane.value() {
Ok(v) => {
assert!((-255..=255).contains(&v));
let expected = if code < 256 {
code as i16
} else {
-((code & 255) as i16)
};
assert_eq!(v, expected);
valid += 1;
}
Err(ProbeError::Nan) => {
assert_eq!(code, 0x100);
nan += 1;
}
Err(ProbeError::Overflow) => panic!("decoding cannot overflow"),
}
}
assert_eq!((valid, nan), (511, 1));
assert_eq!(SignedMagnitude9::from_code(0).value(), Ok(0));
assert_eq!(SignedMagnitude9::from_code(0x100).value(), Err(ProbeError::Nan));
}

#[test]
fn exhaustive_fused_mul_add_matches_independent_signed_oracle() {
for a_code in 0..512u16 {
for b_code in 0..512u16 {
let a = SignedMagnitude9::from_code(a_code);
let b = SignedMagnitude9::from_code(b_code);
let expected = match (a.value(), b.value()) {
(Ok(x), Ok(y)) => Ok(i32::from(x) * i32::from(y) + 17),
_ => Err(ProbeError::Nan),
};
assert_eq!(a.mul_add(b, 17), expected, "a={a_code},b={b_code}");
}
}
}

#[test]
fn sixty_four_lane_sign_plane_matches_wide_reference() {
assert_eq!(std::mem::size_of::<MagnitudeSignBlock64>(), 72);
let lhs = std::array::from_fn(|i| match i % 4 {
0 => 0x0FF, // +255
1 => 0x1FF, // -255
2 => 0x000, // +0
_ => 0x100 | i as u16, // negative magnitudes 3..63
});
let rhs = std::array::from_fn(|i| match i % 3 {
0 => 0x1FF,
1 => 0x0FF,
_ => 0x000,
});
let a = MagnitudeSignBlock64::from_codes(&lhs);
let b = MagnitudeSignBlock64::from_codes(&rhs);
let wide_reference: i64 = (0..64)
.map(|i| i64::from(a.lane(i).value().unwrap()) * i64::from(b.lane(i).value().unwrap()))
.sum();
assert_eq!(a.fused_dot(&b), Ok(wide_reference as i32));
assert_eq!(wide_reference, i64::from(wide_reference as i32));
}

#[test]
fn nan_propagates_even_when_other_operand_is_zero() {
let zeros = [0u16; 64];
let mut negzero = zeros;
negzero[19] = 0x100;
let z = MagnitudeSignBlock64::from_codes(&zeros);
let n = MagnitudeSignBlock64::from_codes(&negzero);
assert_eq!(n.fused_dot(&z), Err(ProbeError::Nan));
assert_eq!(z.fused_dot(&n), Err(ProbeError::Nan));
}

#[test]
fn overflow_is_not_misreported_as_nan() {
let plus = SignedMagnitude9::from_code(255);
let minus = SignedMagnitude9::from_code(511);
assert_eq!(plus.mul_add(plus, i32::MAX), Err(ProbeError::Overflow));
assert_eq!(minus.mul_add(plus, i32::MIN), Err(ProbeError::Overflow));
}
}
Loading