pub const SAE_FIXED_COORD_SLOT: usize = usize::MAX;
#[derive(Debug, Clone, Copy)]
pub enum RowGate {
Softmax { inv_tau: f64 },
PerAtomLogistic { inv_tau: f64 },
}
#[derive(Debug, Clone)]
pub struct AtomRowBasisJet {
pub phi: Vec<f64>,
pub d_phi: Vec<Vec<f64>>,
pub d2_phi: Vec<Vec<Vec<f64>>>,
pub decoder: Vec<Vec<f64>>,
pub latent_dim: usize,
}
impl AtomRowBasisJet {
fn out_dim(&self) -> usize {
self.decoder.first().map_or(0, Vec::len)
}
}
#[derive(Debug, Clone)]
pub struct SaeReconstructionRowProgram {
pub atoms: Vec<AtomRowBasisJet>,
pub gate_value: Vec<f64>,
pub logits: Vec<f64>,
pub gate_shift: Vec<f64>,
pub gate: RowGate,
pub logit_slot: Vec<Option<usize>>,
pub coord_slot: Vec<Vec<usize>>,
pub fixed_gate_value: Vec<Option<f64>>,
pub n_primaries: usize,
}
impl SaeReconstructionRowProgram {
#[must_use]
pub fn out_dim(&self) -> usize {
self.atoms.first().map_or(0, AtomRowBasisJet::out_dim)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SaeRowPrimary {
Logit { atom: usize },
Coord { atom: usize, axis: usize },
}
pub(crate) trait SaeOrder2RowProgramSource {
fn n_atoms(&self) -> usize;
fn out_dim(&self) -> usize;
fn n_primaries(&self) -> usize;
fn primary(&self, slot: usize) -> SaeRowPrimary;
fn gate_value(&self, atom: usize) -> f64;
fn atom_is_active(&self, atom: usize) -> bool;
fn fill_decoded(&self, atom: usize, out: &mut [f64]);
fn fill_decoded_first(&self, atom: usize, axis: usize, out: &mut [f64]);
fn fill_decoded_second(&self, atom: usize, axis_a: usize, axis_b: usize, out: &mut [f64]);
fn n_beta_borders(&self) -> usize;
fn beta_border_atom(&self, border: usize) -> usize;
fn beta_border_basis_value(&self, border: usize) -> f64;
fn beta_border_basis_first(&self, border: usize, axis: usize) -> f64;
fn beta_border_output(&self, border: usize) -> &[f64];
}
#[derive(Debug, Clone)]
pub(crate) struct SaeScheduledRowJets {
data: Vec<f64>,
q: usize,
p: usize,
n_beta: usize,
}
thread_local! {
static SAE_ORDER2_ROW_WORKSPACE: std::cell::RefCell<Vec<f64>> =
const { std::cell::RefCell::new(Vec::new()) };
}
impl SaeScheduledRowJets {
pub(crate) fn zeros(q: usize, p: usize, n_beta: usize) -> Self {
let first = q.checked_mul(p);
let second = q.checked_mul(q).and_then(|value| value.checked_mul(p));
let beta = n_beta.checked_mul(p);
let mixed = q.checked_mul(n_beta).and_then(|value| value.checked_mul(p));
let total = first
.and_then(|value| second.and_then(|next| value.checked_add(next)))
.and_then(|value| beta.and_then(|next| value.checked_add(next)))
.and_then(|value| {
mixed.and_then(|next| {
next.checked_mul(2)
.and_then(|twice| value.checked_add(twice))
})
})
.expect("SAE row-jet packed channel length overflow");
Self {
data: vec![0.0; total],
q,
p,
n_beta,
}
}
#[inline]
fn second_offset(&self) -> usize {
self.q * self.p
}
#[inline]
fn beta_offset(&self) -> usize {
self.second_offset() + self.q * self.q * self.p
}
#[inline]
fn beta_deriv_offset(&self) -> usize {
self.beta_offset() + self.n_beta * self.p
}
#[inline]
fn beta_l_deriv_offset(&self) -> usize {
self.beta_deriv_offset() + self.q * self.n_beta * self.p
}
#[inline]
pub(crate) fn q(&self) -> usize {
self.q
}
#[inline]
pub(crate) fn p(&self) -> usize {
self.p
}
#[inline]
pub(crate) fn n_beta(&self) -> usize {
self.n_beta
}
#[inline]
pub(crate) fn first(&self, primary: usize) -> &[f64] {
let start = primary * self.p;
&self.data[start..start + self.p]
}
#[inline]
pub(crate) fn first_mut(&mut self, primary: usize) -> &mut [f64] {
let start = primary * self.p;
&mut self.data[start..start + self.p]
}
#[inline]
pub(crate) fn second(&self, a: usize, b: usize) -> &[f64] {
let start = self.second_offset() + (a * self.q + b) * self.p;
&self.data[start..start + self.p]
}
#[inline]
pub(crate) fn second_mut(&mut self, a: usize, b: usize) -> &mut [f64] {
let start = self.second_offset() + (a * self.q + b) * self.p;
&mut self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta(&self, border: usize) -> &[f64] {
let start = self.beta_offset() + border * self.p;
&self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta_mut(&mut self, border: usize) -> &mut [f64] {
let start = self.beta_offset() + border * self.p;
&mut self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta_deriv(&self, primary: usize, border: usize) -> &[f64] {
let start = self.beta_deriv_offset() + (primary * self.n_beta + border) * self.p;
&self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta_deriv_mut(&mut self, primary: usize, border: usize) -> &mut [f64] {
let start = self.beta_deriv_offset() + (primary * self.n_beta + border) * self.p;
&mut self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta_l_deriv(&self, primary: usize, border: usize) -> &[f64] {
let start = self.beta_l_deriv_offset() + (primary * self.n_beta + border) * self.p;
&self.data[start..start + self.p]
}
#[inline]
pub(crate) fn beta_l_deriv_mut(&mut self, primary: usize, border: usize) -> &mut [f64] {
let start = self.beta_l_deriv_offset() + (primary * self.n_beta + border) * self.p;
&mut self.data[start..start + self.p]
}
}
struct SoftmaxMoment<'a, S> {
source: &'a S,
inv_tau: f64,
}
impl<S: SaeOrder2RowProgramSource> SoftmaxMoment<'_, S> {
#[inline]
fn expectation_first_coefficient(&self, atom_j: usize) -> f64 {
self.inv_tau * self.source.gate_value(atom_j)
}
#[inline]
fn expectation_second_coefficients(&self, atom_j: usize, atom_l: usize) -> (f64, f64) {
let z_j = self.source.gate_value(atom_j);
let z_l = self.source.gate_value(atom_l);
let diagonal = if atom_j == atom_l { 1.0 } else { 0.0 };
let common = self.inv_tau * self.inv_tau * z_j;
(common * (diagonal - z_l), -common * z_l)
}
#[inline]
fn gate_first(&self, gated_atom: usize, logit_atom: usize) -> f64 {
let diagonal = if gated_atom == logit_atom { 1.0 } else { 0.0 };
self.source.gate_value(gated_atom)
* (diagonal - self.source.gate_value(logit_atom))
* self.inv_tau
}
}
pub(crate) fn execute_softmax_row_program<S: SaeOrder2RowProgramSource>(
source: &S,
inv_tau: f64,
sqrt_row_w: f64,
) -> SaeScheduledRowJets {
let k = source.n_atoms();
let p = source.out_dim();
let q = source.n_primaries();
let n_beta = source.n_beta_borders();
let mut out = SaeScheduledRowJets::zeros(q, p, n_beta);
let decoded_len = k
.checked_mul(p)
.expect("SAE row-program decoded workspace length overflow");
let tail_len = p
.checked_mul(2)
.expect("SAE row-program scratch workspace length overflow");
let work_len = decoded_len
.checked_add(tail_len)
.expect("SAE row-program total workspace length overflow");
SAE_ORDER2_ROW_WORKSPACE.with(|workspace| {
let mut workspace = workspace.borrow_mut();
if workspace.len() < work_len {
workspace.resize(work_len, 0.0);
}
let work = &mut workspace[..work_len];
work.fill(0.0);
let (decoded, tail) = work.split_at_mut(decoded_len);
let (mean, scratch) = tail.split_at_mut(p);
for atom in 0..k {
if !source.atom_is_active(atom) {
continue;
}
let component = &mut decoded[atom * p..(atom + 1) * p];
source.fill_decoded(atom, component);
let z = source.gate_value(atom);
for c in 0..p {
mean[c] += z * component[c];
}
}
let moment = SoftmaxMoment { source, inv_tau };
for atom in 0..k {
let component = &mut decoded[atom * p..(atom + 1) * p];
for c in 0..p {
component[c] -= mean[c];
}
}
for slot_j in 0..q {
let SaeRowPrimary::Logit { atom: atom_j } = source.primary(slot_j) else {
continue;
};
let centered_j = &decoded[atom_j * p..(atom_j + 1) * p];
let first_coefficient = sqrt_row_w * moment.expectation_first_coefficient(atom_j);
for (target, &value) in out.first_mut(slot_j).iter_mut().zip(centered_j) {
*target = first_coefficient * value;
}
for slot_l in 0..q {
let SaeRowPrimary::Logit { atom: atom_l } = source.primary(slot_l) else {
continue;
};
let centered_l = &decoded[atom_l * p..(atom_l + 1) * p];
let (j_coefficient, l_coefficient) =
moment.expectation_second_coefficients(atom_j, atom_l);
let j_coefficient = sqrt_row_w * j_coefficient;
let l_coefficient = sqrt_row_w * l_coefficient;
for (c, target) in out.second_mut(slot_j, slot_l).iter_mut().enumerate() {
*target = j_coefficient * centered_j[c] + l_coefficient * centered_l[c];
}
}
}
for coord_slot in 0..q {
let SaeRowPrimary::Coord { atom, axis } = source.primary(coord_slot) else {
continue;
};
if !source.atom_is_active(atom) {
continue;
}
source.fill_decoded_first(atom, axis, scratch);
let z = source.gate_value(atom);
let coordinate_coefficient = z * sqrt_row_w;
for (target, &value) in out.first_mut(coord_slot).iter_mut().zip(&*scratch) {
*target = coordinate_coefficient * value;
}
for logit_slot in 0..q {
let SaeRowPrimary::Logit { atom: logit_atom } = source.primary(logit_slot) else {
continue;
};
let coefficient = moment.gate_first(atom, logit_atom) * sqrt_row_w;
for (target, &value) in out
.second_mut(logit_slot, coord_slot)
.iter_mut()
.zip(&*scratch)
{
*target = coefficient * value;
}
for (target, &value) in out
.second_mut(coord_slot, logit_slot)
.iter_mut()
.zip(&*scratch)
{
*target = coefficient * value;
}
}
}
for slot_a in 0..q {
let SaeRowPrimary::Coord {
atom: atom_a,
axis: axis_a,
} = source.primary(slot_a)
else {
continue;
};
if !source.atom_is_active(atom_a) {
continue;
}
for slot_b in 0..q {
let SaeRowPrimary::Coord {
atom: atom_b,
axis: axis_b,
} = source.primary(slot_b)
else {
continue;
};
if atom_a != atom_b {
continue;
}
source.fill_decoded_second(atom_a, axis_a, axis_b, scratch);
let coefficient = source.gate_value(atom_a) * sqrt_row_w;
for (target, &value) in out.second_mut(slot_a, slot_b).iter_mut().zip(&*scratch) {
*target = coefficient * value;
}
}
}
for border in 0..n_beta {
let atom = source.beta_border_atom(border);
if !source.atom_is_active(atom) {
continue;
}
let phi = source.beta_border_basis_value(border);
let output = source.beta_border_output(border);
let base = source.gate_value(atom) * phi * sqrt_row_w;
for (target, &value) in out.beta_mut(border).iter_mut().zip(output) {
*target = base * value;
}
for slot in 0..q {
let SaeRowPrimary::Logit { atom: logit_atom } = source.primary(slot) else {
continue;
};
let scalar = moment.gate_first(atom, logit_atom) * phi * sqrt_row_w;
for (target, &value) in out.beta_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
for (target, &value) in out.beta_l_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
}
for slot in 0..q {
let SaeRowPrimary::Coord {
atom: coord_atom,
axis,
} = source.primary(slot)
else {
continue;
};
if coord_atom != atom {
continue;
}
let scalar = source.gate_value(atom)
* source.beta_border_basis_first(border, axis)
* sqrt_row_w;
for (target, &value) in out.beta_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
for (target, &value) in out.beta_l_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
}
}
});
out
}
pub(crate) fn execute_independent_logistic_row_program<S: SaeOrder2RowProgramSource>(
source: &S,
inv_tau: f64,
sqrt_row_w: f64,
) -> SaeScheduledRowJets {
let k = source.n_atoms();
let p = source.out_dim();
let q = source.n_primaries();
let n_beta = source.n_beta_borders();
let mut out = SaeScheduledRowJets::zeros(q, p, n_beta);
let decoded_len = k
.checked_mul(p)
.expect("SAE independent row-program decoded workspace length overflow");
let work_len = decoded_len
.checked_add(p)
.expect("SAE independent row-program workspace length overflow");
SAE_ORDER2_ROW_WORKSPACE.with(|workspace| {
let mut workspace = workspace.borrow_mut();
if workspace.len() < work_len {
workspace.resize(work_len, 0.0);
}
let work = &mut workspace[..work_len];
work.fill(0.0);
let (decoded, scratch) = work.split_at_mut(decoded_len);
for atom in 0..k {
if source.atom_is_active(atom) {
source.fill_decoded(atom, &mut decoded[atom * p..(atom + 1) * p]);
}
}
for slot_a in 0..q {
let SaeRowPrimary::Logit { atom } = source.primary(slot_a) else {
continue;
};
let z = source.gate_value(atom);
let dz = inv_tau * z * (1.0 - z);
let d2z = inv_tau * inv_tau * z * (1.0 - z) * (1.0 - 2.0 * z);
let component = &decoded[atom * p..(atom + 1) * p];
for (target, &value) in out.first_mut(slot_a).iter_mut().zip(component) {
*target = sqrt_row_w * dz * value;
}
for slot_b in 0..q {
if source.primary(slot_b) != (SaeRowPrimary::Logit { atom }) {
continue;
}
for (target, &value) in out.second_mut(slot_a, slot_b).iter_mut().zip(component) {
*target = sqrt_row_w * d2z * value;
}
}
}
for coord_slot in 0..q {
let SaeRowPrimary::Coord { atom, axis } = source.primary(coord_slot) else {
continue;
};
if !source.atom_is_active(atom) {
continue;
}
let z = source.gate_value(atom);
source.fill_decoded_first(atom, axis, scratch);
for (target, &value) in out.first_mut(coord_slot).iter_mut().zip(&*scratch) {
*target = sqrt_row_w * z * value;
}
for logit_slot in 0..q {
if source.primary(logit_slot) != (SaeRowPrimary::Logit { atom }) {
continue;
}
let dz = inv_tau * z * (1.0 - z);
for (target, &value) in out
.second_mut(logit_slot, coord_slot)
.iter_mut()
.zip(&*scratch)
{
*target = sqrt_row_w * dz * value;
}
for (target, &value) in out
.second_mut(coord_slot, logit_slot)
.iter_mut()
.zip(&*scratch)
{
*target = sqrt_row_w * dz * value;
}
}
for other_slot in 0..q {
let SaeRowPrimary::Coord {
atom: other_atom,
axis: other_axis,
} = source.primary(other_slot)
else {
continue;
};
if other_atom != atom {
continue;
}
source.fill_decoded_second(atom, axis, other_axis, scratch);
for (target, &value) in out
.second_mut(coord_slot, other_slot)
.iter_mut()
.zip(&*scratch)
{
*target = sqrt_row_w * z * value;
}
}
}
for border in 0..n_beta {
let atom = source.beta_border_atom(border);
if !source.atom_is_active(atom) {
continue;
}
let z = source.gate_value(atom);
let phi = source.beta_border_basis_value(border);
let output = source.beta_border_output(border);
let base = sqrt_row_w * z * phi;
for (target, &value) in out.beta_mut(border).iter_mut().zip(output) {
*target = base * value;
}
for slot in 0..q {
let scalar = match source.primary(slot) {
SaeRowPrimary::Logit { atom: logit_atom } if logit_atom == atom => {
sqrt_row_w * inv_tau * z * (1.0 - z) * phi
}
SaeRowPrimary::Coord {
atom: coord_atom,
axis,
} if coord_atom == atom => {
sqrt_row_w * z * source.beta_border_basis_first(border, axis)
}
_ => 0.0,
};
if scalar == 0.0 {
continue;
}
for (target, &value) in out.beta_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
for (target, &value) in out.beta_l_deriv_mut(slot, border).iter_mut().zip(output) {
*target = scalar * value;
}
}
}
});
out
}