#![warn(missing_docs)]
extern crate self as eredu_nn;
use std::fmt::Debug;
use eredu_checkpoint::LinearFormat;
pub use eredu_nn_macros::Parameterized;
pub mod multimodal;
pub mod operation_geometry;
pub mod routing_intervention;
pub mod sequence_layout;
#[derive(Debug, Clone, thiserror::Error)]
#[error("{message}")]
pub struct Error {
message: String,
}
impl Error {
pub fn backend(error: impl std::fmt::Display) -> Self {
Self {
message: error.to_string(),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Index {
Full,
At(i32),
Range(i32, i32),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PadMode {
Constant,
Edge,
}
#[derive(Debug, Clone, Copy)]
pub enum AttentionMask<'a, T> {
None,
Causal,
Tensor(&'a T),
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct HeadExpansion {
pub axis: usize,
pub source_heads: i32,
pub target_heads: i32,
}
impl HeadExpansion {
pub fn validate<T: Tensor>(&self, input: &T) -> Result<(), Error> {
let shape = input.shape();
if self.source_heads <= 0
|| self.target_heads <= 0
|| self.target_heads % self.source_heads != 0
|| shape.get(self.axis).copied() != Some(self.source_heads)
{
return Err(Error::backend(format!(
"invalid head expansion axis={} source={} target={} shape={shape:?}",
self.axis, self.source_heads, self.target_heads
)));
}
Ok(())
}
pub const fn repeats(self) -> i32 {
self.target_heads / self.source_heads
}
}
#[derive(Debug, Clone, Copy)]
pub struct SegmentedAttentionInput<'a, T> {
pub queries: &'a T,
pub keys: &'a T,
pub values: &'a T,
pub segment_lengths: &'a [i32],
pub scale: f32,
}
impl<T: Tensor> SegmentedAttentionInput<'_, T> {
pub fn validate(&self) -> Result<(), Error> {
let query = self.queries.shape();
let key = self.keys.shape();
let value = self.values.shape();
if query.len() != 3
|| key.len() != 3
|| value.len() != 3
|| query[0] <= 0
|| query[1] <= 0
|| query[2] <= 0
|| query[0] != key[0]
|| query[0] != value[0]
|| query[1] != key[1]
|| query[1] != value[1]
|| query[2] != key[2]
|| value[2] <= 0
|| !self.scale.is_finite()
|| self.scale <= 0.0
{
return Err(Error::backend(format!(
"invalid segmented attention geometry q={query:?} k={key:?} v={value:?} scale={}",
self.scale
)));
}
validate_segment_lengths(query[0], self.segment_lengths)
}
}
pub fn validate_segment_lengths(total: i32, segment_lengths: &[i32]) -> Result<(), Error> {
if total <= 0 || segment_lengths.is_empty() {
return Err(Error::backend(format!(
"segmented attention requires a positive total and at least one segment, got total={total} segments={segment_lengths:?}"
)));
}
let mut sum = 0i32;
for &length in segment_lengths {
if length <= 0 {
return Err(Error::backend(format!(
"segmented attention lengths must be positive, got {segment_lengths:?}"
)));
}
sum = sum.checked_add(length).ok_or_else(|| {
Error::backend("segmented attention length total overflowed signed 32-bit geometry")
})?;
if sum > total {
return Err(Error::backend(format!(
"segmented attention lengths exceed total {total}: {segment_lengths:?}"
)));
}
}
if sum != total {
return Err(Error::backend(format!(
"segmented attention lengths sum to {sum}, expected {total}"
)));
}
Ok(())
}
pub fn reference_expand_heads(
values: &[f32],
shape: &[usize],
axis: usize,
target_heads: usize,
) -> Result<(Vec<f32>, Vec<usize>), Error> {
let source_heads = shape.get(axis).copied().unwrap_or(0);
if source_heads == 0 || target_heads == 0 || !target_heads.is_multiple_of(source_heads) {
return Err(Error::backend(format!(
"invalid reference head expansion axis={axis} target={target_heads} shape={shape:?}"
)));
}
let elements = shape.iter().try_fold(1usize, |total, width| {
total
.checked_mul(*width)
.ok_or_else(|| Error::backend("reference head expansion element count overflowed"))
})?;
if elements != values.len() {
return Err(Error::backend(format!(
"reference head expansion expected {elements} values, got {}",
values.len()
)));
}
let outer = shape[..axis].iter().product::<usize>();
let inner = shape[axis + 1..].iter().product::<usize>();
let repeats = target_heads / source_heads;
let mut output = Vec::with_capacity(outer * target_heads * inner);
for outer_index in 0..outer {
for source in 0..source_heads {
let start = (outer_index * source_heads + source) * inner;
for _ in 0..repeats {
output.extend_from_slice(&values[start..start + inner]);
}
}
}
let mut output_shape = shape.to_vec();
output_shape[axis] = target_heads;
Ok((output, output_shape))
}
#[allow(clippy::too_many_arguments)]
pub fn reference_segmented_attention(
tokens: usize,
heads: usize,
dimensions: usize,
value_dimensions: usize,
queries: &[f32],
keys: &[f32],
values: &[f32],
segment_lengths: &[i32],
scale: f32,
) -> Result<Vec<f32>, Error> {
let tokens_i32 = i32::try_from(tokens)
.map_err(|_| Error::backend("reference segmented attention token count exceeds i32"))?;
validate_segment_lengths(tokens_i32, segment_lengths)?;
if heads == 0
|| dimensions == 0
|| value_dimensions == 0
|| !scale.is_finite()
|| scale <= 0.0
|| queries.len() != tokens * heads * dimensions
|| keys.len() != tokens * heads * dimensions
|| values.len() != tokens * heads * value_dimensions
{
return Err(Error::backend(
"invalid reference segmented attention geometry",
));
}
let mut output = vec![0.0f32; tokens * heads * value_dimensions];
let mut segment_start = 0usize;
for &length in segment_lengths {
let length = usize::try_from(length).expect("validated positive segment length");
let segment_end = segment_start + length;
for query_token in segment_start..segment_end {
for head in 0..heads {
let mut scores = Vec::with_capacity(length);
for key_token in segment_start..segment_end {
let mut score = 0.0f32;
for dimension in 0..dimensions {
let query_index = (query_token * heads + head) * dimensions + dimension;
let key_index = (key_token * heads + head) * dimensions + dimension;
score += queries[query_index] * keys[key_index];
}
scores.push(score * scale);
}
let maximum = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
let denominator = scores
.iter_mut()
.map(|score| {
*score = (*score - maximum).exp();
*score
})
.sum::<f32>();
for value_dimension in 0..value_dimensions {
let mut result = 0.0f32;
for (relative, key_token) in (segment_start..segment_end).enumerate() {
let value_index =
(key_token * heads + head) * value_dimensions + value_dimension;
result += scores[relative] / denominator * values[value_index];
}
let output_index =
(query_token * heads + head) * value_dimensions + value_dimension;
output[output_index] = result;
}
}
}
segment_start = segment_end;
}
Ok(output)
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum AttentionValueSource {
Projected,
ReuseKey,
}
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum AttentionStateSource {
Local {
value: AttentionValueSource,
},
Publish {
value: AttentionValueSource,
},
Shared,
}
impl AttentionStateSource {
pub const fn owns_state(self) -> bool {
!matches!(self, Self::Shared)
}
pub const fn publishes_state(self) -> bool {
matches!(self, Self::Publish { .. })
}
pub const fn value(self) -> Option<AttentionValueSource> {
match self {
Self::Local { value } | Self::Publish { value } => Some(value),
Self::Shared => None,
}
}
}
#[cfg(test)]
mod attention_state_source_tests {
use super::{AttentionStateSource, AttentionValueSource};
#[test]
fn ownership_publication_and_key_as_value_are_independent() {
let local = AttentionStateSource::Local {
value: AttentionValueSource::Projected,
};
let publisher = AttentionStateSource::Publish {
value: AttentionValueSource::ReuseKey,
};
assert!(local.owns_state());
assert!(!local.publishes_state());
assert_eq!(local.value(), Some(AttentionValueSource::Projected));
assert!(publisher.owns_state());
assert!(publisher.publishes_state());
assert_eq!(publisher.value(), Some(AttentionValueSource::ReuseKey));
assert!(!AttentionStateSource::Shared.owns_state());
assert_eq!(AttentionStateSource::Shared.value(), None);
}
}
#[cfg(test)]
mod recurrent_encoder_contract_tests {
use super::{
reference_expand_heads, reference_segmented_attention, validate_segment_lengths,
NormalizationConstructionSpec, NormalizationScale,
};
#[test]
fn normalization_construction_rejects_invalid_geometry_and_scalars() {
assert!(NormalizationConstructionSpec {
dimensions: 8,
epsilon: 1e-6,
scale: NormalizationScale::Unit,
}
.validate()
.is_ok());
assert!(NormalizationConstructionSpec {
dimensions: 0,
epsilon: 1e-6,
scale: NormalizationScale::Unit,
}
.validate()
.is_err());
assert!(NormalizationConstructionSpec {
dimensions: 8,
epsilon: f32::NAN,
scale: NormalizationScale::Unit,
}
.validate()
.is_err());
}
#[test]
fn head_expansion_reference_preserves_grouped_row_order() {
let (values, shape) =
reference_expand_heads(&[1.0, 2.0, 3.0, 4.0], &[1, 2, 2], 1, 4).unwrap();
assert_eq!(shape, vec![1, 4, 2]);
assert_eq!(values, vec![1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0]);
assert!(reference_expand_heads(&[1.0, 2.0], &[1, 2], 1, 3).is_err());
}
#[test]
fn segmented_attention_reference_is_independent_per_contiguous_segment() {
let output = reference_segmented_attention(
3,
1,
1,
1,
&[0.0, 0.0, 0.0],
&[0.0, 0.0, 0.0],
&[2.0, 4.0, 9.0],
&[2, 1],
1.0,
)
.unwrap();
assert_eq!(output, vec![3.0, 3.0, 9.0]);
assert!(validate_segment_lengths(3, &[]).is_err());
assert!(validate_segment_lengths(3, &[2, 0, 1]).is_err());
assert!(validate_segment_lengths(3, &[2]).is_err());
assert!(validate_segment_lengths(3, &[2, 2]).is_err());
assert!(validate_segment_lengths(i32::MAX, &[i32::MAX, 1]).is_err());
}
}
#[derive(Debug, Clone, Copy)]
pub struct IndexedAttentionInput<'a, T> {
pub queries: &'a T,
pub local_keys: &'a T,
pub local_values: &'a T,
pub pooled_keys: &'a T,
pub pooled_values: &'a T,
pub selected_positions: &'a T,
pub scale: f32,
pub local_mask: Option<&'a T>,
pub pooled_mask: Option<&'a T>,
pub sinks: Option<&'a T>,
}
#[derive(Debug, Clone, Copy)]
pub struct PooledAttentionInput<'a, T> {
pub queries: &'a T,
pub local: &'a T,
pub pooled: &'a T,
pub scale: f32,
pub local_mask: Option<&'a T>,
pub pooled_mask: Option<&'a T>,
pub sinks: Option<&'a T>,
}
#[derive(Debug, Clone, Copy)]
pub struct PooledPositionInput<'a, T> {
pub queries: &'a T,
pub pooled_keys: &'a T,
pub head_weights: &'a T,
pub mask: Option<&'a T>,
pub top_k: i32,
pub scale: f32,
pub head_scale: f32,
}
#[derive(Debug, Clone, Copy)]
pub struct RelativeAttentionInput<'a, T> {
pub queries: &'a T,
pub keys: &'a T,
pub values: &'a T,
pub profiles: &'a T,
pub query_offset: i32,
pub key_offset: i32,
pub window: Option<i32>,
pub log_scaling_floor: Option<i32>,
pub log_scaling_alpha: f32,
}
impl<T: Tensor> RelativeAttentionInput<'_, T> {
pub fn validate(&self) -> Result<(), Error> {
let query = self.queries.shape();
let key = self.keys.shape();
let value = self.values.shape();
let profiles = self.profiles.shape();
if query.len() != 4
|| key.len() != 4
|| value.len() != 4
|| profiles.len() != 4
|| query[0] != key[0]
|| key != value
|| query[2] != profiles[2]
|| query[0] != profiles[0]
|| query[1] != profiles[1]
|| query[3] != key[3]
|| query[1] % key[1] != 0
|| profiles[3] <= 0
|| self.window.is_some_and(|window| window <= 0)
|| self.log_scaling_floor.is_some_and(|floor| floor <= 0)
|| !self.log_scaling_alpha.is_finite()
{
return Err(Error::backend(format!(
"invalid relative attention geometry q={query:?} k={key:?} v={value:?} profiles={profiles:?} window={:?} floor={:?} alpha={}",
self.window, self.log_scaling_floor, self.log_scaling_alpha
)));
}
Ok(())
}
}
impl<T: Tensor> IndexedAttentionInput<'_, T> {
pub fn validate(&self) -> Result<(), Error> {
let query = self.queries.shape();
let local_keys = self.local_keys.shape();
let local_values = self.local_values.shape();
let pooled_keys = self.pooled_keys.shape();
let pooled_values = self.pooled_values.shape();
let selected = self.selected_positions.shape();
if query.len() != 4
|| local_keys.len() != 3
|| local_values.len() != 3
|| pooled_keys.len() != 3
|| pooled_values.len() != 3
|| selected.len() != 3
|| query[0] != local_keys[0]
|| query[0] != local_values[0]
|| query[0] != pooled_keys[0]
|| query[0] != pooled_values[0]
|| query[0] != selected[0]
|| query[2] != selected[1]
|| query[3] != local_keys[2]
|| query[3] != pooled_keys[2]
|| local_keys[1] != local_values[1]
|| pooled_keys[1] != pooled_values[1]
|| local_values[2] != pooled_values[2]
|| selected[2] <= 0
|| pooled_keys[1] <= 0
{
return Err(Error::backend(format!(
"invalid indexed-attention geometry: queries={query:?} local_keys={local_keys:?} local_values={local_values:?} pooled_keys={pooled_keys:?} pooled_values={pooled_values:?} selected={selected:?}"
)));
}
if !self.scale.is_finite() || self.scale <= 0.0 {
return Err(Error::backend(format!(
"indexed-attention scale must be finite and positive, got {}",
self.scale
)));
}
if let Some(sinks) = self.sinks {
if sinks.shape() != [query[1]] {
return Err(Error::backend(format!(
"indexed-attention sinks require shape [{}], got {:?}",
query[1],
sinks.shape()
)));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ParameterId(String);
impl ParameterId {
pub fn new(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
let id = id.into();
if id.trim().is_empty() {
return Err(ParameterTopologyError::EmptyId);
}
Ok(Self(id))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for ParameterId {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParameterSpec {
pub id: ParameterId,
pub trainable: bool,
pub alias_of: Option<ParameterId>,
pub group: Option<String>,
pub linear_companion: Option<LinearCompanionRole>,
pub linear_companion_of: Option<ParameterId>,
}
impl ParameterSpec {
pub fn trainable(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
Ok(Self {
id: ParameterId::new(id)?,
trainable: true,
alias_of: None,
group: None,
linear_companion: None,
linear_companion_of: None,
})
}
}
#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub enum LinearCompanionRole {
Scale,
AffineBias,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParameterMetadata {
pub id: ParameterId,
pub trainable: bool,
pub alias_of: Option<ParameterId>,
pub group: Option<String>,
pub linear_companion: Option<LinearCompanionRole>,
pub linear_companion_of: Option<ParameterId>,
}
impl ParameterMetadata {
pub fn from_spec(spec: &ParameterSpec, trainable: bool) -> Self {
Self {
id: spec.id.clone(),
trainable,
alias_of: spec.alias_of.clone(),
group: spec.group.clone(),
linear_companion: spec.linear_companion,
linear_companion_of: spec.linear_companion_of.clone(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
pub enum ParameterTopologyError {
#[error("parameter identity must not be empty")]
EmptyId,
#[error("parameter identity {0} is duplicated")]
DuplicateId(ParameterId),
#[error("parameter alias {alias} points to missing destination {destination}")]
MissingAliasDestination {
alias: ParameterId,
destination: ParameterId,
},
#[error("parameter alias {alias} points to non-authoritative alias {destination}")]
AliasTargetsAlias {
alias: ParameterId,
destination: ParameterId,
},
}
pub trait ParameterVisitor<'a, T: 'a> {
fn visit(&mut self, metadata: ParameterMetadata, value: &'a T);
}
pub trait ParameterVisitorMut<'a, T: 'a> {
fn visit_mut(&mut self, metadata: ParameterMetadata, value: &'a mut T);
}
pub trait Parameterized<T: 'static> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>;
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>;
fn set_trainable(&mut self, trainable: bool);
}
pub fn validate_parameter_topology<T: 'static, M>(
module: &M,
) -> Result<Vec<ParameterMetadata>, ParameterTopologyError>
where
M: Parameterized<T>,
{
struct Collector(Vec<ParameterMetadata>);
impl<'a, T: 'a> ParameterVisitor<'a, T> for Collector {
fn visit(&mut self, metadata: ParameterMetadata, _value: &'a T) {
self.0.push(metadata);
}
}
let mut collector = Collector(Vec::new());
module.visit_parameters(&mut collector);
let mut topology = std::collections::BTreeMap::new();
for metadata in &collector.0 {
if topology.insert(metadata.id.clone(), metadata).is_some() {
return Err(ParameterTopologyError::DuplicateId(metadata.id.clone()));
}
}
for metadata in &collector.0 {
let Some(destination) = &metadata.alias_of else {
continue;
};
let Some(target) = topology.get(destination) else {
return Err(ParameterTopologyError::MissingAliasDestination {
alias: metadata.id.clone(),
destination: destination.clone(),
});
};
if target.alias_of.is_some() {
return Err(ParameterTopologyError::AliasTargetsAlias {
alias: metadata.id.clone(),
destination: destination.clone(),
});
}
}
Ok(collector.0)
}
#[derive(Debug, Clone)]
pub struct LinearSpec {
pub input: i32,
pub output: i32,
pub weight: ParameterSpec,
pub bias: Option<ParameterSpec>,
pub format: LinearFormatSpec,
}
#[derive(Debug, Clone)]
pub struct EmbeddingSpec {
pub vocabulary: i32,
pub dimensions: i32,
pub weight: ParameterSpec,
pub format: LinearFormatSpec,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LinearFormatSpec {
format: LinearFormat,
scale: Option<ParameterSpec>,
affine_bias: Option<ParameterSpec>,
}
impl LinearFormatSpec {
pub fn unscaled(format: LinearFormat) -> Result<Self, Error> {
let spec = Self {
format,
scale: None,
affine_bias: None,
};
spec.validate()?;
Ok(spec)
}
pub fn scaled(format: LinearFormat, scale: ParameterSpec) -> Result<Self, Error> {
let mut scale = scale;
scale.linear_companion = Some(LinearCompanionRole::Scale);
scale.linear_companion_of = None;
let spec = Self {
format,
scale: Some(scale),
affine_bias: None,
};
spec.validate()?;
Ok(spec)
}
pub fn affine(
format: LinearFormat,
scale: ParameterSpec,
affine_bias: ParameterSpec,
) -> Result<Self, Error> {
let mut scale = scale;
scale.linear_companion = Some(LinearCompanionRole::Scale);
scale.linear_companion_of = None;
let mut affine_bias = affine_bias;
affine_bias.linear_companion = Some(LinearCompanionRole::AffineBias);
affine_bias.linear_companion_of = None;
let spec = Self {
format,
scale: Some(scale),
affine_bias: Some(affine_bias),
};
spec.validate()?;
Ok(spec)
}
pub const fn encoding(&self) -> LinearFormat {
self.format
}
pub const fn scale(&self) -> Option<&ParameterSpec> {
self.scale.as_ref()
}
pub const fn affine_bias(&self) -> Option<&ParameterSpec> {
self.affine_bias.as_ref()
}
pub fn validate(&self) -> Result<(), Error> {
self.format.validate().map_err(Error::backend)?;
let expected = match self.format {
LinearFormat::Dense | LinearFormat::GgufIQuant { .. } => (false, false),
LinearFormat::MxFp4 | LinearFormat::E4M3BlockFp8(_) => (true, false),
LinearFormat::Affine(_) => (true, true),
};
if (self.scale.is_some(), self.affine_bias.is_some()) != expected {
return Err(Error::backend(format!(
"linear format {:?} requires scale/bias companions {:?}, got {:?}",
self.format,
expected,
(self.scale.is_some(), self.affine_bias.is_some())
)));
}
if self
.scale
.as_ref()
.zip(self.affine_bias.as_ref())
.is_some_and(|(scale, bias)| scale.id == bias.id)
{
return Err(Error::backend(
"linear scale and affine-bias companions require distinct identities",
));
}
if self
.scale
.as_ref()
.is_some_and(|scale| scale.linear_companion != Some(LinearCompanionRole::Scale))
|| self
.affine_bias
.as_ref()
.is_some_and(|bias| bias.linear_companion != Some(LinearCompanionRole::AffineBias))
{
return Err(Error::backend(
"linear format companions have invalid semantic roles",
));
}
Ok(())
}
pub fn validate_for_weight(&self, weight: &ParameterSpec) -> Result<(), Error> {
self.validate()?;
if self
.scale
.as_ref()
.into_iter()
.chain(self.affine_bias.as_ref())
.any(|companion| companion.id == weight.id)
{
return Err(Error::backend(format!(
"linear format companion reuses primary weight identity {}",
weight.id
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VocabularyParallelRange {
pub global_vocabulary: usize,
pub local: std::ops::Range<usize>,
}
impl VocabularyParallelRange {
pub fn validate(&self) -> Result<(), Error> {
if self.global_vocabulary == 0
|| self.local.is_empty()
|| self.local.end > self.global_vocabulary
{
return Err(Error::backend(format!(
"invalid vocabulary-parallel range {:?} of {}",
self.local, self.global_vocabulary
)));
}
Ok(())
}
pub fn validate_global_rows(&self, rows: i32) -> Result<(), Error> {
self.validate()?;
if usize::try_from(rows).ok() != Some(self.global_vocabulary) {
return Err(Error::backend(format!(
"vocabulary-parallel operator declares {rows} rows but ownership covers {}",
self.global_vocabulary
)));
}
Ok(())
}
pub fn balanced_peer_widths(
&self,
partitions: usize,
rank: usize,
) -> Result<Vec<usize>, Error> {
self.validate()?;
if partitions == 0 || rank >= partitions {
return Err(Error::backend(format!(
"invalid vocabulary partition rank {rank} of {partitions}"
)));
}
let base = self.global_vocabulary / partitions;
let remainder = self.global_vocabulary % partitions;
let widths = (0..partitions)
.map(|peer| base + usize::from(peer < remainder))
.collect::<Vec<_>>();
let start = widths[..rank].iter().sum::<usize>();
let expected = start..start + widths[rank];
if self.local != expected {
return Err(Error::backend(format!(
"vocabulary-parallel range {:?} differs from balanced rank {rank} ownership {expected:?}",
self.local
)));
}
Ok(widths)
}
}
#[cfg(test)]
mod vocabulary_parallel_range_tests {
use super::VocabularyParallelRange;
#[test]
fn balanced_peer_widths_are_neutral_and_reject_local_layout_drift() {
let range = VocabularyParallelRange {
global_vocabulary: 11,
local: 4..8,
};
assert_eq!(range.balanced_peer_widths(3, 1).unwrap(), [4, 4, 3]);
let drifted = VocabularyParallelRange {
global_vocabulary: 11,
local: 3..7,
};
assert!(drifted.balanced_peer_widths(3, 1).is_err());
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum EmbeddingLookupPolicy {
Strict,
ZeroSentinel(i32),
}
impl EmbeddingLookupPolicy {
pub fn validate(self) -> Result<(), Error> {
if let Self::ZeroSentinel(sentinel) = self {
if sentinel >= 0 {
return Err(Error::backend(format!(
"embedding zero sentinel must be negative, got {sentinel}"
)));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FusedProjectionSegment {
name: String,
width: i32,
}
impl FusedProjectionSegment {
pub fn new(name: impl Into<String>, width: i32) -> Result<Self, Error> {
let name = name.into();
if name.trim().is_empty() || width <= 0 {
return Err(Error::backend(format!(
"fused projection segments require a name and positive width, got name={name:?} width={width}"
)));
}
Ok(Self { name, width })
}
pub fn name(&self) -> &str {
&self.name
}
pub const fn width(&self) -> i32 {
self.width
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FusedProjectionLayout {
segments: Vec<FusedProjectionSegment>,
output_width: i32,
}
impl FusedProjectionLayout {
pub fn new(segments: impl IntoIterator<Item = FusedProjectionSegment>) -> Result<Self, Error> {
let segments = segments.into_iter().collect::<Vec<_>>();
if segments.is_empty() {
return Err(Error::backend(
"fused projection layout must contain at least one segment",
));
}
let mut names = std::collections::BTreeSet::new();
let mut output_width = 0i32;
for segment in &segments {
if !names.insert(segment.name.clone()) {
return Err(Error::backend(format!(
"fused projection segment {:?} is duplicated",
segment.name
)));
}
output_width = output_width.checked_add(segment.width).ok_or_else(|| {
Error::backend("fused projection output width overflowed signed 32-bit geometry")
})?;
}
Ok(Self {
segments,
output_width,
})
}
pub fn segments(&self) -> &[FusedProjectionSegment] {
&self.segments
}
pub const fn output_width(&self) -> i32 {
self.output_width
}
pub fn split<T: Tensor>(&self, output: &T, context: &T::Context) -> Result<Vec<T>, Error> {
let actual = output
.shape()
.last()
.copied()
.ok_or_else(|| Error::backend("fused projection output has no feature axis"))?;
if actual != self.output_width {
return Err(Error::backend(format!(
"fused projection emitted width {actual}, expected {}",
self.output_width
)));
}
let mut start = 0i32;
let mut indexes = vec![Index::Full; output.shape().len()];
self.segments
.iter()
.map(|segment| {
let end = start + segment.width;
let last = indexes.len() - 1;
indexes[last] = Index::Range(start, end);
let selected = output.index(&indexes, context);
start = end;
selected
})
.collect()
}
}
#[derive(Debug, Clone)]
pub enum NormalizationScale {
Learned(ParameterSpec),
LearnedOffset {
weight: ParameterSpec,
offset: f32,
},
Unit,
}
#[derive(Debug, Clone)]
pub struct NormalizationConstructionSpec {
pub dimensions: i32,
pub epsilon: f32,
pub scale: NormalizationScale,
}
impl NormalizationConstructionSpec {
pub fn learned(dimensions: i32, epsilon: f32, weight: ParameterSpec) -> Self {
Self {
dimensions,
epsilon,
scale: NormalizationScale::Learned(weight),
}
}
pub fn validate(&self) -> Result<(), Error> {
let offset = match &self.scale {
NormalizationScale::LearnedOffset { offset, .. } => Some(*offset),
NormalizationScale::Learned(_) | NormalizationScale::Unit => None,
};
if self.dimensions <= 0
|| !self.epsilon.is_finite()
|| self.epsilon <= 0.0
|| offset.is_some_and(|offset| !offset.is_finite())
{
return Err(Error::backend(format!(
"invalid RMS normalization construction: dimensions={} epsilon={} offset={offset:?}",
self.dimensions, self.epsilon
)));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RotaryAlgorithm {
Default,
Linear {
factor: f32,
},
Llama3 {
factor: f32,
low_frequency_factor: f32,
high_frequency_factor: f32,
original_max_positions: i32,
},
Proportional {
factor: f32,
rotary_fraction: f32,
},
Yarn {
factor: f32,
original_max_positions: i32,
beta_fast: f32,
beta_slow: f32,
concentration: f32,
attention_factor: f32,
truncate: bool,
},
}
impl RotaryAlgorithm {
pub fn validate(self) -> Result<(), Error> {
let positive = |value: f32| value.is_finite() && value > 0.0;
let valid = match self {
Self::Default => true,
Self::Linear { factor } => positive(factor),
Self::Llama3 {
factor,
low_frequency_factor,
high_frequency_factor,
original_max_positions,
} => {
positive(factor)
&& positive(low_frequency_factor)
&& positive(high_frequency_factor)
&& high_frequency_factor > low_frequency_factor
&& original_max_positions > 0
}
Self::Proportional {
factor,
rotary_fraction,
} => positive(factor) && positive(rotary_fraction) && rotary_fraction <= 1.0,
Self::Yarn {
factor,
original_max_positions,
beta_fast,
beta_slow,
concentration,
attention_factor,
..
} => {
positive(factor)
&& original_max_positions > 0
&& positive(beta_fast)
&& positive(beta_slow)
&& beta_fast > beta_slow
&& positive(concentration)
&& attention_factor.is_finite()
&& attention_factor >= 0.0
}
};
if valid {
Ok(())
} else {
Err(Error::backend(format!(
"invalid normalized rotary algorithm: {self:?}"
)))
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct RotarySpec {
pub dimensions: i32,
pub base: f32,
pub traditional: bool,
pub algorithm: RotaryAlgorithm,
}
pub trait LinearOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
}
pub trait EmbeddingOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
fn lookup(
&mut self,
input: &T,
policy: EmbeddingLookupPolicy,
context: &T::Context,
) -> Result<T, Error> {
policy.validate()?;
match policy {
EmbeddingLookupPolicy::Strict => self.forward(input, context),
EmbeddingLookupPolicy::ZeroSentinel(sentinel) => Err(Error::backend(format!(
"embedding backend does not implement zero sentinel {sentinel}"
))),
}
}
fn as_linear(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
}
pub trait NormalizationOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
}
#[derive(Debug, Clone)]
pub struct LowRankProjectionSpec {
pub first: Option<LinearSpec>,
pub normalization: NormalizationConstructionSpec,
pub second: LinearSpec,
}
impl LowRankProjectionSpec {
pub fn validate(&self) -> Result<(), Error> {
let rank = self.normalization.dimensions;
if rank <= 0 {
return Err(Error::backend(format!(
"low-rank normalization dimensions must be positive, got {rank}"
)));
}
if self.second.input != rank {
return Err(Error::backend(format!(
"low-rank second projection expects {} inputs but rank width is {rank}",
self.second.input
)));
}
if let Some(first) = &self.first {
if first.output != rank {
return Err(Error::backend(format!(
"low-rank first projection emits {} values but rank width is {rank}",
first.output
)));
}
}
Ok(())
}
}
#[derive(Debug, Clone, Parameterized)]
#[parameterized(tensor = "B::Tensor")]
pub struct LowRankProjection<B: NeuralBackend> {
pub first: Option<B::Linear>,
pub normalization: B::Normalization,
pub second: B::Linear,
}
impl<B: NeuralBackend> LowRankProjection<B> {
pub fn new(
spec: LowRankProjectionSpec,
context: &<B::Tensor as Tensor>::Context,
) -> Result<Self, Error> {
spec.validate()?;
Ok(Self {
first: spec
.first
.map(|projection| B::linear(projection, context))
.transpose()?,
normalization: B::normalization(spec.normalization, context)?,
second: B::linear(spec.second, context)?,
})
}
pub fn forward(
&mut self,
input: &B::Tensor,
context: &<B::Tensor as Tensor>::Context,
) -> Result<B::Tensor, Error> {
let rank = match &mut self.first {
Some(first) => first.forward(input, context)?,
None => input.clone(),
};
let rank = self.normalization.forward(&rank, context)?;
self.second.forward(&rank, context)
}
}
pub trait RotaryOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn forward(
&mut self,
input: &T,
position: RotaryPosition<'_, T>,
context: &T::Context,
) -> Result<T, Error>;
fn forward_subspace(
&mut self,
input: &T,
subspace: RotarySubspace,
position: RotaryPosition<'_, T>,
context: &T::Context,
) -> Result<T, Error> {
let width = *input
.shape()
.last()
.ok_or_else(|| Error::backend("rotary input must have a feature axis"))?;
let (start, dimensions) = subspace.resolve(width)?;
if start == 0 && dimensions == width {
return self.forward(input, position, context);
}
let end = start + dimensions;
let mut indexes = vec![Index::Full; input.shape().len()];
indexes[input.shape().len() - 1] = Index::Range(start, end);
let selected = input.index(&indexes, context)?;
let rotated = self.forward(&selected, position, context)?;
let mut pieces = Vec::with_capacity(3);
if start > 0 {
indexes[input.shape().len() - 1] = Index::Range(0, start);
pieces.push(input.index(&indexes, context)?);
}
pieces.push(rotated);
if end < width {
indexes[input.shape().len() - 1] = Index::Range(end, width);
pieces.push(input.index(&indexes, context)?);
}
T::concatenate(&pieces, -1, context)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum RotarySubspace {
Full,
Range {
start: i32,
dimensions: i32,
},
}
impl RotarySubspace {
fn resolve(self, width: i32) -> Result<(i32, i32), Error> {
let (start, dimensions) = match self {
Self::Full => (0, width),
Self::Range { start, dimensions } => (start, dimensions),
};
if width <= 0
|| start < 0
|| dimensions <= 0
|| dimensions % 2 != 0
|| start > width - dimensions
{
return Err(Error::backend(format!(
"rotary subspace start={start} dimensions={dimensions} is invalid for width {width}"
)));
}
Ok((start, dimensions))
}
}
#[derive(Debug)]
pub enum RotaryPosition<'a, T> {
Offset(i32),
Embeddings {
cosine: &'a T,
sine: &'a T,
},
}
impl<T> Copy for RotaryPosition<'_, T> {}
impl<T> Clone for RotaryPosition<'_, T> {
fn clone(&self) -> Self {
*self
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum GroupScoring {
Softmax,
SelectedSoftmax,
Sigmoid,
SqrtSoftplus,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TopKGroupSelectionSpec {
group_count: i32,
top_k: i32,
scoring: GroupScoring,
normalize_selected: bool,
normalization_epsilon: f32,
coefficient_scale: f32,
selection_partitions: i32,
selected_groups: i32,
}
#[derive(Debug, Clone)]
pub struct TopKGroupSelectorSpec {
input_dimensions: i32,
weight: ParameterSpec,
bias: Option<ParameterSpec>,
correction_bias: Option<ParameterSpec>,
input_transform: Option<SelectorInputTransformSpec>,
coefficient_scale: Option<ParameterSpec>,
format: LinearFormatSpec,
selection: TopKGroupSelectionSpec,
}
#[derive(Debug, Clone)]
pub struct SelectorInputTransformSpec {
epsilon: f32,
scale: ParameterSpec,
inverse_sqrt_dimensions: bool,
}
impl SelectorInputTransformSpec {
pub fn new(
epsilon: f32,
scale: ParameterSpec,
inverse_sqrt_dimensions: bool,
) -> Result<Self, Error> {
if !epsilon.is_finite() || epsilon < 0.0 {
return Err(Error::backend(
"selector input RMS epsilon must be finite and nonnegative",
));
}
Ok(Self {
epsilon,
scale,
inverse_sqrt_dimensions,
})
}
pub const fn epsilon(&self) -> f32 {
self.epsilon
}
pub const fn scale(&self) -> &ParameterSpec {
&self.scale
}
pub const fn inverse_sqrt_dimensions(&self) -> bool {
self.inverse_sqrt_dimensions
}
}
impl TopKGroupSelectorSpec {
pub fn new(
input_dimensions: i32,
weight: ParameterSpec,
format: LinearFormatSpec,
selection: TopKGroupSelectionSpec,
) -> Result<Self, Error> {
let spec = Self {
input_dimensions,
weight,
bias: None,
correction_bias: None,
input_transform: None,
coefficient_scale: None,
format,
selection,
};
spec.validate()?;
Ok(spec)
}
pub fn with_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
self.bias = Some(bias);
self.validate()?;
Ok(self)
}
pub fn with_correction_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
self.correction_bias = Some(bias);
self.validate()?;
Ok(self)
}
pub fn with_input_transform(mut self, transform: SelectorInputTransformSpec) -> Self {
self.input_transform = Some(transform);
self
}
pub fn with_coefficient_scale(mut self, scale: ParameterSpec) -> Self {
self.coefficient_scale = Some(scale);
self
}
pub const fn input_dimensions(&self) -> i32 {
self.input_dimensions
}
pub const fn weight(&self) -> &ParameterSpec {
&self.weight
}
pub const fn bias(&self) -> Option<&ParameterSpec> {
self.bias.as_ref()
}
pub const fn correction_bias(&self) -> Option<&ParameterSpec> {
self.correction_bias.as_ref()
}
pub const fn input_transform(&self) -> Option<&SelectorInputTransformSpec> {
self.input_transform.as_ref()
}
pub const fn coefficient_scale(&self) -> Option<&ParameterSpec> {
self.coefficient_scale.as_ref()
}
pub const fn format(&self) -> &LinearFormatSpec {
&self.format
}
pub const fn selection(&self) -> TopKGroupSelectionSpec {
self.selection
}
pub fn validate(&self) -> Result<(), Error> {
self.format.validate_for_weight(&self.weight)?;
if self.input_dimensions <= 0 {
return Err(Error::backend(format!(
"selector input dimensions must be positive, got {}",
self.input_dimensions
)));
}
if self
.input_transform
.as_ref()
.is_some_and(|transform| !transform.epsilon.is_finite() || transform.epsilon < 0.0)
{
return Err(Error::backend(
"selector input RMS epsilon must be finite and nonnegative",
));
}
if self
.bias
.as_ref()
.zip(self.correction_bias.as_ref())
.is_some_and(|(bias, correction_bias)| bias.id == correction_bias.id)
{
return Err(Error::backend(
"selector projection bias and correction bias require distinct parameter identities",
));
}
Ok(())
}
}
impl TopKGroupSelectionSpec {
pub fn new(
group_count: i32,
top_k: i32,
scoring: GroupScoring,
normalize_selected: bool,
) -> Result<Self, Error> {
if group_count <= 0 {
return Err(Error::backend(format!(
"group count must be positive, got {group_count}"
)));
}
if top_k <= 0 || top_k > group_count {
return Err(Error::backend(format!(
"top-k selection count must be in 1..={group_count}, got {top_k}"
)));
}
Ok(Self {
group_count,
top_k,
scoring,
normalize_selected,
normalization_epsilon: 0.0,
coefficient_scale: 1.0,
selection_partitions: 1,
selected_groups: 1,
})
}
pub fn with_groups(
mut self,
selection_partitions: i32,
selected_groups: i32,
) -> Result<Self, Error> {
if selection_partitions <= 0
|| selected_groups <= 0
|| selected_groups > selection_partitions
|| self.group_count % selection_partitions != 0
|| self.top_k > selected_groups * (self.group_count / selection_partitions)
{
return Err(Error::backend(format!(
"invalid grouped selection geometry: group_count={} top_k={} partitions={selection_partitions} selected_partitions={selected_groups}",
self.group_count, self.top_k
)));
}
self.selection_partitions = selection_partitions;
self.selected_groups = selected_groups;
Ok(self)
}
pub fn with_weight_policy(
mut self,
normalization_epsilon: f32,
coefficient_scale: f32,
) -> Result<Self, Error> {
if !normalization_epsilon.is_finite()
|| normalization_epsilon < 0.0
|| !coefficient_scale.is_finite()
|| coefficient_scale <= 0.0
{
return Err(Error::backend(
"selection normalization epsilon must be finite and nonnegative and grouped scaling must be finite and positive",
));
}
self.normalization_epsilon = normalization_epsilon;
self.coefficient_scale = coefficient_scale;
Ok(self)
}
pub const fn group_count(self) -> i32 {
self.group_count
}
pub const fn top_k(self) -> i32 {
self.top_k
}
pub const fn scoring(self) -> GroupScoring {
self.scoring
}
pub const fn normalize_selected(self) -> bool {
self.normalize_selected
}
pub const fn normalization_epsilon(self) -> f32 {
self.normalization_epsilon
}
pub const fn coefficient_scale(self) -> f32 {
self.coefficient_scale
}
pub const fn selection_partitions(self) -> i32 {
self.selection_partitions
}
pub const fn selected_groups(self) -> i32 {
self.selected_groups
}
}
#[derive(Debug, Clone)]
pub struct GroupSelection<T> {
group_indices: T,
selected_scores: T,
coefficients: T,
}
impl<T> GroupSelection<T> {
pub fn into_parts(self) -> (T, T, T) {
(self.group_indices, self.selected_scores, self.coefficients)
}
pub fn new(group_indices: T, selected_scores: T, coefficients: T) -> Self {
Self {
group_indices,
selected_scores,
coefficients,
}
}
pub const fn group_indices(&self) -> &T {
&self.group_indices
}
pub const fn selected_scores(&self) -> &T {
&self.selected_scores
}
pub const fn coefficients(&self) -> &T {
&self.coefficients
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct JointGroupSelectionSpec {
selectable_groups: i32,
always_on_groups: i32,
top_k: i32,
coefficient_scale: f32,
}
impl JointGroupSelectionSpec {
pub fn new(
selectable_groups: i32,
always_on_groups: i32,
top_k: i32,
coefficient_scale: f32,
) -> Result<Self, Error> {
if selectable_groups <= 0
|| always_on_groups <= 0
|| top_k <= 0
|| top_k > selectable_groups
|| !coefficient_scale.is_finite()
|| coefficient_scale <= 0.0
{
return Err(Error::backend(format!(
"invalid joint group-selection geometry selectable={selectable_groups} always_on={always_on_groups} top_k={top_k} coefficient_scale={coefficient_scale}"
)));
}
Ok(Self {
selectable_groups,
always_on_groups,
top_k,
coefficient_scale,
})
}
pub const fn selectable_groups(self) -> i32 {
self.selectable_groups
}
pub const fn always_on_groups(self) -> i32 {
self.always_on_groups
}
pub const fn top_k(self) -> i32 {
self.top_k
}
pub const fn coefficient_scale(self) -> f32 {
self.coefficient_scale
}
}
#[derive(Debug, Clone, Copy)]
pub struct JointGroupSelectionInput<'a, T> {
hidden: &'a T,
weight: &'a T,
correction_bias: &'a T,
global_scale: &'a T,
selection: JointGroupSelectionSpec,
}
impl<'a, T: Tensor> JointGroupSelectionInput<'a, T> {
pub fn new(
hidden: &'a T,
weight: &'a T,
correction_bias: &'a T,
global_scale: &'a T,
selection: JointGroupSelectionSpec,
) -> Result<Self, Error> {
let input = Self {
hidden,
weight,
correction_bias,
global_scale,
selection,
};
input.validate()?;
Ok(input)
}
pub const fn hidden(&self) -> &'a T {
self.hidden
}
pub const fn weight(&self) -> &'a T {
self.weight
}
pub const fn correction_bias(&self) -> &'a T {
self.correction_bias
}
pub const fn global_scale(&self) -> &'a T {
self.global_scale
}
pub const fn selectable_groups(&self) -> i32 {
self.selection.selectable_groups()
}
pub const fn always_on_groups(&self) -> i32 {
self.selection.always_on_groups()
}
pub const fn top_k(&self) -> i32 {
self.selection.top_k()
}
pub const fn coefficient_scale(&self) -> f32 {
self.selection.coefficient_scale()
}
}
impl<T: Tensor> JointGroupSelectionInput<'_, T> {
pub fn validate(&self) -> Result<(), Error> {
let hidden = self.hidden.shape();
let weight = self.weight.shape();
let bias = self.correction_bias.shape();
let scale = self.global_scale.shape();
let hidden_width = hidden.last().copied().unwrap_or(0);
if hidden.len() < 2
|| weight
!= [
self.selectable_groups() + self.always_on_groups(),
hidden_width,
]
|| bias != [self.selectable_groups()]
|| scale != [1]
{
return Err(Error::backend(format!(
"invalid joint group selection tensors hidden={hidden:?} weight={weight:?} bias={bias:?} scale={scale:?} selectable={} always_on={} top_k={}",
self.selectable_groups(),
self.always_on_groups(),
self.top_k(),
)));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct JointGroupSelection<T> {
primary_indices: T,
primary_coefficients: T,
always_on_coefficients: T,
}
impl<T> JointGroupSelection<T> {
pub fn new(primary_indices: T, primary_coefficients: T, always_on_coefficients: T) -> Self {
Self {
primary_indices,
primary_coefficients,
always_on_coefficients,
}
}
pub const fn primary_indices(&self) -> &T {
&self.primary_indices
}
pub const fn primary_coefficients(&self) -> &T {
&self.primary_coefficients
}
pub const fn always_on_coefficients(&self) -> &T {
&self.always_on_coefficients
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum GatedProductActivation {
Silu,
GeluApproximate,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GatedProductPolicy {
activation: GatedProductActivation,
gate_upper_bound: Option<f32>,
up_absolute_bound: Option<f32>,
sigmoid_multiplier: f32,
up_offset: f32,
}
impl GatedProductPolicy {
pub fn new(
activation: GatedProductActivation,
gate_upper_bound: Option<f32>,
up_absolute_bound: Option<f32>,
sigmoid_multiplier: f32,
up_offset: f32,
) -> Result<Self, Error> {
let policy = Self {
activation,
gate_upper_bound,
up_absolute_bound,
sigmoid_multiplier,
up_offset,
};
policy.validate()?;
Ok(policy)
}
pub const fn ordinary_silu() -> Self {
Self {
activation: GatedProductActivation::Silu,
gate_upper_bound: None,
up_absolute_bound: None,
sigmoid_multiplier: 1.0,
up_offset: 0.0,
}
}
pub const fn ordinary_gelu_approximate() -> Self {
Self {
activation: GatedProductActivation::GeluApproximate,
..Self::ordinary_silu()
}
}
pub fn bounded_silu(bound: f32) -> Result<Self, Error> {
Self::new(
GatedProductActivation::Silu,
Some(bound),
Some(bound),
1.0,
0.0,
)
}
pub fn validate(self) -> Result<(), Error> {
if self
.gate_upper_bound
.is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
|| self
.up_absolute_bound
.is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
|| !self.sigmoid_multiplier.is_finite()
|| self.sigmoid_multiplier <= 0.0
|| !self.up_offset.is_finite()
{
return Err(Error::backend(format!(
"invalid gated-product policy: {self:?}"
)));
}
Ok(())
}
pub const fn activation(self) -> GatedProductActivation {
self.activation
}
pub const fn gate_upper_bound(self) -> Option<f32> {
self.gate_upper_bound
}
pub const fn up_absolute_bound(self) -> Option<f32> {
self.up_absolute_bound
}
pub const fn sigmoid_multiplier(self) -> f32 {
self.sigmoid_multiplier
}
pub const fn up_offset(self) -> f32 {
self.up_offset
}
}
impl Default for GatedProductPolicy {
fn default() -> Self {
Self::ordinary_silu()
}
}
pub trait GroupSelectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn select_intervened(
&mut self,
_input: &T,
_control: &routing_intervention::GroupSelectionControl,
_context: &T::Context,
) -> Result<routing_intervention::IntervenedGroupSelection<T>, Error> {
Err(Error::backend(
"pre-dispatch routing interventions are unsupported",
))
}
fn select(&mut self, logits: &T, context: &T::Context) -> Result<GroupSelection<T>, Error>;
fn select_indices(
&mut self,
input: &T,
group_indices: &T,
context: &T::Context,
) -> Result<GroupSelection<T>, Error>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct GatedProductGroupParameters {
gate: GroupedProjectionSpec,
up: GroupedProjectionSpec,
down: GroupedProjectionSpec,
}
impl GatedProductGroupParameters {
pub fn new(
gate: GroupedProjectionSpec,
up: GroupedProjectionSpec,
down: GroupedProjectionSpec,
) -> Self {
Self { gate, up, down }
}
pub const fn gate(&self) -> &GroupedProjectionSpec {
&self.gate
}
pub const fn up(&self) -> &GroupedProjectionSpec {
&self.up
}
pub const fn down(&self) -> &GroupedProjectionSpec {
&self.down
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct GroupedProjectionSpec {
weight: ParameterSpec,
bias: Option<ParameterSpec>,
format: LinearFormatSpec,
}
impl GroupedProjectionSpec {
pub fn new(
weight: ParameterSpec,
bias: Option<ParameterSpec>,
format: LinearFormatSpec,
) -> Result<Self, Error> {
let spec = Self {
weight,
bias,
format,
};
spec.validate()?;
Ok(spec)
}
pub const fn weight(&self) -> &ParameterSpec {
&self.weight
}
pub const fn bias(&self) -> Option<&ParameterSpec> {
self.bias.as_ref()
}
pub const fn format(&self) -> &LinearFormatSpec {
&self.format
}
fn validate(&self) -> Result<(), Error> {
self.format.validate_for_weight(&self.weight)?;
let parameters = self.parameters();
for (index, parameter) in parameters.iter().enumerate() {
if parameters[index + 1..]
.iter()
.any(|candidate| candidate.id == parameter.id)
{
return Err(Error::backend(format!(
"grouped projection reuses parameter identity {:?}",
parameter.id
)));
}
}
Ok(())
}
pub fn parameters(&self) -> Vec<&ParameterSpec> {
let mut parameters = vec![&self.weight];
parameters.extend(self.bias.as_ref());
parameters.extend(self.format.scale());
parameters.extend(self.format.affine_bias());
parameters
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)] #[non_exhaustive]
pub enum GatedProductGroupLayout {
Packed {
gate_up: GroupedProjectionSpec,
down: GroupedProjectionSpec,
},
Independent(Vec<GatedProductGroupParameters>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct GroupedGatedProductSpec {
group_count: i32,
input_dimensions: i32,
intermediate_dimensions: i32,
output_dimensions: i32,
policy: GatedProductPolicy,
layout: GatedProductGroupLayout,
}
impl GroupedGatedProductSpec {
pub fn new(
group_count: i32,
input_dimensions: i32,
intermediate_dimensions: i32,
output_dimensions: i32,
policy: GatedProductPolicy,
layout: GatedProductGroupLayout,
) -> Result<Self, Error> {
let spec = Self {
group_count,
input_dimensions,
intermediate_dimensions,
output_dimensions,
policy,
layout,
};
spec.validate()?;
Ok(spec)
}
pub fn with_group_geometry(
mut self,
group_count: i32,
intermediate_dimensions: i32,
) -> Result<Self, Error> {
self.group_count = group_count;
self.intermediate_dimensions = intermediate_dimensions;
self.validate()?;
Ok(self)
}
pub const fn group_count(&self) -> i32 {
self.group_count
}
pub const fn input_dimensions(&self) -> i32 {
self.input_dimensions
}
pub const fn intermediate_dimensions(&self) -> i32 {
self.intermediate_dimensions
}
pub const fn output_dimensions(&self) -> i32 {
self.output_dimensions
}
pub const fn policy(&self) -> GatedProductPolicy {
self.policy
}
pub const fn layout(&self) -> &GatedProductGroupLayout {
&self.layout
}
pub fn validate(&self) -> Result<(), Error> {
for (name, value) in [
("group_count", self.group_count),
("input_dimensions", self.input_dimensions),
("intermediate_dimensions", self.intermediate_dimensions),
("output_dimensions", self.output_dimensions),
] {
if value <= 0 {
return Err(Error::backend(format!(
"gated-product group-bank {name} must be positive, got {value}"
)));
}
}
self.policy.validate()?;
if let GatedProductGroupLayout::Independent(groups) = &self.layout {
let expected = usize::try_from(self.group_count).map_err(Error::backend)?;
if groups.len() != expected {
return Err(Error::backend(format!(
"independent gated-product bank has {} groups, expected {expected}",
groups.len()
)));
}
}
let projections = match &self.layout {
GatedProductGroupLayout::Packed { gate_up, down } => vec![gate_up, down],
GatedProductGroupLayout::Independent(groups) => groups
.iter()
.flat_map(|group| [&group.gate, &group.up, &group.down])
.collect(),
};
let mut identities = std::collections::BTreeSet::new();
for projection in projections {
projection.validate()?;
for parameter in projection.parameters() {
let identity = ¶meter.id;
if !identities.insert(identity) {
return Err(Error::backend(format!(
"gated-product group parameter identity {identity} is duplicated"
)));
}
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TensorParallelGroupedOutput<T> {
reducible: T,
post_reduce: Option<T>,
}
impl<T> TensorParallelGroupedOutput<T> {
pub fn new(reducible: T, post_reduce: Option<T>) -> Self {
Self {
reducible,
post_reduce,
}
}
pub const fn reducible(&self) -> &T {
&self.reducible
}
pub const fn post_reduce(&self) -> Option<&T> {
self.post_reduce.as_ref()
}
pub fn into_parts(self) -> (T, Option<T>) {
(self.reducible, self.post_reduce)
}
}
pub trait GroupedGatedProductOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn spec(&self) -> &GroupedGatedProductSpec;
fn forward_grouped(
&mut self,
input: &T,
selections: &GroupSelection<T>,
context: &T::Context,
) -> Result<T, Error>;
}
pub trait TensorParallelGroupedGatedProductOperator<T: Tensor>:
GroupedGatedProductOperator<T>
{
fn forward_grouped_tensor_parallel(
&mut self,
input: &T,
selections: &GroupSelection<T>,
partitions: usize,
context: &T::Context,
) -> Result<TensorParallelGroupedOutput<T>, Error>;
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct GroupedRelu2Spec {
group_count: i32,
hidden_dimensions: i32,
intermediate_dimensions: i32,
up: GroupedProjectionSpec,
down: GroupedProjectionSpec,
}
impl GroupedRelu2Spec {
pub fn new(
group_count: i32,
hidden_dimensions: i32,
intermediate_dimensions: i32,
up: GroupedProjectionSpec,
down: GroupedProjectionSpec,
) -> Result<Self, Error> {
let spec = Self {
group_count,
hidden_dimensions,
intermediate_dimensions,
up,
down,
};
spec.validate()?;
Ok(spec)
}
pub fn with_group_count(mut self, group_count: i32) -> Result<Self, Error> {
self.group_count = group_count;
self.validate()?;
Ok(self)
}
pub const fn group_count(&self) -> i32 {
self.group_count
}
pub const fn hidden_dimensions(&self) -> i32 {
self.hidden_dimensions
}
pub const fn intermediate_dimensions(&self) -> i32 {
self.intermediate_dimensions
}
pub const fn up(&self) -> &GroupedProjectionSpec {
&self.up
}
pub const fn down(&self) -> &GroupedProjectionSpec {
&self.down
}
pub fn validate(&self) -> Result<(), Error> {
if self.group_count <= 0 || self.hidden_dimensions <= 0 || self.intermediate_dimensions <= 0
{
return Err(Error::backend("invalid ReLU2 group-bank geometry"));
}
self.up.validate()?;
self.down.validate()?;
let mut identities = std::collections::BTreeSet::new();
for projection in [&self.up, &self.down] {
for parameter in projection.parameters() {
if !identities.insert(¶meter.id) {
return Err(Error::backend(format!(
"ReLU2 group parameter identity {} is duplicated",
parameter.id
)));
}
}
}
Ok(())
}
}
pub trait GroupedRelu2Operator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn spec(&self) -> &GroupedRelu2Spec;
fn forward_grouped(
&mut self,
input: &T,
selections: &GroupSelection<T>,
context: &T::Context,
) -> Result<T, Error>;
}
pub trait TensorParallelGroupedRelu2Operator<T: Tensor>: GroupedRelu2Operator<T> {
fn forward_grouped_tensor_parallel(
&mut self,
input: &T,
selections: &GroupSelection<T>,
partitions: usize,
context: &T::Context,
) -> Result<TensorParallelGroupedOutput<T>, Error>;
}
pub trait GroupedNeuralBackend: NeuralBackend {
type Selector: GroupSelectionOperator<Self::Tensor>;
type GatedProductGroups: GroupedGatedProductOperator<Self::Tensor>;
type Relu2Groups: GroupedRelu2Operator<Self::Tensor>;
fn grouped_linear(
linear: &mut Self::Linear,
input: &Self::Tensor,
groups: i32,
output_per_group: i32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn top_k_group_selector(
spec: TopKGroupSelectorSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Selector, Error>;
fn grouped_gated_product(
spec: GroupedGatedProductSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::GatedProductGroups, Error>;
fn grouped_relu2(
spec: GroupedRelu2Spec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Relu2Groups, Error>;
fn joint_group_selection(
input: JointGroupSelectionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<JointGroupSelection<Self::Tensor>, Error>;
}
pub trait TensorParallelGroupedNeuralBackend: GroupedNeuralBackend {
fn gated_product_groups_tensor_parallel(
groups: &mut Self::GatedProductGroups,
input: &Self::Tensor,
selections: &GroupSelection<Self::Tensor>,
partitions: usize,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
fn relu2_groups_tensor_parallel(
groups: &mut Self::Relu2Groups,
input: &Self::Tensor,
selections: &GroupSelection<Self::Tensor>,
partitions: usize,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
}
impl<B> TensorParallelGroupedNeuralBackend for B
where
B: GroupedNeuralBackend,
B::GatedProductGroups: TensorParallelGroupedGatedProductOperator<B::Tensor>,
B::Relu2Groups: TensorParallelGroupedRelu2Operator<B::Tensor>,
{
fn gated_product_groups_tensor_parallel(
groups: &mut Self::GatedProductGroups,
input: &Self::Tensor,
selections: &GroupSelection<Self::Tensor>,
partitions: usize,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
}
fn relu2_groups_tensor_parallel(
groups: &mut Self::Relu2Groups,
input: &Self::Tensor,
selections: &GroupSelection<Self::Tensor>,
partitions: usize,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
}
}
#[derive(Debug, Clone)]
pub struct HyperConnectionSpec {
pub streams: i32,
pub hidden_size: i32,
pub sinkhorn_iterations: usize,
pub epsilon: f32,
pub function: ParameterSpec,
pub base: ParameterSpec,
pub scale: ParameterSpec,
}
impl HyperConnectionSpec {
pub fn validate(&self) -> Result<(), Error> {
if self.streams <= 0 || self.hidden_size <= 0 {
return Err(Error::backend(
"hyper-connection streams and hidden size must be positive",
));
}
if self.sinkhorn_iterations == 0 {
return Err(Error::backend(
"hyper-connection Sinkhorn iteration count must be positive",
));
}
if !self.epsilon.is_finite() || self.epsilon <= 0.0 {
return Err(Error::backend(
"hyper-connection epsilon must be finite and positive",
));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HyperHeadSpec {
pub streams: i32,
pub hidden_size: i32,
pub norm_epsilon: f32,
pub epsilon: f32,
pub function: ParameterSpec,
pub base: ParameterSpec,
pub scale: ParameterSpec,
}
impl HyperHeadSpec {
pub fn validate(&self) -> Result<(), Error> {
if self.streams <= 0 || self.hidden_size <= 0 {
return Err(Error::backend(
"hyper-head streams and hidden size must be positive",
));
}
if !self.norm_epsilon.is_finite()
|| self.norm_epsilon <= 0.0
|| !self.epsilon.is_finite()
|| self.epsilon <= 0.0
{
return Err(Error::backend(
"hyper-head epsilons must be finite and positive",
));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HyperConnectionState<T> {
pub collapsed: T,
pub pre: T,
pub post: T,
pub combination: T,
}
pub trait HyperConnectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn collapse(
&mut self,
residual: &T,
norm_epsilon: f32,
context: &T::Context,
) -> Result<HyperConnectionState<T>, Error>;
fn expand(
&mut self,
sublayer: &T,
residual: &T,
state: &HyperConnectionState<T>,
context: &T::Context,
) -> Result<T, Error>;
}
pub trait HyperHeadOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
fn forward(&mut self, residual: &T, context: &T::Context) -> Result<T, Error>;
}
pub trait HyperNeuralBackend: NeuralBackend {
type HyperConnection: HyperConnectionOperator<Self::Tensor>;
type HyperHead: HyperHeadOperator<Self::Tensor>;
fn hyper_connection(
spec: HyperConnectionSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::HyperConnection, Error>;
fn hyper_head(
spec: HyperHeadSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::HyperHead, Error>;
}
#[derive(Debug, Clone, Parameterized)]
#[parameterized(tensor = "B::Tensor")]
pub struct HyperConnection<B: HyperNeuralBackend> {
operator: B::HyperConnection,
}
impl<B: HyperNeuralBackend> HyperConnection<B> {
pub fn new(
spec: HyperConnectionSpec,
context: &<B::Tensor as Tensor>::Context,
) -> Result<Self, Error> {
spec.validate()?;
Ok(Self {
operator: B::hyper_connection(spec, context)?,
})
}
pub fn collapse(
&mut self,
residual: &B::Tensor,
norm_epsilon: f32,
context: &<B::Tensor as Tensor>::Context,
) -> Result<HyperConnectionState<B::Tensor>, Error> {
self.operator.collapse(residual, norm_epsilon, context)
}
pub fn expand(
&mut self,
sublayer: &B::Tensor,
residual: &B::Tensor,
state: &HyperConnectionState<B::Tensor>,
context: &<B::Tensor as Tensor>::Context,
) -> Result<B::Tensor, Error> {
self.operator.expand(sublayer, residual, state, context)
}
}
#[derive(Debug, Parameterized)]
#[parameterized(tensor = "B::Tensor")]
pub struct HyperHead<B: HyperNeuralBackend> {
operator: B::HyperHead,
}
impl<B: HyperNeuralBackend> Clone for HyperHead<B> {
fn clone(&self) -> Self {
Self {
operator: self.operator.clone(),
}
}
}
impl<B: HyperNeuralBackend> HyperHead<B> {
pub fn new(
spec: HyperHeadSpec,
context: &<B::Tensor as Tensor>::Context,
) -> Result<Self, Error> {
spec.validate()?;
Ok(Self {
operator: B::hyper_head(spec, context)?,
})
}
pub fn forward(
&mut self,
residual: &B::Tensor,
context: &<B::Tensor as Tensor>::Context,
) -> Result<B::Tensor, Error> {
self.operator.forward(residual, context)
}
}
#[derive(Debug)]
pub struct AttentionRequest<'a, T> {
pub queries: T,
pub keys: T,
pub values: T,
pub scale: f32,
pub mask: Option<&'a T>,
pub sinks: Option<&'a T>,
}
impl<T: Tensor> AttentionRequest<'_, T> {
pub fn validate(&self) -> Result<(), Error> {
let queries = self.queries.shape();
let keys = self.keys.shape();
let values = self.values.shape();
if queries.len() != 4
|| keys.len() != 4
|| values.len() != 4
|| queries[0] != keys[0]
|| keys[..3] != values[..3]
|| queries[3] != keys[3]
|| queries[1] <= 0
|| keys[1] <= 0
|| queries[1] % keys[1] != 0
|| queries[2] <= 0
|| keys[2] <= 0
|| values[3] <= 0
|| !self.scale.is_finite()
|| self.scale <= 0.0
{
return Err(Error::backend(format!(
"invalid attention request geometry queries={queries:?} keys={keys:?} values={values:?} scale={}",
self.scale
)));
}
if let Some(sinks) = self.sinks {
if sinks.shape() != [queries[1]] {
return Err(Error::backend(format!(
"attention sinks require shape [{}], got {:?}",
queries[1],
sinks.shape()
)));
}
}
Ok(())
}
}
pub trait AttentionCache<T: Tensor> {
fn offset(&self) -> i32;
fn max_size(&self) -> Option<i32>;
fn update_for_attention(
&mut self,
keys: T,
values: T,
context: &T::Context,
) -> Result<(T, T), Error>;
fn attention(
&mut self,
request: AttentionRequest<'_, T>,
context: &T::Context,
) -> Result<T, Error>;
}
pub trait AuxiliaryConvolutionState<T: Tensor>: AttentionCache<T> {
fn convolution_state(&mut self, slot: u32) -> Result<&mut Option<T>, Error>;
}
#[derive(Debug, Clone)]
pub struct CompressedAttentionState<T> {
pub latent: T,
pub rotary: T,
}
#[derive(Debug, Clone)]
pub enum CompressedAttentionView<T> {
Resident(CompressedAttentionState<T>),
Paged {
appended: CompressedAttentionState<T>,
},
}
impl<T> CompressedAttentionView<T> {
pub const fn resident(&self) -> Option<&CompressedAttentionState<T>> {
match self {
Self::Resident(state) => Some(state),
Self::Paged { .. } => None,
}
}
pub const fn observable(&self) -> &CompressedAttentionState<T> {
match self {
Self::Resident(state) | Self::Paged { appended: state } => state,
}
}
pub const fn is_paged(&self) -> bool {
matches!(self, Self::Paged { .. })
}
}
#[derive(Debug, Clone)]
pub struct CompressedAttentionBlock<T> {
pub start: i64,
pub end: i64,
pub state: CompressedAttentionState<T>,
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct CompressedAttentionScan {
pub blocks: u64,
pub bytes: u64,
pub reconstruction_scratch_bytes: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct BlockwiseAttentionSpec<'a, T> {
pub queries: &'a T,
pub scale: f32,
pub mask: Option<&'a T>,
pub query_start: i64,
pub context_end: i64,
pub sliding_window: Option<i32>,
pub prefix_tokens: i64,
pub sinks: Option<&'a T>,
}
pub trait BlockwiseAttentionBackend: NeuralBackend {
type BlockwiseAccumulator;
fn begin_blockwise_attention(
spec: BlockwiseAttentionSpec<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::BlockwiseAccumulator, Error>;
fn accumulate_blockwise_attention(
accumulator: &mut Self::BlockwiseAccumulator,
start: i64,
end: i64,
keys: Self::Tensor,
values: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<u64, Error>;
fn finish_blockwise_attention(
accumulator: Self::BlockwiseAccumulator,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
}
pub trait CompressedAttentionCache<T: Tensor>: Debug {
type Checkpoint: Clone + Debug;
fn offset(&self) -> i32;
fn is_paged(&self) -> bool;
fn append(
&mut self,
state: CompressedAttentionState<T>,
context: &T::Context,
) -> Result<CompressedAttentionView<T>, Error>;
fn visit_blocks<F>(
&mut self,
query_tokens: i32,
context: &T::Context,
visitor: F,
) -> Result<CompressedAttentionScan, Error>
where
F: FnMut(CompressedAttentionBlock<T>) -> Result<u64, Error>;
fn checkpoint(&self) -> Self::Checkpoint;
fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
-> Result<(), Error>;
fn finalize(&mut self) -> Result<(), Error>;
fn clear(&mut self) -> Result<(), Error>;
}
#[derive(Debug, Clone)]
pub struct PoolingWindows<T> {
pub values: T,
pub gates: T,
pub base_position: i32,
}
#[derive(Debug, Clone)]
pub struct PoolingOverlap<T> {
pub values: Option<T>,
pub gates: Option<T>,
}
pub trait PoolingAttentionCache<T: Tensor>: Debug {
type Checkpoint: Clone + Debug;
fn offset(&self) -> i32;
fn pooling_ratio(&self, stream: u32) -> Option<i32>;
fn append_local(&mut self, keys: T, context: &T::Context) -> Result<T, Error>;
fn local_mask(&self, query_tokens: i32, offset: i32, context: &T::Context) -> Result<T, Error>;
fn accumulate_pooling_windows(
&mut self,
stream: u32,
values: T,
gates: T,
absolute_offset: i32,
context: &T::Context,
) -> Result<PoolingWindows<T>, Error>;
fn replace_pooling_overlap(
&mut self,
stream: u32,
values: T,
gates: T,
) -> Result<PoolingOverlap<T>, Error>;
fn append_pooled(&mut self, stream: u32, values: T, context: &T::Context) -> Result<T, Error>;
fn pooling_mask(
&self,
stream: u32,
query_tokens: i32,
offset: i32,
context: &T::Context,
) -> Result<Option<T>, Error>;
fn checkpoint(&self) -> Self::Checkpoint;
fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
-> Result<(), Error>;
fn finalize(&mut self) -> Result<(), Error>;
fn clear(&mut self) -> Result<(), Error>;
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
pub struct NeuralOperatorCapabilities(u64);
impl NeuralOperatorCapabilities {
pub const NONE: Self = Self(0);
pub const GELU_APPROXIMATE: Self = Self(1 << 0);
pub const SIGMOID: Self = Self(1 << 1);
pub const SOFTPLUS: Self = Self(1 << 2);
pub const EXP: Self = Self(1 << 3);
pub const GATED_GROUP_RMS_NORM: Self = Self(1 << 4);
pub const L2_NORMALIZE: Self = Self(1 << 5);
pub const SILU_GATED_GROUP_RMS_NORM: Self = Self(1 << 6);
pub const SEGMENTED_ATTENTION: Self = Self(1 << 7);
pub const GATED_DELTA_SCAN: Self = Self(1 << 8);
pub const SELECTIVE_STATE_SPACE_SCAN: Self = Self(1 << 9);
pub const INDEXED_ATTENTION: Self = Self(1 << 10);
pub const POOLED_ATTENTION: Self = Self(1 << 11);
pub const POOLED_POSITION_SELECTION: Self = Self(1 << 12);
pub const POOLED_MASK_GATHER: Self = Self(1 << 13);
pub const ATTENTION_SINKS: Self = Self(1 << 14);
pub const RELATIVE_ATTENTION: Self = Self(1 << 15);
pub const JOINT_GROUP_SELECTION: Self = Self(1 << 16);
pub const RMS_NORM_WITHOUT_WEIGHT: Self = Self(1 << 17);
pub const GROUPED_LINEAR: Self = Self(1 << 18);
pub const SUM_PARALLEL: Self = Self(1 << 19);
pub const UNLOADED_I32: Self = Self(1 << 20);
pub const FROM_I32_SLICE: Self = Self(1 << 21);
pub const TO_F32_VEC: Self = Self(1 << 22);
pub const TO_I32_VEC: Self = Self(1 << 23);
pub const FULL_F32: Self = Self(1 << 24);
pub const FULL_I32: Self = Self(1 << 25);
pub const TANH: Self = Self(1 << 26);
pub const CLIP: Self = Self(1 << 27);
pub const SOFTMAX_AXIS: Self = Self(1 << 28);
pub const BROADCAST_TO: Self = Self(1 << 29);
pub const ZEROS_LIKE: Self = Self(1 << 30);
pub const EQUAL_I32: Self = Self(1 << 31);
pub const LOGICAL_OR: Self = Self(1 << 32);
pub const WHERE_CONDITION: Self = Self(1 << 33);
pub const MASKED_SCATTER: Self = Self(1 << 34);
pub const ROPE_WITH_FREQUENCIES: Self = Self(1 << 35);
pub const CONV2D: Self = Self(1 << 36);
pub const MULTI_AXIS_ROTARY_EMBEDDINGS: Self = Self(1 << 37);
pub const MASKED_OUTPUT_PROJECTION: Self = Self(1 << 38);
pub const ALL: Self = Self((1 << 39) - 1);
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn contains(self, required: Self) -> bool {
self.0 & required.0 == required.0
}
pub fn missing_capability_names(self, required: Self) -> Vec<&'static str> {
const NAMES: &[(NeuralOperatorCapabilities, &str)] = &[
(
NeuralOperatorCapabilities::GELU_APPROXIMATE,
"gelu_approximate",
),
(NeuralOperatorCapabilities::SIGMOID, "sigmoid"),
(NeuralOperatorCapabilities::SOFTPLUS, "softplus"),
(NeuralOperatorCapabilities::EXP, "exp"),
(
NeuralOperatorCapabilities::GATED_GROUP_RMS_NORM,
"gated_group_rms_norm",
),
(NeuralOperatorCapabilities::L2_NORMALIZE, "l2_normalize"),
(
NeuralOperatorCapabilities::SILU_GATED_GROUP_RMS_NORM,
"silu_gated_group_rms_norm",
),
(
NeuralOperatorCapabilities::SEGMENTED_ATTENTION,
"segmented_attention",
),
(
NeuralOperatorCapabilities::GATED_DELTA_SCAN,
"gated_delta_scan",
),
(
NeuralOperatorCapabilities::SELECTIVE_STATE_SPACE_SCAN,
"selective_state_space_scan",
),
(
NeuralOperatorCapabilities::INDEXED_ATTENTION,
"indexed_attention",
),
(
NeuralOperatorCapabilities::POOLED_ATTENTION,
"pooled_attention",
),
(
NeuralOperatorCapabilities::POOLED_POSITION_SELECTION,
"select_pooled_positions",
),
(
NeuralOperatorCapabilities::POOLED_MASK_GATHER,
"gather_pooled_mask",
),
(
NeuralOperatorCapabilities::ATTENTION_SINKS,
"attention_sinks",
),
(
NeuralOperatorCapabilities::RELATIVE_ATTENTION,
"relative_attention",
),
(
NeuralOperatorCapabilities::JOINT_GROUP_SELECTION,
"joint_group_selection",
),
(
NeuralOperatorCapabilities::RMS_NORM_WITHOUT_WEIGHT,
"rms_norm_without_weight",
),
(NeuralOperatorCapabilities::GROUPED_LINEAR, "grouped_linear"),
(NeuralOperatorCapabilities::SUM_PARALLEL, "sum_parallel"),
(NeuralOperatorCapabilities::UNLOADED_I32, "unloaded_i32"),
(NeuralOperatorCapabilities::FROM_I32_SLICE, "from_i32_slice"),
(NeuralOperatorCapabilities::TO_F32_VEC, "to_f32_vec"),
(NeuralOperatorCapabilities::TO_I32_VEC, "to_i32_vec"),
(NeuralOperatorCapabilities::FULL_F32, "full_f32"),
(NeuralOperatorCapabilities::FULL_I32, "full_i32"),
(NeuralOperatorCapabilities::TANH, "tanh"),
(NeuralOperatorCapabilities::CLIP, "clip"),
(NeuralOperatorCapabilities::SOFTMAX_AXIS, "softmax_axis"),
(NeuralOperatorCapabilities::BROADCAST_TO, "broadcast_to"),
(NeuralOperatorCapabilities::ZEROS_LIKE, "zeros_like"),
(NeuralOperatorCapabilities::EQUAL_I32, "equal_i32"),
(NeuralOperatorCapabilities::LOGICAL_OR, "logical_or"),
(
NeuralOperatorCapabilities::WHERE_CONDITION,
"where_condition",
),
(NeuralOperatorCapabilities::MASKED_SCATTER, "masked_scatter"),
(
NeuralOperatorCapabilities::ROPE_WITH_FREQUENCIES,
"rope_with_frequencies",
),
(NeuralOperatorCapabilities::CONV2D, "conv2d"),
(
NeuralOperatorCapabilities::MULTI_AXIS_ROTARY_EMBEDDINGS,
"multi_axis_rotary_embeddings",
),
(
NeuralOperatorCapabilities::MASKED_OUTPUT_PROJECTION,
"masked_output_projection",
),
];
NAMES
.iter()
.filter_map(|(capability, name)| {
(required.contains(*capability) && !self.contains(*capability)).then_some(*name)
})
.collect()
}
}
#[cfg(test)]
mod neural_operator_capability_tests {
use super::NeuralOperatorCapabilities as C;
#[test]
fn all_includes_every_fail_closed_tensor_operation() {
for (capability, name) in [
(C::UNLOADED_I32, "unloaded_i32"),
(C::FROM_I32_SLICE, "from_i32_slice"),
(C::TO_F32_VEC, "to_f32_vec"),
(C::TO_I32_VEC, "to_i32_vec"),
(C::FULL_F32, "full_f32"),
(C::FULL_I32, "full_i32"),
(C::TANH, "tanh"),
(C::CLIP, "clip"),
(C::SOFTMAX_AXIS, "softmax_axis"),
(C::BROADCAST_TO, "broadcast_to"),
(C::ZEROS_LIKE, "zeros_like"),
(C::EQUAL_I32, "equal_i32"),
(C::LOGICAL_OR, "logical_or"),
(C::WHERE_CONDITION, "where_condition"),
(C::MASKED_SCATTER, "masked_scatter"),
(C::ROPE_WITH_FREQUENCIES, "rope_with_frequencies"),
(C::CONV2D, "conv2d"),
(
C::MULTI_AXIS_ROTARY_EMBEDDINGS,
"multi_axis_rotary_embeddings",
),
(C::MASKED_OUTPUT_PROJECTION, "masked_output_projection"),
] {
assert!(C::ALL.contains(capability));
assert_eq!(C::NONE.missing_capability_names(capability), [name]);
}
}
}
pub trait NeuralBackend: Sized + 'static {
const OPERATOR_CAPABILITIES: NeuralOperatorCapabilities = NeuralOperatorCapabilities::NONE;
type Tensor: Tensor;
type Linear: LinearOperator<Self::Tensor>;
type Embedding: EmbeddingOperator<Self::Tensor>;
type Normalization: NormalizationOperator<Self::Tensor>;
type Rotary: RotaryOperator<Self::Tensor>;
type ParallelContext: ?Sized;
fn require_operator_capabilities(
architecture: &'static str,
required: NeuralOperatorCapabilities,
) -> Result<(), Error> {
let available = Self::OPERATOR_CAPABILITIES;
if available.contains(required) {
return Ok(());
}
Err(Error::backend(format!(
"{architecture} requires unsupported backend operators: {}",
available.missing_capability_names(required).join(", ")
)))
}
fn linear(
spec: LinearSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Linear, Error>;
fn embedding(
spec: EmbeddingSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Embedding, Error>;
fn normalization(
spec: NormalizationConstructionSpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Normalization, Error>;
fn rotary(
spec: RotarySpec,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Rotary, Error>;
fn silu(
input: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn gelu_approximate(
input: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"approximate GELU is not implemented by this backend",
))
}
fn sigmoid(
input: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend("sigmoid is not implemented by this backend"))
}
fn softplus(
input: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"softplus is not implemented by this backend",
))
}
fn exp(
input: Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"exponential is not implemented by this backend",
))
}
fn gated_group_rms_norm(
input: &Self::Tensor,
gate: &Self::Tensor,
weight: &Self::Tensor,
groups: i32,
epsilon: f32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, gate, weight, groups, epsilon, context);
Err(Error::backend(
"gated grouped RMS normalization is not implemented by this backend",
))
}
fn l2_normalize(
input: &Self::Tensor,
epsilon: f32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, epsilon, context);
Err(Error::backend(
"L2 normalization is not implemented by this backend",
))
}
fn silu_gated_group_rms_norm(
input: &Self::Tensor,
gate: &Self::Tensor,
weight: &Self::Tensor,
groups: i32,
epsilon: f32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, gate, weight, groups, epsilon, context);
Err(Error::backend(
"SiLU-gated grouped RMS normalization is not implemented by this backend",
))
}
fn expand_heads(
input: &Self::Tensor,
expansion: HeadExpansion,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
expansion.validate(input)?;
if expansion.source_heads == expansion.target_heads {
return Ok(input.clone());
}
let mut expanded_shape = input.shape().to_vec();
expanded_shape.insert(expansion.axis + 1, 1);
let expanded = input.reshape(&expanded_shape, context)?;
expanded_shape[expansion.axis + 1] = expansion.repeats();
let expanded = expanded.broadcast_to(&expanded_shape, context)?;
expanded_shape[expansion.axis] = expansion.target_heads;
expanded_shape.remove(expansion.axis + 1);
expanded.reshape(&expanded_shape, context)
}
fn segmented_attention(
input: SegmentedAttentionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
input.validate()?;
let _ = context;
Err(Error::backend(
"segmented attention is not implemented by this backend",
))
}
fn add_residual(
residual: &Self::Tensor,
branch: &Self::Tensor,
fp32: bool,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = fp32;
residual.add(branch, context)
}
fn gated_delta_scan(
input: GatedDeltaScanInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<GatedDeltaScanOutput<Self::Tensor>, Error> {
let _ = (input, context);
Err(Error::backend(
"gated-delta scan is not implemented by this backend",
))
}
fn selective_state_space_scan(
input: SelectiveStateSpaceScanInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<SelectiveStateSpaceScanOutput<Self::Tensor>, Error> {
let _ = (input, context);
Err(Error::backend(
"selective state-space scan is not implemented by this backend",
))
}
fn indexed_attention(
input: IndexedAttentionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"indexed attention is not implemented by this backend",
))
}
fn pooled_attention(
input: PooledAttentionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"pooled attention is not implemented by this backend",
))
}
fn select_pooled_positions(
input: PooledPositionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"pooled-position selection is not implemented by this backend",
))
}
fn gather_pooled_mask(
mask: &Self::Tensor,
selected_positions: &Self::Tensor,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (mask, selected_positions, context);
Err(Error::backend(
"pooled-mask gathering is not implemented by this backend",
))
}
fn attention_with_sinks(
request: AttentionRequest<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
request.validate()?;
if request.sinks.is_some() {
return Err(Error::backend(
"attention sinks are not implemented by this backend",
));
}
Self::attention(
request.queries,
request.keys,
request.values,
request.scale,
request.mask,
context,
)
}
fn sliding_window_attention_with_sinks(
request: AttentionRequest<'_, Self::Tensor>,
window: i32,
position_offset: i32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
request.validate()?;
if request.sinks.is_some() {
return Err(Error::backend(
"sliding-window attention sinks are not implemented by this backend",
));
}
Self::sliding_window_attention(
request.queries,
request.keys,
request.values,
request.scale,
window,
position_offset,
context,
)
}
fn relative_attention(
input: RelativeAttentionInput<'_, Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, context);
Err(Error::backend(
"relative-profile attention is not implemented by this backend",
))
}
fn rms_norm_without_weight(
input: &Self::Tensor,
epsilon: f32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
let _ = (input, epsilon, context);
Err(Error::backend(
"weightless RMS normalization is not implemented by this backend",
))
}
fn rms_norm_with_weight(
input: &Self::Tensor,
weight: &Self::Tensor,
epsilon: f32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error> {
Self::rms_norm_without_weight(input, epsilon, context)?.multiply(weight, context)
}
fn gated_product(
gate: Self::Tensor,
up: Self::Tensor,
policy: GatedProductPolicy,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn attention(
queries: Self::Tensor,
keys: Self::Tensor,
values: Self::Tensor,
scale: f32,
mask: Option<&Self::Tensor>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
#[allow(clippy::too_many_arguments)]
fn sliding_window_attention(
queries: Self::Tensor,
keys: Self::Tensor,
values: Self::Tensor,
scale: f32,
window: i32,
position_offset: i32,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn causal_mask(
sequence: i32,
offset: i32,
window: Option<i32>,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn row_parallel_linear(
linear: &mut Self::Linear,
input: &Self::Tensor,
parallel: &Self::ParallelContext,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn parallel_size(_parallel: &Self::ParallelContext) -> usize {
1
}
}
pub trait DistributedNeuralBackend: NeuralBackend {
fn vocabulary_parallel_embedding(
spec: EmbeddingSpec,
range: VocabularyParallelRange,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Embedding, Error>;
fn vocabulary_parallel_linear(
spec: LinearSpec,
range: VocabularyParallelRange,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Linear, Error>;
fn vocabulary_parallel_lookup(
embedding: &mut Self::Embedding,
input: &Self::Tensor,
policy: EmbeddingLookupPolicy,
parallel: &Self::ParallelContext,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn vocabulary_parallel_project(
linear: &mut Self::Linear,
input: &Self::Tensor,
parallel: &Self::ParallelContext,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn vocabulary_parallel_embedding_project(
embedding: &mut Self::Embedding,
input: &Self::Tensor,
parallel: &Self::ParallelContext,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
fn sum_parallel(
value: Self::Tensor,
parallel: &Self::ParallelContext,
context: &<Self::Tensor as Tensor>::Context,
) -> Result<Self::Tensor, Error>;
}
#[derive(Debug, Clone, Copy)]
pub struct GatedDeltaScanInput<'a, T> {
pub query: &'a T,
pub key: &'a T,
pub value: &'a T,
pub log_decay: &'a T,
pub beta: &'a T,
pub initial_state: Option<&'a T>,
}
#[derive(Debug, Clone)]
pub struct GatedDeltaScanOutput<T> {
pub state: T,
pub output: T,
}
#[derive(Debug, Clone, Copy)]
pub struct SelectiveStateSpaceScanInput<'a, T> {
pub values: &'a T,
pub input_state: &'a T,
pub output_state: &'a T,
pub time_step: &'a T,
pub time_step_bias: &'a T,
pub transition_log: &'a T,
pub skip: &'a T,
pub initial_state: Option<&'a T>,
pub time_step_floor: f32,
pub chunk_size: usize,
}
#[derive(Debug, Clone)]
pub struct SelectiveStateSpaceScanOutput<T> {
pub state: T,
pub output: T,
}
#[allow(clippy::too_many_arguments)]
pub fn reference_selective_state_space_scan(
batch: usize,
sequence: usize,
heads: usize,
head_dimensions: usize,
state_dimensions: usize,
values: &[f32],
input_state: &[f32],
output_state: &[f32],
time_step: &[f32],
time_step_bias: &[f32],
transition_log: &[f32],
skip: &[f32],
time_step_floor: f32,
initial_state: Option<&[f32]>,
) -> Result<(Vec<f32>, Vec<f32>), Error> {
let groups = batch * sequence * heads;
let values_len = groups * head_dimensions;
let vectors_len = groups * state_dimensions;
let state_len = batch * heads * head_dimensions * state_dimensions;
if values.len() != values_len
|| input_state.len() != vectors_len
|| output_state.len() != vectors_len
|| time_step.len() != groups
|| time_step_bias.len() != heads
|| transition_log.len() != heads
|| skip.len() != heads
|| initial_state.is_some_and(|state| state.len() != state_len)
|| !time_step_floor.is_finite()
|| time_step_floor < 0.0
{
return Err(Error::backend(
"invalid selective state-space reference geometry",
));
}
let mut state = initial_state.map_or_else(|| vec![0.0; state_len], <[f32]>::to_vec);
let mut output = vec![0.0; values_len];
for batch_index in 0..batch {
for token in 0..sequence {
for head in 0..heads {
let group = (batch_index * sequence + token) * heads + head;
let dt =
((time_step[group] + time_step_bias[head]).exp().ln_1p()).max(time_step_floor);
let transition = (-transition_log[head].exp() * dt).exp();
let vector_base = group * state_dimensions;
for dimension in 0..head_dimensions {
let value_index = group * head_dimensions + dimension;
let state_base =
(batch_index * heads + head) * head_dimensions * state_dimensions
+ dimension * state_dimensions;
let value = values[value_index];
let mut projected = 0.0f32;
for state_dimension in 0..state_dimensions {
let state_index = state_base + state_dimension;
state[state_index] = state[state_index] * transition
+ dt * input_state[vector_base + state_dimension] * value;
projected +=
state[state_index] * output_state[vector_base + state_dimension];
}
output[value_index] = projected + value * skip[head];
}
}
}
}
Ok((state, output))
}
#[allow(clippy::too_many_arguments)]
pub fn reference_gated_delta_scan(
batch: usize,
sequence: usize,
heads: usize,
key_dim: usize,
value_dim: usize,
query: &[f32],
key: &[f32],
value: &[f32],
log_decay: &[f32],
vector_decay: bool,
beta: &[f32],
initial_state: Option<&[f32]>,
) -> Result<(Vec<f32>, Vec<f32>), Error> {
let key_values = batch * sequence * heads * key_dim;
let values = batch * sequence * heads * value_dim;
let groups = batch * sequence * heads;
let state_values = batch * heads * key_dim * value_dim;
if query.len() != key_values
|| key.len() != key_values
|| value.len() != values
|| beta.len() != groups
|| log_decay.len() != if vector_decay { key_values } else { groups }
|| initial_state.is_some_and(|state| state.len() != state_values)
{
return Err(Error::backend("invalid gated-delta reference geometry"));
}
let mut state = initial_state.map_or_else(|| vec![0.0; state_values], <[f32]>::to_vec);
let mut output = vec![0.0; values];
for batch_index in 0..batch {
for token in 0..sequence {
for head in 0..heads {
let group = (batch_index * sequence + token) * heads + head;
let state_group = (batch_index * heads + head) * key_dim * value_dim;
for value_index in 0..value_dim {
let mut memory = 0.0f32;
for key_index in 0..key_dim {
let vector_index = group * key_dim + key_index;
let decay = if vector_decay {
log_decay[vector_index]
} else {
log_decay[group]
}
.exp();
let state_index = state_group + key_index * value_dim + value_index;
state[state_index] *= decay;
memory += state[state_index] * key[vector_index];
}
let value_index_flat = group * value_dim + value_index;
let delta = (value[value_index_flat] - memory) * beta[group];
let mut accumulated = 0.0f32;
for key_index in 0..key_dim {
let vector_index = group * key_dim + key_index;
let state_index = state_group + key_index * value_dim + value_index;
state[state_index] += key[vector_index] * delta;
accumulated += state[state_index] * query[vector_index];
}
output[value_index_flat] = accumulated;
}
}
}
}
Ok((state, output))
}
#[cfg(test)]
mod gated_delta_reference_tests {
use super::reference_gated_delta_scan;
#[test]
fn chunked_continuation_matches_one_scan() {
let query = [0.5, -0.25, 0.1, 0.2, -0.4, 0.8];
let key = [0.3, 0.4, -0.2, 0.7, 0.6, -0.1];
let value = [1.0, -0.5, 0.25, 0.75, -0.3, 0.9];
let decay = [-0.2, -0.1, -0.4, -0.3, -0.5, -0.25];
let beta = [0.8, 0.6, 0.4];
let (expected_state, expected) = reference_gated_delta_scan(
1, 3, 1, 2, 2, &query, &key, &value, &decay, true, &beta, None,
)
.unwrap();
let (state, mut actual) = reference_gated_delta_scan(
1,
2,
1,
2,
2,
&query[..4],
&key[..4],
&value[..4],
&decay[..4],
true,
&beta[..2],
None,
)
.unwrap();
let (actual_state, tail) = reference_gated_delta_scan(
1,
1,
1,
2,
2,
&query[4..],
&key[4..],
&value[4..],
&decay[4..],
true,
&beta[2..],
Some(&state),
)
.unwrap();
actual.extend(tail);
assert!(expected
.iter()
.zip(actual)
.all(|(left, right)| (left - right).abs() < 1e-6));
assert!(expected_state
.iter()
.zip(actual_state)
.all(|(left, right)| (left - right).abs() < 1e-6));
}
}
#[cfg(test)]
mod selective_state_space_reference_tests {
use super::reference_selective_state_space_scan;
#[test]
fn continuation_matches_one_scan() {
let values = [0.2, -0.4, 0.8, 0.5, -0.3, 0.7];
let input_state = [0.1, 0.3, -0.2, 0.4, 0.6, -0.5];
let output_state = [0.7, -0.1, 0.2, 0.5, -0.4, 0.9];
let time_step = [-0.3, 0.1, -0.2];
let bias = [0.05];
let transition = [-0.4];
let skip = [0.25];
let (expected_state, expected) = reference_selective_state_space_scan(
1,
3,
1,
2,
2,
&values,
&input_state,
&output_state,
&time_step,
&bias,
&transition,
&skip,
0.001,
None,
)
.unwrap();
let (state, mut actual) = reference_selective_state_space_scan(
1,
2,
1,
2,
2,
&values[..4],
&input_state[..4],
&output_state[..4],
&time_step[..2],
&bias,
&transition,
&skip,
0.001,
None,
)
.unwrap();
let (actual_state, tail) = reference_selective_state_space_scan(
1,
1,
1,
2,
2,
&values[4..],
&input_state[4..],
&output_state[4..],
&time_step[2..],
&bias,
&transition,
&skip,
0.001,
Some(&state),
)
.unwrap();
actual.extend(tail);
assert!(expected
.iter()
.zip(actual)
.all(|(left, right)| (left - right).abs() < 1e-6));
assert!(expected_state
.iter()
.zip(actual_state)
.all(|(left, right)| (left - right).abs() < 1e-6));
}
}
pub trait Tensor: Clone + Debug + Sized + 'static {
type Context: ?Sized;
fn shape(&self) -> &[i32];
fn dim(&self, axis: usize) -> i32 {
self.shape()[axis]
}
fn unloaded_f32(shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
fn unloaded_i32(shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
let _ = (shape, context);
Err(Error::backend(
"I32 parameter allocation is not implemented by this backend",
))
}
fn from_f32_slice(
values: &[f32],
shape: &[i32],
context: &Self::Context,
) -> Result<Self, Error>;
fn from_i32_slice(
values: &[i32],
shape: &[i32],
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (values, shape, context);
Err(Error::backend(
"I32 tensor construction is not implemented by this backend",
))
}
fn to_f32_vec(&self, context: &Self::Context) -> Result<Vec<f32>, Error> {
let _ = context;
Err(Error::backend(
"F32 host materialization is not implemented by this backend",
))
}
fn to_i32_vec(&self, context: &Self::Context) -> Result<Vec<i32>, Error> {
let _ = context;
Err(Error::backend(
"I32 host materialization is not implemented by this backend",
))
}
fn full_f32(value: f32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
let _ = (value, shape, context);
Err(Error::backend(
"filled tensor construction is not implemented by this backend",
))
}
fn full_i32(value: i32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
let _ = (value, shape, context);
Err(Error::backend(
"filled I32 tensor construction is not implemented by this backend",
))
}
fn add(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
fn subtract(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
fn multiply(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
fn multiply_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
fn divide(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
fn square(&self, context: &Self::Context) -> Result<Self, Error>;
fn tanh(&self, context: &Self::Context) -> Result<Self, Error> {
let _ = context;
Err(Error::backend(
"tanh is not implemented by this tensor backend",
))
}
fn maximum_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
fn maximum_i32(&self, rhs: i32, context: &Self::Context) -> Result<Self, Error> {
self.maximum_scalar(rhs as f32, context)
}
fn clip(&self, minimum: &Self, maximum: &Self, context: &Self::Context) -> Result<Self, Error> {
let _ = (minimum, maximum, context);
Err(Error::backend(
"clip is not implemented by this tensor backend",
))
}
fn softmax_axis(
&self,
axis: i32,
precise: bool,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (axis, precise, context);
Err(Error::backend(
"softmax is not implemented by this tensor backend",
))
}
fn reshape(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
fn broadcast_to(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
let _ = (shape, context);
Err(Error::backend(
"broadcasting is not implemented by this tensor backend",
))
}
fn transpose_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
fn swap_axes(&self, left: i32, right: i32, context: &Self::Context) -> Result<Self, Error>;
fn transpose(&self, context: &Self::Context) -> Result<Self, Error>;
fn expand_dims(&self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
fn squeeze_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
fn index(&self, indexes: &[Index], context: &Self::Context) -> Result<Self, Error>;
fn take_axis(&self, indexes: &Self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
fn zeros_like(&self, context: &Self::Context) -> Result<Self, Error> {
let _ = context;
Err(Error::backend(
"dtype-preserving zero allocation is not implemented by this tensor backend",
))
}
fn equal_i32(&self, value: i32, context: &Self::Context) -> Result<Self, Error> {
let _ = (value, context);
Err(Error::backend(
"integer scalar comparison is not implemented by this tensor backend",
))
}
fn logical_or(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error> {
let _ = (rhs, context);
Err(Error::backend(
"logical disjunction is not implemented by this tensor backend",
))
}
fn where_condition(
condition: &Self,
when_true: &Self,
when_false: &Self,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (condition, when_true, when_false, context);
Err(Error::backend(
"conditional selection is not implemented by this tensor backend",
))
}
fn masked_scatter(
&self,
mask: &Self,
source: &Self,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (mask, source, context);
Err(Error::backend(
"masked scatter is not implemented by this tensor backend",
))
}
fn rope_with_frequencies(
&self,
dimensions: i32,
traditional: bool,
offset: i32,
frequencies: &Self,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (dimensions, traditional, offset, frequencies, context);
Err(Error::backend(
"explicit-frequency rotary positions are not implemented by this tensor backend",
))
}
fn concatenate(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
fn stack(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
fn matmul(lhs: &Self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
fn sum_axis(
value: &Self,
axis: i32,
keep_dims: bool,
context: &Self::Context,
) -> Result<Self, Error>;
fn mean_axis(
value: &Self,
axis: i32,
keep_dims: bool,
context: &Self::Context,
) -> Result<Self, Error> {
let width = value
.shape()
.get(if axis < 0 {
usize::try_from(value.shape().len() as i32 + axis).unwrap_or(usize::MAX)
} else {
usize::try_from(axis).unwrap_or(usize::MAX)
})
.copied()
.ok_or_else(|| Error::backend(format!("mean axis {axis} is out of range")))?;
Self::sum_axis(value, axis, keep_dims, context)?
.multiply_scalar(1.0 / width as f32, context)
}
fn argmin_axis(
value: &Self,
axis: i32,
keep_dims: bool,
context: &Self::Context,
) -> Result<Self, Error>;
fn pad(
value: &Self,
widths: &[(i32, i32)],
mode: PadMode,
context: &Self::Context,
) -> Result<Self, Error>;
#[allow(clippy::too_many_arguments)]
fn conv1d(
input: &Self,
weight: &Self,
stride: i32,
padding: i32,
dilation: i32,
groups: i32,
context: &Self::Context,
) -> Result<Self, Error>;
#[allow(clippy::too_many_arguments)]
fn conv2d(
input: &Self,
weight: &Self,
stride: (i32, i32),
padding: (i32, i32),
dilation: (i32, i32),
groups: i32,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (input, weight, stride, padding, dilation, groups, context);
Err(Error::backend(
"two-dimensional convolution is not implemented by this backend",
))
}
#[allow(clippy::too_many_arguments)]
fn conv_transpose1d(
input: &Self,
weight: &Self,
stride: i32,
padding: i32,
dilation: i32,
output_padding: i32,
groups: i32,
context: &Self::Context,
) -> Result<Self, Error>;
fn linear(
input: &Self,
weight: &Self,
bias: Option<&Self>,
context: &Self::Context,
) -> Result<Self, Error>;
fn layer_norm(
input: &Self,
weight: Option<&Self>,
bias: Option<&Self>,
epsilon: f32,
context: &Self::Context,
) -> Result<Self, Error>;
fn gelu(input: &Self, context: &Self::Context) -> Result<Self, Error>;
fn elu(input: &Self, alpha: f32, context: &Self::Context) -> Result<Self, Error>;
#[allow(clippy::too_many_arguments)]
fn rope(
input: &Self,
dimensions: i32,
traditional: bool,
base: f32,
scale: f32,
offset: i32,
context: &Self::Context,
) -> Result<Self, Error>;
fn multi_axis_rotary_embeddings(
position_ids: &Self,
spec: &multimodal::MultiAxisRotarySpec,
context: &Self::Context,
) -> Result<(Self, Self), Error> {
let _ = (position_ids, spec, context);
Err(Error::backend(
"multi-axis rotary embeddings are not implemented by this backend",
))
}
fn masked_output_projection(
input: multimodal::MaskedOutputProjectionInput<'_, Self>,
context: &Self::Context,
) -> Result<Self, Error> {
let _ = (input, context);
Err(Error::backend(
"masked output projection is not implemented by this backend",
))
}
fn scaled_dot_product_attention(
queries: &Self,
keys: &Self,
values: &Self,
scale: f32,
mask: AttentionMask<'_, Self>,
context: &Self::Context,
) -> Result<Self, Error>;
}
#[derive(Debug, Clone)]
pub struct Parameter<T> {
spec: ParameterSpec,
trainable: bool,
value: T,
}
impl<T> Parameter<T> {
pub fn new(spec: ParameterSpec, value: T) -> Self {
let trainable = spec.trainable;
Self {
spec,
trainable,
value,
}
}
pub const fn as_ref(&self) -> &T {
&self.value
}
pub fn replace(&mut self, value: T) {
self.value = value;
}
}
impl<T: Tensor> Parameter<T> {
pub fn unloaded(
spec: ParameterSpec,
shape: &[i32],
context: &T::Context,
) -> Result<Self, Error> {
Ok(Self::new(spec, T::unloaded_f32(shape, context)?))
}
pub fn unloaded_i32(
spec: ParameterSpec,
shape: &[i32],
context: &T::Context,
) -> Result<Self, Error> {
Ok(Self::new(spec, T::unloaded_i32(shape, context)?))
}
}
impl<T: 'static> Parameterized<T> for Parameter<T> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>,
{
visitor.visit(
ParameterMetadata::from_spec(&self.spec, self.trainable),
&self.value,
);
}
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>,
{
visitor.visit_mut(
ParameterMetadata::from_spec(&self.spec, self.trainable),
&mut self.value,
);
}
fn set_trainable(&mut self, trainable: bool) {
self.trainable = trainable;
}
}
impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Vec<M> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>,
{
for module in self {
module.visit_parameters(visitor);
}
}
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>,
{
for module in self {
module.visit_parameters_mut(visitor);
}
}
fn set_trainable(&mut self, trainable: bool) {
for module in self {
module.set_trainable(trainable);
}
}
}
impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Option<M> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>,
{
if let Some(module) = self {
module.visit_parameters(visitor);
}
}
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>,
{
if let Some(module) = self {
module.visit_parameters_mut(visitor);
}
}
fn set_trainable(&mut self, trainable: bool) {
if let Some(module) = self {
module.set_trainable(trainable);
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ConvolutionActivation {
Identity,
Silu,
}
#[derive(Debug, Clone)]
pub struct CausalDepthwiseConvolutionSpec {
pub channels: i32,
pub kernel_size: i32,
pub weight: ParameterSpec,
pub bias: Option<ParameterSpec>,
pub activation: ConvolutionActivation,
}
impl CausalDepthwiseConvolutionSpec {
pub fn validate(&self) -> Result<(), Error> {
if self.channels <= 0 {
return Err(Error::backend(format!(
"causal depthwise convolution channels must be positive, got {}",
self.channels
)));
}
if self.kernel_size <= 0 {
return Err(Error::backend(format!(
"causal depthwise convolution kernel size must be positive, got {}",
self.kernel_size
)));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CausalDepthwiseConvolutionOutput<T> {
pub output: T,
pub history: Option<T>,
}
#[derive(Debug, Clone, Parameterized)]
#[parameterized(tensor = "B::Tensor")]
pub struct CausalDepthwiseConvolution<B: NeuralBackend> {
pub weight: Parameter<B::Tensor>,
pub bias: Option<Parameter<B::Tensor>>,
#[parameter(skip)]
channels: i32,
#[parameter(skip)]
kernel_size: i32,
#[parameter(skip)]
activation: ConvolutionActivation,
}
impl<B: NeuralBackend> CausalDepthwiseConvolution<B> {
pub fn new(
spec: CausalDepthwiseConvolutionSpec,
context: &<B::Tensor as Tensor>::Context,
) -> Result<Self, Error> {
spec.validate()?;
Ok(Self {
weight: Parameter::unloaded(
spec.weight,
&[spec.channels, 1, spec.kernel_size],
context,
)?,
bias: spec
.bias
.map(|bias| Parameter::unloaded(bias, &[spec.channels], context))
.transpose()?,
channels: spec.channels,
kernel_size: spec.kernel_size,
activation: spec.activation,
})
}
pub const fn history_len(&self) -> i32 {
self.kernel_size - 1
}
pub fn forward(
&self,
input: &B::Tensor,
history: Option<&B::Tensor>,
context: &<B::Tensor as Tensor>::Context,
) -> Result<CausalDepthwiseConvolutionOutput<B::Tensor>, Error> {
let shape = input.shape();
if shape.len() != 3 || shape[0] <= 0 || shape[1] <= 0 || shape[2] != self.channels {
return Err(Error::backend(format!(
"causal depthwise convolution expects [batch, sequence, {}], got {shape:?}",
self.channels
)));
}
let history_len = self.history_len();
let padded = if history_len == 0 {
if history.is_some() {
return Err(Error::backend(
"width-one causal convolution does not accept history",
));
}
input.clone()
} else if let Some(history) = history {
let expected = [shape[0], history_len, self.channels];
if history.shape() != expected {
return Err(Error::backend(format!(
"causal depthwise convolution history must have shape {expected:?}, got {:?}",
history.shape()
)));
}
B::Tensor::concatenate(&[history.clone(), input.clone()], 1, context)?
} else {
B::Tensor::pad(
input,
&[(0, 0), (history_len, 0), (0, 0)],
PadMode::Constant,
context,
)?
};
let execution_weight = self.weight.as_ref().swap_axes(1, 2, context)?;
let mut output =
B::Tensor::conv1d(&padded, &execution_weight, 1, 0, 1, self.channels, context)?;
if output.shape() != shape {
return Err(Error::backend(format!(
"causal depthwise convolution backend returned shape {:?}, expected {shape:?}",
output.shape()
)));
}
if let Some(bias) = &self.bias {
let bias = bias
.as_ref()
.reshape(&[1, 1, self.channels], context)?
.broadcast_to(shape, context)?;
output = output.add(&bias, context)?;
}
if self.activation == ConvolutionActivation::Silu {
output = B::silu(output, context)?;
}
let history = (history_len > 0)
.then(|| {
padded.index(
&[
Index::Full,
Index::Range(shape[1], shape[1] + history_len),
Index::Full,
],
context,
)
})
.transpose()?;
Ok(CausalDepthwiseConvolutionOutput { output, history })
}
}
#[derive(Debug, Clone)]
pub struct GatedShortConvolutionSpec {
pub input_dimensions: i32,
pub channels: i32,
pub output_dimensions: i32,
pub input_projection: LinearSpec,
pub output_projection: LinearSpec,
pub convolution: CausalDepthwiseConvolutionSpec,
}
impl GatedShortConvolutionSpec {
pub fn validate(&self) -> Result<(), Error> {
self.convolution.validate()?;
let fused = self
.channels
.checked_mul(3)
.ok_or_else(|| Error::backend("gated short-convolution width overflowed"))?;
if self.input_dimensions <= 0
|| self.channels <= 0
|| self.output_dimensions <= 0
|| self.convolution.channels != self.channels
|| self.input_projection.input != self.input_dimensions
|| self.input_projection.output != fused
|| self.output_projection.input != self.channels
|| self.output_projection.output != self.output_dimensions
{
return Err(Error::backend(format!(
"invalid gated short-convolution geometry input={} channels={} output={} fused_projection={}x{} output_projection={}x{} convolution_channels={}",
self.input_dimensions,
self.channels,
self.output_dimensions,
self.input_projection.input,
self.input_projection.output,
self.output_projection.input,
self.output_projection.output,
self.convolution.channels,
)));
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct GatedShortConvolutionOutput<T> {
pub output: T,
pub history: Option<T>,
}
#[derive(Debug, Clone, Parameterized)]
#[parameterized(tensor = "B::Tensor")]
pub struct GatedShortConvolution<B: NeuralBackend> {
pub input_projection: B::Linear,
pub convolution: CausalDepthwiseConvolution<B>,
pub output_projection: B::Linear,
#[parameter(skip)]
channels: i32,
}
impl<B: NeuralBackend> GatedShortConvolution<B> {
pub fn new(
spec: GatedShortConvolutionSpec,
context: &<B::Tensor as Tensor>::Context,
) -> Result<Self, Error> {
spec.validate()?;
Ok(Self {
input_projection: B::linear(spec.input_projection, context)?,
convolution: CausalDepthwiseConvolution::new(spec.convolution, context)?,
output_projection: B::linear(spec.output_projection, context)?,
channels: spec.channels,
})
}
fn hidden(
&mut self,
input: &B::Tensor,
history: Option<&B::Tensor>,
context: &<B::Tensor as Tensor>::Context,
) -> Result<(B::Tensor, Option<B::Tensor>), Error> {
let projected = self.input_projection.forward(input, context)?;
let rank = projected.shape().len();
if rank == 0 || projected.shape()[rank - 1] != 3 * self.channels {
return Err(Error::backend(format!(
"gated short-convolution projection returned shape {:?}, expected final width {}",
projected.shape(),
3 * self.channels
)));
}
let mut segment = vec![Index::Full; rank];
segment[rank - 1] = Index::Range(0, self.channels);
let b = projected.index(&segment, context)?;
segment[rank - 1] = Index::Range(self.channels, 2 * self.channels);
let c = projected.index(&segment, context)?;
segment[rank - 1] = Index::Range(2 * self.channels, 3 * self.channels);
let x = projected.index(&segment, context)?;
let convolution = self
.convolution
.forward(&b.multiply(&x, context)?, history, context)?;
Ok((
c.multiply(&convolution.output, context)?,
convolution.history,
))
}
pub fn forward(
&mut self,
input: &B::Tensor,
history: Option<&B::Tensor>,
context: &<B::Tensor as Tensor>::Context,
) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
let (hidden, history) = self.hidden(input, history, context)?;
Ok(GatedShortConvolutionOutput {
output: self.output_projection.forward(&hidden, context)?,
history,
})
}
pub fn forward_parallel(
&mut self,
input: &B::Tensor,
history: Option<&B::Tensor>,
parallel: &B::ParallelContext,
context: &<B::Tensor as Tensor>::Context,
) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
let (hidden, history) = self.hidden(input, history, context)?;
Ok(GatedShortConvolutionOutput {
output: B::row_parallel_linear(
&mut self.output_projection,
&hidden,
parallel,
context,
)?,
history,
})
}
}
#[cfg(test)]
mod grouped_contract_tests {
use super::*;
fn dense_format() -> LinearFormatSpec {
LinearFormatSpec::unscaled(LinearFormat::Dense).unwrap()
}
fn parameters(prefix: &str) -> GatedProductGroupParameters {
let projection = |name| {
GroupedProjectionSpec::new(
ParameterSpec::trainable(name).unwrap(),
None,
dense_format(),
)
.unwrap()
};
GatedProductGroupParameters::new(
projection(format!("{prefix}.gate.weight")),
projection(format!("{prefix}.up.weight")),
projection(format!("{prefix}.down.weight")),
)
}
#[test]
fn top_k_selection_policy_rejects_invalid_counts() {
assert!(TopKGroupSelectionSpec::new(8, 2, GroupScoring::Softmax, true).is_ok());
assert!(TopKGroupSelectionSpec::new(0, 1, GroupScoring::Softmax, false).is_err());
assert!(TopKGroupSelectionSpec::new(8, 9, GroupScoring::Softmax, false).is_err());
}
#[test]
fn gated_product_policy_rejects_malformed_scalars() {
assert!(
GatedProductPolicy::new(GatedProductActivation::Silu, Some(0.0), None, 1.0, 0.0,)
.is_err()
);
assert!(GatedProductPolicy::new(
GatedProductActivation::Silu,
None,
Some(f32::NAN),
1.0,
0.0,
)
.is_err());
assert!(
GatedProductPolicy::new(GatedProductActivation::Silu, None, None, 0.0, 0.0,).is_err()
);
assert!(GatedProductPolicy::new(
GatedProductActivation::Silu,
None,
None,
1.0,
f32::INFINITY,
)
.is_err());
}
#[test]
fn selector_projection_and_correction_biases_require_distinct_identities() {
let shared_bias = ParameterSpec::trainable("selector.bias").unwrap();
let spec = TopKGroupSelectorSpec::new(
4,
ParameterSpec::trainable("selector.weight").unwrap(),
dense_format(),
TopKGroupSelectionSpec::new(2, 1, GroupScoring::SelectedSoftmax, false).unwrap(),
)
.unwrap()
.with_bias(shared_bias.clone())
.unwrap();
assert!(spec.with_correction_bias(shared_bias).is_err());
}
#[test]
fn independent_group_layout_requires_exact_cardinality() {
assert!(GroupedGatedProductSpec::new(
2,
16,
8,
16,
eredu_nn::GatedProductPolicy::ordinary_silu(),
GatedProductGroupLayout::Independent(vec![parameters("e0"), parameters("e1")]),
)
.is_ok());
assert!(GroupedGatedProductSpec::new(
2,
16,
8,
16,
eredu_nn::GatedProductPolicy::ordinary_silu(),
GatedProductGroupLayout::Independent(vec![parameters("e0")]),
)
.is_err());
}
#[test]
fn gated_product_bank_rejects_reused_projection_bias_identity() {
let shared = ParameterSpec::trainable("groups.gate_up").unwrap();
let gate_up = GroupedProjectionSpec::new(shared.clone(), Some(shared), dense_format());
assert!(gate_up.is_err());
}
#[test]
fn quantized_group_projection_requires_explicit_companion_identities() {
let format =
LinearFormat::Affine(eredu_checkpoint::AffineQuantization::new(32, 4).unwrap());
let projection = |format| {
GroupedProjectionSpec::new(
ParameterSpec::trainable("arbitrary.group.matrix").unwrap(),
None,
format,
)
};
assert!(LinearFormatSpec::unscaled(format).is_err());
assert!(projection(
LinearFormatSpec::affine(
format,
ParameterSpec::trainable("unrelated.scale.identity").unwrap(),
ParameterSpec::trainable("unrelated.affine.identity").unwrap(),
)
.unwrap()
)
.is_ok());
}
}
#[derive(Debug, Clone)]
pub struct Linear<T> {
pub weight: Parameter<T>,
pub bias: Option<Parameter<T>>,
}
impl<T: Tensor> Linear<T> {
pub fn unloaded(spec: LinearSpec, context: &T::Context) -> Result<Self, Error> {
Ok(Self {
weight: Parameter::unloaded(spec.weight, &[spec.output, spec.input], context)?,
bias: spec
.bias
.map(|bias| Parameter::unloaded(bias, &[spec.output], context))
.transpose()?,
})
}
pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
T::linear(
input,
self.weight.as_ref(),
self.bias.as_ref().map(Parameter::as_ref),
context,
)
}
}
impl<T: 'static> Parameterized<T> for Linear<T> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>,
{
self.weight.visit_parameters(visitor);
if let Some(bias) = &self.bias {
bias.visit_parameters(visitor);
}
}
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>,
{
self.weight.visit_parameters_mut(visitor);
if let Some(bias) = &mut self.bias {
bias.visit_parameters_mut(visitor);
}
}
fn set_trainable(&mut self, trainable: bool) {
self.weight.set_trainable(trainable);
if let Some(bias) = &mut self.bias {
bias.set_trainable(trainable);
}
}
}
#[derive(Debug, Clone)]
pub struct LayerNorm<T> {
pub epsilon: f32,
pub weight: Option<Parameter<T>>,
pub bias: Option<Parameter<T>>,
}
impl<T: Tensor> LayerNorm<T> {
pub fn unloaded(
dimensions: i32,
epsilon: f32,
weight: Option<ParameterSpec>,
bias: Option<ParameterSpec>,
context: &T::Context,
) -> Result<Self, Error> {
Ok(Self {
epsilon,
weight: weight
.map(|weight| Parameter::unloaded(weight, &[dimensions], context))
.transpose()?,
bias: bias
.map(|bias| Parameter::unloaded(bias, &[dimensions], context))
.transpose()?,
})
}
pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
T::layer_norm(
input,
self.weight.as_ref().map(Parameter::as_ref),
self.bias.as_ref().map(Parameter::as_ref),
self.epsilon,
context,
)
}
}
impl<T: 'static> Parameterized<T> for LayerNorm<T> {
fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
where
V: ParameterVisitor<'a, T>,
{
if let Some(weight) = &self.weight {
weight.visit_parameters(visitor);
}
if let Some(bias) = &self.bias {
bias.visit_parameters(visitor);
}
}
fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
where
V: ParameterVisitorMut<'a, T>,
{
if let Some(weight) = &mut self.weight {
weight.visit_parameters_mut(visitor);
}
if let Some(bias) = &mut self.bias {
bias.visit_parameters_mut(visitor);
}
}
fn set_trainable(&mut self, trainable: bool) {
if let Some(weight) = &mut self.weight {
weight.set_trainable(trainable);
}
if let Some(bias) = &mut self.bias {
bias.set_trainable(trainable);
}
}
}
#[cfg(test)]
mod parameter_topology_tests {
use super::*;
#[derive(Parameterized)]
#[parameterized(tensor = "i32")]
struct DerivedModule {
first: Parameter<i32>,
second: Option<Parameter<i32>>,
#[parameter(skip)]
label: &'static str,
}
#[derive(Parameterized)]
#[parameterized(tensor = "i32")]
enum DerivedChoice {
Present(Parameter<i32>),
Empty,
}
fn parameter(id: &str, value: i32) -> Parameter<i32> {
Parameter::new(ParameterSpec::trainable(id).unwrap(), value)
}
#[test]
fn derive_recurses_through_structs_options_and_enums() {
let mut module = DerivedModule {
first: parameter("first.weight", 1),
second: Some(parameter("second.weight", 2)),
label: "not a parameter",
};
assert_eq!(module.label, "not a parameter");
let metadata = validate_parameter_topology::<i32, _>(&module).unwrap();
assert_eq!(
metadata
.iter()
.map(|entry| entry.id.as_str())
.collect::<Vec<_>>(),
["first.weight", "second.weight"]
);
module.set_trainable(false);
assert!(validate_parameter_topology::<i32, _>(&module)
.unwrap()
.iter()
.all(|entry| !entry.trainable));
let choice = DerivedChoice::Present(parameter("choice.weight", 3));
assert_eq!(
validate_parameter_topology::<i32, _>(&choice).unwrap()[0]
.id
.as_str(),
"choice.weight"
);
assert!(validate_parameter_topology::<i32, _>(&DerivedChoice::Empty)
.unwrap()
.is_empty());
}
#[test]
fn validation_rejects_duplicates_and_invalid_aliases() {
let duplicate = vec![parameter("same.weight", 1), parameter("same.weight", 2)];
assert!(matches!(
validate_parameter_topology::<i32, _>(&duplicate),
Err(ParameterTopologyError::DuplicateId(id)) if id.as_str() == "same.weight"
));
let alias = Parameter::new(
ParameterSpec {
id: ParameterId::new("alias.weight").unwrap(),
trainable: true,
alias_of: Some(ParameterId::new("missing.weight").unwrap()),
group: None,
linear_companion: None,
linear_companion_of: None,
},
1,
);
assert!(matches!(
validate_parameter_topology::<i32, _>(&alias),
Err(ParameterTopologyError::MissingAliasDestination { .. })
));
}
}
#[cfg(test)]
mod fused_projection_layout_tests {
use super::*;
#[test]
fn component_major_layout_is_checked_and_stable() {
let layout = FusedProjectionLayout::new([
FusedProjectionSegment::new("query", 8).unwrap(),
FusedProjectionSegment::new("key", 4).unwrap(),
FusedProjectionSegment::new("value", 4).unwrap(),
])
.unwrap();
assert_eq!(layout.output_width(), 16);
assert_eq!(
layout
.segments()
.iter()
.map(|segment| (segment.name(), segment.width()))
.collect::<Vec<_>>(),
[("query", 8), ("key", 4), ("value", 4)]
);
assert!(FusedProjectionLayout::new(Vec::new()).is_err());
assert!(FusedProjectionLayout::new([
FusedProjectionSegment::new("same", 1).unwrap(),
FusedProjectionSegment::new("same", 1).unwrap(),
])
.is_err());
assert!(FusedProjectionSegment::new("", 1).is_err());
assert!(FusedProjectionSegment::new("bad", 0).is_err());
}
#[test]
fn zero_sentinel_cannot_alias_an_embedding_row() {
EmbeddingLookupPolicy::Strict.validate().unwrap();
EmbeddingLookupPolicy::ZeroSentinel(-1).validate().unwrap();
assert!(EmbeddingLookupPolicy::ZeroSentinel(0).validate().is_err());
}
#[test]
fn vocabulary_parallel_ownership_requires_exact_global_rows() {
let range = VocabularyParallelRange {
global_vocabulary: 5,
local: 0..3,
};
range.validate_global_rows(5).unwrap();
assert!(range.validate_global_rows(4).is_err());
assert!(range.validate_global_rows(-1).is_err());
}
}
#[derive(Debug, Clone, Copy)]
pub struct Rope {
dimensions: i32,
traditional: bool,
base: f32,
scale: f32,
}
impl Rope {
pub const fn new(dimensions: i32, traditional: bool, base: f32, scale: f32) -> Self {
Self {
dimensions,
traditional,
base,
scale,
}
}
pub fn forward<T: Tensor>(
&self,
input: &T,
offset: i32,
context: &T::Context,
) -> Result<T, Error> {
T::rope(
input,
self.dimensions,
self.traditional,
self.base,
self.scale,
offset,
context,
)
}
}