Skip to main content

eredu_nn/
lib.rs

1//! Backend-neutral neural computation contracts.
2//!
3//! Architecture implementations use opaque backend tensors through this
4//! interface. Tensor storage, physical layout, laziness, device placement, and
5//! synchronization remain owned by the selected backend.
6
7#![warn(missing_docs)]
8
9extern crate self as eredu_nn;
10
11use std::fmt::Debug;
12
13use eredu_checkpoint::LinearFormat;
14
15pub use eredu_nn_macros::Parameterized;
16
17/// Reusable patch projection and multi-axis position operations.
18pub mod multimodal;
19/// Checked tensor-independent normalization and mask geometry.
20pub mod operation_geometry;
21/// Pure sequence layouts shared by patch-based encoders.
22pub mod sequence_layout;
23
24/// Backend operation failure.
25#[derive(Debug, Clone, thiserror::Error)]
26#[error("{message}")]
27pub struct Error {
28    message: String,
29}
30
31impl Error {
32    /// Creates a backend operation failure without exposing backend-native
33    /// exception types through architecture code.
34    pub fn backend(error: impl std::fmt::Display) -> Self {
35        Self {
36            message: error.to_string(),
37        }
38    }
39}
40
41/// One axis of a backend-neutral tensor view.
42#[derive(Debug, Clone, Copy, Eq, PartialEq)]
43pub enum Index {
44    /// Retains the complete axis.
45    Full,
46    /// Selects one element and removes the axis.
47    At(i32),
48    /// Retains the half-open interval `[start, end)`.
49    Range(i32, i32),
50}
51
52/// Padding behavior for convolutional architecture components.
53#[derive(Debug, Clone, Copy, Eq, PartialEq)]
54pub enum PadMode {
55    /// Pads with zeroes.
56    Constant,
57    /// Repeats the nearest edge value.
58    Edge,
59}
60
61/// Attention masking selected by architecture code.
62#[derive(Debug, Clone, Copy)]
63pub enum AttentionMask<'a, T> {
64    /// No attention mask.
65    None,
66    /// Standard causal mask.
67    Causal,
68    /// Backend tensor added to attention logits.
69    Tensor(&'a T),
70}
71
72/// Validated expansion from a smaller logical head axis to a repeated head
73/// axis. Repetition preserves grouped order: each source head is repeated
74/// contiguously `target_heads / source_heads` times.
75#[derive(Debug, Clone, Copy, Eq, PartialEq)]
76pub struct HeadExpansion {
77    /// Tensor axis containing source heads.
78    pub axis: usize,
79    /// Number of source heads.
80    pub source_heads: i32,
81    /// Number of heads after expansion.
82    pub target_heads: i32,
83}
84
85impl HeadExpansion {
86    /// Validates the head counts and the selected tensor axis.
87    pub fn validate<T: Tensor>(&self, input: &T) -> Result<(), Error> {
88        let shape = input.shape();
89        if self.source_heads <= 0
90            || self.target_heads <= 0
91            || self.target_heads % self.source_heads != 0
92            || shape.get(self.axis).copied() != Some(self.source_heads)
93        {
94            return Err(Error::backend(format!(
95                "invalid head expansion axis={} source={} target={} shape={shape:?}",
96                self.axis, self.source_heads, self.target_heads
97            )));
98        }
99        Ok(())
100    }
101
102    /// Number of adjacent copies of each source head.
103    pub const fn repeats(self) -> i32 {
104        self.target_heads / self.source_heads
105    }
106}
107
108/// One unmasked attention request over validated contiguous sequence segments.
109#[derive(Debug, Clone, Copy)]
110pub struct SegmentedAttentionInput<'a, T> {
111    /// Queries shaped `[tokens, heads, dimensions]`.
112    pub queries: &'a T,
113    /// Keys shaped `[tokens, heads, dimensions]`.
114    pub keys: &'a T,
115    /// Values shaped `[tokens, heads, value_dimensions]`.
116    pub values: &'a T,
117    /// Positive contiguous segment lengths whose sum equals `tokens`.
118    pub segment_lengths: &'a [i32],
119    /// Query/key score multiplier.
120    pub scale: f32,
121}
122
123impl<T: Tensor> SegmentedAttentionInput<'_, T> {
124    /// Validates tensor and segment geometry without inspecting tensor values.
125    pub fn validate(&self) -> Result<(), Error> {
126        let query = self.queries.shape();
127        let key = self.keys.shape();
128        let value = self.values.shape();
129        if query.len() != 3
130            || key.len() != 3
131            || value.len() != 3
132            || query[0] <= 0
133            || query[1] <= 0
134            || query[2] <= 0
135            || query[0] != key[0]
136            || query[0] != value[0]
137            || query[1] != key[1]
138            || query[1] != value[1]
139            || query[2] != key[2]
140            || value[2] <= 0
141            || !self.scale.is_finite()
142            || self.scale <= 0.0
143        {
144            return Err(Error::backend(format!(
145                "invalid segmented attention geometry q={query:?} k={key:?} v={value:?} scale={}",
146                self.scale
147            )));
148        }
149        validate_segment_lengths(query[0], self.segment_lengths)
150    }
151}
152
153/// Validates positive contiguous segment lengths and their exact total.
154pub fn validate_segment_lengths(total: i32, segment_lengths: &[i32]) -> Result<(), Error> {
155    if total <= 0 || segment_lengths.is_empty() {
156        return Err(Error::backend(format!(
157            "segmented attention requires a positive total and at least one segment, got total={total} segments={segment_lengths:?}"
158        )));
159    }
160    let mut sum = 0i32;
161    for &length in segment_lengths {
162        if length <= 0 {
163            return Err(Error::backend(format!(
164                "segmented attention lengths must be positive, got {segment_lengths:?}"
165            )));
166        }
167        sum = sum.checked_add(length).ok_or_else(|| {
168            Error::backend("segmented attention length total overflowed signed 32-bit geometry")
169        })?;
170        if sum > total {
171            return Err(Error::backend(format!(
172                "segmented attention lengths exceed total {total}: {segment_lengths:?}"
173            )));
174        }
175    }
176    if sum != total {
177        return Err(Error::backend(format!(
178            "segmented attention lengths sum to {sum}, expected {total}"
179        )));
180    }
181    Ok(())
182}
183
184/// Deterministic host reference for grouped head repetition.
185pub fn reference_expand_heads(
186    values: &[f32],
187    shape: &[usize],
188    axis: usize,
189    target_heads: usize,
190) -> Result<(Vec<f32>, Vec<usize>), Error> {
191    let source_heads = shape.get(axis).copied().unwrap_or(0);
192    if source_heads == 0 || target_heads == 0 || !target_heads.is_multiple_of(source_heads) {
193        return Err(Error::backend(format!(
194            "invalid reference head expansion axis={axis} target={target_heads} shape={shape:?}"
195        )));
196    }
197    let elements = shape.iter().try_fold(1usize, |total, width| {
198        total
199            .checked_mul(*width)
200            .ok_or_else(|| Error::backend("reference head expansion element count overflowed"))
201    })?;
202    if elements != values.len() {
203        return Err(Error::backend(format!(
204            "reference head expansion expected {elements} values, got {}",
205            values.len()
206        )));
207    }
208    let outer = shape[..axis].iter().product::<usize>();
209    let inner = shape[axis + 1..].iter().product::<usize>();
210    let repeats = target_heads / source_heads;
211    let mut output = Vec::with_capacity(outer * target_heads * inner);
212    for outer_index in 0..outer {
213        for source in 0..source_heads {
214            let start = (outer_index * source_heads + source) * inner;
215            for _ in 0..repeats {
216                output.extend_from_slice(&values[start..start + inner]);
217            }
218        }
219    }
220    let mut output_shape = shape.to_vec();
221    output_shape[axis] = target_heads;
222    Ok((output, output_shape))
223}
224
225/// Deterministic host reference for unmasked segmented scaled-dot-product
226/// attention. Inputs and output use token-major `[tokens, heads, dimensions]`
227/// storage.
228#[allow(clippy::too_many_arguments)]
229pub fn reference_segmented_attention(
230    tokens: usize,
231    heads: usize,
232    dimensions: usize,
233    value_dimensions: usize,
234    queries: &[f32],
235    keys: &[f32],
236    values: &[f32],
237    segment_lengths: &[i32],
238    scale: f32,
239) -> Result<Vec<f32>, Error> {
240    let tokens_i32 = i32::try_from(tokens)
241        .map_err(|_| Error::backend("reference segmented attention token count exceeds i32"))?;
242    validate_segment_lengths(tokens_i32, segment_lengths)?;
243    if heads == 0
244        || dimensions == 0
245        || value_dimensions == 0
246        || !scale.is_finite()
247        || scale <= 0.0
248        || queries.len() != tokens * heads * dimensions
249        || keys.len() != tokens * heads * dimensions
250        || values.len() != tokens * heads * value_dimensions
251    {
252        return Err(Error::backend(
253            "invalid reference segmented attention geometry",
254        ));
255    }
256    let mut output = vec![0.0f32; tokens * heads * value_dimensions];
257    let mut segment_start = 0usize;
258    for &length in segment_lengths {
259        let length = usize::try_from(length).expect("validated positive segment length");
260        let segment_end = segment_start + length;
261        for query_token in segment_start..segment_end {
262            for head in 0..heads {
263                let mut scores = Vec::with_capacity(length);
264                for key_token in segment_start..segment_end {
265                    let mut score = 0.0f32;
266                    for dimension in 0..dimensions {
267                        let query_index = (query_token * heads + head) * dimensions + dimension;
268                        let key_index = (key_token * heads + head) * dimensions + dimension;
269                        score += queries[query_index] * keys[key_index];
270                    }
271                    scores.push(score * scale);
272                }
273                let maximum = scores.iter().copied().fold(f32::NEG_INFINITY, f32::max);
274                let denominator = scores
275                    .iter_mut()
276                    .map(|score| {
277                        *score = (*score - maximum).exp();
278                        *score
279                    })
280                    .sum::<f32>();
281                for value_dimension in 0..value_dimensions {
282                    let mut result = 0.0f32;
283                    for (relative, key_token) in (segment_start..segment_end).enumerate() {
284                        let value_index =
285                            (key_token * heads + head) * value_dimensions + value_dimension;
286                        result += scores[relative] / denominator * values[value_index];
287                    }
288                    let output_index =
289                        (query_token * heads + head) * value_dimensions + value_dimension;
290                    output[output_index] = result;
291                }
292            }
293        }
294        segment_start = segment_end;
295    }
296    Ok(output)
297}
298
299/// Value source for an attention unit that owns key/value state.
300#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
301pub enum AttentionValueSource {
302    /// Values are produced by an independent projection.
303    Projected,
304    /// Projected keys are reused as values.
305    ReuseKey,
306}
307
308/// Typed source and publication policy for attention key/value state.
309#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
310pub enum AttentionStateSource {
311    /// Own projections and retain state only for this attention unit.
312    Local {
313        /// Value projection topology.
314        value: AttentionValueSource,
315    },
316    /// Own projections and publish state for later consumers.
317    Publish {
318        /// Value projection topology.
319        value: AttentionValueSource,
320    },
321    /// Consume state published by another unit or supplied externally.
322    Shared,
323}
324
325impl AttentionStateSource {
326    /// Returns whether the attention unit owns projections and mutable state.
327    pub const fn owns_state(self) -> bool {
328        !matches!(self, Self::Shared)
329    }
330
331    /// Returns whether the resulting state must be published.
332    pub const fn publishes_state(self) -> bool {
333        matches!(self, Self::Publish { .. })
334    }
335
336    /// Returns the value source for a state-owning unit.
337    pub const fn value(self) -> Option<AttentionValueSource> {
338        match self {
339            Self::Local { value } | Self::Publish { value } => Some(value),
340            Self::Shared => None,
341        }
342    }
343}
344
345#[cfg(test)]
346mod attention_state_source_tests {
347    use super::{AttentionStateSource, AttentionValueSource};
348
349    #[test]
350    fn ownership_publication_and_key_as_value_are_independent() {
351        let local = AttentionStateSource::Local {
352            value: AttentionValueSource::Projected,
353        };
354        let publisher = AttentionStateSource::Publish {
355            value: AttentionValueSource::ReuseKey,
356        };
357        assert!(local.owns_state());
358        assert!(!local.publishes_state());
359        assert_eq!(local.value(), Some(AttentionValueSource::Projected));
360        assert!(publisher.owns_state());
361        assert!(publisher.publishes_state());
362        assert_eq!(publisher.value(), Some(AttentionValueSource::ReuseKey));
363        assert!(!AttentionStateSource::Shared.owns_state());
364        assert_eq!(AttentionStateSource::Shared.value(), None);
365    }
366}
367
368#[cfg(test)]
369mod recurrent_encoder_contract_tests {
370    use super::{
371        reference_expand_heads, reference_segmented_attention, validate_segment_lengths,
372        NormalizationConstructionSpec, NormalizationScale,
373    };
374
375    #[test]
376    fn normalization_construction_rejects_invalid_geometry_and_scalars() {
377        assert!(NormalizationConstructionSpec {
378            dimensions: 8,
379            epsilon: 1e-6,
380            scale: NormalizationScale::Unit,
381        }
382        .validate()
383        .is_ok());
384        assert!(NormalizationConstructionSpec {
385            dimensions: 0,
386            epsilon: 1e-6,
387            scale: NormalizationScale::Unit,
388        }
389        .validate()
390        .is_err());
391        assert!(NormalizationConstructionSpec {
392            dimensions: 8,
393            epsilon: f32::NAN,
394            scale: NormalizationScale::Unit,
395        }
396        .validate()
397        .is_err());
398    }
399
400    #[test]
401    fn head_expansion_reference_preserves_grouped_row_order() {
402        let (values, shape) =
403            reference_expand_heads(&[1.0, 2.0, 3.0, 4.0], &[1, 2, 2], 1, 4).unwrap();
404        assert_eq!(shape, vec![1, 4, 2]);
405        assert_eq!(values, vec![1.0, 2.0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0]);
406        assert!(reference_expand_heads(&[1.0, 2.0], &[1, 2], 1, 3).is_err());
407    }
408
409    #[test]
410    fn segmented_attention_reference_is_independent_per_contiguous_segment() {
411        let output = reference_segmented_attention(
412            3,
413            1,
414            1,
415            1,
416            &[0.0, 0.0, 0.0],
417            &[0.0, 0.0, 0.0],
418            &[2.0, 4.0, 9.0],
419            &[2, 1],
420            1.0,
421        )
422        .unwrap();
423        assert_eq!(output, vec![3.0, 3.0, 9.0]);
424        assert!(validate_segment_lengths(3, &[]).is_err());
425        assert!(validate_segment_lengths(3, &[2, 0, 1]).is_err());
426        assert!(validate_segment_lengths(3, &[2]).is_err());
427        assert!(validate_segment_lengths(3, &[2, 2]).is_err());
428        assert!(validate_segment_lengths(i32::MAX, &[i32::MAX, 1]).is_err());
429    }
430}
431
432/// Typed sparse/indexed attention request.
433///
434/// Architecture code owns how positions are selected, causal eligibility,
435/// compression ratios, top-k, and sink policy. A backend may fuse gathering,
436/// shared-softmax attention, and value reduction without exposing an
437/// accelerator-specific indexing API.
438#[derive(Debug, Clone, Copy)]
439pub struct IndexedAttentionInput<'a, T> {
440    /// Queries shaped `[batch, heads, query_tokens, key_dimensions]`.
441    pub queries: &'a T,
442    /// Bounded local keys shaped `[batch, local_tokens, key_dimensions]`.
443    pub local_keys: &'a T,
444    /// Bounded local values shaped `[batch, local_tokens, value_dimensions]`.
445    pub local_values: &'a T,
446    /// Compressed/indexable keys shaped `[batch, pooled_tokens, key_dimensions]`.
447    pub pooled_keys: &'a T,
448    /// Compressed/indexable values shaped `[batch, pooled_tokens, value_dimensions]`.
449    pub pooled_values: &'a T,
450    /// Selected pooled positions shaped `[batch, query_tokens, selected]`.
451    pub selected_positions: &'a T,
452    /// Query/key score multiplier.
453    pub scale: f32,
454    /// Optional mask broadcastable to local scores.
455    pub local_mask: Option<&'a T>,
456    /// Optional mask broadcastable to selected pooled scores.
457    pub pooled_mask: Option<&'a T>,
458    /// Optional learned per-head sink logits.
459    pub sinks: Option<&'a T>,
460}
461
462/// Dense attention over bounded local keys plus complete pooled history.
463#[derive(Debug, Clone, Copy)]
464pub struct PooledAttentionInput<'a, T> {
465    /// Queries shaped `[batch, heads, query_tokens, dimensions]`.
466    pub queries: &'a T,
467    /// Bounded local keys and values shaped `[batch, local_tokens, dimensions]`.
468    pub local: &'a T,
469    /// Complete pooled keys and values shaped `[batch, pooled_tokens, dimensions]`.
470    pub pooled: &'a T,
471    /// Query/key score multiplier.
472    pub scale: f32,
473    /// Optional mask broadcastable to local scores.
474    pub local_mask: Option<&'a T>,
475    /// Optional mask broadcastable to pooled scores.
476    pub pooled_mask: Option<&'a T>,
477    /// Optional learned per-head sink logits.
478    pub sinks: Option<&'a T>,
479}
480
481/// Architecture-selected pooled-position scoring request.
482///
483/// Backends may fuse score construction, causal masking, and top-k selection
484/// without exposing device-specific partition or gather operations.
485#[derive(Debug, Clone, Copy)]
486pub struct PooledPositionInput<'a, T> {
487    /// Rotary queries shaped `[batch, heads, query_tokens, dimensions]`.
488    pub queries: &'a T,
489    /// Compressed index keys shaped `[batch, pooled_tokens, dimensions]`.
490    pub pooled_keys: &'a T,
491    /// Per-token head weights shaped `[batch, query_tokens, heads]`.
492    pub head_weights: &'a T,
493    /// Optional eligibility mask shaped `[query_tokens, pooled_tokens]` or a
494    /// broadcast-compatible batch variant.
495    pub mask: Option<&'a T>,
496    /// Number of pooled positions selected per query token.
497    pub top_k: i32,
498    /// Score multiplier applied after nonnegative clamping.
499    pub scale: f32,
500    /// Head-weight multiplier applied before reducing the head axis.
501    pub head_scale: f32,
502}
503
504/// Causal attention with a learned relative-position profile.
505///
506/// Architecture code projects per-token relative features into `profiles`;
507/// the backend owns position-index gathering and score materialization so no
508/// device values cross the host boundary.
509#[derive(Debug, Clone, Copy)]
510pub struct RelativeAttentionInput<'a, T> {
511    /// Normalized queries shaped `[batch, heads, query_tokens, dimensions]`.
512    pub queries: &'a T,
513    /// Normalized keys shaped `[batch, kv_heads, key_tokens, dimensions]`.
514    pub keys: &'a T,
515    /// Values shaped `[batch, kv_heads, key_tokens, dimensions]`.
516    pub values: &'a T,
517    /// Learned profiles shaped `[batch, heads, query_tokens, relative_extent]`.
518    pub profiles: &'a T,
519    /// Absolute position of the first query token.
520    pub query_offset: i32,
521    /// Absolute position of the first retained key token.
522    pub key_offset: i32,
523    /// Optional causal sliding-window width.
524    pub window: Option<i32>,
525    /// Optional position floor for logarithmic global-attention scaling.
526    pub log_scaling_floor: Option<i32>,
527    /// Multiplier applied to the logarithmic scale above the floor.
528    pub log_scaling_alpha: f32,
529}
530
531impl<T: Tensor> RelativeAttentionInput<'_, T> {
532    /// Validates exact head, sequence, and relative-profile geometry.
533    pub fn validate(&self) -> Result<(), Error> {
534        let query = self.queries.shape();
535        let key = self.keys.shape();
536        let value = self.values.shape();
537        let profiles = self.profiles.shape();
538        if query.len() != 4
539            || key.len() != 4
540            || value.len() != 4
541            || profiles.len() != 4
542            || query[0] != key[0]
543            || key != value
544            || query[2] != profiles[2]
545            || query[0] != profiles[0]
546            || query[1] != profiles[1]
547            || query[3] != key[3]
548            || query[1] % key[1] != 0
549            || profiles[3] <= 0
550            || self.window.is_some_and(|window| window <= 0)
551            || self.log_scaling_floor.is_some_and(|floor| floor <= 0)
552            || !self.log_scaling_alpha.is_finite()
553        {
554            return Err(Error::backend(format!(
555                "invalid relative attention geometry q={query:?} k={key:?} v={value:?} profiles={profiles:?} window={:?} floor={:?} alpha={}",
556                self.window, self.log_scaling_floor, self.log_scaling_alpha
557            )));
558        }
559        Ok(())
560    }
561}
562
563impl<T: Tensor> IndexedAttentionInput<'_, T> {
564    /// Validates semantic ranks and exact non-broadcast geometry without
565    /// materializing any backend values.
566    pub fn validate(&self) -> Result<(), Error> {
567        let query = self.queries.shape();
568        let local_keys = self.local_keys.shape();
569        let local_values = self.local_values.shape();
570        let pooled_keys = self.pooled_keys.shape();
571        let pooled_values = self.pooled_values.shape();
572        let selected = self.selected_positions.shape();
573        if query.len() != 4
574            || local_keys.len() != 3
575            || local_values.len() != 3
576            || pooled_keys.len() != 3
577            || pooled_values.len() != 3
578            || selected.len() != 3
579            || query[0] != local_keys[0]
580            || query[0] != local_values[0]
581            || query[0] != pooled_keys[0]
582            || query[0] != pooled_values[0]
583            || query[0] != selected[0]
584            || query[2] != selected[1]
585            || query[3] != local_keys[2]
586            || query[3] != pooled_keys[2]
587            || local_keys[1] != local_values[1]
588            || pooled_keys[1] != pooled_values[1]
589            || local_values[2] != pooled_values[2]
590            || selected[2] <= 0
591            || pooled_keys[1] <= 0
592        {
593            return Err(Error::backend(format!(
594                "invalid indexed-attention geometry: queries={query:?} local_keys={local_keys:?} local_values={local_values:?} pooled_keys={pooled_keys:?} pooled_values={pooled_values:?} selected={selected:?}"
595            )));
596        }
597        if !self.scale.is_finite() || self.scale <= 0.0 {
598            return Err(Error::backend(format!(
599                "indexed-attention scale must be finite and positive, got {}",
600                self.scale
601            )));
602        }
603        if let Some(sinks) = self.sinks {
604            if sinks.shape() != [query[1]] {
605                return Err(Error::backend(format!(
606                    "indexed-attention sinks require shape [{}], got {:?}",
607                    query[1],
608                    sinks.shape()
609                )));
610            }
611        }
612        Ok(())
613    }
614}
615
616/// Stable authoritative identity of one logical model parameter.
617#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
618pub struct ParameterId(String);
619
620impl ParameterId {
621    /// Creates a non-empty parameter identity.
622    pub fn new(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
623        let id = id.into();
624        if id.trim().is_empty() {
625            return Err(ParameterTopologyError::EmptyId);
626        }
627        Ok(Self(id))
628    }
629
630    /// Returns the stable checkpoint-facing identity.
631    pub fn as_str(&self) -> &str {
632        &self.0
633    }
634}
635
636impl std::fmt::Display for ParameterId {
637    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638        formatter.write_str(&self.0)
639    }
640}
641
642/// Complete logical declaration for one parameter slot.
643#[derive(Debug, Clone, Eq, PartialEq)]
644pub struct ParameterSpec {
645    /// Stable authoritative identity.
646    pub id: ParameterId,
647    /// Whether optimizers may update this parameter by default.
648    pub trainable: bool,
649    /// Authoritative destination when this slot aliases a tied parameter.
650    pub alias_of: Option<ParameterId>,
651    /// Optional atomic encoding or sharding group.
652    pub group: Option<String>,
653    /// Semantic role within an encoded linear parameter group.
654    pub linear_companion: Option<LinearCompanionRole>,
655    /// Primary linear weight owned by this physical companion.
656    pub linear_companion_of: Option<ParameterId>,
657}
658
659impl ParameterSpec {
660    /// Declares an ordinary trainable parameter.
661    pub fn trainable(id: impl Into<String>) -> Result<Self, ParameterTopologyError> {
662        Ok(Self {
663            id: ParameterId::new(id)?,
664            trainable: true,
665            alias_of: None,
666            group: None,
667            linear_companion: None,
668            linear_companion_of: None,
669        })
670    }
671}
672
673/// Semantic role of a physical companion in an encoded linear parameter.
674#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
675pub enum LinearCompanionRole {
676    /// Per-group or per-block scale tensor.
677    Scale,
678    /// Per-group affine zero-point/bias tensor.
679    AffineBias,
680}
681
682/// Parameter metadata observed during traversal.
683#[derive(Debug, Clone, Eq, PartialEq)]
684pub struct ParameterMetadata {
685    /// Stable authoritative identity.
686    pub id: ParameterId,
687    /// Current trainable state.
688    pub trainable: bool,
689    /// Authoritative destination for a tied alias.
690    pub alias_of: Option<ParameterId>,
691    /// Optional atomic parameter group.
692    pub group: Option<String>,
693    /// Semantic role within an encoded linear parameter group.
694    pub linear_companion: Option<LinearCompanionRole>,
695    /// Primary linear weight owned by this physical companion.
696    pub linear_companion_of: Option<ParameterId>,
697}
698
699impl ParameterMetadata {
700    /// Creates traversal metadata from a construction specification.
701    pub fn from_spec(spec: &ParameterSpec, trainable: bool) -> Self {
702        Self {
703            id: spec.id.clone(),
704            trainable,
705            alias_of: spec.alias_of.clone(),
706            group: spec.group.clone(),
707            linear_companion: spec.linear_companion,
708            linear_companion_of: spec.linear_companion_of.clone(),
709        }
710    }
711}
712
713/// Invalid stable parameter topology.
714#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
715pub enum ParameterTopologyError {
716    /// A parameter identity is empty.
717    #[error("parameter identity must not be empty")]
718    EmptyId,
719    /// The same stable identity was visited more than once.
720    #[error("parameter identity {0} is duplicated")]
721    DuplicateId(ParameterId),
722    /// A tied alias points to an identity that is not in the topology.
723    #[error("parameter alias {alias} points to missing destination {destination}")]
724    MissingAliasDestination {
725        /// Identity of the aliasing slot.
726        alias: ParameterId,
727        /// Missing authoritative destination.
728        destination: ParameterId,
729    },
730    /// A tied alias points to another alias instead of an authoritative slot.
731    #[error("parameter alias {alias} points to non-authoritative alias {destination}")]
732    AliasTargetsAlias {
733        /// Identity of the aliasing slot.
734        alias: ParameterId,
735        /// Non-authoritative destination.
736        destination: ParameterId,
737    },
738}
739
740/// Immutable statically dispatched parameter visitor.
741pub trait ParameterVisitor<'a, T: 'a> {
742    /// Visits one authoritative parameter slot.
743    fn visit(&mut self, metadata: ParameterMetadata, value: &'a T);
744}
745
746/// Mutable statically dispatched parameter visitor.
747pub trait ParameterVisitorMut<'a, T: 'a> {
748    /// Visits one authoritative mutable parameter slot.
749    fn visit_mut(&mut self, metadata: ParameterMetadata, value: &'a mut T);
750}
751
752/// Backend-neutral parameter topology for a module or operator.
753///
754/// Immutable and mutable traversal must expose the same complete set of stable
755/// identities. Repeated mutable traversals must preserve that set until a
756/// visitor replaces a parameter value; runtime binding relies on this law to
757/// validate the whole topology before publishing any replacement.
758pub trait Parameterized<T: 'static> {
759    /// Visits every parameter exactly once using stable identities.
760    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
761    where
762        V: ParameterVisitor<'a, T>;
763
764    /// Mutably visits every parameter exactly once using stable identities.
765    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
766    where
767        V: ParameterVisitorMut<'a, T>;
768
769    /// Updates whether all parameters in this module are trainable.
770    fn set_trainable(&mut self, trainable: bool);
771}
772
773/// Collects and validates the stable parameter topology exposed by a module.
774pub fn validate_parameter_topology<T: 'static, M>(
775    module: &M,
776) -> Result<Vec<ParameterMetadata>, ParameterTopologyError>
777where
778    M: Parameterized<T>,
779{
780    struct Collector(Vec<ParameterMetadata>);
781    impl<'a, T: 'a> ParameterVisitor<'a, T> for Collector {
782        fn visit(&mut self, metadata: ParameterMetadata, _value: &'a T) {
783            self.0.push(metadata);
784        }
785    }
786
787    let mut collector = Collector(Vec::new());
788    module.visit_parameters(&mut collector);
789    let mut topology = std::collections::BTreeMap::new();
790    for metadata in &collector.0 {
791        if topology.insert(metadata.id.clone(), metadata).is_some() {
792            return Err(ParameterTopologyError::DuplicateId(metadata.id.clone()));
793        }
794    }
795    for metadata in &collector.0 {
796        let Some(destination) = &metadata.alias_of else {
797            continue;
798        };
799        let Some(target) = topology.get(destination) else {
800            return Err(ParameterTopologyError::MissingAliasDestination {
801                alias: metadata.id.clone(),
802                destination: destination.clone(),
803            });
804        };
805        if target.alias_of.is_some() {
806            return Err(ParameterTopologyError::AliasTargetsAlias {
807                alias: metadata.id.clone(),
808                destination: destination.clone(),
809            });
810        }
811    }
812    Ok(collector.0)
813}
814
815/// Shape and checkpoint identity for an affine projection.
816#[derive(Debug, Clone)]
817pub struct LinearSpec {
818    /// Input feature count.
819    pub input: i32,
820    /// Output feature count.
821    pub output: i32,
822    /// Stable weight slot.
823    pub weight: ParameterSpec,
824    /// Optional stable bias slot.
825    pub bias: Option<ParameterSpec>,
826    /// Complete physical checkpoint encoding and exact companion identities.
827    pub format: LinearFormatSpec,
828}
829
830/// Complete construction specification for a token embedding table.
831#[derive(Debug, Clone)]
832pub struct EmbeddingSpec {
833    /// Vocabulary row count.
834    pub vocabulary: i32,
835    /// Embedding width.
836    pub dimensions: i32,
837    /// Stable embedding weight slot.
838    pub weight: ParameterSpec,
839    /// Complete physical checkpoint encoding and exact companion identities.
840    pub format: LinearFormatSpec,
841}
842
843/// Architecture-owned physical encoding and exact companion parameters.
844///
845/// Backends consume these identities literally. They must never derive scale
846/// or affine-bias names from the primary weight identity.
847#[derive(Debug, Clone, Eq, PartialEq)]
848pub struct LinearFormatSpec {
849    format: LinearFormat,
850    scale: Option<ParameterSpec>,
851    affine_bias: Option<ParameterSpec>,
852}
853
854impl LinearFormatSpec {
855    /// Declares a dense or checkpoint-native encoding with no companions.
856    pub fn unscaled(format: LinearFormat) -> Result<Self, Error> {
857        let spec = Self {
858            format,
859            scale: None,
860            affine_bias: None,
861        };
862        spec.validate()?;
863        Ok(spec)
864    }
865
866    /// Declares an encoding with one exact scale companion.
867    pub fn scaled(format: LinearFormat, scale: ParameterSpec) -> Result<Self, Error> {
868        let mut scale = scale;
869        scale.linear_companion = Some(LinearCompanionRole::Scale);
870        scale.linear_companion_of = None;
871        let spec = Self {
872            format,
873            scale: Some(scale),
874            affine_bias: None,
875        };
876        spec.validate()?;
877        Ok(spec)
878    }
879
880    /// Declares an affine encoding with exact scale and bias companions.
881    pub fn affine(
882        format: LinearFormat,
883        scale: ParameterSpec,
884        affine_bias: ParameterSpec,
885    ) -> Result<Self, Error> {
886        let mut scale = scale;
887        scale.linear_companion = Some(LinearCompanionRole::Scale);
888        scale.linear_companion_of = None;
889        let mut affine_bias = affine_bias;
890        affine_bias.linear_companion = Some(LinearCompanionRole::AffineBias);
891        affine_bias.linear_companion_of = None;
892        let spec = Self {
893            format,
894            scale: Some(scale),
895            affine_bias: Some(affine_bias),
896        };
897        spec.validate()?;
898        Ok(spec)
899    }
900
901    /// Physical tensor encoding.
902    pub const fn encoding(&self) -> LinearFormat {
903        self.format
904    }
905
906    /// Exact scale companion, when stored separately.
907    pub const fn scale(&self) -> Option<&ParameterSpec> {
908        self.scale.as_ref()
909    }
910
911    /// Exact affine-bias companion, when stored separately.
912    pub const fn affine_bias(&self) -> Option<&ParameterSpec> {
913        self.affine_bias.as_ref()
914    }
915
916    /// Validates that companion cardinality matches the physical encoding.
917    pub fn validate(&self) -> Result<(), Error> {
918        self.format.validate().map_err(Error::backend)?;
919        let expected = match self.format {
920            LinearFormat::Dense | LinearFormat::GgufIQuant { .. } => (false, false),
921            LinearFormat::MxFp4 | LinearFormat::E4M3BlockFp8(_) => (true, false),
922            LinearFormat::Affine(_) => (true, true),
923        };
924        if (self.scale.is_some(), self.affine_bias.is_some()) != expected {
925            return Err(Error::backend(format!(
926                "linear format {:?} requires scale/bias companions {:?}, got {:?}",
927                self.format,
928                expected,
929                (self.scale.is_some(), self.affine_bias.is_some())
930            )));
931        }
932        if self
933            .scale
934            .as_ref()
935            .zip(self.affine_bias.as_ref())
936            .is_some_and(|(scale, bias)| scale.id == bias.id)
937        {
938            return Err(Error::backend(
939                "linear scale and affine-bias companions require distinct identities",
940            ));
941        }
942        if self
943            .scale
944            .as_ref()
945            .is_some_and(|scale| scale.linear_companion != Some(LinearCompanionRole::Scale))
946            || self
947                .affine_bias
948                .as_ref()
949                .is_some_and(|bias| bias.linear_companion != Some(LinearCompanionRole::AffineBias))
950        {
951            return Err(Error::backend(
952                "linear format companions have invalid semantic roles",
953            ));
954        }
955        Ok(())
956    }
957
958    /// Validates companions against the primary weight identity.
959    pub fn validate_for_weight(&self, weight: &ParameterSpec) -> Result<(), Error> {
960        self.validate()?;
961        if self
962            .scale
963            .as_ref()
964            .into_iter()
965            .chain(self.affine_bias.as_ref())
966            .any(|companion| companion.id == weight.id)
967        {
968            return Err(Error::backend(format!(
969                "linear format companion reuses primary weight identity {}",
970                weight.id
971            )));
972        }
973        Ok(())
974    }
975}
976
977/// One rank's validated contiguous vocabulary ownership.
978#[derive(Debug, Clone, Eq, PartialEq)]
979pub struct VocabularyParallelRange {
980    /// Complete logical vocabulary size.
981    pub global_vocabulary: usize,
982    /// Half-open row range materialized by this rank.
983    pub local: std::ops::Range<usize>,
984}
985
986impl VocabularyParallelRange {
987    /// Validates non-empty in-bounds ownership.
988    pub fn validate(&self) -> Result<(), Error> {
989        if self.global_vocabulary == 0
990            || self.local.is_empty()
991            || self.local.end > self.global_vocabulary
992        {
993            return Err(Error::backend(format!(
994                "invalid vocabulary-parallel range {:?} of {}",
995                self.local, self.global_vocabulary
996            )));
997        }
998        Ok(())
999    }
1000
1001    /// Validates that an operator's declared global row count is exactly this
1002    /// ownership range's global vocabulary.
1003    pub fn validate_global_rows(&self, rows: i32) -> Result<(), Error> {
1004        self.validate()?;
1005        if usize::try_from(rows).ok() != Some(self.global_vocabulary) {
1006            return Err(Error::backend(format!(
1007                "vocabulary-parallel operator declares {rows} rows but ownership covers {}",
1008                self.global_vocabulary
1009            )));
1010        }
1011        Ok(())
1012    }
1013
1014    /// Returns the exact balanced peer widths after proving this rank's local
1015    /// range belongs to that partition.
1016    ///
1017    /// Vocabulary-parallel architectures select balanced contiguous
1018    /// rows. Encoding that invariant here prevents a concrete backend from
1019    /// silently substituting its own peer layout during uneven gather.
1020    pub fn balanced_peer_widths(
1021        &self,
1022        partitions: usize,
1023        rank: usize,
1024    ) -> Result<Vec<usize>, Error> {
1025        self.validate()?;
1026        if partitions == 0 || rank >= partitions {
1027            return Err(Error::backend(format!(
1028                "invalid vocabulary partition rank {rank} of {partitions}"
1029            )));
1030        }
1031        let base = self.global_vocabulary / partitions;
1032        let remainder = self.global_vocabulary % partitions;
1033        let widths = (0..partitions)
1034            .map(|peer| base + usize::from(peer < remainder))
1035            .collect::<Vec<_>>();
1036        let start = widths[..rank].iter().sum::<usize>();
1037        let expected = start..start + widths[rank];
1038        if self.local != expected {
1039            return Err(Error::backend(format!(
1040                "vocabulary-parallel range {:?} differs from balanced rank {rank} ownership {expected:?}",
1041                self.local
1042            )));
1043        }
1044        Ok(widths)
1045    }
1046}
1047
1048#[cfg(test)]
1049mod vocabulary_parallel_range_tests {
1050    use super::VocabularyParallelRange;
1051
1052    #[test]
1053    fn balanced_peer_widths_are_neutral_and_reject_local_layout_drift() {
1054        let range = VocabularyParallelRange {
1055            global_vocabulary: 11,
1056            local: 4..8,
1057        };
1058        assert_eq!(range.balanced_peer_widths(3, 1).unwrap(), [4, 4, 3]);
1059
1060        let drifted = VocabularyParallelRange {
1061            global_vocabulary: 11,
1062            local: 3..7,
1063        };
1064        assert!(drifted.balanced_peer_widths(3, 1).is_err());
1065    }
1066}
1067
1068/// Token validation and sentinel behavior for one embedding lookup.
1069#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1070pub enum EmbeddingLookupPolicy {
1071    /// Every token must be a non-negative row index in the embedding table.
1072    Strict,
1073    /// One negative token value produces an exact zero row instead of indexing
1074    /// the embedding table. Every other token remains subject to strict lookup.
1075    ZeroSentinel(i32),
1076}
1077
1078impl EmbeddingLookupPolicy {
1079    /// Validates that the optional sentinel cannot alias an ordinary row.
1080    pub fn validate(self) -> Result<(), Error> {
1081        if let Self::ZeroSentinel(sentinel) = self {
1082            if sentinel >= 0 {
1083                return Err(Error::backend(format!(
1084                    "embedding zero sentinel must be negative, got {sentinel}"
1085                )));
1086            }
1087        }
1088        Ok(())
1089    }
1090}
1091
1092/// One named, positive-width component of a fused projection output.
1093#[derive(Debug, Clone, Eq, PartialEq)]
1094pub struct FusedProjectionSegment {
1095    name: String,
1096    width: i32,
1097}
1098
1099impl FusedProjectionSegment {
1100    /// Creates a validated component declaration.
1101    pub fn new(name: impl Into<String>, width: i32) -> Result<Self, Error> {
1102        let name = name.into();
1103        if name.trim().is_empty() || width <= 0 {
1104            return Err(Error::backend(format!(
1105                "fused projection segments require a name and positive width, got name={name:?} width={width}"
1106            )));
1107        }
1108        Ok(Self { name, width })
1109    }
1110
1111    /// Returns the stable component name.
1112    pub fn name(&self) -> &str {
1113        &self.name
1114    }
1115
1116    /// Returns the component width on the final projection axis.
1117    pub const fn width(&self) -> i32 {
1118        self.width
1119    }
1120}
1121
1122/// Validated component-major layout of one fused affine projection.
1123#[derive(Debug, Clone, Eq, PartialEq)]
1124pub struct FusedProjectionLayout {
1125    segments: Vec<FusedProjectionSegment>,
1126    output_width: i32,
1127}
1128
1129impl FusedProjectionLayout {
1130    /// Validates ordered unique components and checked total width.
1131    pub fn new(segments: impl IntoIterator<Item = FusedProjectionSegment>) -> Result<Self, Error> {
1132        let segments = segments.into_iter().collect::<Vec<_>>();
1133        if segments.is_empty() {
1134            return Err(Error::backend(
1135                "fused projection layout must contain at least one segment",
1136            ));
1137        }
1138        let mut names = std::collections::BTreeSet::new();
1139        let mut output_width = 0i32;
1140        for segment in &segments {
1141            if !names.insert(segment.name.clone()) {
1142                return Err(Error::backend(format!(
1143                    "fused projection segment {:?} is duplicated",
1144                    segment.name
1145                )));
1146            }
1147            output_width = output_width.checked_add(segment.width).ok_or_else(|| {
1148                Error::backend("fused projection output width overflowed signed 32-bit geometry")
1149            })?;
1150        }
1151        Ok(Self {
1152            segments,
1153            output_width,
1154        })
1155    }
1156
1157    /// Returns component declarations in physical output order.
1158    pub fn segments(&self) -> &[FusedProjectionSegment] {
1159        &self.segments
1160    }
1161
1162    /// Returns the checked total output width.
1163    pub const fn output_width(&self) -> i32 {
1164        self.output_width
1165    }
1166
1167    /// Splits one fused result into component-major final-axis views.
1168    pub fn split<T: Tensor>(&self, output: &T, context: &T::Context) -> Result<Vec<T>, Error> {
1169        let actual = output
1170            .shape()
1171            .last()
1172            .copied()
1173            .ok_or_else(|| Error::backend("fused projection output has no feature axis"))?;
1174        if actual != self.output_width {
1175            return Err(Error::backend(format!(
1176                "fused projection emitted width {actual}, expected {}",
1177                self.output_width
1178            )));
1179        }
1180        let mut start = 0i32;
1181        let mut indexes = vec![Index::Full; output.shape().len()];
1182        self.segments
1183            .iter()
1184            .map(|segment| {
1185                let end = start + segment.width;
1186                let last = indexes.len() - 1;
1187                indexes[last] = Index::Range(start, end);
1188                let selected = output.index(&indexes, context);
1189                start = end;
1190                selected
1191            })
1192            .collect()
1193    }
1194}
1195
1196/// Parameterization policy for a reusable RMS normalization operator.
1197///
1198/// Architectures select the semantic scale form while the backend retains the
1199/// tensor implementation. In particular, learned-offset scales are evaluated
1200/// as `offset + weight`; the offset is not folded into checkpoint storage.
1201#[derive(Debug, Clone)]
1202pub enum NormalizationScale {
1203    /// Ordinary learned multiplicative scale.
1204    Learned(ParameterSpec),
1205    /// Learned scale offset by a fixed scalar at execution time.
1206    LearnedOffset {
1207        /// Stable checkpoint slot containing the learned offset tensor.
1208        weight: ParameterSpec,
1209        /// Fixed scalar added to every learned scale value.
1210        offset: f32,
1211    },
1212    /// RMS normalization without a learned scale.
1213    Unit,
1214}
1215
1216/// Complete construction policy for an RMS normalization operator.
1217#[derive(Debug, Clone)]
1218pub struct NormalizationConstructionSpec {
1219    /// Normalized feature count.
1220    pub dimensions: i32,
1221    /// Numerical stability epsilon.
1222    pub epsilon: f32,
1223    /// Learned, learned-offset, or weightless scale policy.
1224    pub scale: NormalizationScale,
1225}
1226
1227impl NormalizationConstructionSpec {
1228    /// Creates an RMS normalization with an ordinary learned scale.
1229    pub fn learned(dimensions: i32, epsilon: f32, weight: ParameterSpec) -> Self {
1230        Self {
1231            dimensions,
1232            epsilon,
1233            scale: NormalizationScale::Learned(weight),
1234        }
1235    }
1236
1237    /// Validates feature geometry and fixed scalar policy.
1238    pub fn validate(&self) -> Result<(), Error> {
1239        let offset = match &self.scale {
1240            NormalizationScale::LearnedOffset { offset, .. } => Some(*offset),
1241            NormalizationScale::Learned(_) | NormalizationScale::Unit => None,
1242        };
1243        if self.dimensions <= 0
1244            || !self.epsilon.is_finite()
1245            || self.epsilon <= 0.0
1246            || offset.is_some_and(|offset| !offset.is_finite())
1247        {
1248            return Err(Error::backend(format!(
1249                "invalid RMS normalization construction: dimensions={} epsilon={} offset={offset:?}",
1250                self.dimensions, self.epsilon
1251            )));
1252        }
1253        Ok(())
1254    }
1255}
1256
1257/// Fully normalized rotary-position algorithm selected by an architecture.
1258#[derive(Debug, Clone, Copy, PartialEq)]
1259pub enum RotaryAlgorithm {
1260    /// Unscaled rotary embeddings.
1261    Default,
1262    /// Uniform position interpolation by an extension factor.
1263    Linear {
1264        /// Context extension factor.
1265        factor: f32,
1266    },
1267    /// Llama 3 piecewise wavelength scaling.
1268    Llama3 {
1269        /// Context extension factor.
1270        factor: f32,
1271        /// Low-frequency wavelength boundary.
1272        low_frequency_factor: f32,
1273        /// High-frequency wavelength boundary.
1274        high_frequency_factor: f32,
1275        /// Context length used during training.
1276        original_max_positions: i32,
1277    },
1278    /// Rotary embeddings over a configurable prefix of each head.
1279    Proportional {
1280        /// Frequency scaling factor.
1281        factor: f32,
1282        /// Fraction of the head covered by rotary embeddings.
1283        rotary_fraction: f32,
1284    },
1285    /// YaRN frequency interpolation and attention concentration.
1286    Yarn {
1287        /// Context extension factor.
1288        factor: f32,
1289        /// Context length used during training.
1290        original_max_positions: i32,
1291        /// Fast correction rotation count.
1292        beta_fast: f32,
1293        /// Slow correction rotation count.
1294        beta_slow: f32,
1295        /// Rotary concentration coefficient.
1296        concentration: f32,
1297        /// All-dimension attention-scale coefficient.
1298        attention_factor: f32,
1299        /// Whether correction boundaries are rounded to integer frequency slots.
1300        truncate: bool,
1301    },
1302}
1303
1304impl RotaryAlgorithm {
1305    /// Validates the complete scalar geometry of this normalized algorithm.
1306    pub fn validate(self) -> Result<(), Error> {
1307        let positive = |value: f32| value.is_finite() && value > 0.0;
1308        let valid = match self {
1309            Self::Default => true,
1310            Self::Linear { factor } => positive(factor),
1311            Self::Llama3 {
1312                factor,
1313                low_frequency_factor,
1314                high_frequency_factor,
1315                original_max_positions,
1316            } => {
1317                positive(factor)
1318                    && positive(low_frequency_factor)
1319                    && positive(high_frequency_factor)
1320                    && high_frequency_factor > low_frequency_factor
1321                    && original_max_positions > 0
1322            }
1323            Self::Proportional {
1324                factor,
1325                rotary_fraction,
1326            } => positive(factor) && positive(rotary_fraction) && rotary_fraction <= 1.0,
1327            Self::Yarn {
1328                factor,
1329                original_max_positions,
1330                beta_fast,
1331                beta_slow,
1332                concentration,
1333                attention_factor,
1334                ..
1335            } => {
1336                positive(factor)
1337                    && original_max_positions > 0
1338                    && positive(beta_fast)
1339                    && positive(beta_slow)
1340                    && beta_fast > beta_slow
1341                    && positive(concentration)
1342                    && attention_factor.is_finite()
1343                    && attention_factor >= 0.0
1344            }
1345        };
1346        if valid {
1347            Ok(())
1348        } else {
1349            Err(Error::backend(format!(
1350                "invalid normalized rotary algorithm: {self:?}"
1351            )))
1352        }
1353    }
1354}
1355
1356/// Complete backend-neutral rotary-position construction specification.
1357#[derive(Debug, Clone, Copy)]
1358pub struct RotarySpec {
1359    /// Rotated head dimensions.
1360    pub dimensions: i32,
1361    /// Base frequency.
1362    pub base: f32,
1363    /// Whether adjacent pairs are rotated instead of split halves.
1364    pub traditional: bool,
1365    /// Fully normalized algorithm and scalar policy.
1366    pub algorithm: RotaryAlgorithm,
1367}
1368
1369/// Backend-native affine projection used by shared architectures.
1370pub trait LinearOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1371    /// Applies the projection without host materialization.
1372    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1373}
1374
1375/// Backend-native token embedding used by shared architectures.
1376pub trait EmbeddingOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1377    /// Looks up token embeddings.
1378    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1379    /// Looks up token embeddings under an explicit validation/sentinel policy.
1380    fn lookup(
1381        &mut self,
1382        input: &T,
1383        policy: EmbeddingLookupPolicy,
1384        context: &T::Context,
1385    ) -> Result<T, Error> {
1386        policy.validate()?;
1387        match policy {
1388            EmbeddingLookupPolicy::Strict => self.forward(input, context),
1389            EmbeddingLookupPolicy::ZeroSentinel(sentinel) => Err(Error::backend(format!(
1390                "embedding backend does not implement zero sentinel {sentinel}"
1391            ))),
1392        }
1393    }
1394    /// Projects hidden states through the transposed embedding table.
1395    fn as_linear(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1396}
1397
1398/// Backend-native normalization operator.
1399pub trait NormalizationOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1400    /// Applies normalization.
1401    fn forward(&mut self, input: &T, context: &T::Context) -> Result<T, Error>;
1402}
1403
1404/// Construction specification for a normalized low-rank projection.
1405#[derive(Debug, Clone)]
1406pub struct LowRankProjectionSpec {
1407    /// Optional input-to-rank projection. When omitted, the input is already
1408    /// represented in rank space.
1409    pub first: Option<LinearSpec>,
1410    /// Normalization applied in rank space.
1411    pub normalization: NormalizationConstructionSpec,
1412    /// Rank-to-output projection.
1413    pub second: LinearSpec,
1414}
1415
1416impl LowRankProjectionSpec {
1417    /// Validates that both projections and normalization agree on rank width.
1418    pub fn validate(&self) -> Result<(), Error> {
1419        let rank = self.normalization.dimensions;
1420        if rank <= 0 {
1421            return Err(Error::backend(format!(
1422                "low-rank normalization dimensions must be positive, got {rank}"
1423            )));
1424        }
1425        if self.second.input != rank {
1426            return Err(Error::backend(format!(
1427                "low-rank second projection expects {} inputs but rank width is {rank}",
1428                self.second.input
1429            )));
1430        }
1431        if let Some(first) = &self.first {
1432            if first.output != rank {
1433                return Err(Error::backend(format!(
1434                    "low-rank first projection emits {} values but rank width is {rank}",
1435                    first.output
1436                )));
1437            }
1438        }
1439        Ok(())
1440    }
1441}
1442
1443/// Reusable normalized low-rank projection with statically dispatched backend
1444/// operators.
1445#[derive(Debug, Clone, Parameterized)]
1446#[parameterized(tensor = "B::Tensor")]
1447pub struct LowRankProjection<B: NeuralBackend> {
1448    /// Optional input-to-rank projection.
1449    pub first: Option<B::Linear>,
1450    /// Rank-space normalization.
1451    pub normalization: B::Normalization,
1452    /// Rank-to-output projection.
1453    pub second: B::Linear,
1454}
1455
1456impl<B: NeuralBackend> LowRankProjection<B> {
1457    /// Builds an unloaded low-rank projection from architecture-owned
1458    /// parameter identities and physical formats.
1459    pub fn new(
1460        spec: LowRankProjectionSpec,
1461        context: &<B::Tensor as Tensor>::Context,
1462    ) -> Result<Self, Error> {
1463        spec.validate()?;
1464        Ok(Self {
1465            first: spec
1466                .first
1467                .map(|projection| B::linear(projection, context))
1468                .transpose()?,
1469            normalization: B::normalization(spec.normalization, context)?,
1470            second: B::linear(spec.second, context)?,
1471        })
1472    }
1473
1474    /// Applies the optional first projection, rank normalization, and second
1475    /// projection without backend-value conversion.
1476    pub fn forward(
1477        &mut self,
1478        input: &B::Tensor,
1479        context: &<B::Tensor as Tensor>::Context,
1480    ) -> Result<B::Tensor, Error> {
1481        let rank = match &mut self.first {
1482            Some(first) => first.forward(input, context)?,
1483            None => input.clone(),
1484        };
1485        let rank = self.normalization.forward(&rank, context)?;
1486        self.second.forward(&rank, context)
1487    }
1488}
1489
1490/// Backend-native rotary-position operator.
1491pub trait RotaryOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
1492    /// Applies the architecture-selected rotary position source.
1493    fn forward(
1494        &mut self,
1495        input: &T,
1496        position: RotaryPosition<'_, T>,
1497        context: &T::Context,
1498    ) -> Result<T, Error>;
1499
1500    /// Applies rotary positions only to the selected final-axis subspace and
1501    /// leaves every other feature unchanged.
1502    fn forward_subspace(
1503        &mut self,
1504        input: &T,
1505        subspace: RotarySubspace,
1506        position: RotaryPosition<'_, T>,
1507        context: &T::Context,
1508    ) -> Result<T, Error> {
1509        let width = *input
1510            .shape()
1511            .last()
1512            .ok_or_else(|| Error::backend("rotary input must have a feature axis"))?;
1513        let (start, dimensions) = subspace.resolve(width)?;
1514        if start == 0 && dimensions == width {
1515            return self.forward(input, position, context);
1516        }
1517        let end = start + dimensions;
1518        let mut indexes = vec![Index::Full; input.shape().len()];
1519        indexes[input.shape().len() - 1] = Index::Range(start, end);
1520        let selected = input.index(&indexes, context)?;
1521        let rotated = self.forward(&selected, position, context)?;
1522        let mut pieces = Vec::with_capacity(3);
1523        if start > 0 {
1524            indexes[input.shape().len() - 1] = Index::Range(0, start);
1525            pieces.push(input.index(&indexes, context)?);
1526        }
1527        pieces.push(rotated);
1528        if end < width {
1529            indexes[input.shape().len() - 1] = Index::Range(end, width);
1530            pieces.push(input.index(&indexes, context)?);
1531        }
1532        T::concatenate(&pieces, -1, context)
1533    }
1534}
1535
1536/// Final-axis feature range selected for rotary position encoding.
1537#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1538pub enum RotarySubspace {
1539    /// Rotate the complete final feature axis.
1540    Full,
1541    /// Rotate one contiguous half-open final-axis range.
1542    Range {
1543        /// First rotated feature.
1544        start: i32,
1545        /// Number of rotated features.
1546        dimensions: i32,
1547    },
1548}
1549
1550impl RotarySubspace {
1551    fn resolve(self, width: i32) -> Result<(i32, i32), Error> {
1552        let (start, dimensions) = match self {
1553            Self::Full => (0, width),
1554            Self::Range { start, dimensions } => (start, dimensions),
1555        };
1556        if width <= 0
1557            || start < 0
1558            || dimensions <= 0
1559            || dimensions % 2 != 0
1560            || start > width - dimensions
1561        {
1562            return Err(Error::backend(format!(
1563                "rotary subspace start={start} dimensions={dimensions} is invalid for width {width}"
1564            )));
1565        }
1566        Ok((start, dimensions))
1567    }
1568}
1569
1570/// Position data supplied to a backend-native rotary operator.
1571#[derive(Debug)]
1572pub enum RotaryPosition<'a, T> {
1573    /// Ordinary contiguous sequence positions beginning at this offset.
1574    Offset(i32),
1575    /// Caller-provided cosine and sine tensors for explicit positions.
1576    Embeddings {
1577        /// Cosine values shaped for the input sequence and rotary dimensions.
1578        cosine: &'a T,
1579        /// Sine values shaped for the input sequence and rotary dimensions.
1580        sine: &'a T,
1581    },
1582}
1583
1584impl<T> Copy for RotaryPosition<'_, T> {}
1585
1586impl<T> Clone for RotaryPosition<'_, T> {
1587    fn clone(&self) -> Self {
1588        *self
1589    }
1590}
1591
1592/// Architecture-selected scoring policy for top-k group selection.
1593#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1594#[non_exhaustive]
1595pub enum GroupScoring {
1596    /// Apply softmax across all group logits before selection.
1597    Softmax,
1598    /// Select the largest raw logits, then softmax only the selected entries.
1599    SelectedSoftmax,
1600    /// Apply elementwise sigmoid scores before grouped selection.
1601    Sigmoid,
1602    /// Apply the square root of softplus before selection.
1603    SqrtSoftplus,
1604}
1605
1606/// Backend-neutral top-k group-selection semantics.
1607#[derive(Debug, Clone, Copy, PartialEq)]
1608pub struct TopKGroupSelectionSpec {
1609    group_count: i32,
1610    top_k: i32,
1611    scoring: GroupScoring,
1612    normalize_selected: bool,
1613    normalization_epsilon: f32,
1614    coefficient_scale: f32,
1615    selection_partitions: i32,
1616    selected_groups: i32,
1617}
1618
1619/// Construction specification for a learned top-k selector projection.
1620#[derive(Debug, Clone)]
1621pub struct TopKGroupSelectorSpec {
1622    /// Hidden width consumed by the selector projection.
1623    input_dimensions: i32,
1624    /// Stable selector projection parameter identity.
1625    weight: ParameterSpec,
1626    /// Optional ordinary projection bias. This contributes to selector logits
1627    /// before scoring, selection, and selected-score normalization.
1628    bias: Option<ParameterSpec>,
1629    /// Optional correction bias used only to choose group IDs. Gathered
1630    /// selection scores remain unbiased.
1631    correction_bias: Option<ParameterSpec>,
1632    /// Optional learned scaling applied after weightless RMS normalization of
1633    /// the selector input.
1634    input_transform: Option<SelectorInputTransformSpec>,
1635    /// Optional learned per-group multiplier gathered after selection.
1636    coefficient_scale: Option<ParameterSpec>,
1637    /// Physical encoding and exact companion identities of the selector projection.
1638    format: LinearFormatSpec,
1639    /// Architecture-selected scoring and selection semantics.
1640    selection: TopKGroupSelectionSpec,
1641}
1642
1643/// Learned normalization and scale applied before a selector projection.
1644#[derive(Debug, Clone)]
1645pub struct SelectorInputTransformSpec {
1646    /// Weightless RMS-normalization epsilon.
1647    epsilon: f32,
1648    /// Learned feature-wise multiplier.
1649    scale: ParameterSpec,
1650    /// Whether to additionally multiply by `1 / sqrt(input_dimensions)`.
1651    inverse_sqrt_dimensions: bool,
1652}
1653
1654impl SelectorInputTransformSpec {
1655    /// Creates a validated selector-input transformation.
1656    pub fn new(
1657        epsilon: f32,
1658        scale: ParameterSpec,
1659        inverse_sqrt_dimensions: bool,
1660    ) -> Result<Self, Error> {
1661        if !epsilon.is_finite() || epsilon < 0.0 {
1662            return Err(Error::backend(
1663                "selector input RMS epsilon must be finite and nonnegative",
1664            ));
1665        }
1666        Ok(Self {
1667            epsilon,
1668            scale,
1669            inverse_sqrt_dimensions,
1670        })
1671    }
1672
1673    /// Returns the RMS epsilon.
1674    pub const fn epsilon(&self) -> f32 {
1675        self.epsilon
1676    }
1677    /// Returns the learned input scale.
1678    pub const fn scale(&self) -> &ParameterSpec {
1679        &self.scale
1680    }
1681    /// Returns whether inverse-square-root width scaling is selected.
1682    pub const fn inverse_sqrt_dimensions(&self) -> bool {
1683        self.inverse_sqrt_dimensions
1684    }
1685}
1686
1687impl TopKGroupSelectorSpec {
1688    /// Creates a selector projection with no optional parameters.
1689    pub fn new(
1690        input_dimensions: i32,
1691        weight: ParameterSpec,
1692        format: LinearFormatSpec,
1693        selection: TopKGroupSelectionSpec,
1694    ) -> Result<Self, Error> {
1695        let spec = Self {
1696            input_dimensions,
1697            weight,
1698            bias: None,
1699            correction_bias: None,
1700            input_transform: None,
1701            coefficient_scale: None,
1702            format,
1703            selection,
1704        };
1705        spec.validate()?;
1706        Ok(spec)
1707    }
1708
1709    /// Adds an ordinary projection bias.
1710    pub fn with_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
1711        self.bias = Some(bias);
1712        self.validate()?;
1713        Ok(self)
1714    }
1715    /// Adds a selection-only correction bias.
1716    pub fn with_correction_bias(mut self, bias: ParameterSpec) -> Result<Self, Error> {
1717        self.correction_bias = Some(bias);
1718        self.validate()?;
1719        Ok(self)
1720    }
1721    /// Adds an input normalization transformation.
1722    pub fn with_input_transform(mut self, transform: SelectorInputTransformSpec) -> Self {
1723        self.input_transform = Some(transform);
1724        self
1725    }
1726    /// Adds a learned coefficient multiplier.
1727    pub fn with_coefficient_scale(mut self, scale: ParameterSpec) -> Self {
1728        self.coefficient_scale = Some(scale);
1729        self
1730    }
1731    /// Returns the input width.
1732    pub const fn input_dimensions(&self) -> i32 {
1733        self.input_dimensions
1734    }
1735    /// Returns the selector projection parameter.
1736    pub const fn weight(&self) -> &ParameterSpec {
1737        &self.weight
1738    }
1739    /// Returns the ordinary projection bias.
1740    pub const fn bias(&self) -> Option<&ParameterSpec> {
1741        self.bias.as_ref()
1742    }
1743    /// Returns the selection correction bias.
1744    pub const fn correction_bias(&self) -> Option<&ParameterSpec> {
1745        self.correction_bias.as_ref()
1746    }
1747    /// Returns the optional input transformation.
1748    pub const fn input_transform(&self) -> Option<&SelectorInputTransformSpec> {
1749        self.input_transform.as_ref()
1750    }
1751    /// Returns the learned coefficient multiplier.
1752    pub const fn coefficient_scale(&self) -> Option<&ParameterSpec> {
1753        self.coefficient_scale.as_ref()
1754    }
1755    /// Returns the physical projection format.
1756    pub const fn format(&self) -> &LinearFormatSpec {
1757        &self.format
1758    }
1759    /// Returns the group-selection semantics.
1760    pub const fn selection(&self) -> TopKGroupSelectionSpec {
1761        self.selection
1762    }
1763
1764    /// Validates positive input geometry.
1765    pub fn validate(&self) -> Result<(), Error> {
1766        self.format.validate_for_weight(&self.weight)?;
1767        if self.input_dimensions <= 0 {
1768            return Err(Error::backend(format!(
1769                "selector input dimensions must be positive, got {}",
1770                self.input_dimensions
1771            )));
1772        }
1773        if self
1774            .input_transform
1775            .as_ref()
1776            .is_some_and(|transform| !transform.epsilon.is_finite() || transform.epsilon < 0.0)
1777        {
1778            return Err(Error::backend(
1779                "selector input RMS epsilon must be finite and nonnegative",
1780            ));
1781        }
1782        if self
1783            .bias
1784            .as_ref()
1785            .zip(self.correction_bias.as_ref())
1786            .is_some_and(|(bias, correction_bias)| bias.id == correction_bias.id)
1787        {
1788            return Err(Error::backend(
1789                "selector projection bias and correction bias require distinct parameter identities",
1790            ));
1791        }
1792        Ok(())
1793    }
1794}
1795
1796impl TopKGroupSelectionSpec {
1797    /// Creates a validated top-k group-selection policy.
1798    pub fn new(
1799        group_count: i32,
1800        top_k: i32,
1801        scoring: GroupScoring,
1802        normalize_selected: bool,
1803    ) -> Result<Self, Error> {
1804        if group_count <= 0 {
1805            return Err(Error::backend(format!(
1806                "group count must be positive, got {group_count}"
1807            )));
1808        }
1809        if top_k <= 0 || top_k > group_count {
1810            return Err(Error::backend(format!(
1811                "top-k selection count must be in 1..={group_count}, got {top_k}"
1812            )));
1813        }
1814        Ok(Self {
1815            group_count,
1816            top_k,
1817            scoring,
1818            normalize_selected,
1819            normalization_epsilon: 0.0,
1820            coefficient_scale: 1.0,
1821            selection_partitions: 1,
1822            selected_groups: 1,
1823        })
1824    }
1825
1826    /// Selects grouped selection semantics.
1827    pub fn with_groups(
1828        mut self,
1829        selection_partitions: i32,
1830        selected_groups: i32,
1831    ) -> Result<Self, Error> {
1832        if selection_partitions <= 0
1833            || selected_groups <= 0
1834            || selected_groups > selection_partitions
1835            || self.group_count % selection_partitions != 0
1836            || self.top_k > selected_groups * (self.group_count / selection_partitions)
1837        {
1838            return Err(Error::backend(format!(
1839                "invalid grouped selection geometry: group_count={} top_k={} partitions={selection_partitions} selected_partitions={selected_groups}",
1840                self.group_count, self.top_k
1841            )));
1842        }
1843        self.selection_partitions = selection_partitions;
1844        self.selected_groups = selected_groups;
1845        Ok(self)
1846    }
1847
1848    /// Selects denominator epsilon and final grouped contribution scale.
1849    pub fn with_weight_policy(
1850        mut self,
1851        normalization_epsilon: f32,
1852        coefficient_scale: f32,
1853    ) -> Result<Self, Error> {
1854        if !normalization_epsilon.is_finite()
1855            || normalization_epsilon < 0.0
1856            || !coefficient_scale.is_finite()
1857            || coefficient_scale <= 0.0
1858        {
1859            return Err(Error::backend(
1860                "selection normalization epsilon must be finite and nonnegative and grouped scaling must be finite and positive",
1861            ));
1862        }
1863        self.normalization_epsilon = normalization_epsilon;
1864        self.coefficient_scale = coefficient_scale;
1865        Ok(self)
1866    }
1867
1868    /// Returns the total number of selectable groups.
1869    pub const fn group_count(self) -> i32 {
1870        self.group_count
1871    }
1872
1873    /// Returns the number of selected groups per token.
1874    pub const fn top_k(self) -> i32 {
1875        self.top_k
1876    }
1877
1878    /// Returns the score transformation applied before selection.
1879    pub const fn scoring(self) -> GroupScoring {
1880        self.scoring
1881    }
1882
1883    /// Returns whether selected scores are renormalized to sum to one.
1884    pub const fn normalize_selected(self) -> bool {
1885        self.normalize_selected
1886    }
1887
1888    /// Returns the epsilon added to selected-score normalization.
1889    pub const fn normalization_epsilon(self) -> f32 {
1890        self.normalization_epsilon
1891    }
1892
1893    /// Returns the final grouped contribution multiplier.
1894    pub const fn coefficient_scale(self) -> f32 {
1895        self.coefficient_scale
1896    }
1897
1898    /// Returns the number of equal contiguous groups.
1899    pub const fn selection_partitions(self) -> i32 {
1900        self.selection_partitions
1901    }
1902
1903    /// Returns the number of groups eligible for group selection.
1904    pub const fn selected_groups(self) -> i32 {
1905        self.selected_groups
1906    }
1907}
1908
1909/// Backend-native result of one top-k group selection.
1910#[derive(Debug, Clone)]
1911pub struct GroupSelection<T> {
1912    /// Selected group IDs shaped `[..., top_k]`.
1913    group_indices: T,
1914    /// Selected scores before optional top-k renormalization.
1915    selected_scores: T,
1916    /// Final normalized or unnormalized selection weights.
1917    coefficients: T,
1918}
1919
1920impl<T> GroupSelection<T> {
1921    /// Creates one selected group batch.
1922    pub fn new(group_indices: T, selected_scores: T, coefficients: T) -> Self {
1923        Self {
1924            group_indices,
1925            selected_scores,
1926            coefficients,
1927        }
1928    }
1929    /// Returns selected group indices.
1930    pub const fn group_indices(&self) -> &T {
1931        &self.group_indices
1932    }
1933    /// Returns selected pre-normalization scores.
1934    pub const fn selected_scores(&self) -> &T {
1935        &self.selected_scores
1936    }
1937    /// Returns final group coefficients.
1938    pub const fn coefficients(&self) -> &T {
1939        &self.coefficients
1940    }
1941}
1942
1943/// Geometry for joint selected-group and always-on-group weighting.
1944#[derive(Debug, Clone, Copy, PartialEq)]
1945pub struct JointGroupSelectionSpec {
1946    selectable_groups: i32,
1947    always_on_groups: i32,
1948    top_k: i32,
1949    coefficient_scale: f32,
1950}
1951
1952impl JointGroupSelectionSpec {
1953    /// Creates validated joint-selection geometry.
1954    pub fn new(
1955        selectable_groups: i32,
1956        always_on_groups: i32,
1957        top_k: i32,
1958        coefficient_scale: f32,
1959    ) -> Result<Self, Error> {
1960        if selectable_groups <= 0
1961            || always_on_groups <= 0
1962            || top_k <= 0
1963            || top_k > selectable_groups
1964            || !coefficient_scale.is_finite()
1965            || coefficient_scale <= 0.0
1966        {
1967            return Err(Error::backend(format!(
1968                "invalid joint group-selection geometry selectable={selectable_groups} always_on={always_on_groups} top_k={top_k} coefficient_scale={coefficient_scale}"
1969            )));
1970        }
1971        Ok(Self {
1972            selectable_groups,
1973            always_on_groups,
1974            top_k,
1975            coefficient_scale,
1976        })
1977    }
1978
1979    /// Returns the number of selectable groups.
1980    pub const fn selectable_groups(self) -> i32 {
1981        self.selectable_groups
1982    }
1983
1984    /// Returns the number of always-on groups.
1985    pub const fn always_on_groups(self) -> i32 {
1986        self.always_on_groups
1987    }
1988
1989    /// Returns the selected group count per row.
1990    pub const fn top_k(self) -> i32 {
1991        self.top_k
1992    }
1993
1994    /// Returns the fixed coefficient multiplier.
1995    pub const fn coefficient_scale(self) -> f32 {
1996        self.coefficient_scale
1997    }
1998}
1999
2000/// Joint sigmoid selection request with selected groups and always-on
2001/// shared groups normalized in one probability distribution.
2002#[derive(Debug, Clone, Copy)]
2003pub struct JointGroupSelectionInput<'a, T> {
2004    /// Hidden states shaped `[..., hidden]`.
2005    hidden: &'a T,
2006    /// Projection shaped `[selectable_groups + always_on_groups, hidden]`.
2007    weight: &'a T,
2008    /// Correction bias used only for grouped top-k selection.
2009    correction_bias: &'a T,
2010    /// Learned scalar multiplier applied to all final selection weights.
2011    global_scale: &'a T,
2012    /// Joint-selection geometry.
2013    selection: JointGroupSelectionSpec,
2014}
2015
2016impl<'a, T: Tensor> JointGroupSelectionInput<'a, T> {
2017    /// Creates a validated joint group-selection request.
2018    pub fn new(
2019        hidden: &'a T,
2020        weight: &'a T,
2021        correction_bias: &'a T,
2022        global_scale: &'a T,
2023        selection: JointGroupSelectionSpec,
2024    ) -> Result<Self, Error> {
2025        let input = Self {
2026            hidden,
2027            weight,
2028            correction_bias,
2029            global_scale,
2030            selection,
2031        };
2032        input.validate()?;
2033        Ok(input)
2034    }
2035    /// Returns hidden states.
2036    pub const fn hidden(&self) -> &'a T {
2037        self.hidden
2038    }
2039    /// Returns the selection projection.
2040    pub const fn weight(&self) -> &'a T {
2041        self.weight
2042    }
2043    /// Returns the selection-only correction bias.
2044    pub const fn correction_bias(&self) -> &'a T {
2045        self.correction_bias
2046    }
2047    /// Returns the learned global scale.
2048    pub const fn global_scale(&self) -> &'a T {
2049        self.global_scale
2050    }
2051    /// Returns the number of selectable groups.
2052    pub const fn selectable_groups(&self) -> i32 {
2053        self.selection.selectable_groups()
2054    }
2055    /// Returns the number of always-on groups.
2056    pub const fn always_on_groups(&self) -> i32 {
2057        self.selection.always_on_groups()
2058    }
2059    /// Returns the selected group count per row.
2060    pub const fn top_k(&self) -> i32 {
2061        self.selection.top_k()
2062    }
2063    /// Returns the fixed coefficient multiplier.
2064    pub const fn coefficient_scale(&self) -> f32 {
2065        self.selection.coefficient_scale()
2066    }
2067}
2068
2069impl<T: Tensor> JointGroupSelectionInput<'_, T> {
2070    /// Validates exact projection, bias, and scalar geometry.
2071    pub fn validate(&self) -> Result<(), Error> {
2072        let hidden = self.hidden.shape();
2073        let weight = self.weight.shape();
2074        let bias = self.correction_bias.shape();
2075        let scale = self.global_scale.shape();
2076        let hidden_width = hidden.last().copied().unwrap_or(0);
2077        if hidden.len() < 2
2078            || weight
2079                != [
2080                    self.selectable_groups() + self.always_on_groups(),
2081                    hidden_width,
2082                ]
2083            || bias != [self.selectable_groups()]
2084            || scale != [1]
2085        {
2086            return Err(Error::backend(format!(
2087                "invalid joint group selection tensors hidden={hidden:?} weight={weight:?} bias={bias:?} scale={scale:?} selectable={} always_on={} top_k={}",
2088                self.selectable_groups(),
2089                self.always_on_groups(),
2090                self.top_k(),
2091            )));
2092        }
2093        Ok(())
2094    }
2095}
2096
2097/// Backend-native result of joint grouped and shared group weighting.
2098#[derive(Debug, Clone)]
2099pub struct JointGroupSelection<T> {
2100    /// Selected primary group IDs shaped `[tokens, top_k]`; integer dtype is
2101    /// backend-defined and accepted by the corresponding group operator.
2102    primary_indices: T,
2103    /// Grouped group weights shaped `[tokens, top_k]`.
2104    primary_coefficients: T,
2105    /// Always-on shared group weights shaped `[tokens, always_on_groups]`.
2106    always_on_coefficients: T,
2107}
2108
2109impl<T> JointGroupSelection<T> {
2110    /// Creates one joint selection result.
2111    pub fn new(primary_indices: T, primary_coefficients: T, always_on_coefficients: T) -> Self {
2112        Self {
2113            primary_indices,
2114            primary_coefficients,
2115            always_on_coefficients,
2116        }
2117    }
2118    /// Returns selected primary-group indices.
2119    pub const fn primary_indices(&self) -> &T {
2120        &self.primary_indices
2121    }
2122    /// Returns primary-group coefficients.
2123    pub const fn primary_coefficients(&self) -> &T {
2124        &self.primary_coefficients
2125    }
2126    /// Returns always-on group coefficients.
2127    pub const fn always_on_coefficients(&self) -> &T {
2128        &self.always_on_coefficients
2129    }
2130}
2131
2132/// Activation applied to the gate branch of a grouped gated product.
2133#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2134#[non_exhaustive]
2135pub enum GatedProductActivation {
2136    /// `gate * sigmoid(sigmoid_multiplier * gate)`.
2137    Silu,
2138    /// Approximate Gaussian error linear unit.
2139    GeluApproximate,
2140}
2141
2142/// Validated equation policy for a gated product group.
2143#[derive(Debug, Clone, Copy, PartialEq)]
2144pub struct GatedProductPolicy {
2145    activation: GatedProductActivation,
2146    gate_upper_bound: Option<f32>,
2147    up_absolute_bound: Option<f32>,
2148    sigmoid_multiplier: f32,
2149    up_offset: f32,
2150}
2151
2152impl GatedProductPolicy {
2153    /// Creates a validated gated-product equation.
2154    pub fn new(
2155        activation: GatedProductActivation,
2156        gate_upper_bound: Option<f32>,
2157        up_absolute_bound: Option<f32>,
2158        sigmoid_multiplier: f32,
2159        up_offset: f32,
2160    ) -> Result<Self, Error> {
2161        let policy = Self {
2162            activation,
2163            gate_upper_bound,
2164            up_absolute_bound,
2165            sigmoid_multiplier,
2166            up_offset,
2167        };
2168        policy.validate()?;
2169        Ok(policy)
2170    }
2171
2172    /// Ordinary unbounded SiLU gating.
2173    pub const fn ordinary_silu() -> Self {
2174        Self {
2175            activation: GatedProductActivation::Silu,
2176            gate_upper_bound: None,
2177            up_absolute_bound: None,
2178            sigmoid_multiplier: 1.0,
2179            up_offset: 0.0,
2180        }
2181    }
2182
2183    /// Ordinary unbounded approximate-GELU gating.
2184    pub const fn ordinary_gelu_approximate() -> Self {
2185        Self {
2186            activation: GatedProductActivation::GeluApproximate,
2187            ..Self::ordinary_silu()
2188        }
2189    }
2190
2191    /// Creates ordinary SiLU gating with the same positive gate and up bound.
2192    pub fn bounded_silu(bound: f32) -> Result<Self, Error> {
2193        Self::new(
2194            GatedProductActivation::Silu,
2195            Some(bound),
2196            Some(bound),
2197            1.0,
2198            0.0,
2199        )
2200    }
2201
2202    /// Validates finite scalars and positive bounds/multiplier.
2203    pub fn validate(self) -> Result<(), Error> {
2204        if self
2205            .gate_upper_bound
2206            .is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
2207            || self
2208                .up_absolute_bound
2209                .is_some_and(|bound| !bound.is_finite() || bound <= 0.0)
2210            || !self.sigmoid_multiplier.is_finite()
2211            || self.sigmoid_multiplier <= 0.0
2212            || !self.up_offset.is_finite()
2213        {
2214            return Err(Error::backend(format!(
2215                "invalid gated-product policy: {self:?}"
2216            )));
2217        }
2218        Ok(())
2219    }
2220
2221    /// Gate activation.
2222    pub const fn activation(self) -> GatedProductActivation {
2223        self.activation
2224    }
2225
2226    /// Optional upper bound applied to the gate branch before activation.
2227    pub const fn gate_upper_bound(self) -> Option<f32> {
2228        self.gate_upper_bound
2229    }
2230
2231    /// Optional symmetric absolute bound applied to the up branch.
2232    pub const fn up_absolute_bound(self) -> Option<f32> {
2233        self.up_absolute_bound
2234    }
2235
2236    /// Multiplier inside the sigmoid for SiLU gating.
2237    pub const fn sigmoid_multiplier(self) -> f32 {
2238        self.sigmoid_multiplier
2239    }
2240
2241    /// Offset added to the up branch after optional clipping.
2242    pub const fn up_offset(self) -> f32 {
2243        self.up_offset
2244    }
2245}
2246
2247impl Default for GatedProductPolicy {
2248    fn default() -> Self {
2249        Self::ordinary_silu()
2250    }
2251}
2252
2253/// Statically dispatched top-k selector.
2254pub trait GroupSelectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2255    /// Selects group IDs and selection weights without host materialization.
2256    fn select(&mut self, logits: &T, context: &T::Context) -> Result<GroupSelection<T>, Error>;
2257
2258    /// Computes scores and weights for caller-selected global group IDs.
2259    fn select_indices(
2260        &mut self,
2261        input: &T,
2262        group_indices: &T,
2263        context: &T::Context,
2264    ) -> Result<GroupSelection<T>, Error>;
2265}
2266
2267/// Parameter identities for one gated-product group or one packed group axis.
2268#[derive(Debug, Clone, Eq, PartialEq)]
2269pub struct GatedProductGroupParameters {
2270    /// Gating projection weight.
2271    gate: GroupedProjectionSpec,
2272    /// Up projection weight.
2273    up: GroupedProjectionSpec,
2274    /// Down projection weight.
2275    down: GroupedProjectionSpec,
2276}
2277
2278impl GatedProductGroupParameters {
2279    /// Creates one independently addressable gated-product parameter group.
2280    pub fn new(
2281        gate: GroupedProjectionSpec,
2282        up: GroupedProjectionSpec,
2283        down: GroupedProjectionSpec,
2284    ) -> Self {
2285        Self { gate, up, down }
2286    }
2287    /// Returns the gate projection.
2288    pub const fn gate(&self) -> &GroupedProjectionSpec {
2289        &self.gate
2290    }
2291    /// Returns the up projection.
2292    pub const fn up(&self) -> &GroupedProjectionSpec {
2293        &self.up
2294    }
2295    /// Returns the down projection.
2296    pub const fn down(&self) -> &GroupedProjectionSpec {
2297        &self.down
2298    }
2299}
2300
2301/// One group projection identity and optional physical encoding.
2302#[derive(Debug, Clone, Eq, PartialEq)]
2303pub struct GroupedProjectionSpec {
2304    /// Stable logical parameter identity.
2305    weight: ParameterSpec,
2306    /// Optional ordinary per-output projection bias.
2307    bias: Option<ParameterSpec>,
2308    /// Complete physical checkpoint encoding and exact companion identities.
2309    format: LinearFormatSpec,
2310}
2311
2312impl GroupedProjectionSpec {
2313    /// Creates one validated grouped projection description.
2314    pub fn new(
2315        weight: ParameterSpec,
2316        bias: Option<ParameterSpec>,
2317        format: LinearFormatSpec,
2318    ) -> Result<Self, Error> {
2319        let spec = Self {
2320            weight,
2321            bias,
2322            format,
2323        };
2324        spec.validate()?;
2325        Ok(spec)
2326    }
2327    /// Returns the matrix parameter.
2328    pub const fn weight(&self) -> &ParameterSpec {
2329        &self.weight
2330    }
2331    /// Returns the optional output bias.
2332    pub const fn bias(&self) -> Option<&ParameterSpec> {
2333        self.bias.as_ref()
2334    }
2335    /// Returns the physical matrix format.
2336    pub const fn format(&self) -> &LinearFormatSpec {
2337        &self.format
2338    }
2339    fn validate(&self) -> Result<(), Error> {
2340        self.format.validate_for_weight(&self.weight)?;
2341        let parameters = self.parameters();
2342        for (index, parameter) in parameters.iter().enumerate() {
2343            if parameters[index + 1..]
2344                .iter()
2345                .any(|candidate| candidate.id == parameter.id)
2346            {
2347                return Err(Error::backend(format!(
2348                    "grouped projection reuses parameter identity {:?}",
2349                    parameter.id
2350                )));
2351            }
2352        }
2353        Ok(())
2354    }
2355
2356    /// Returns weight, optional bias, and physical companions in binding order.
2357    pub fn parameters(&self) -> Vec<&ParameterSpec> {
2358        let mut parameters = vec![&self.weight];
2359        parameters.extend(self.bias.as_ref());
2360        parameters.extend(self.format.scale());
2361        parameters.extend(self.format.affine_bias());
2362        parameters
2363    }
2364}
2365
2366/// Logical parameter layout for a gated-product bank.
2367#[derive(Debug, Clone, PartialEq)]
2368#[allow(clippy::large_enum_variant)] // Packed layout stays inline on the construction hot path.
2369#[non_exhaustive]
2370pub enum GatedProductGroupLayout {
2371    /// Component-major fused gate-then-up and down tensors whose leading axis
2372    /// indexes groups.
2373    Packed {
2374        /// Concatenated gate/up projection.
2375        gate_up: GroupedProjectionSpec,
2376        /// Down projection.
2377        down: GroupedProjectionSpec,
2378    },
2379    /// Independently materialized group parameter triples in group-ID order.
2380    Independent(Vec<GatedProductGroupParameters>),
2381}
2382
2383/// Complete architecture-owned construction specification for grouped gated-product groups.
2384#[derive(Debug, Clone, PartialEq)]
2385pub struct GroupedGatedProductSpec {
2386    /// Number of groups.
2387    group_count: i32,
2388    /// Input hidden width.
2389    input_dimensions: i32,
2390    /// Per-group intermediate width.
2391    intermediate_dimensions: i32,
2392    /// Output hidden width.
2393    output_dimensions: i32,
2394    /// Exact gate activation, bounds, multiplier, and up offset.
2395    policy: GatedProductPolicy,
2396    /// Stable logical parameter identities and storage organization.
2397    layout: GatedProductGroupLayout,
2398}
2399
2400impl GroupedGatedProductSpec {
2401    /// Creates one validated grouped gated-product mechanism request.
2402    pub fn new(
2403        group_count: i32,
2404        input_dimensions: i32,
2405        intermediate_dimensions: i32,
2406        output_dimensions: i32,
2407        policy: GatedProductPolicy,
2408        layout: GatedProductGroupLayout,
2409    ) -> Result<Self, Error> {
2410        let spec = Self {
2411            group_count,
2412            input_dimensions,
2413            intermediate_dimensions,
2414            output_dimensions,
2415            policy,
2416            layout,
2417        };
2418        spec.validate()?;
2419        Ok(spec)
2420    }
2421    /// Returns a copy with placement-resolved group geometry.
2422    pub fn with_group_geometry(
2423        mut self,
2424        group_count: i32,
2425        intermediate_dimensions: i32,
2426    ) -> Result<Self, Error> {
2427        self.group_count = group_count;
2428        self.intermediate_dimensions = intermediate_dimensions;
2429        self.validate()?;
2430        Ok(self)
2431    }
2432    /// Returns the number of parameter groups.
2433    pub const fn group_count(&self) -> i32 {
2434        self.group_count
2435    }
2436    /// Returns the input width.
2437    pub const fn input_dimensions(&self) -> i32 {
2438        self.input_dimensions
2439    }
2440    /// Returns the per-group intermediate width.
2441    pub const fn intermediate_dimensions(&self) -> i32 {
2442        self.intermediate_dimensions
2443    }
2444    /// Returns the output width.
2445    pub const fn output_dimensions(&self) -> i32 {
2446        self.output_dimensions
2447    }
2448    /// Returns the gated-product equation policy.
2449    pub const fn policy(&self) -> GatedProductPolicy {
2450        self.policy
2451    }
2452    /// Returns the parameter-bank layout.
2453    pub const fn layout(&self) -> &GatedProductGroupLayout {
2454        &self.layout
2455    }
2456    /// Validates positive geometry and exact independent-group cardinality.
2457    pub fn validate(&self) -> Result<(), Error> {
2458        for (name, value) in [
2459            ("group_count", self.group_count),
2460            ("input_dimensions", self.input_dimensions),
2461            ("intermediate_dimensions", self.intermediate_dimensions),
2462            ("output_dimensions", self.output_dimensions),
2463        ] {
2464            if value <= 0 {
2465                return Err(Error::backend(format!(
2466                    "gated-product group-bank {name} must be positive, got {value}"
2467                )));
2468            }
2469        }
2470        self.policy.validate()?;
2471        if let GatedProductGroupLayout::Independent(groups) = &self.layout {
2472            let expected = usize::try_from(self.group_count).map_err(Error::backend)?;
2473            if groups.len() != expected {
2474                return Err(Error::backend(format!(
2475                    "independent gated-product bank has {} groups, expected {expected}",
2476                    groups.len()
2477                )));
2478            }
2479        }
2480        let projections = match &self.layout {
2481            GatedProductGroupLayout::Packed { gate_up, down } => vec![gate_up, down],
2482            GatedProductGroupLayout::Independent(groups) => groups
2483                .iter()
2484                .flat_map(|group| [&group.gate, &group.up, &group.down])
2485                .collect(),
2486        };
2487        let mut identities = std::collections::BTreeSet::new();
2488        for projection in projections {
2489            projection.validate()?;
2490            for parameter in projection.parameters() {
2491                let identity = &parameter.id;
2492                if !identities.insert(identity) {
2493                    return Err(Error::backend(format!(
2494                        "gated-product group parameter identity {identity} is duplicated"
2495                    )));
2496                }
2497            }
2498        }
2499        Ok(())
2500    }
2501}
2502
2503/// Rank-local grouped output split around the tensor-parallel reduction.
2504#[derive(Debug, Clone)]
2505pub struct TensorParallelGroupedOutput<T> {
2506    /// Rank-local projection contribution to all-sum.
2507    reducible: T,
2508    /// Replicated selection-weighted down bias added once after all-sum.
2509    post_reduce: Option<T>,
2510}
2511
2512impl<T> TensorParallelGroupedOutput<T> {
2513    /// Creates one rank-local grouped projection output.
2514    pub fn new(reducible: T, post_reduce: Option<T>) -> Self {
2515        Self {
2516            reducible,
2517            post_reduce,
2518        }
2519    }
2520    /// Returns the rank-local reducible tensor.
2521    pub const fn reducible(&self) -> &T {
2522        &self.reducible
2523    }
2524    /// Returns the optional post-reduction term.
2525    pub const fn post_reduce(&self) -> Option<&T> {
2526        self.post_reduce.as_ref()
2527    }
2528    /// Consumes the output into its reduction contribution and post-reduction term.
2529    pub fn into_parts(self) -> (T, Option<T>) {
2530        (self.reducible, self.post_reduce)
2531    }
2532}
2533
2534/// Statically dispatched grouped gated-product bank.
2535pub trait GroupedGatedProductOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2536    /// Returns the architecture-owned construction specification used by this bank.
2537    ///
2538    /// Runtime providers use this metadata when they substitute cached or remote
2539    /// parameters for the resident bank, so every execution path retains the
2540    /// same geometry, encoding, bias, and activation policy.
2541    fn spec(&self) -> &GroupedGatedProductSpec;
2542
2543    /// Executes selected groups and combines their outputs by selection weight.
2544    fn forward_grouped(
2545        &mut self,
2546        input: &T,
2547        selections: &GroupSelection<T>,
2548        context: &T::Context,
2549    ) -> Result<T, Error>;
2550}
2551
2552/// Additive mechanism for tensor-parallel grouped gated-product partials.
2553pub trait TensorParallelGroupedGatedProductOperator<T: Tensor>:
2554    GroupedGatedProductOperator<T>
2555{
2556    /// Executes a rank-local partial and separates replicated down bias for one
2557    /// literal post-reduction addition.
2558    fn forward_grouped_tensor_parallel(
2559        &mut self,
2560        input: &T,
2561        selections: &GroupSelection<T>,
2562        partitions: usize,
2563        context: &T::Context,
2564    ) -> Result<TensorParallelGroupedOutput<T>, Error>;
2565}
2566
2567/// Complete construction specification for packed grouped ReLU-squared groups.
2568#[derive(Debug, Clone, Eq, PartialEq)]
2569pub struct GroupedRelu2Spec {
2570    /// Number of groups.
2571    group_count: i32,
2572    /// Input and output hidden width.
2573    hidden_dimensions: i32,
2574    /// Per-group intermediate width.
2575    intermediate_dimensions: i32,
2576    /// Packed up-projection identity and physical format.
2577    up: GroupedProjectionSpec,
2578    /// Packed down-projection identity and physical format.
2579    down: GroupedProjectionSpec,
2580}
2581
2582impl GroupedRelu2Spec {
2583    /// Creates one validated grouped ReLU-squared mechanism request.
2584    pub fn new(
2585        group_count: i32,
2586        hidden_dimensions: i32,
2587        intermediate_dimensions: i32,
2588        up: GroupedProjectionSpec,
2589        down: GroupedProjectionSpec,
2590    ) -> Result<Self, Error> {
2591        let spec = Self {
2592            group_count,
2593            hidden_dimensions,
2594            intermediate_dimensions,
2595            up,
2596            down,
2597        };
2598        spec.validate()?;
2599        Ok(spec)
2600    }
2601    /// Returns a copy with placement-resolved group geometry.
2602    pub fn with_group_count(mut self, group_count: i32) -> Result<Self, Error> {
2603        self.group_count = group_count;
2604        self.validate()?;
2605        Ok(self)
2606    }
2607    /// Returns the number of parameter groups.
2608    pub const fn group_count(&self) -> i32 {
2609        self.group_count
2610    }
2611    /// Returns the input and output width.
2612    pub const fn hidden_dimensions(&self) -> i32 {
2613        self.hidden_dimensions
2614    }
2615    /// Returns the per-group intermediate width.
2616    pub const fn intermediate_dimensions(&self) -> i32 {
2617        self.intermediate_dimensions
2618    }
2619    /// Returns the up projection.
2620    pub const fn up(&self) -> &GroupedProjectionSpec {
2621        &self.up
2622    }
2623    /// Returns the down projection.
2624    pub const fn down(&self) -> &GroupedProjectionSpec {
2625        &self.down
2626    }
2627    /// Validates positive geometry.
2628    pub fn validate(&self) -> Result<(), Error> {
2629        if self.group_count <= 0 || self.hidden_dimensions <= 0 || self.intermediate_dimensions <= 0
2630        {
2631            return Err(Error::backend("invalid ReLU2 group-bank geometry"));
2632        }
2633        self.up.validate()?;
2634        self.down.validate()?;
2635        let mut identities = std::collections::BTreeSet::new();
2636        for projection in [&self.up, &self.down] {
2637            for parameter in projection.parameters() {
2638                if !identities.insert(&parameter.id) {
2639                    return Err(Error::backend(format!(
2640                        "ReLU2 group parameter identity {} is duplicated",
2641                        parameter.id
2642                    )));
2643                }
2644            }
2645        }
2646        Ok(())
2647    }
2648}
2649
2650/// Statically dispatched grouped ReLU-squared bank.
2651pub trait GroupedRelu2Operator<T: Tensor>: Clone + Debug + Parameterized<T> {
2652    /// Returns the exact grouped specification used to construct this bank.
2653    fn spec(&self) -> &GroupedRelu2Spec;
2654
2655    /// Executes selected groups and combines their outputs by selection weight.
2656    fn forward_grouped(
2657        &mut self,
2658        input: &T,
2659        selections: &GroupSelection<T>,
2660        context: &T::Context,
2661    ) -> Result<T, Error>;
2662}
2663
2664/// Additive mechanism for tensor-parallel grouped ReLU-squared partials.
2665pub trait TensorParallelGroupedRelu2Operator<T: Tensor>: GroupedRelu2Operator<T> {
2666    /// Executes a rank-local partial and separates replicated down bias for one
2667    /// literal post-reduction addition.
2668    fn forward_grouped_tensor_parallel(
2669        &mut self,
2670        input: &T,
2671        selections: &GroupSelection<T>,
2672        partitions: usize,
2673        context: &T::Context,
2674    ) -> Result<TensorParallelGroupedOutput<T>, Error>;
2675}
2676
2677/// Neural backend extension for grouped computation.
2678pub trait GroupedNeuralBackend: NeuralBackend {
2679    /// Concrete top-k selector.
2680    type Selector: GroupSelectionOperator<Self::Tensor>;
2681    /// Concrete packed or independently materialized gated-product bank.
2682    type GatedProductGroups: GroupedGatedProductOperator<Self::Tensor>;
2683    /// Concrete packed or independently materialized ReLU-squared bank.
2684    type Relu2Groups: GroupedRelu2Operator<Self::Tensor>;
2685
2686    /// Applies one packed block-diagonal projection independently across an
2687    /// explicit group axis.
2688    fn grouped_linear(
2689        linear: &mut Self::Linear,
2690        input: &Self::Tensor,
2691        groups: i32,
2692        output_per_group: i32,
2693        context: &<Self::Tensor as Tensor>::Context,
2694    ) -> Result<Self::Tensor, Error>;
2695
2696    /// Builds a selector with architecture-selected top-k semantics.
2697    fn top_k_group_selector(
2698        spec: TopKGroupSelectorSpec,
2699        context: &<Self::Tensor as Tensor>::Context,
2700    ) -> Result<Self::Selector, Error>;
2701
2702    /// Builds a grouped gated-product bank.
2703    fn grouped_gated_product(
2704        spec: GroupedGatedProductSpec,
2705        context: &<Self::Tensor as Tensor>::Context,
2706    ) -> Result<Self::GatedProductGroups, Error>;
2707
2708    /// Builds a grouped ReLU-squared bank.
2709    fn grouped_relu2(
2710        spec: GroupedRelu2Spec,
2711        context: &<Self::Tensor as Tensor>::Context,
2712    ) -> Result<Self::Relu2Groups, Error>;
2713
2714    /// Selects primary groups and jointly normalizes always-on group coefficients.
2715    fn joint_group_selection(
2716        input: JointGroupSelectionInput<'_, Self::Tensor>,
2717        context: &<Self::Tensor as Tensor>::Context,
2718    ) -> Result<JointGroupSelection<Self::Tensor>, Error>;
2719}
2720
2721/// Grouped backend whose selected realization provides every required
2722/// tensor-parallel grouped partial operation.
2723pub trait TensorParallelGroupedNeuralBackend: GroupedNeuralBackend {
2724    /// Executes one rank-local gated-product grouped partial.
2725    fn gated_product_groups_tensor_parallel(
2726        groups: &mut Self::GatedProductGroups,
2727        input: &Self::Tensor,
2728        selections: &GroupSelection<Self::Tensor>,
2729        partitions: usize,
2730        context: &<Self::Tensor as Tensor>::Context,
2731    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
2732
2733    /// Executes one rank-local ReLU-squared grouped partial.
2734    fn relu2_groups_tensor_parallel(
2735        groups: &mut Self::Relu2Groups,
2736        input: &Self::Tensor,
2737        selections: &GroupSelection<Self::Tensor>,
2738        partitions: usize,
2739        context: &<Self::Tensor as Tensor>::Context,
2740    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error>;
2741}
2742
2743impl<B> TensorParallelGroupedNeuralBackend for B
2744where
2745    B: GroupedNeuralBackend,
2746    B::GatedProductGroups: TensorParallelGroupedGatedProductOperator<B::Tensor>,
2747    B::Relu2Groups: TensorParallelGroupedRelu2Operator<B::Tensor>,
2748{
2749    fn gated_product_groups_tensor_parallel(
2750        groups: &mut Self::GatedProductGroups,
2751        input: &Self::Tensor,
2752        selections: &GroupSelection<Self::Tensor>,
2753        partitions: usize,
2754        context: &<Self::Tensor as Tensor>::Context,
2755    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
2756        groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
2757    }
2758
2759    fn relu2_groups_tensor_parallel(
2760        groups: &mut Self::Relu2Groups,
2761        input: &Self::Tensor,
2762        selections: &GroupSelection<Self::Tensor>,
2763        partitions: usize,
2764        context: &<Self::Tensor as Tensor>::Context,
2765    ) -> Result<TensorParallelGroupedOutput<Self::Tensor>, Error> {
2766        groups.forward_grouped_tensor_parallel(input, selections, partitions, context)
2767    }
2768}
2769
2770/// Complete construction specification for one multi-stream residual mix.
2771#[derive(Debug, Clone)]
2772pub struct HyperConnectionSpec {
2773    /// Number of parallel residual streams.
2774    pub streams: i32,
2775    /// Hidden width of each stream.
2776    pub hidden_size: i32,
2777    /// Sinkhorn row/column normalization passes.
2778    pub sinkhorn_iterations: usize,
2779    /// Positive numerical epsilon used by Sinkhorn normalization.
2780    pub epsilon: f32,
2781    /// Projection producing pre, post, and stream-mixing logits.
2782    pub function: ParameterSpec,
2783    /// Additive base for the mixing logits.
2784    pub base: ParameterSpec,
2785    /// Learned scales for pre, post, and matrix logits.
2786    pub scale: ParameterSpec,
2787}
2788
2789impl HyperConnectionSpec {
2790    /// Validates geometry and numerical policy without inspecting parameters.
2791    pub fn validate(&self) -> Result<(), Error> {
2792        if self.streams <= 0 || self.hidden_size <= 0 {
2793            return Err(Error::backend(
2794                "hyper-connection streams and hidden size must be positive",
2795            ));
2796        }
2797        if self.sinkhorn_iterations == 0 {
2798            return Err(Error::backend(
2799                "hyper-connection Sinkhorn iteration count must be positive",
2800            ));
2801        }
2802        if !self.epsilon.is_finite() || self.epsilon <= 0.0 {
2803            return Err(Error::backend(
2804                "hyper-connection epsilon must be finite and positive",
2805            ));
2806        }
2807        Ok(())
2808    }
2809}
2810
2811/// Complete construction specification for the final stream collapse.
2812#[derive(Debug, Clone)]
2813pub struct HyperHeadSpec {
2814    /// Number of parallel residual streams.
2815    pub streams: i32,
2816    /// Hidden width of each stream.
2817    pub hidden_size: i32,
2818    /// RMS preparation epsilon.
2819    pub norm_epsilon: f32,
2820    /// Positive offset added to collapse coefficients.
2821    pub epsilon: f32,
2822    /// Projection producing per-stream collapse logits.
2823    pub function: ParameterSpec,
2824    /// Additive base for collapse logits.
2825    pub base: ParameterSpec,
2826    /// Learned collapse-logit scale.
2827    pub scale: ParameterSpec,
2828}
2829
2830impl HyperHeadSpec {
2831    /// Validates geometry and numerical policy without inspecting parameters.
2832    pub fn validate(&self) -> Result<(), Error> {
2833        if self.streams <= 0 || self.hidden_size <= 0 {
2834            return Err(Error::backend(
2835                "hyper-head streams and hidden size must be positive",
2836            ));
2837        }
2838        if !self.norm_epsilon.is_finite()
2839            || self.norm_epsilon <= 0.0
2840            || !self.epsilon.is_finite()
2841            || self.epsilon <= 0.0
2842        {
2843            return Err(Error::backend(
2844                "hyper-head epsilons must be finite and positive",
2845            ));
2846        }
2847        Ok(())
2848    }
2849}
2850
2851/// Coefficients and collapsed value produced before one residual sublayer.
2852#[derive(Debug, Clone)]
2853pub struct HyperConnectionState<T> {
2854    /// Residual streams reduced to one sublayer input.
2855    pub collapsed: T,
2856    /// Coefficients used for the pre-sublayer reduction.
2857    pub pre: T,
2858    /// Coefficients used to inject the sublayer result into each stream.
2859    pub post: T,
2860    /// Doubly-stochastic stream-mixing matrix.
2861    pub combination: T,
2862}
2863
2864/// Backend-native operator for one hyper-connected residual cycle.
2865pub trait HyperConnectionOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2866    /// Performs FP32 RMS preparation, predicts mixing coefficients, applies
2867    /// Sinkhorn normalization, and collapses streams for a sublayer.
2868    fn collapse(
2869        &mut self,
2870        residual: &T,
2871        norm_epsilon: f32,
2872        context: &T::Context,
2873    ) -> Result<HyperConnectionState<T>, Error>;
2874
2875    /// Injects a sublayer result and mixes the previous residual streams.
2876    fn expand(
2877        &mut self,
2878        sublayer: &T,
2879        residual: &T,
2880        state: &HyperConnectionState<T>,
2881        context: &T::Context,
2882    ) -> Result<T, Error>;
2883}
2884
2885/// Backend-native operator for the final learned stream collapse.
2886pub trait HyperHeadOperator<T: Tensor>: Clone + Debug + Parameterized<T> {
2887    /// Collapses `[batch, tokens, streams, hidden]` into one hidden state.
2888    fn forward(&mut self, residual: &T, context: &T::Context) -> Result<T, Error>;
2889}
2890
2891/// Neural backend extension for hyper-connected residual architectures.
2892pub trait HyperNeuralBackend: NeuralBackend {
2893    /// Concrete multi-stream residual operator.
2894    type HyperConnection: HyperConnectionOperator<Self::Tensor>;
2895    /// Concrete final stream-collapse operator.
2896    type HyperHead: HyperHeadOperator<Self::Tensor>;
2897
2898    /// Builds one unloaded hyper-connection operator.
2899    fn hyper_connection(
2900        spec: HyperConnectionSpec,
2901        context: &<Self::Tensor as Tensor>::Context,
2902    ) -> Result<Self::HyperConnection, Error>;
2903
2904    /// Builds one unloaded final hyper-head operator.
2905    fn hyper_head(
2906        spec: HyperHeadSpec,
2907        context: &<Self::Tensor as Tensor>::Context,
2908    ) -> Result<Self::HyperHead, Error>;
2909}
2910
2911/// Neutral, statically dispatched multi-stream residual layer.
2912#[derive(Debug, Clone, Parameterized)]
2913#[parameterized(tensor = "B::Tensor")]
2914pub struct HyperConnection<B: HyperNeuralBackend> {
2915    operator: B::HyperConnection,
2916}
2917
2918impl<B: HyperNeuralBackend> HyperConnection<B> {
2919    /// Builds the backend operator from architecture-owned parameter slots.
2920    pub fn new(
2921        spec: HyperConnectionSpec,
2922        context: &<B::Tensor as Tensor>::Context,
2923    ) -> Result<Self, Error> {
2924        spec.validate()?;
2925        Ok(Self {
2926            operator: B::hyper_connection(spec, context)?,
2927        })
2928    }
2929
2930    /// Collapses residual streams for one sublayer.
2931    pub fn collapse(
2932        &mut self,
2933        residual: &B::Tensor,
2934        norm_epsilon: f32,
2935        context: &<B::Tensor as Tensor>::Context,
2936    ) -> Result<HyperConnectionState<B::Tensor>, Error> {
2937        self.operator.collapse(residual, norm_epsilon, context)
2938    }
2939
2940    /// Expands a sublayer result back into residual streams.
2941    pub fn expand(
2942        &mut self,
2943        sublayer: &B::Tensor,
2944        residual: &B::Tensor,
2945        state: &HyperConnectionState<B::Tensor>,
2946        context: &<B::Tensor as Tensor>::Context,
2947    ) -> Result<B::Tensor, Error> {
2948        self.operator.expand(sublayer, residual, state, context)
2949    }
2950}
2951
2952/// Neutral, statically dispatched final stream-collapse layer.
2953#[derive(Debug, Parameterized)]
2954#[parameterized(tensor = "B::Tensor")]
2955pub struct HyperHead<B: HyperNeuralBackend> {
2956    operator: B::HyperHead,
2957}
2958
2959impl<B: HyperNeuralBackend> Clone for HyperHead<B> {
2960    fn clone(&self) -> Self {
2961        Self {
2962            operator: self.operator.clone(),
2963        }
2964    }
2965}
2966
2967impl<B: HyperNeuralBackend> HyperHead<B> {
2968    /// Builds the backend operator from architecture-owned parameter slots.
2969    pub fn new(
2970        spec: HyperHeadSpec,
2971        context: &<B::Tensor as Tensor>::Context,
2972    ) -> Result<Self, Error> {
2973        spec.validate()?;
2974        Ok(Self {
2975            operator: B::hyper_head(spec, context)?,
2976        })
2977    }
2978
2979    /// Collapses all residual streams to one hidden state.
2980    pub fn forward(
2981        &mut self,
2982        residual: &B::Tensor,
2983        context: &<B::Tensor as Tensor>::Context,
2984    ) -> Result<B::Tensor, Error> {
2985        self.operator.forward(residual, context)
2986    }
2987}
2988
2989/// One backend-native scaled-dot-product attention request.
2990///
2991/// Projected queries, keys, and values remain owned backend tensors so cached,
2992/// uncached, paged, and sliding implementations can consume the same request
2993/// without cloning or host materialization. Masks and learned per-query-head
2994/// sink logits are borrowed architecture state.
2995#[derive(Debug)]
2996pub struct AttentionRequest<'a, T> {
2997    /// Queries shaped `[batch, query_heads, query_tokens, head_dimensions]`.
2998    pub queries: T,
2999    /// Keys shaped `[batch, key_value_heads, key_tokens, head_dimensions]`.
3000    pub keys: T,
3001    /// Values shaped `[batch, key_value_heads, key_tokens, value_dimensions]`.
3002    pub values: T,
3003    /// Positive finite score scale.
3004    pub scale: f32,
3005    /// Optional additive or boolean attention mask.
3006    pub mask: Option<&'a T>,
3007    /// Optional learned sink logit for every query head.
3008    pub sinks: Option<&'a T>,
3009}
3010
3011impl<T: Tensor> AttentionRequest<'_, T> {
3012    /// Validates common grouped-query and sink geometry without inspecting values.
3013    pub fn validate(&self) -> Result<(), Error> {
3014        let queries = self.queries.shape();
3015        let keys = self.keys.shape();
3016        let values = self.values.shape();
3017        if queries.len() != 4
3018            || keys.len() != 4
3019            || values.len() != 4
3020            || queries[0] != keys[0]
3021            || keys[..3] != values[..3]
3022            || queries[3] != keys[3]
3023            || queries[1] <= 0
3024            || keys[1] <= 0
3025            || queries[1] % keys[1] != 0
3026            || queries[2] <= 0
3027            || keys[2] <= 0
3028            || values[3] <= 0
3029            || !self.scale.is_finite()
3030            || self.scale <= 0.0
3031        {
3032            return Err(Error::backend(format!(
3033                "invalid attention request geometry queries={queries:?} keys={keys:?} values={values:?} scale={}",
3034                self.scale
3035            )));
3036        }
3037        if let Some(sinks) = self.sinks {
3038            if sinks.shape() != [queries[1]] {
3039                return Err(Error::backend(format!(
3040                    "attention sinks require shape [{}], got {:?}",
3041                    queries[1],
3042                    sinks.shape()
3043                )));
3044            }
3045        }
3046        Ok(())
3047    }
3048}
3049
3050/// Backend-native key/value cache operations required by attention.
3051pub trait AttentionCache<T: Tensor> {
3052    /// Current absolute sequence offset.
3053    fn offset(&self) -> i32;
3054    /// Maximum retained history for a sliding cache.
3055    fn max_size(&self) -> Option<i32>;
3056    /// Appends projected keys and values and returns the tensors used by attention.
3057    fn update_for_attention(
3058        &mut self,
3059        keys: T,
3060        values: T,
3061        context: &T::Context,
3062    ) -> Result<(T, T), Error>;
3063    /// Runs cache-aware attention, including paged or quantized kernels where applicable.
3064    fn attention(
3065        &mut self,
3066        request: AttentionRequest<'_, T>,
3067        context: &T::Context,
3068    ) -> Result<T, Error>;
3069}
3070
3071/// Attention cache with a fixed set of bounded causal-convolution histories.
3072///
3073/// Slot identity is architecture policy; storage, persistence, and device
3074/// realization remain runtime/backend concerns.
3075pub trait AuxiliaryConvolutionState<T: Tensor>: AttentionCache<T> {
3076    /// Borrows one bounded convolution-history slot.
3077    fn convolution_state(&mut self, slot: u32) -> Result<&mut Option<T>, Error>;
3078}
3079
3080/// Semantic latent and rotary components retained by compressed attention.
3081#[derive(Debug, Clone)]
3082pub struct CompressedAttentionState<T> {
3083    /// Head-independent latent key/value representation.
3084    pub latent: T,
3085    /// Rotary-key representation aligned to the same token range.
3086    pub rotary: T,
3087}
3088
3089/// Result of appending compressed state.
3090#[derive(Debug, Clone)]
3091pub enum CompressedAttentionView<T> {
3092    /// Complete resident state used directly by prefill or decode attention.
3093    Resident(CompressedAttentionState<T>),
3094    /// Paged state; the appended components are retained for diagnostics while
3095    /// attention scans semantic blocks through the cache operator.
3096    Paged {
3097        /// Components appended by this update.
3098        appended: CompressedAttentionState<T>,
3099    },
3100}
3101
3102impl<T> CompressedAttentionView<T> {
3103    /// Returns the resident state when paging is not active.
3104    pub const fn resident(&self) -> Option<&CompressedAttentionState<T>> {
3105        match self {
3106            Self::Resident(state) => Some(state),
3107            Self::Paged { .. } => None,
3108        }
3109    }
3110
3111    /// Returns components suitable for non-materializing observation.
3112    pub const fn observable(&self) -> &CompressedAttentionState<T> {
3113        match self {
3114            Self::Resident(state) | Self::Paged { appended: state } => state,
3115        }
3116    }
3117
3118    /// Returns whether block-addressable paging is active.
3119    pub const fn is_paged(&self) -> bool {
3120        matches!(self, Self::Paged { .. })
3121    }
3122}
3123
3124/// One absolute token range supplied to blockwise compressed attention.
3125#[derive(Debug, Clone)]
3126pub struct CompressedAttentionBlock<T> {
3127    /// Inclusive absolute token start.
3128    pub start: i64,
3129    /// Exclusive absolute token end.
3130    pub end: i64,
3131    /// Latent and rotary components for this range.
3132    pub state: CompressedAttentionState<T>,
3133}
3134
3135/// Aggregate observations from one paged blockwise attention scan.
3136#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3137pub struct CompressedAttentionScan {
3138    /// Number of scanned sealed and tail blocks.
3139    pub blocks: u64,
3140    /// Number of persistent compressed bytes scanned.
3141    pub bytes: u64,
3142    /// Maximum caller-reported transient reconstruction bytes.
3143    pub reconstruction_scratch_bytes: u64,
3144}
3145
3146/// Fixed request metadata for an online blockwise attention recurrence.
3147#[derive(Debug, Clone, Copy)]
3148pub struct BlockwiseAttentionSpec<'a, T> {
3149    /// Queries shaped `[batch, heads, query_tokens, dimensions]`.
3150    pub queries: &'a T,
3151    /// Query/key score multiplier.
3152    pub scale: f32,
3153    /// Optional complete-context mask sliced by the backend for each block.
3154    pub mask: Option<&'a T>,
3155    /// Absolute position of the first query.
3156    pub query_start: i64,
3157    /// Absolute exclusive end of the complete visible context.
3158    pub context_end: i64,
3159    /// Optional causal sliding-window width.
3160    pub sliding_window: Option<i32>,
3161    /// Number of prefix tokens visible outside a sliding window.
3162    pub prefix_tokens: i64,
3163    /// Optional learned per-head sink logits.
3164    pub sinks: Option<&'a T>,
3165}
3166
3167/// Typed backend fusion for exact online softmax across ordered attention
3168/// blocks. Persistent caches remain compressed; only one reconstructed block
3169/// is live at a time.
3170pub trait BlockwiseAttentionBackend: NeuralBackend {
3171    /// Backend-native running maximum, normalization, and value accumulator.
3172    type BlockwiseAccumulator;
3173
3174    /// Starts an empty online attention recurrence.
3175    fn begin_blockwise_attention(
3176        spec: BlockwiseAttentionSpec<'_, Self::Tensor>,
3177        context: &<Self::Tensor as Tensor>::Context,
3178    ) -> Result<Self::BlockwiseAccumulator, Error>;
3179
3180    /// Incorporates one absolute key/value block and reports transient bytes.
3181    fn accumulate_blockwise_attention(
3182        accumulator: &mut Self::BlockwiseAccumulator,
3183        start: i64,
3184        end: i64,
3185        keys: Self::Tensor,
3186        values: Self::Tensor,
3187        context: &<Self::Tensor as Tensor>::Context,
3188    ) -> Result<u64, Error>;
3189
3190    /// Finishes the online recurrence.
3191    fn finish_blockwise_attention(
3192        accumulator: Self::BlockwiseAccumulator,
3193        context: &<Self::Tensor as Tensor>::Context,
3194    ) -> Result<Self::Tensor, Error>;
3195}
3196
3197/// Backend-owned compressed-attention state over semantic components.
3198///
3199/// Checkpoints are cheap backend snapshots used by speculative fork/rollback.
3200/// `finalize` seals mutable state before runtime prompt-cache persistence.
3201pub trait CompressedAttentionCache<T: Tensor>: Debug {
3202    /// Backend snapshot preserving paging and residency identity.
3203    type Checkpoint: Clone + Debug;
3204
3205    /// Current absolute token frontier.
3206    fn offset(&self) -> i32;
3207    /// Whether state is block-addressable rather than fully resident.
3208    fn is_paged(&self) -> bool;
3209    /// Appends aligned latent and rotary components.
3210    fn append(
3211        &mut self,
3212        state: CompressedAttentionState<T>,
3213        context: &T::Context,
3214    ) -> Result<CompressedAttentionView<T>, Error>;
3215    /// Visits all paged blocks in absolute order without host tensor
3216    /// materialization. The callback reports its transient scratch usage.
3217    fn visit_blocks<F>(
3218        &mut self,
3219        query_tokens: i32,
3220        context: &T::Context,
3221        visitor: F,
3222    ) -> Result<CompressedAttentionScan, Error>
3223    where
3224        F: FnMut(CompressedAttentionBlock<T>) -> Result<u64, Error>;
3225    /// Captures a cheap speculative checkpoint.
3226    fn checkpoint(&self) -> Self::Checkpoint;
3227    /// Restores a previous checkpoint, removing any later paged state.
3228    fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
3229        -> Result<(), Error>;
3230    /// Seals mutable tails before prompt-cache snapshot persistence.
3231    fn finalize(&mut self) -> Result<(), Error>;
3232    /// Clears all resident or paged state.
3233    fn clear(&mut self) -> Result<(), Error>;
3234}
3235
3236/// Complete source windows emitted by an append-only gated-pooling stream.
3237#[derive(Debug, Clone)]
3238pub struct PoolingWindows<T> {
3239    /// Source values shaped `[batch, complete_source_tokens, width]`.
3240    pub values: T,
3241    /// Gate logits aligned to `values`.
3242    pub gates: T,
3243    /// Absolute source-token position of the first returned value.
3244    pub base_position: i32,
3245}
3246
3247/// Previous overlap carried between adjacent gated-pooling windows.
3248#[derive(Debug, Clone)]
3249pub struct PoolingOverlap<T> {
3250    /// Previous-window source values, when a complete predecessor exists.
3251    pub values: Option<T>,
3252    /// Previous-window gate logits aligned to `values`.
3253    pub gates: Option<T>,
3254}
3255
3256/// Backend-owned local-key and compressed-pooling state used by architectures
3257/// that mix bounded local attention with append-only pooled history.
3258///
3259/// Stream ordinals are architecture-owned. Implementations preserve pending,
3260/// pooled, and overlap components through checkpoint, rollback, prompt-cache,
3261/// resident, and paged realizations without exposing storage classes here.
3262pub trait PoolingAttentionCache<T: Tensor>: Debug {
3263    /// Backend snapshot preserving all local and pooling state.
3264    type Checkpoint: Clone + Debug;
3265
3266    /// Current absolute source-token frontier.
3267    fn offset(&self) -> i32;
3268    /// Returns the configured source-token ratio for one pooling stream.
3269    fn pooling_ratio(&self, stream: u32) -> Option<i32>;
3270    /// Appends local keys and returns the bounded history used by attention,
3271    /// shaped `[batch, local_tokens, dimensions]`.
3272    fn append_local(&mut self, keys: T, context: &T::Context) -> Result<T, Error>;
3273    /// Builds causal eligibility for the currently retained local history.
3274    fn local_mask(&self, query_tokens: i32, offset: i32, context: &T::Context) -> Result<T, Error>;
3275    /// Accumulates source values and gates, retaining any incomplete suffix.
3276    fn accumulate_pooling_windows(
3277        &mut self,
3278        stream: u32,
3279        values: T,
3280        gates: T,
3281        absolute_offset: i32,
3282        context: &T::Context,
3283    ) -> Result<PoolingWindows<T>, Error>;
3284    /// Replaces overlap carried by one stream and returns its previous pair.
3285    fn replace_pooling_overlap(
3286        &mut self,
3287        stream: u32,
3288        values: T,
3289        gates: T,
3290    ) -> Result<PoolingOverlap<T>, Error>;
3291    /// Appends newly pooled values and returns the complete pooled history.
3292    fn append_pooled(&mut self, stream: u32, values: T, context: &T::Context) -> Result<T, Error>;
3293    /// Builds causal eligibility for one stream's complete pooled history.
3294    fn pooling_mask(
3295        &self,
3296        stream: u32,
3297        query_tokens: i32,
3298        offset: i32,
3299        context: &T::Context,
3300    ) -> Result<Option<T>, Error>;
3301    /// Captures a cheap speculative checkpoint.
3302    fn checkpoint(&self) -> Self::Checkpoint;
3303    /// Restores a previous checkpoint.
3304    fn restore(&mut self, checkpoint: &Self::Checkpoint, context: &T::Context)
3305        -> Result<(), Error>;
3306    /// Seals mutable local and pooled tails before persistence.
3307    fn finalize(&mut self) -> Result<(), Error>;
3308    /// Clears all local and pooling state.
3309    fn clear(&mut self) -> Result<(), Error>;
3310}
3311
3312/// Explicit optional operations a backend promises to execute.
3313///
3314/// These capabilities cover explicitly admitted methods on [`NeuralBackend`]
3315/// and every optional [`Tensor`] method whose default implementation fails
3316/// closed. Architectures validate their required set while constructing
3317/// modules, before parameters are loaded or a forward pass can begin.
3318#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
3319pub struct NeuralOperatorCapabilities(u64);
3320
3321impl NeuralOperatorCapabilities {
3322    /// No optional forward operators.
3323    pub const NONE: Self = Self(0);
3324    /// Tanh-approximated GELU.
3325    pub const GELU_APPROXIMATE: Self = Self(1 << 0);
3326    /// Logistic sigmoid.
3327    pub const SIGMOID: Self = Self(1 << 1);
3328    /// Softplus.
3329    pub const SOFTPLUS: Self = Self(1 << 2);
3330    /// Natural exponential.
3331    pub const EXP: Self = Self(1 << 3);
3332    /// Gated grouped RMS normalization.
3333    pub const GATED_GROUP_RMS_NORM: Self = Self(1 << 4);
3334    /// L2 normalization.
3335    pub const L2_NORMALIZE: Self = Self(1 << 5);
3336    /// SiLU-gated grouped RMS normalization.
3337    pub const SILU_GATED_GROUP_RMS_NORM: Self = Self(1 << 6);
3338    /// Segmented attention.
3339    pub const SEGMENTED_ATTENTION: Self = Self(1 << 7);
3340    /// Gated-delta recurrent scan.
3341    pub const GATED_DELTA_SCAN: Self = Self(1 << 8);
3342    /// Selective state-space scan.
3343    pub const SELECTIVE_STATE_SPACE_SCAN: Self = Self(1 << 9);
3344    /// Indexed sparse attention.
3345    pub const INDEXED_ATTENTION: Self = Self(1 << 10);
3346    /// Dense pooled attention.
3347    pub const POOLED_ATTENTION: Self = Self(1 << 11);
3348    /// Pooled-position selection.
3349    pub const POOLED_POSITION_SELECTION: Self = Self(1 << 12);
3350    /// Pooled-mask gathering.
3351    pub const POOLED_MASK_GATHER: Self = Self(1 << 13);
3352    /// Attention with learned sink logits.
3353    pub const ATTENTION_SINKS: Self = Self(1 << 14);
3354    /// Learned relative-profile attention.
3355    pub const RELATIVE_ATTENTION: Self = Self(1 << 15);
3356    /// Joint grouped/shared group selection.
3357    pub const JOINT_GROUP_SELECTION: Self = Self(1 << 16);
3358    /// Weightless RMS normalization.
3359    pub const RMS_NORM_WITHOUT_WEIGHT: Self = Self(1 << 17);
3360    /// Grouped block-diagonal linear projection.
3361    pub const GROUPED_LINEAR: Self = Self(1 << 18);
3362    /// Tensor-parallel sum reduction.
3363    pub const SUM_PARALLEL: Self = Self(1 << 19);
3364    /// Unloaded signed 32-bit integer parameter allocation.
3365    pub const UNLOADED_I32: Self = Self(1 << 20);
3366    /// Signed 32-bit integer tensor construction from host data.
3367    pub const FROM_I32_SLICE: Self = Self(1 << 21);
3368    /// Floating-point host materialization.
3369    pub const TO_F32_VEC: Self = Self(1 << 22);
3370    /// Signed 32-bit integer host materialization.
3371    pub const TO_I32_VEC: Self = Self(1 << 23);
3372    /// Floating-point filled tensor construction.
3373    pub const FULL_F32: Self = Self(1 << 24);
3374    /// Signed 32-bit integer filled tensor construction.
3375    pub const FULL_I32: Self = Self(1 << 25);
3376    /// Elementwise hyperbolic tangent.
3377    pub const TANH: Self = Self(1 << 26);
3378    /// Elementwise clamp with tensor bounds.
3379    pub const CLIP: Self = Self(1 << 27);
3380    /// Axis-wise softmax.
3381    pub const SOFTMAX_AXIS: Self = Self(1 << 28);
3382    /// Tensor broadcasting.
3383    pub const BROADCAST_TO: Self = Self(1 << 29);
3384    /// Dtype-preserving zero allocation.
3385    pub const ZEROS_LIKE: Self = Self(1 << 30);
3386    /// Signed integer scalar comparison.
3387    pub const EQUAL_I32: Self = Self(1 << 31);
3388    /// Elementwise logical disjunction.
3389    pub const LOGICAL_OR: Self = Self(1 << 32);
3390    /// Conditional tensor selection.
3391    pub const WHERE_CONDITION: Self = Self(1 << 33);
3392    /// Masked tensor scatter.
3393    pub const MASKED_SCATTER: Self = Self(1 << 34);
3394    /// Rotary positions with caller-supplied frequencies.
3395    pub const ROPE_WITH_FREQUENCIES: Self = Self(1 << 35);
3396    /// Two-dimensional convolution.
3397    pub const CONV2D: Self = Self(1 << 36);
3398    /// Multi-axis rotary embedding construction.
3399    pub const MULTI_AXIS_ROTARY_EMBEDDINGS: Self = Self(1 << 37);
3400    /// Masked vocabulary output projection.
3401    pub const MASKED_OUTPUT_PROJECTION: Self = Self(1 << 38);
3402    /// Every currently declared optional operation.
3403    pub const ALL: Self = Self((1 << 39) - 1);
3404
3405    /// Returns the union of two capability sets.
3406    pub const fn union(self, other: Self) -> Self {
3407        Self(self.0 | other.0)
3408    }
3409
3410    /// Returns whether this set contains every required capability.
3411    pub const fn contains(self, required: Self) -> bool {
3412        self.0 & required.0 == required.0
3413    }
3414
3415    /// Returns stable names for every required capability absent from this set.
3416    pub fn missing_capability_names(self, required: Self) -> Vec<&'static str> {
3417        const NAMES: &[(NeuralOperatorCapabilities, &str)] = &[
3418            (
3419                NeuralOperatorCapabilities::GELU_APPROXIMATE,
3420                "gelu_approximate",
3421            ),
3422            (NeuralOperatorCapabilities::SIGMOID, "sigmoid"),
3423            (NeuralOperatorCapabilities::SOFTPLUS, "softplus"),
3424            (NeuralOperatorCapabilities::EXP, "exp"),
3425            (
3426                NeuralOperatorCapabilities::GATED_GROUP_RMS_NORM,
3427                "gated_group_rms_norm",
3428            ),
3429            (NeuralOperatorCapabilities::L2_NORMALIZE, "l2_normalize"),
3430            (
3431                NeuralOperatorCapabilities::SILU_GATED_GROUP_RMS_NORM,
3432                "silu_gated_group_rms_norm",
3433            ),
3434            (
3435                NeuralOperatorCapabilities::SEGMENTED_ATTENTION,
3436                "segmented_attention",
3437            ),
3438            (
3439                NeuralOperatorCapabilities::GATED_DELTA_SCAN,
3440                "gated_delta_scan",
3441            ),
3442            (
3443                NeuralOperatorCapabilities::SELECTIVE_STATE_SPACE_SCAN,
3444                "selective_state_space_scan",
3445            ),
3446            (
3447                NeuralOperatorCapabilities::INDEXED_ATTENTION,
3448                "indexed_attention",
3449            ),
3450            (
3451                NeuralOperatorCapabilities::POOLED_ATTENTION,
3452                "pooled_attention",
3453            ),
3454            (
3455                NeuralOperatorCapabilities::POOLED_POSITION_SELECTION,
3456                "select_pooled_positions",
3457            ),
3458            (
3459                NeuralOperatorCapabilities::POOLED_MASK_GATHER,
3460                "gather_pooled_mask",
3461            ),
3462            (
3463                NeuralOperatorCapabilities::ATTENTION_SINKS,
3464                "attention_sinks",
3465            ),
3466            (
3467                NeuralOperatorCapabilities::RELATIVE_ATTENTION,
3468                "relative_attention",
3469            ),
3470            (
3471                NeuralOperatorCapabilities::JOINT_GROUP_SELECTION,
3472                "joint_group_selection",
3473            ),
3474            (
3475                NeuralOperatorCapabilities::RMS_NORM_WITHOUT_WEIGHT,
3476                "rms_norm_without_weight",
3477            ),
3478            (NeuralOperatorCapabilities::GROUPED_LINEAR, "grouped_linear"),
3479            (NeuralOperatorCapabilities::SUM_PARALLEL, "sum_parallel"),
3480            (NeuralOperatorCapabilities::UNLOADED_I32, "unloaded_i32"),
3481            (NeuralOperatorCapabilities::FROM_I32_SLICE, "from_i32_slice"),
3482            (NeuralOperatorCapabilities::TO_F32_VEC, "to_f32_vec"),
3483            (NeuralOperatorCapabilities::TO_I32_VEC, "to_i32_vec"),
3484            (NeuralOperatorCapabilities::FULL_F32, "full_f32"),
3485            (NeuralOperatorCapabilities::FULL_I32, "full_i32"),
3486            (NeuralOperatorCapabilities::TANH, "tanh"),
3487            (NeuralOperatorCapabilities::CLIP, "clip"),
3488            (NeuralOperatorCapabilities::SOFTMAX_AXIS, "softmax_axis"),
3489            (NeuralOperatorCapabilities::BROADCAST_TO, "broadcast_to"),
3490            (NeuralOperatorCapabilities::ZEROS_LIKE, "zeros_like"),
3491            (NeuralOperatorCapabilities::EQUAL_I32, "equal_i32"),
3492            (NeuralOperatorCapabilities::LOGICAL_OR, "logical_or"),
3493            (
3494                NeuralOperatorCapabilities::WHERE_CONDITION,
3495                "where_condition",
3496            ),
3497            (NeuralOperatorCapabilities::MASKED_SCATTER, "masked_scatter"),
3498            (
3499                NeuralOperatorCapabilities::ROPE_WITH_FREQUENCIES,
3500                "rope_with_frequencies",
3501            ),
3502            (NeuralOperatorCapabilities::CONV2D, "conv2d"),
3503            (
3504                NeuralOperatorCapabilities::MULTI_AXIS_ROTARY_EMBEDDINGS,
3505                "multi_axis_rotary_embeddings",
3506            ),
3507            (
3508                NeuralOperatorCapabilities::MASKED_OUTPUT_PROJECTION,
3509                "masked_output_projection",
3510            ),
3511        ];
3512        NAMES
3513            .iter()
3514            .filter_map(|(capability, name)| {
3515                (required.contains(*capability) && !self.contains(*capability)).then_some(*name)
3516            })
3517            .collect()
3518    }
3519}
3520
3521#[cfg(test)]
3522mod neural_operator_capability_tests {
3523    use super::NeuralOperatorCapabilities as C;
3524
3525    #[test]
3526    fn all_includes_every_fail_closed_tensor_operation() {
3527        for (capability, name) in [
3528            (C::UNLOADED_I32, "unloaded_i32"),
3529            (C::FROM_I32_SLICE, "from_i32_slice"),
3530            (C::TO_F32_VEC, "to_f32_vec"),
3531            (C::TO_I32_VEC, "to_i32_vec"),
3532            (C::FULL_F32, "full_f32"),
3533            (C::FULL_I32, "full_i32"),
3534            (C::TANH, "tanh"),
3535            (C::CLIP, "clip"),
3536            (C::SOFTMAX_AXIS, "softmax_axis"),
3537            (C::BROADCAST_TO, "broadcast_to"),
3538            (C::ZEROS_LIKE, "zeros_like"),
3539            (C::EQUAL_I32, "equal_i32"),
3540            (C::LOGICAL_OR, "logical_or"),
3541            (C::WHERE_CONDITION, "where_condition"),
3542            (C::MASKED_SCATTER, "masked_scatter"),
3543            (C::ROPE_WITH_FREQUENCIES, "rope_with_frequencies"),
3544            (C::CONV2D, "conv2d"),
3545            (
3546                C::MULTI_AXIS_ROTARY_EMBEDDINGS,
3547                "multi_axis_rotary_embeddings",
3548            ),
3549            (C::MASKED_OUTPUT_PROJECTION, "masked_output_projection"),
3550        ] {
3551            assert!(C::ALL.contains(capability));
3552            assert_eq!(C::NONE.missing_capability_names(capability), [name]);
3553        }
3554    }
3555}
3556
3557/// General neural-operator family selected by a shared architecture.
3558///
3559/// Associated concrete types make calls statically dispatched. Implementations
3560/// retain ownership of tensor storage, fusion, quantization, and collectives.
3561pub trait NeuralBackend: Sized + 'static {
3562    /// Optional forward operators explicitly supported by this backend.
3563    const OPERATOR_CAPABILITIES: NeuralOperatorCapabilities = NeuralOperatorCapabilities::NONE;
3564
3565    /// Backend tensor handle.
3566    type Tensor: Tensor;
3567    /// Native affine projection, including packed quantized variants.
3568    type Linear: LinearOperator<Self::Tensor>;
3569    /// Native embedding table.
3570    type Embedding: EmbeddingOperator<Self::Tensor>;
3571    /// Native normalization operator.
3572    type Normalization: NormalizationOperator<Self::Tensor>;
3573    /// Native rotary-position operator.
3574    type Rotary: RotaryOperator<Self::Tensor>;
3575    /// Backend collective context used by tensor-parallel execution.
3576    type ParallelContext: ?Sized;
3577
3578    /// Rejects an architecture before module construction when an optional
3579    /// forward operator is unavailable.
3580    fn require_operator_capabilities(
3581        architecture: &'static str,
3582        required: NeuralOperatorCapabilities,
3583    ) -> Result<(), Error> {
3584        let available = Self::OPERATOR_CAPABILITIES;
3585        if available.contains(required) {
3586            return Ok(());
3587        }
3588        Err(Error::backend(format!(
3589            "{architecture} requires unsupported backend operators: {}",
3590            available.missing_capability_names(required).join(", ")
3591        )))
3592    }
3593
3594    /// Builds one affine projection.
3595    fn linear(
3596        spec: LinearSpec,
3597        context: &<Self::Tensor as Tensor>::Context,
3598    ) -> Result<Self::Linear, Error>;
3599    /// Builds one token embedding table.
3600    fn embedding(
3601        spec: EmbeddingSpec,
3602        context: &<Self::Tensor as Tensor>::Context,
3603    ) -> Result<Self::Embedding, Error>;
3604    /// Builds an RMS normalization with an explicit scale policy.
3605    fn normalization(
3606        spec: NormalizationConstructionSpec,
3607        context: &<Self::Tensor as Tensor>::Context,
3608    ) -> Result<Self::Normalization, Error>;
3609    /// Builds the model's rotary-position operator.
3610    fn rotary(
3611        spec: RotarySpec,
3612        context: &<Self::Tensor as Tensor>::Context,
3613    ) -> Result<Self::Rotary, Error>;
3614    /// Applies SiLU using a backend-native implementation.
3615    fn silu(
3616        input: Self::Tensor,
3617        context: &<Self::Tensor as Tensor>::Context,
3618    ) -> Result<Self::Tensor, Error>;
3619    /// Applies the tanh-approximated GELU used by some encoder MLPs.
3620    fn gelu_approximate(
3621        input: Self::Tensor,
3622        context: &<Self::Tensor as Tensor>::Context,
3623    ) -> Result<Self::Tensor, Error> {
3624        let _ = (input, context);
3625        Err(Error::backend(
3626            "approximate GELU is not implemented by this backend",
3627        ))
3628    }
3629    /// Applies the logistic sigmoid elementwise.
3630    fn sigmoid(
3631        input: Self::Tensor,
3632        context: &<Self::Tensor as Tensor>::Context,
3633    ) -> Result<Self::Tensor, Error> {
3634        let _ = (input, context);
3635        Err(Error::backend("sigmoid is not implemented by this backend"))
3636    }
3637    /// Applies softplus elementwise.
3638    fn softplus(
3639        input: Self::Tensor,
3640        context: &<Self::Tensor as Tensor>::Context,
3641    ) -> Result<Self::Tensor, Error> {
3642        let _ = (input, context);
3643        Err(Error::backend(
3644            "softplus is not implemented by this backend",
3645        ))
3646    }
3647    /// Applies the natural exponential elementwise.
3648    fn exp(
3649        input: Self::Tensor,
3650        context: &<Self::Tensor as Tensor>::Context,
3651    ) -> Result<Self::Tensor, Error> {
3652        let _ = (input, context);
3653        Err(Error::backend(
3654            "exponential is not implemented by this backend",
3655        ))
3656    }
3657    /// Applies SiLU gating followed by grouped RMS normalization and scale.
3658    /// Input and gate shapes must match; positive groups must divide the final
3659    /// feature axis, and epsilon must be finite and strictly positive.
3660    fn gated_group_rms_norm(
3661        input: &Self::Tensor,
3662        gate: &Self::Tensor,
3663        weight: &Self::Tensor,
3664        groups: i32,
3665        epsilon: f32,
3666        context: &<Self::Tensor as Tensor>::Context,
3667    ) -> Result<Self::Tensor, Error> {
3668        let _ = (input, gate, weight, groups, epsilon, context);
3669        Err(Error::backend(
3670            "gated grouped RMS normalization is not implemented by this backend",
3671        ))
3672    }
3673    /// Applies `input / sqrt(sum(input * input) + epsilon)` over the final axis.
3674    /// Epsilon must be finite and strictly positive; it is additive, not a clamp.
3675    fn l2_normalize(
3676        input: &Self::Tensor,
3677        epsilon: f32,
3678        context: &<Self::Tensor as Tensor>::Context,
3679    ) -> Result<Self::Tensor, Error> {
3680        let _ = (input, epsilon, context);
3681        Err(Error::backend(
3682            "L2 normalization is not implemented by this backend",
3683        ))
3684    }
3685    /// Applies grouped RMS normalization to `input`, multiplies by a learned
3686    /// scale, and then modulates the result by `silu(gate)`.
3687    /// Input and gate shapes must match; positive groups must divide the final
3688    /// feature axis, and epsilon must be finite and strictly positive.
3689    fn silu_gated_group_rms_norm(
3690        input: &Self::Tensor,
3691        gate: &Self::Tensor,
3692        weight: &Self::Tensor,
3693        groups: i32,
3694        epsilon: f32,
3695        context: &<Self::Tensor as Tensor>::Context,
3696    ) -> Result<Self::Tensor, Error> {
3697        let _ = (input, gate, weight, groups, epsilon, context);
3698        Err(Error::backend(
3699            "SiLU-gated grouped RMS normalization is not implemented by this backend",
3700        ))
3701    }
3702    /// Repeats a validated head axis using backend-native reshape and
3703    /// broadcast operations.
3704    fn expand_heads(
3705        input: &Self::Tensor,
3706        expansion: HeadExpansion,
3707        context: &<Self::Tensor as Tensor>::Context,
3708    ) -> Result<Self::Tensor, Error> {
3709        expansion.validate(input)?;
3710        if expansion.source_heads == expansion.target_heads {
3711            return Ok(input.clone());
3712        }
3713        let mut expanded_shape = input.shape().to_vec();
3714        expanded_shape.insert(expansion.axis + 1, 1);
3715        let expanded = input.reshape(&expanded_shape, context)?;
3716        expanded_shape[expansion.axis + 1] = expansion.repeats();
3717        let expanded = expanded.broadcast_to(&expanded_shape, context)?;
3718        expanded_shape[expansion.axis] = expansion.target_heads;
3719        expanded_shape.remove(expansion.axis + 1);
3720        expanded.reshape(&expanded_shape, context)
3721    }
3722    /// Runs unmasked attention independently over contiguous validated
3723    /// segments without exposing backend slicing to architecture code.
3724    fn segmented_attention(
3725        input: SegmentedAttentionInput<'_, Self::Tensor>,
3726        context: &<Self::Tensor as Tensor>::Context,
3727    ) -> Result<Self::Tensor, Error> {
3728        input.validate()?;
3729        let _ = context;
3730        Err(Error::backend(
3731            "segmented attention is not implemented by this backend",
3732        ))
3733    }
3734    /// Adds a residual branch, optionally retaining the accumulator in FP32.
3735    fn add_residual(
3736        residual: &Self::Tensor,
3737        branch: &Self::Tensor,
3738        fp32: bool,
3739        context: &<Self::Tensor as Tensor>::Context,
3740    ) -> Result<Self::Tensor, Error> {
3741        let _ = fp32;
3742        residual.add(branch, context)
3743    }
3744    /// Executes a gated-delta recurrent scan over projected head-major values.
3745    ///
3746    /// Inputs are `[batch, sequence, heads, dimensions]` except `beta`, which
3747    /// is `[batch, sequence, heads]`. The optional initial and returned state
3748    /// are `[batch, heads, dimensions, dimensions]` in FP32 semantic storage.
3749    fn gated_delta_scan(
3750        input: GatedDeltaScanInput<'_, Self::Tensor>,
3751        context: &<Self::Tensor as Tensor>::Context,
3752    ) -> Result<GatedDeltaScanOutput<Self::Tensor>, Error> {
3753        let _ = (input, context);
3754        Err(Error::backend(
3755            "gated-delta scan is not implemented by this backend",
3756        ))
3757    }
3758    /// Executes a grouped selective state-space recurrence in FP32 state.
3759    fn selective_state_space_scan(
3760        input: SelectiveStateSpaceScanInput<'_, Self::Tensor>,
3761        context: &<Self::Tensor as Tensor>::Context,
3762    ) -> Result<SelectiveStateSpaceScanOutput<Self::Tensor>, Error> {
3763        let _ = (input, context);
3764        Err(Error::backend(
3765            "selective state-space scan is not implemented by this backend",
3766        ))
3767    }
3768    /// Runs sparse attention over bounded local state and caller-selected
3769    /// compressed positions, optionally sharing softmax normalization with
3770    /// learned attention sinks.
3771    fn indexed_attention(
3772        input: IndexedAttentionInput<'_, Self::Tensor>,
3773        context: &<Self::Tensor as Tensor>::Context,
3774    ) -> Result<Self::Tensor, Error> {
3775        let _ = (input, context);
3776        Err(Error::backend(
3777            "indexed attention is not implemented by this backend",
3778        ))
3779    }
3780    /// Runs dense shared-softmax attention over local and pooled history.
3781    fn pooled_attention(
3782        input: PooledAttentionInput<'_, Self::Tensor>,
3783        context: &<Self::Tensor as Tensor>::Context,
3784    ) -> Result<Self::Tensor, Error> {
3785        let _ = (input, context);
3786        Err(Error::backend(
3787            "pooled attention is not implemented by this backend",
3788        ))
3789    }
3790    /// Selects pooled positions for indexed attention.
3791    fn select_pooled_positions(
3792        input: PooledPositionInput<'_, Self::Tensor>,
3793        context: &<Self::Tensor as Tensor>::Context,
3794    ) -> Result<Self::Tensor, Error> {
3795        let _ = (input, context);
3796        Err(Error::backend(
3797            "pooled-position selection is not implemented by this backend",
3798        ))
3799    }
3800    /// Gathers one full pooled-eligibility mask at selected positions.
3801    fn gather_pooled_mask(
3802        mask: &Self::Tensor,
3803        selected_positions: &Self::Tensor,
3804        context: &<Self::Tensor as Tensor>::Context,
3805    ) -> Result<Self::Tensor, Error> {
3806        let _ = (mask, selected_positions, context);
3807        Err(Error::backend(
3808            "pooled-mask gathering is not implemented by this backend",
3809        ))
3810    }
3811    /// Runs dense attention with optional learned per-head sink logits.
3812    fn attention_with_sinks(
3813        request: AttentionRequest<'_, Self::Tensor>,
3814        context: &<Self::Tensor as Tensor>::Context,
3815    ) -> Result<Self::Tensor, Error> {
3816        request.validate()?;
3817        if request.sinks.is_some() {
3818            return Err(Error::backend(
3819                "attention sinks are not implemented by this backend",
3820            ));
3821        }
3822        Self::attention(
3823            request.queries,
3824            request.keys,
3825            request.values,
3826            request.scale,
3827            request.mask,
3828            context,
3829        )
3830    }
3831    /// Runs causal sliding-window prefill attention with optional learned sinks.
3832    fn sliding_window_attention_with_sinks(
3833        request: AttentionRequest<'_, Self::Tensor>,
3834        window: i32,
3835        position_offset: i32,
3836        context: &<Self::Tensor as Tensor>::Context,
3837    ) -> Result<Self::Tensor, Error> {
3838        request.validate()?;
3839        if request.sinks.is_some() {
3840            return Err(Error::backend(
3841                "sliding-window attention sinks are not implemented by this backend",
3842            ));
3843        }
3844        Self::sliding_window_attention(
3845            request.queries,
3846            request.keys,
3847            request.values,
3848            request.scale,
3849            window,
3850            position_offset,
3851            context,
3852        )
3853    }
3854    /// Runs causal attention with caller-projected learned relative profiles.
3855    fn relative_attention(
3856        input: RelativeAttentionInput<'_, Self::Tensor>,
3857        context: &<Self::Tensor as Tensor>::Context,
3858    ) -> Result<Self::Tensor, Error> {
3859        let _ = (input, context);
3860        Err(Error::backend(
3861            "relative-profile attention is not implemented by this backend",
3862        ))
3863    }
3864    /// Applies RMS normalization without a learned scale.
3865    /// The final feature axis must be nonempty, with finite, positive epsilon.
3866    fn rms_norm_without_weight(
3867        input: &Self::Tensor,
3868        epsilon: f32,
3869        context: &<Self::Tensor as Tensor>::Context,
3870    ) -> Result<Self::Tensor, Error> {
3871        let _ = (input, epsilon, context);
3872        Err(Error::backend(
3873            "weightless RMS normalization is not implemented by this backend",
3874        ))
3875    }
3876    /// Applies RMS normalization with a caller-owned learned scale.
3877    /// The final feature axis must be nonempty, with finite, positive epsilon.
3878    ///
3879    /// The default keeps the operation portable by composing weightless
3880    /// normalization and multiplication. Backends may override it with a
3881    /// fused kernel when their tensor runtime provides one.
3882    fn rms_norm_with_weight(
3883        input: &Self::Tensor,
3884        weight: &Self::Tensor,
3885        epsilon: f32,
3886        context: &<Self::Tensor as Tensor>::Context,
3887    ) -> Result<Self::Tensor, Error> {
3888        Self::rms_norm_without_weight(input, epsilon, context)?.multiply(weight, context)
3889    }
3890    /// Applies a validated gated-product equation.
3891    fn gated_product(
3892        gate: Self::Tensor,
3893        up: Self::Tensor,
3894        policy: GatedProductPolicy,
3895        context: &<Self::Tensor as Tensor>::Context,
3896    ) -> Result<Self::Tensor, Error>;
3897    /// Runs un-cached scaled dot-product attention.
3898    fn attention(
3899        queries: Self::Tensor,
3900        keys: Self::Tensor,
3901        values: Self::Tensor,
3902        scale: f32,
3903        mask: Option<&Self::Tensor>,
3904        context: &<Self::Tensor as Tensor>::Context,
3905    ) -> Result<Self::Tensor, Error>;
3906    /// Runs causal sliding-window prefill attention without a square mask.
3907    #[allow(clippy::too_many_arguments)]
3908    fn sliding_window_attention(
3909        queries: Self::Tensor,
3910        keys: Self::Tensor,
3911        values: Self::Tensor,
3912        scale: f32,
3913        window: i32,
3914        position_offset: i32,
3915        context: &<Self::Tensor as Tensor>::Context,
3916    ) -> Result<Self::Tensor, Error>;
3917    /// Builds the backend-native boolean causal mask used for a prefill.
3918    /// `window` is the inclusive maximum backward distance, not a token count:
3919    /// zero admits only the current position and one also admits its predecessor.
3920    ///
3921    /// The returned value remains a lazy/backend-owned tensor. Calling this
3922    /// method must not synchronize or materialize mask contents on the host.
3923    fn causal_mask(
3924        sequence: i32,
3925        offset: i32,
3926        window: Option<i32>,
3927        context: &<Self::Tensor as Tensor>::Context,
3928    ) -> Result<Self::Tensor, Error>;
3929    /// Applies a row-parallel projection and its collective reduction.
3930    fn row_parallel_linear(
3931        linear: &mut Self::Linear,
3932        input: &Self::Tensor,
3933        parallel: &Self::ParallelContext,
3934        context: &<Self::Tensor as Tensor>::Context,
3935    ) -> Result<Self::Tensor, Error>;
3936    /// Number of participants in a tensor-parallel collective context.
3937    fn parallel_size(_parallel: &Self::ParallelContext) -> usize {
3938        1
3939    }
3940}
3941
3942/// Additive mechanisms required only by distributed vocabulary and reduction paths.
3943///
3944/// Ordinary replicated backends implement [`NeuralBackend`] without this trait.
3945/// Every operation here is required, so an admitted distributed architecture cannot
3946/// encounter an inherited forward-time “unsupported” implementation.
3947pub trait DistributedNeuralBackend: NeuralBackend {
3948    /// Builds one rank-local vocabulary embedding under validated ownership.
3949    fn vocabulary_parallel_embedding(
3950        spec: EmbeddingSpec,
3951        range: VocabularyParallelRange,
3952        context: &<Self::Tensor as Tensor>::Context,
3953    ) -> Result<Self::Embedding, Error>;
3954    /// Builds one rank-local vocabulary output projection.
3955    fn vocabulary_parallel_linear(
3956        spec: LinearSpec,
3957        range: VocabularyParallelRange,
3958        context: &<Self::Tensor as Tensor>::Context,
3959    ) -> Result<Self::Linear, Error>;
3960    /// Looks up global token IDs and sums this rank's local contribution.
3961    fn vocabulary_parallel_lookup(
3962        embedding: &mut Self::Embedding,
3963        input: &Self::Tensor,
3964        policy: EmbeddingLookupPolicy,
3965        parallel: &Self::ParallelContext,
3966        context: &<Self::Tensor as Tensor>::Context,
3967    ) -> Result<Self::Tensor, Error>;
3968    /// Projects to local vocabulary rows and gathers complete logits.
3969    fn vocabulary_parallel_project(
3970        linear: &mut Self::Linear,
3971        input: &Self::Tensor,
3972        parallel: &Self::ParallelContext,
3973        context: &<Self::Tensor as Tensor>::Context,
3974    ) -> Result<Self::Tensor, Error>;
3975    /// Projects through a rank-local vocabulary embedding and gathers complete logits.
3976    fn vocabulary_parallel_embedding_project(
3977        embedding: &mut Self::Embedding,
3978        input: &Self::Tensor,
3979        parallel: &Self::ParallelContext,
3980        context: &<Self::Tensor as Tensor>::Context,
3981    ) -> Result<Self::Tensor, Error>;
3982    /// Sums a rank-local tensor contribution across the tensor-parallel group.
3983    fn sum_parallel(
3984        value: Self::Tensor,
3985        parallel: &Self::ParallelContext,
3986        context: &<Self::Tensor as Tensor>::Context,
3987    ) -> Result<Self::Tensor, Error>;
3988}
3989
3990/// Projected inputs to one gated-delta recurrent scan.
3991#[derive(Debug, Clone, Copy)]
3992pub struct GatedDeltaScanInput<'a, T> {
3993    /// Normalized queries `[batch, sequence, heads, dimensions]`.
3994    pub query: &'a T,
3995    /// Normalized keys `[batch, sequence, heads, dimensions]`.
3996    pub key: &'a T,
3997    /// Values `[batch, sequence, heads, dimensions]`.
3998    pub value: &'a T,
3999    /// Log transition decay, scalar- or vector-valued per head.
4000    pub log_decay: &'a T,
4001    /// Update strength `[batch, sequence, heads]`.
4002    pub beta: &'a T,
4003    /// Optional FP32 recurrent matrix.
4004    pub initial_state: Option<&'a T>,
4005}
4006
4007/// Final state and per-token output of a gated-delta scan.
4008#[derive(Debug, Clone)]
4009pub struct GatedDeltaScanOutput<T> {
4010    /// FP32 recurrent state after the final token.
4011    pub state: T,
4012    /// Recurrent output `[batch, sequence, heads, dimensions]`.
4013    pub output: T,
4014}
4015
4016/// Projected inputs to one Mamba-style selective state-space scan.
4017#[derive(Debug, Clone, Copy)]
4018pub struct SelectiveStateSpaceScanInput<'a, T> {
4019    /// Values `[batch, sequence, heads, head_dimensions]`.
4020    pub values: &'a T,
4021    /// Expanded input state vectors `[batch, sequence, heads, state_dimensions]`.
4022    pub input_state: &'a T,
4023    /// Expanded output state vectors `[batch, sequence, heads, state_dimensions]`.
4024    pub output_state: &'a T,
4025    /// Unnormalized timesteps `[batch, sequence, heads]`.
4026    pub time_step: &'a T,
4027    /// Per-head timestep bias `[heads]`.
4028    pub time_step_bias: &'a T,
4029    /// Per-head logarithmic transition magnitude `[heads]`.
4030    pub transition_log: &'a T,
4031    /// Per-head direct skip coefficient `[heads]`.
4032    pub skip: &'a T,
4033    /// Optional FP32 state `[batch, heads, head_dimensions, state_dimensions]`.
4034    pub initial_state: Option<&'a T>,
4035    /// Lower bound applied after softplus timestep discretization.
4036    pub time_step_floor: f32,
4037    /// Maximum number of prefill tokens processed per backend chunk.
4038    pub chunk_size: usize,
4039}
4040
4041/// Final state and per-token output of a selective state-space scan.
4042#[derive(Debug, Clone)]
4043pub struct SelectiveStateSpaceScanOutput<T> {
4044    /// FP32 recurrent state after the final token.
4045    pub state: T,
4046    /// Scan output `[batch, sequence, heads, head_dimensions]`.
4047    pub output: T,
4048}
4049
4050/// Deterministic host reference for the selective state-space recurrence.
4051#[allow(clippy::too_many_arguments)]
4052pub fn reference_selective_state_space_scan(
4053    batch: usize,
4054    sequence: usize,
4055    heads: usize,
4056    head_dimensions: usize,
4057    state_dimensions: usize,
4058    values: &[f32],
4059    input_state: &[f32],
4060    output_state: &[f32],
4061    time_step: &[f32],
4062    time_step_bias: &[f32],
4063    transition_log: &[f32],
4064    skip: &[f32],
4065    time_step_floor: f32,
4066    initial_state: Option<&[f32]>,
4067) -> Result<(Vec<f32>, Vec<f32>), Error> {
4068    let groups = batch * sequence * heads;
4069    let values_len = groups * head_dimensions;
4070    let vectors_len = groups * state_dimensions;
4071    let state_len = batch * heads * head_dimensions * state_dimensions;
4072    if values.len() != values_len
4073        || input_state.len() != vectors_len
4074        || output_state.len() != vectors_len
4075        || time_step.len() != groups
4076        || time_step_bias.len() != heads
4077        || transition_log.len() != heads
4078        || skip.len() != heads
4079        || initial_state.is_some_and(|state| state.len() != state_len)
4080        || !time_step_floor.is_finite()
4081        || time_step_floor < 0.0
4082    {
4083        return Err(Error::backend(
4084            "invalid selective state-space reference geometry",
4085        ));
4086    }
4087    let mut state = initial_state.map_or_else(|| vec![0.0; state_len], <[f32]>::to_vec);
4088    let mut output = vec![0.0; values_len];
4089    for batch_index in 0..batch {
4090        for token in 0..sequence {
4091            for head in 0..heads {
4092                let group = (batch_index * sequence + token) * heads + head;
4093                let dt =
4094                    ((time_step[group] + time_step_bias[head]).exp().ln_1p()).max(time_step_floor);
4095                let transition = (-transition_log[head].exp() * dt).exp();
4096                let vector_base = group * state_dimensions;
4097                for dimension in 0..head_dimensions {
4098                    let value_index = group * head_dimensions + dimension;
4099                    let state_base =
4100                        (batch_index * heads + head) * head_dimensions * state_dimensions
4101                            + dimension * state_dimensions;
4102                    let value = values[value_index];
4103                    let mut projected = 0.0f32;
4104                    for state_dimension in 0..state_dimensions {
4105                        let state_index = state_base + state_dimension;
4106                        state[state_index] = state[state_index] * transition
4107                            + dt * input_state[vector_base + state_dimension] * value;
4108                        projected +=
4109                            state[state_index] * output_state[vector_base + state_dimension];
4110                    }
4111                    output[value_index] = projected + value * skip[head];
4112                }
4113            }
4114        }
4115    }
4116    Ok((state, output))
4117}
4118
4119/// Deterministic host reference for the gated-delta recurrence.
4120///
4121/// Query, key, and value use flattened `[batch, sequence, heads, dimensions]`
4122/// storage. Decay is either `[batch, sequence, heads]` or the same vector
4123/// shape as query/key. Returned state is `[batch, heads, key_dim, value_dim]`.
4124#[allow(clippy::too_many_arguments)]
4125pub fn reference_gated_delta_scan(
4126    batch: usize,
4127    sequence: usize,
4128    heads: usize,
4129    key_dim: usize,
4130    value_dim: usize,
4131    query: &[f32],
4132    key: &[f32],
4133    value: &[f32],
4134    log_decay: &[f32],
4135    vector_decay: bool,
4136    beta: &[f32],
4137    initial_state: Option<&[f32]>,
4138) -> Result<(Vec<f32>, Vec<f32>), Error> {
4139    let key_values = batch * sequence * heads * key_dim;
4140    let values = batch * sequence * heads * value_dim;
4141    let groups = batch * sequence * heads;
4142    let state_values = batch * heads * key_dim * value_dim;
4143    if query.len() != key_values
4144        || key.len() != key_values
4145        || value.len() != values
4146        || beta.len() != groups
4147        || log_decay.len() != if vector_decay { key_values } else { groups }
4148        || initial_state.is_some_and(|state| state.len() != state_values)
4149    {
4150        return Err(Error::backend("invalid gated-delta reference geometry"));
4151    }
4152    let mut state = initial_state.map_or_else(|| vec![0.0; state_values], <[f32]>::to_vec);
4153    let mut output = vec![0.0; values];
4154    for batch_index in 0..batch {
4155        for token in 0..sequence {
4156            for head in 0..heads {
4157                let group = (batch_index * sequence + token) * heads + head;
4158                let state_group = (batch_index * heads + head) * key_dim * value_dim;
4159                for value_index in 0..value_dim {
4160                    let mut memory = 0.0f32;
4161                    for key_index in 0..key_dim {
4162                        let vector_index = group * key_dim + key_index;
4163                        let decay = if vector_decay {
4164                            log_decay[vector_index]
4165                        } else {
4166                            log_decay[group]
4167                        }
4168                        .exp();
4169                        let state_index = state_group + key_index * value_dim + value_index;
4170                        state[state_index] *= decay;
4171                        memory += state[state_index] * key[vector_index];
4172                    }
4173                    let value_index_flat = group * value_dim + value_index;
4174                    let delta = (value[value_index_flat] - memory) * beta[group];
4175                    let mut accumulated = 0.0f32;
4176                    for key_index in 0..key_dim {
4177                        let vector_index = group * key_dim + key_index;
4178                        let state_index = state_group + key_index * value_dim + value_index;
4179                        state[state_index] += key[vector_index] * delta;
4180                        accumulated += state[state_index] * query[vector_index];
4181                    }
4182                    output[value_index_flat] = accumulated;
4183                }
4184            }
4185        }
4186    }
4187    Ok((state, output))
4188}
4189
4190#[cfg(test)]
4191mod gated_delta_reference_tests {
4192    use super::reference_gated_delta_scan;
4193
4194    #[test]
4195    fn chunked_continuation_matches_one_scan() {
4196        let query = [0.5, -0.25, 0.1, 0.2, -0.4, 0.8];
4197        let key = [0.3, 0.4, -0.2, 0.7, 0.6, -0.1];
4198        let value = [1.0, -0.5, 0.25, 0.75, -0.3, 0.9];
4199        let decay = [-0.2, -0.1, -0.4, -0.3, -0.5, -0.25];
4200        let beta = [0.8, 0.6, 0.4];
4201        let (expected_state, expected) = reference_gated_delta_scan(
4202            1, 3, 1, 2, 2, &query, &key, &value, &decay, true, &beta, None,
4203        )
4204        .unwrap();
4205        let (state, mut actual) = reference_gated_delta_scan(
4206            1,
4207            2,
4208            1,
4209            2,
4210            2,
4211            &query[..4],
4212            &key[..4],
4213            &value[..4],
4214            &decay[..4],
4215            true,
4216            &beta[..2],
4217            None,
4218        )
4219        .unwrap();
4220        let (actual_state, tail) = reference_gated_delta_scan(
4221            1,
4222            1,
4223            1,
4224            2,
4225            2,
4226            &query[4..],
4227            &key[4..],
4228            &value[4..],
4229            &decay[4..],
4230            true,
4231            &beta[2..],
4232            Some(&state),
4233        )
4234        .unwrap();
4235        actual.extend(tail);
4236        assert!(expected
4237            .iter()
4238            .zip(actual)
4239            .all(|(left, right)| (left - right).abs() < 1e-6));
4240        assert!(expected_state
4241            .iter()
4242            .zip(actual_state)
4243            .all(|(left, right)| (left - right).abs() < 1e-6));
4244    }
4245}
4246
4247#[cfg(test)]
4248mod selective_state_space_reference_tests {
4249    use super::reference_selective_state_space_scan;
4250
4251    #[test]
4252    fn continuation_matches_one_scan() {
4253        let values = [0.2, -0.4, 0.8, 0.5, -0.3, 0.7];
4254        let input_state = [0.1, 0.3, -0.2, 0.4, 0.6, -0.5];
4255        let output_state = [0.7, -0.1, 0.2, 0.5, -0.4, 0.9];
4256        let time_step = [-0.3, 0.1, -0.2];
4257        let bias = [0.05];
4258        let transition = [-0.4];
4259        let skip = [0.25];
4260        let (expected_state, expected) = reference_selective_state_space_scan(
4261            1,
4262            3,
4263            1,
4264            2,
4265            2,
4266            &values,
4267            &input_state,
4268            &output_state,
4269            &time_step,
4270            &bias,
4271            &transition,
4272            &skip,
4273            0.001,
4274            None,
4275        )
4276        .unwrap();
4277        let (state, mut actual) = reference_selective_state_space_scan(
4278            1,
4279            2,
4280            1,
4281            2,
4282            2,
4283            &values[..4],
4284            &input_state[..4],
4285            &output_state[..4],
4286            &time_step[..2],
4287            &bias,
4288            &transition,
4289            &skip,
4290            0.001,
4291            None,
4292        )
4293        .unwrap();
4294        let (actual_state, tail) = reference_selective_state_space_scan(
4295            1,
4296            1,
4297            1,
4298            2,
4299            2,
4300            &values[4..],
4301            &input_state[4..],
4302            &output_state[4..],
4303            &time_step[2..],
4304            &bias,
4305            &transition,
4306            &skip,
4307            0.001,
4308            Some(&state),
4309        )
4310        .unwrap();
4311        actual.extend(tail);
4312        assert!(expected
4313            .iter()
4314            .zip(actual)
4315            .all(|(left, right)| (left - right).abs() < 1e-6));
4316        assert!(expected_state
4317            .iter()
4318            .zip(actual_state)
4319            .all(|(left, right)| (left - right).abs() < 1e-6));
4320    }
4321}
4322
4323/// Opaque tensor handle and the neural operations required by shared Eredu
4324/// architectures.
4325///
4326/// Implementations must preserve backend-native execution semantics. None of
4327/// these operations imply host materialization or synchronization.
4328pub trait Tensor: Clone + Debug + Sized + 'static {
4329    /// Backend execution context, such as a stream or command queue.
4330    type Context: ?Sized;
4331
4332    /// Logical tensor shape maintained without materializing tensor values.
4333    fn shape(&self) -> &[i32];
4334
4335    /// Returns one logical dimension.
4336    fn dim(&self, axis: usize) -> i32 {
4337        self.shape()[axis]
4338    }
4339
4340    /// Allocates an unloaded floating-point parameter tensor.
4341    fn unloaded_f32(shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
4342    /// Allocates an unloaded signed 32-bit integer parameter tensor.
4343    fn unloaded_i32(shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4344        let _ = (shape, context);
4345        Err(Error::backend(
4346            "I32 parameter allocation is not implemented by this backend",
4347        ))
4348    }
4349    /// Creates a floating-point tensor from host initialization data.
4350    fn from_f32_slice(
4351        values: &[f32],
4352        shape: &[i32],
4353        context: &Self::Context,
4354    ) -> Result<Self, Error>;
4355    /// Creates a signed 32-bit integer tensor from host initialization data.
4356    fn from_i32_slice(
4357        values: &[i32],
4358        shape: &[i32],
4359        context: &Self::Context,
4360    ) -> Result<Self, Error> {
4361        let _ = (values, shape, context);
4362        Err(Error::backend(
4363            "I32 tensor construction is not implemented by this backend",
4364        ))
4365    }
4366    /// Explicitly materializes floating-point tensor values on the host.
4367    fn to_f32_vec(&self, context: &Self::Context) -> Result<Vec<f32>, Error> {
4368        let _ = context;
4369        Err(Error::backend(
4370            "F32 host materialization is not implemented by this backend",
4371        ))
4372    }
4373    /// Explicitly materializes signed 32-bit integer tensor values on the host.
4374    fn to_i32_vec(&self, context: &Self::Context) -> Result<Vec<i32>, Error> {
4375        let _ = context;
4376        Err(Error::backend(
4377            "I32 host materialization is not implemented by this backend",
4378        ))
4379    }
4380    /// Creates a floating-point tensor filled with one scalar.
4381    fn full_f32(value: f32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4382        let _ = (value, shape, context);
4383        Err(Error::backend(
4384            "filled tensor construction is not implemented by this backend",
4385        ))
4386    }
4387    /// Creates a signed 32-bit integer tensor filled with one scalar.
4388    fn full_i32(value: i32, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4389        let _ = (value, shape, context);
4390        Err(Error::backend(
4391            "filled I32 tensor construction is not implemented by this backend",
4392        ))
4393    }
4394    /// Elementwise addition.
4395    fn add(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4396    /// Elementwise subtraction.
4397    fn subtract(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4398    /// Elementwise multiplication.
4399    fn multiply(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4400    /// Elementwise multiplication by a floating-point scalar.
4401    fn multiply_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
4402    /// Elementwise division.
4403    fn divide(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4404    /// Elementwise square.
4405    fn square(&self, context: &Self::Context) -> Result<Self, Error>;
4406    /// Elementwise hyperbolic tangent.
4407    fn tanh(&self, context: &Self::Context) -> Result<Self, Error> {
4408        let _ = context;
4409        Err(Error::backend(
4410            "tanh is not implemented by this tensor backend",
4411        ))
4412    }
4413    /// Elementwise maximum with a scalar.
4414    fn maximum_scalar(&self, rhs: f32, context: &Self::Context) -> Result<Self, Error>;
4415    /// Elementwise maximum with one signed integer while preserving an
4416    /// integral input representation.
4417    fn maximum_i32(&self, rhs: i32, context: &Self::Context) -> Result<Self, Error> {
4418        self.maximum_scalar(rhs as f32, context)
4419    }
4420    /// Elementwise clamp using backend tensor bounds that may be scalar or broadcastable.
4421    fn clip(&self, minimum: &Self, maximum: &Self, context: &Self::Context) -> Result<Self, Error> {
4422        let _ = (minimum, maximum, context);
4423        Err(Error::backend(
4424            "clip is not implemented by this tensor backend",
4425        ))
4426    }
4427
4428    /// Softmax over one axis.
4429    fn softmax_axis(
4430        &self,
4431        axis: i32,
4432        precise: bool,
4433        context: &Self::Context,
4434    ) -> Result<Self, Error> {
4435        let _ = (axis, precise, context);
4436        Err(Error::backend(
4437            "softmax is not implemented by this tensor backend",
4438        ))
4439    }
4440
4441    /// Reshapes without changing logical element order.
4442    fn reshape(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error>;
4443    /// Broadcasts to a compatible target shape.
4444    fn broadcast_to(&self, shape: &[i32], context: &Self::Context) -> Result<Self, Error> {
4445        let _ = (shape, context);
4446        Err(Error::backend(
4447            "broadcasting is not implemented by this tensor backend",
4448        ))
4449    }
4450    /// Permutes axes.
4451    fn transpose_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
4452    /// Swaps two axes.
4453    fn swap_axes(&self, left: i32, right: i32, context: &Self::Context) -> Result<Self, Error>;
4454    /// Reverses the axes of a rank-two tensor.
4455    fn transpose(&self, context: &Self::Context) -> Result<Self, Error>;
4456    /// Inserts one unit dimension.
4457    fn expand_dims(&self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
4458    /// Removes unit dimensions.
4459    fn squeeze_axes(&self, axes: &[i32], context: &Self::Context) -> Result<Self, Error>;
4460    /// Creates a tensor view using backend-neutral axis indexes.
4461    fn index(&self, indexes: &[Index], context: &Self::Context) -> Result<Self, Error>;
4462    /// Takes rows along one axis using a backend index tensor.
4463    fn take_axis(&self, indexes: &Self, axis: i32, context: &Self::Context) -> Result<Self, Error>;
4464    /// Creates a zero tensor with the same shape and physical dtype.
4465    fn zeros_like(&self, context: &Self::Context) -> Result<Self, Error> {
4466        let _ = context;
4467        Err(Error::backend(
4468            "dtype-preserving zero allocation is not implemented by this tensor backend",
4469        ))
4470    }
4471    /// Compares every element with one signed integer scalar.
4472    fn equal_i32(&self, value: i32, context: &Self::Context) -> Result<Self, Error> {
4473        let _ = (value, context);
4474        Err(Error::backend(
4475            "integer scalar comparison is not implemented by this tensor backend",
4476        ))
4477    }
4478    /// Elementwise logical disjunction over two boolean tensors.
4479    fn logical_or(&self, rhs: &Self, context: &Self::Context) -> Result<Self, Error> {
4480        let _ = (rhs, context);
4481        Err(Error::backend(
4482            "logical disjunction is not implemented by this tensor backend",
4483        ))
4484    }
4485    /// Selects elements from two broadcast-compatible tensors using a boolean condition.
4486    fn where_condition(
4487        condition: &Self,
4488        when_true: &Self,
4489        when_false: &Self,
4490        context: &Self::Context,
4491    ) -> Result<Self, Error> {
4492        let _ = (condition, when_true, when_false, context);
4493        Err(Error::backend(
4494            "conditional selection is not implemented by this tensor backend",
4495        ))
4496    }
4497    /// Scatters source rows into the true entries of a boolean mask.
4498    fn masked_scatter(
4499        &self,
4500        mask: &Self,
4501        source: &Self,
4502        context: &Self::Context,
4503    ) -> Result<Self, Error> {
4504        let _ = (mask, source, context);
4505        Err(Error::backend(
4506            "masked scatter is not implemented by this tensor backend",
4507        ))
4508    }
4509
4510    /// Applies rotary positions using caller-supplied reciprocal frequencies.
4511    fn rope_with_frequencies(
4512        &self,
4513        dimensions: i32,
4514        traditional: bool,
4515        offset: i32,
4516        frequencies: &Self,
4517        context: &Self::Context,
4518    ) -> Result<Self, Error> {
4519        let _ = (dimensions, traditional, offset, frequencies, context);
4520        Err(Error::backend(
4521            "explicit-frequency rotary positions are not implemented by this tensor backend",
4522        ))
4523    }
4524
4525    /// Concatenates tensors along an axis.
4526    fn concatenate(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
4527    /// Stacks tensors along a new axis.
4528    fn stack(values: &[Self], axis: i32, context: &Self::Context) -> Result<Self, Error>;
4529    /// Matrix multiplication.
4530    fn matmul(lhs: &Self, rhs: &Self, context: &Self::Context) -> Result<Self, Error>;
4531    /// Reduction sum.
4532    fn sum_axis(
4533        value: &Self,
4534        axis: i32,
4535        keep_dims: bool,
4536        context: &Self::Context,
4537    ) -> Result<Self, Error>;
4538    /// Reduction mean.
4539    fn mean_axis(
4540        value: &Self,
4541        axis: i32,
4542        keep_dims: bool,
4543        context: &Self::Context,
4544    ) -> Result<Self, Error> {
4545        let width = value
4546            .shape()
4547            .get(if axis < 0 {
4548                usize::try_from(value.shape().len() as i32 + axis).unwrap_or(usize::MAX)
4549            } else {
4550                usize::try_from(axis).unwrap_or(usize::MAX)
4551            })
4552            .copied()
4553            .ok_or_else(|| Error::backend(format!("mean axis {axis} is out of range")))?;
4554        Self::sum_axis(value, axis, keep_dims, context)?
4555            .multiply_scalar(1.0 / width as f32, context)
4556    }
4557    /// Reduction argmin.
4558    fn argmin_axis(
4559        value: &Self,
4560        axis: i32,
4561        keep_dims: bool,
4562        context: &Self::Context,
4563    ) -> Result<Self, Error>;
4564    /// Pads a tensor.
4565    fn pad(
4566        value: &Self,
4567        widths: &[(i32, i32)],
4568        mode: PadMode,
4569        context: &Self::Context,
4570    ) -> Result<Self, Error>;
4571
4572    /// One-dimensional convolution.
4573    #[allow(clippy::too_many_arguments)]
4574    fn conv1d(
4575        input: &Self,
4576        weight: &Self,
4577        stride: i32,
4578        padding: i32,
4579        dilation: i32,
4580        groups: i32,
4581        context: &Self::Context,
4582    ) -> Result<Self, Error>;
4583    /// Two-dimensional convolution over canonical NHWC inputs and OHWI weights.
4584    #[allow(clippy::too_many_arguments)]
4585    fn conv2d(
4586        input: &Self,
4587        weight: &Self,
4588        stride: (i32, i32),
4589        padding: (i32, i32),
4590        dilation: (i32, i32),
4591        groups: i32,
4592        context: &Self::Context,
4593    ) -> Result<Self, Error> {
4594        let _ = (input, weight, stride, padding, dilation, groups, context);
4595        Err(Error::backend(
4596            "two-dimensional convolution is not implemented by this backend",
4597        ))
4598    }
4599    /// One-dimensional transposed convolution.
4600    #[allow(clippy::too_many_arguments)]
4601    fn conv_transpose1d(
4602        input: &Self,
4603        weight: &Self,
4604        stride: i32,
4605        padding: i32,
4606        dilation: i32,
4607        output_padding: i32,
4608        groups: i32,
4609        context: &Self::Context,
4610    ) -> Result<Self, Error>;
4611    /// Affine linear projection. Backends may use fused or quantized paths.
4612    fn linear(
4613        input: &Self,
4614        weight: &Self,
4615        bias: Option<&Self>,
4616        context: &Self::Context,
4617    ) -> Result<Self, Error>;
4618    /// Layer normalization.
4619    fn layer_norm(
4620        input: &Self,
4621        weight: Option<&Self>,
4622        bias: Option<&Self>,
4623        epsilon: f32,
4624        context: &Self::Context,
4625    ) -> Result<Self, Error>;
4626    /// Gaussian error linear unit.
4627    fn gelu(input: &Self, context: &Self::Context) -> Result<Self, Error>;
4628    /// Exponential linear unit.
4629    fn elu(input: &Self, alpha: f32, context: &Self::Context) -> Result<Self, Error>;
4630    /// Rotary positional encoding.
4631    #[allow(clippy::too_many_arguments)]
4632    fn rope(
4633        input: &Self,
4634        dimensions: i32,
4635        traditional: bool,
4636        base: f32,
4637        scale: f32,
4638        offset: i32,
4639        context: &Self::Context,
4640    ) -> Result<Self, Error>;
4641    /// Builds cosine and sine tensors for explicit multi-axis positions.
4642    fn multi_axis_rotary_embeddings(
4643        position_ids: &Self,
4644        spec: &multimodal::MultiAxisRotarySpec,
4645        context: &Self::Context,
4646    ) -> Result<(Self, Self), Error> {
4647        let _ = (position_ids, spec, context);
4648        Err(Error::backend(
4649            "multi-axis rotary embeddings are not implemented by this backend",
4650        ))
4651    }
4652    /// Projects selected output rows and scatters them into vocabulary order.
4653    fn masked_output_projection(
4654        input: multimodal::MaskedOutputProjectionInput<'_, Self>,
4655        context: &Self::Context,
4656    ) -> Result<Self, Error> {
4657        let _ = (input, context);
4658        Err(Error::backend(
4659            "masked output projection is not implemented by this backend",
4660        ))
4661    }
4662    /// Scaled dot-product attention. Backends retain control over fusion.
4663    fn scaled_dot_product_attention(
4664        queries: &Self,
4665        keys: &Self,
4666        values: &Self,
4667        scale: f32,
4668        mask: AttentionMask<'_, Self>,
4669        context: &Self::Context,
4670    ) -> Result<Self, Error>;
4671}
4672
4673/// One trainable parameter owned by a generic architecture module.
4674#[derive(Debug, Clone)]
4675pub struct Parameter<T> {
4676    spec: ParameterSpec,
4677    trainable: bool,
4678    value: T,
4679}
4680
4681impl<T> Parameter<T> {
4682    /// Wraps a backend tensor as an authoritative parameter slot.
4683    pub fn new(spec: ParameterSpec, value: T) -> Self {
4684        let trainable = spec.trainable;
4685        Self {
4686            spec,
4687            trainable,
4688            value,
4689        }
4690    }
4691    /// Borrows the backend tensor.
4692    pub const fn as_ref(&self) -> &T {
4693        &self.value
4694    }
4695    /// Replaces the backend tensor.
4696    pub fn replace(&mut self, value: T) {
4697        self.value = value;
4698    }
4699}
4700
4701impl<T: Tensor> Parameter<T> {
4702    /// Creates an unloaded floating-point parameter.
4703    pub fn unloaded(
4704        spec: ParameterSpec,
4705        shape: &[i32],
4706        context: &T::Context,
4707    ) -> Result<Self, Error> {
4708        Ok(Self::new(spec, T::unloaded_f32(shape, context)?))
4709    }
4710
4711    /// Creates an unloaded signed 32-bit integer parameter.
4712    pub fn unloaded_i32(
4713        spec: ParameterSpec,
4714        shape: &[i32],
4715        context: &T::Context,
4716    ) -> Result<Self, Error> {
4717        Ok(Self::new(spec, T::unloaded_i32(shape, context)?))
4718    }
4719}
4720
4721impl<T: 'static> Parameterized<T> for Parameter<T> {
4722    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4723    where
4724        V: ParameterVisitor<'a, T>,
4725    {
4726        visitor.visit(
4727            ParameterMetadata::from_spec(&self.spec, self.trainable),
4728            &self.value,
4729        );
4730    }
4731
4732    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4733    where
4734        V: ParameterVisitorMut<'a, T>,
4735    {
4736        visitor.visit_mut(
4737            ParameterMetadata::from_spec(&self.spec, self.trainable),
4738            &mut self.value,
4739        );
4740    }
4741
4742    fn set_trainable(&mut self, trainable: bool) {
4743        self.trainable = trainable;
4744    }
4745}
4746
4747impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Vec<M> {
4748    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4749    where
4750        V: ParameterVisitor<'a, T>,
4751    {
4752        for module in self {
4753            module.visit_parameters(visitor);
4754        }
4755    }
4756
4757    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4758    where
4759        V: ParameterVisitorMut<'a, T>,
4760    {
4761        for module in self {
4762            module.visit_parameters_mut(visitor);
4763        }
4764    }
4765
4766    fn set_trainable(&mut self, trainable: bool) {
4767        for module in self {
4768            module.set_trainable(trainable);
4769        }
4770    }
4771}
4772
4773impl<T: 'static, M: Parameterized<T>> Parameterized<T> for Option<M> {
4774    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
4775    where
4776        V: ParameterVisitor<'a, T>,
4777    {
4778        if let Some(module) = self {
4779            module.visit_parameters(visitor);
4780        }
4781    }
4782
4783    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
4784    where
4785        V: ParameterVisitorMut<'a, T>,
4786    {
4787        if let Some(module) = self {
4788            module.visit_parameters_mut(visitor);
4789        }
4790    }
4791
4792    fn set_trainable(&mut self, trainable: bool) {
4793        if let Some(module) = self {
4794            module.set_trainable(trainable);
4795        }
4796    }
4797}
4798
4799/// Post-convolution activation selected by architecture policy.
4800#[derive(Debug, Clone, Copy, Eq, PartialEq)]
4801pub enum ConvolutionActivation {
4802    /// Preserve the affine convolution output.
4803    Identity,
4804    /// Apply the sigmoid linear unit.
4805    Silu,
4806}
4807
4808/// Geometry and parameter identity for a causal depthwise convolution.
4809#[derive(Debug, Clone)]
4810pub struct CausalDepthwiseConvolutionSpec {
4811    /// Number of independent channels.
4812    pub channels: i32,
4813    /// Causal kernel width, including the current token.
4814    pub kernel_size: i32,
4815    /// Checkpoint-facing kernel stored as `[channels, 1, kernel]`.
4816    pub weight: ParameterSpec,
4817    /// Optional per-channel affine bias.
4818    pub bias: Option<ParameterSpec>,
4819    /// Activation applied after convolution and bias.
4820    pub activation: ConvolutionActivation,
4821}
4822
4823impl CausalDepthwiseConvolutionSpec {
4824    /// Validates positive geometry before allocating backend parameters.
4825    pub fn validate(&self) -> Result<(), Error> {
4826        if self.channels <= 0 {
4827            return Err(Error::backend(format!(
4828                "causal depthwise convolution channels must be positive, got {}",
4829                self.channels
4830            )));
4831        }
4832        if self.kernel_size <= 0 {
4833            return Err(Error::backend(format!(
4834                "causal depthwise convolution kernel size must be positive, got {}",
4835                self.kernel_size
4836            )));
4837        }
4838        Ok(())
4839    }
4840}
4841
4842/// Output and exact bounded history produced by one causal convolution call.
4843#[derive(Debug, Clone)]
4844pub struct CausalDepthwiseConvolutionOutput<T> {
4845    /// Activated convolution output shaped like the input.
4846    pub output: T,
4847    /// Last `kernel_size - 1` inputs, or `None` for a width-one kernel.
4848    pub history: Option<T>,
4849}
4850
4851/// Backend-neutral causal depthwise convolution.
4852///
4853/// The layer owns only parameters and equations. Callers keep the returned
4854/// bounded history in their runtime state realization.
4855#[derive(Debug, Clone, Parameterized)]
4856#[parameterized(tensor = "B::Tensor")]
4857pub struct CausalDepthwiseConvolution<B: NeuralBackend> {
4858    /// Checkpoint-layout kernel shaped `[channels, 1, kernel]`.
4859    pub weight: Parameter<B::Tensor>,
4860    /// Optional per-channel bias.
4861    pub bias: Option<Parameter<B::Tensor>>,
4862    #[parameter(skip)]
4863    channels: i32,
4864    #[parameter(skip)]
4865    kernel_size: i32,
4866    #[parameter(skip)]
4867    activation: ConvolutionActivation,
4868}
4869
4870impl<B: NeuralBackend> CausalDepthwiseConvolution<B> {
4871    /// Creates unloaded convolution parameters.
4872    pub fn new(
4873        spec: CausalDepthwiseConvolutionSpec,
4874        context: &<B::Tensor as Tensor>::Context,
4875    ) -> Result<Self, Error> {
4876        spec.validate()?;
4877        Ok(Self {
4878            weight: Parameter::unloaded(
4879                spec.weight,
4880                &[spec.channels, 1, spec.kernel_size],
4881                context,
4882            )?,
4883            bias: spec
4884                .bias
4885                .map(|bias| Parameter::unloaded(bias, &[spec.channels], context))
4886                .transpose()?,
4887            channels: spec.channels,
4888            kernel_size: spec.kernel_size,
4889            activation: spec.activation,
4890        })
4891    }
4892
4893    /// Returns the exact retained causal-history length.
4894    pub const fn history_len(&self) -> i32 {
4895        self.kernel_size - 1
4896    }
4897
4898    /// Applies the convolution and returns the replacement bounded history.
4899    pub fn forward(
4900        &self,
4901        input: &B::Tensor,
4902        history: Option<&B::Tensor>,
4903        context: &<B::Tensor as Tensor>::Context,
4904    ) -> Result<CausalDepthwiseConvolutionOutput<B::Tensor>, Error> {
4905        let shape = input.shape();
4906        if shape.len() != 3 || shape[0] <= 0 || shape[1] <= 0 || shape[2] != self.channels {
4907            return Err(Error::backend(format!(
4908                "causal depthwise convolution expects [batch, sequence, {}], got {shape:?}",
4909                self.channels
4910            )));
4911        }
4912        let history_len = self.history_len();
4913        let padded = if history_len == 0 {
4914            if history.is_some() {
4915                return Err(Error::backend(
4916                    "width-one causal convolution does not accept history",
4917                ));
4918            }
4919            input.clone()
4920        } else if let Some(history) = history {
4921            let expected = [shape[0], history_len, self.channels];
4922            if history.shape() != expected {
4923                return Err(Error::backend(format!(
4924                    "causal depthwise convolution history must have shape {expected:?}, got {:?}",
4925                    history.shape()
4926                )));
4927            }
4928            B::Tensor::concatenate(&[history.clone(), input.clone()], 1, context)?
4929        } else {
4930            B::Tensor::pad(
4931                input,
4932                &[(0, 0), (history_len, 0), (0, 0)],
4933                PadMode::Constant,
4934                context,
4935            )?
4936        };
4937        let execution_weight = self.weight.as_ref().swap_axes(1, 2, context)?;
4938        let mut output =
4939            B::Tensor::conv1d(&padded, &execution_weight, 1, 0, 1, self.channels, context)?;
4940        if output.shape() != shape {
4941            return Err(Error::backend(format!(
4942                "causal depthwise convolution backend returned shape {:?}, expected {shape:?}",
4943                output.shape()
4944            )));
4945        }
4946        if let Some(bias) = &self.bias {
4947            let bias = bias
4948                .as_ref()
4949                .reshape(&[1, 1, self.channels], context)?
4950                .broadcast_to(shape, context)?;
4951            output = output.add(&bias, context)?;
4952        }
4953        if self.activation == ConvolutionActivation::Silu {
4954            output = B::silu(output, context)?;
4955        }
4956        let history = (history_len > 0)
4957            .then(|| {
4958                padded.index(
4959                    &[
4960                        Index::Full,
4961                        Index::Range(shape[1], shape[1] + history_len),
4962                        Index::Full,
4963                    ],
4964                    context,
4965                )
4966            })
4967            .transpose()?;
4968        Ok(CausalDepthwiseConvolutionOutput { output, history })
4969    }
4970}
4971
4972/// Construction policy for a gated causal short convolution.
4973#[derive(Debug, Clone)]
4974pub struct GatedShortConvolutionSpec {
4975    /// Hidden width accepted by the fused input projection.
4976    pub input_dimensions: i32,
4977    /// Rank-local convolution channel count.
4978    pub channels: i32,
4979    /// Hidden width returned by the output projection.
4980    pub output_dimensions: i32,
4981    /// Fused B/C/x projection with output width `3 * channels`.
4982    pub input_projection: LinearSpec,
4983    /// Projection from convolution channels to the output width.
4984    pub output_projection: LinearSpec,
4985    /// Shared causal depthwise convolution parameters.
4986    pub convolution: CausalDepthwiseConvolutionSpec,
4987}
4988
4989impl GatedShortConvolutionSpec {
4990    /// Validates the fused segment and convolution geometry.
4991    pub fn validate(&self) -> Result<(), Error> {
4992        self.convolution.validate()?;
4993        let fused = self
4994            .channels
4995            .checked_mul(3)
4996            .ok_or_else(|| Error::backend("gated short-convolution width overflowed"))?;
4997        if self.input_dimensions <= 0
4998            || self.channels <= 0
4999            || self.output_dimensions <= 0
5000            || self.convolution.channels != self.channels
5001            || self.input_projection.input != self.input_dimensions
5002            || self.input_projection.output != fused
5003            || self.output_projection.input != self.channels
5004            || self.output_projection.output != self.output_dimensions
5005        {
5006            return Err(Error::backend(format!(
5007                "invalid gated short-convolution geometry input={} channels={} output={} fused_projection={}x{} output_projection={}x{} convolution_channels={}",
5008                self.input_dimensions,
5009                self.channels,
5010                self.output_dimensions,
5011                self.input_projection.input,
5012                self.input_projection.output,
5013                self.output_projection.input,
5014                self.output_projection.output,
5015                self.convolution.channels,
5016            )));
5017        }
5018        Ok(())
5019    }
5020}
5021
5022/// Output and replacement bounded state from a gated short convolution.
5023#[derive(Debug, Clone)]
5024pub struct GatedShortConvolutionOutput<T> {
5025    /// Projected hidden states.
5026    pub output: T,
5027    /// Last causal input values retained by the depthwise convolution.
5028    pub history: Option<T>,
5029}
5030
5031/// Fused gated short-convolution layer shared by hybrid decoders.
5032#[derive(Debug, Clone, Parameterized)]
5033#[parameterized(tensor = "B::Tensor")]
5034pub struct GatedShortConvolution<B: NeuralBackend> {
5035    /// Fused B/C/x input projection.
5036    pub input_projection: B::Linear,
5037    /// Shared causal depthwise convolution.
5038    pub convolution: CausalDepthwiseConvolution<B>,
5039    /// Output projection.
5040    pub output_projection: B::Linear,
5041    #[parameter(skip)]
5042    channels: i32,
5043}
5044
5045impl<B: NeuralBackend> GatedShortConvolution<B> {
5046    /// Builds one unloaded gated short convolution.
5047    pub fn new(
5048        spec: GatedShortConvolutionSpec,
5049        context: &<B::Tensor as Tensor>::Context,
5050    ) -> Result<Self, Error> {
5051        spec.validate()?;
5052        Ok(Self {
5053            input_projection: B::linear(spec.input_projection, context)?,
5054            convolution: CausalDepthwiseConvolution::new(spec.convolution, context)?,
5055            output_projection: B::linear(spec.output_projection, context)?,
5056            channels: spec.channels,
5057        })
5058    }
5059
5060    fn hidden(
5061        &mut self,
5062        input: &B::Tensor,
5063        history: Option<&B::Tensor>,
5064        context: &<B::Tensor as Tensor>::Context,
5065    ) -> Result<(B::Tensor, Option<B::Tensor>), Error> {
5066        let projected = self.input_projection.forward(input, context)?;
5067        let rank = projected.shape().len();
5068        if rank == 0 || projected.shape()[rank - 1] != 3 * self.channels {
5069            return Err(Error::backend(format!(
5070                "gated short-convolution projection returned shape {:?}, expected final width {}",
5071                projected.shape(),
5072                3 * self.channels
5073            )));
5074        }
5075        let mut segment = vec![Index::Full; rank];
5076        segment[rank - 1] = Index::Range(0, self.channels);
5077        let b = projected.index(&segment, context)?;
5078        segment[rank - 1] = Index::Range(self.channels, 2 * self.channels);
5079        let c = projected.index(&segment, context)?;
5080        segment[rank - 1] = Index::Range(2 * self.channels, 3 * self.channels);
5081        let x = projected.index(&segment, context)?;
5082        let convolution = self
5083            .convolution
5084            .forward(&b.multiply(&x, context)?, history, context)?;
5085        Ok((
5086            c.multiply(&convolution.output, context)?,
5087            convolution.history,
5088        ))
5089    }
5090
5091    /// Executes the replicated layer and returns replacement bounded state.
5092    pub fn forward(
5093        &mut self,
5094        input: &B::Tensor,
5095        history: Option<&B::Tensor>,
5096        context: &<B::Tensor as Tensor>::Context,
5097    ) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
5098        let (hidden, history) = self.hidden(input, history, context)?;
5099        Ok(GatedShortConvolutionOutput {
5100            output: self.output_projection.forward(&hidden, context)?,
5101            history,
5102        })
5103    }
5104
5105    /// Executes the same layer with a row-parallel output projection.
5106    pub fn forward_parallel(
5107        &mut self,
5108        input: &B::Tensor,
5109        history: Option<&B::Tensor>,
5110        parallel: &B::ParallelContext,
5111        context: &<B::Tensor as Tensor>::Context,
5112    ) -> Result<GatedShortConvolutionOutput<B::Tensor>, Error> {
5113        let (hidden, history) = self.hidden(input, history, context)?;
5114        Ok(GatedShortConvolutionOutput {
5115            output: B::row_parallel_linear(
5116                &mut self.output_projection,
5117                &hidden,
5118                parallel,
5119                context,
5120            )?,
5121            history,
5122        })
5123    }
5124}
5125
5126#[cfg(test)]
5127mod grouped_contract_tests {
5128    use super::*;
5129
5130    fn dense_format() -> LinearFormatSpec {
5131        LinearFormatSpec::unscaled(LinearFormat::Dense).unwrap()
5132    }
5133
5134    fn parameters(prefix: &str) -> GatedProductGroupParameters {
5135        let projection = |name| {
5136            GroupedProjectionSpec::new(
5137                ParameterSpec::trainable(name).unwrap(),
5138                None,
5139                dense_format(),
5140            )
5141            .unwrap()
5142        };
5143        GatedProductGroupParameters::new(
5144            projection(format!("{prefix}.gate.weight")),
5145            projection(format!("{prefix}.up.weight")),
5146            projection(format!("{prefix}.down.weight")),
5147        )
5148    }
5149
5150    #[test]
5151    fn top_k_selection_policy_rejects_invalid_counts() {
5152        assert!(TopKGroupSelectionSpec::new(8, 2, GroupScoring::Softmax, true).is_ok());
5153        assert!(TopKGroupSelectionSpec::new(0, 1, GroupScoring::Softmax, false).is_err());
5154        assert!(TopKGroupSelectionSpec::new(8, 9, GroupScoring::Softmax, false).is_err());
5155    }
5156
5157    #[test]
5158    fn gated_product_policy_rejects_malformed_scalars() {
5159        assert!(
5160            GatedProductPolicy::new(GatedProductActivation::Silu, Some(0.0), None, 1.0, 0.0,)
5161                .is_err()
5162        );
5163        assert!(GatedProductPolicy::new(
5164            GatedProductActivation::Silu,
5165            None,
5166            Some(f32::NAN),
5167            1.0,
5168            0.0,
5169        )
5170        .is_err());
5171        assert!(
5172            GatedProductPolicy::new(GatedProductActivation::Silu, None, None, 0.0, 0.0,).is_err()
5173        );
5174        assert!(GatedProductPolicy::new(
5175            GatedProductActivation::Silu,
5176            None,
5177            None,
5178            1.0,
5179            f32::INFINITY,
5180        )
5181        .is_err());
5182    }
5183
5184    #[test]
5185    fn selector_projection_and_correction_biases_require_distinct_identities() {
5186        let shared_bias = ParameterSpec::trainable("selector.bias").unwrap();
5187        let spec = TopKGroupSelectorSpec::new(
5188            4,
5189            ParameterSpec::trainable("selector.weight").unwrap(),
5190            dense_format(),
5191            TopKGroupSelectionSpec::new(2, 1, GroupScoring::SelectedSoftmax, false).unwrap(),
5192        )
5193        .unwrap()
5194        .with_bias(shared_bias.clone())
5195        .unwrap();
5196
5197        assert!(spec.with_correction_bias(shared_bias).is_err());
5198    }
5199
5200    #[test]
5201    fn independent_group_layout_requires_exact_cardinality() {
5202        assert!(GroupedGatedProductSpec::new(
5203            2,
5204            16,
5205            8,
5206            16,
5207            eredu_nn::GatedProductPolicy::ordinary_silu(),
5208            GatedProductGroupLayout::Independent(vec![parameters("e0"), parameters("e1")]),
5209        )
5210        .is_ok());
5211        assert!(GroupedGatedProductSpec::new(
5212            2,
5213            16,
5214            8,
5215            16,
5216            eredu_nn::GatedProductPolicy::ordinary_silu(),
5217            GatedProductGroupLayout::Independent(vec![parameters("e0")]),
5218        )
5219        .is_err());
5220    }
5221
5222    #[test]
5223    fn gated_product_bank_rejects_reused_projection_bias_identity() {
5224        let shared = ParameterSpec::trainable("groups.gate_up").unwrap();
5225        let gate_up = GroupedProjectionSpec::new(shared.clone(), Some(shared), dense_format());
5226        assert!(gate_up.is_err());
5227    }
5228
5229    #[test]
5230    fn quantized_group_projection_requires_explicit_companion_identities() {
5231        let format =
5232            LinearFormat::Affine(eredu_checkpoint::AffineQuantization::new(32, 4).unwrap());
5233        let projection = |format| {
5234            GroupedProjectionSpec::new(
5235                ParameterSpec::trainable("arbitrary.group.matrix").unwrap(),
5236                None,
5237                format,
5238            )
5239        };
5240        assert!(LinearFormatSpec::unscaled(format).is_err());
5241        assert!(projection(
5242            LinearFormatSpec::affine(
5243                format,
5244                ParameterSpec::trainable("unrelated.scale.identity").unwrap(),
5245                ParameterSpec::trainable("unrelated.affine.identity").unwrap(),
5246            )
5247            .unwrap()
5248        )
5249        .is_ok());
5250    }
5251}
5252
5253/// Generic affine projection.
5254#[derive(Debug, Clone)]
5255pub struct Linear<T> {
5256    /// Projection weights shaped `[output, input]`.
5257    pub weight: Parameter<T>,
5258    /// Optional output bias.
5259    pub bias: Option<Parameter<T>>,
5260}
5261
5262impl<T: Tensor> Linear<T> {
5263    /// Creates unloaded projection parameters.
5264    pub fn unloaded(spec: LinearSpec, context: &T::Context) -> Result<Self, Error> {
5265        Ok(Self {
5266            weight: Parameter::unloaded(spec.weight, &[spec.output, spec.input], context)?,
5267            bias: spec
5268                .bias
5269                .map(|bias| Parameter::unloaded(bias, &[spec.output], context))
5270                .transpose()?,
5271        })
5272    }
5273
5274    /// Applies the projection without materializing backend tensors.
5275    pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
5276        T::linear(
5277            input,
5278            self.weight.as_ref(),
5279            self.bias.as_ref().map(Parameter::as_ref),
5280            context,
5281        )
5282    }
5283}
5284
5285impl<T: 'static> Parameterized<T> for Linear<T> {
5286    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
5287    where
5288        V: ParameterVisitor<'a, T>,
5289    {
5290        self.weight.visit_parameters(visitor);
5291        if let Some(bias) = &self.bias {
5292            bias.visit_parameters(visitor);
5293        }
5294    }
5295
5296    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
5297    where
5298        V: ParameterVisitorMut<'a, T>,
5299    {
5300        self.weight.visit_parameters_mut(visitor);
5301        if let Some(bias) = &mut self.bias {
5302            bias.visit_parameters_mut(visitor);
5303        }
5304    }
5305
5306    fn set_trainable(&mut self, trainable: bool) {
5307        self.weight.set_trainable(trainable);
5308        if let Some(bias) = &mut self.bias {
5309            bias.set_trainable(trainable);
5310        }
5311    }
5312}
5313
5314/// Generic affine layer normalization.
5315#[derive(Debug, Clone)]
5316pub struct LayerNorm<T> {
5317    /// Numerical stability epsilon.
5318    pub epsilon: f32,
5319    /// Optional trainable scale.
5320    pub weight: Option<Parameter<T>>,
5321    /// Optional trainable bias.
5322    pub bias: Option<Parameter<T>>,
5323}
5324
5325impl<T: Tensor> LayerNorm<T> {
5326    /// Creates an unloaded affine layer normalization.
5327    pub fn unloaded(
5328        dimensions: i32,
5329        epsilon: f32,
5330        weight: Option<ParameterSpec>,
5331        bias: Option<ParameterSpec>,
5332        context: &T::Context,
5333    ) -> Result<Self, Error> {
5334        Ok(Self {
5335            epsilon,
5336            weight: weight
5337                .map(|weight| Parameter::unloaded(weight, &[dimensions], context))
5338                .transpose()?,
5339            bias: bias
5340                .map(|bias| Parameter::unloaded(bias, &[dimensions], context))
5341                .transpose()?,
5342        })
5343    }
5344
5345    /// Applies layer normalization through the selected backend.
5346    pub fn forward(&self, input: &T, context: &T::Context) -> Result<T, Error> {
5347        T::layer_norm(
5348            input,
5349            self.weight.as_ref().map(Parameter::as_ref),
5350            self.bias.as_ref().map(Parameter::as_ref),
5351            self.epsilon,
5352            context,
5353        )
5354    }
5355}
5356
5357impl<T: 'static> Parameterized<T> for LayerNorm<T> {
5358    fn visit_parameters<'a, V>(&'a self, visitor: &mut V)
5359    where
5360        V: ParameterVisitor<'a, T>,
5361    {
5362        if let Some(weight) = &self.weight {
5363            weight.visit_parameters(visitor);
5364        }
5365        if let Some(bias) = &self.bias {
5366            bias.visit_parameters(visitor);
5367        }
5368    }
5369
5370    fn visit_parameters_mut<'a, V>(&'a mut self, visitor: &mut V)
5371    where
5372        V: ParameterVisitorMut<'a, T>,
5373    {
5374        if let Some(weight) = &mut self.weight {
5375            weight.visit_parameters_mut(visitor);
5376        }
5377        if let Some(bias) = &mut self.bias {
5378            bias.visit_parameters_mut(visitor);
5379        }
5380    }
5381
5382    fn set_trainable(&mut self, trainable: bool) {
5383        if let Some(weight) = &mut self.weight {
5384            weight.set_trainable(trainable);
5385        }
5386        if let Some(bias) = &mut self.bias {
5387            bias.set_trainable(trainable);
5388        }
5389    }
5390}
5391
5392#[cfg(test)]
5393mod parameter_topology_tests {
5394    use super::*;
5395
5396    #[derive(Parameterized)]
5397    #[parameterized(tensor = "i32")]
5398    struct DerivedModule {
5399        first: Parameter<i32>,
5400        second: Option<Parameter<i32>>,
5401        #[parameter(skip)]
5402        label: &'static str,
5403    }
5404
5405    #[derive(Parameterized)]
5406    #[parameterized(tensor = "i32")]
5407    enum DerivedChoice {
5408        Present(Parameter<i32>),
5409        Empty,
5410    }
5411
5412    fn parameter(id: &str, value: i32) -> Parameter<i32> {
5413        Parameter::new(ParameterSpec::trainable(id).unwrap(), value)
5414    }
5415
5416    #[test]
5417    fn derive_recurses_through_structs_options_and_enums() {
5418        let mut module = DerivedModule {
5419            first: parameter("first.weight", 1),
5420            second: Some(parameter("second.weight", 2)),
5421            label: "not a parameter",
5422        };
5423        assert_eq!(module.label, "not a parameter");
5424        let metadata = validate_parameter_topology::<i32, _>(&module).unwrap();
5425        assert_eq!(
5426            metadata
5427                .iter()
5428                .map(|entry| entry.id.as_str())
5429                .collect::<Vec<_>>(),
5430            ["first.weight", "second.weight"]
5431        );
5432
5433        module.set_trainable(false);
5434        assert!(validate_parameter_topology::<i32, _>(&module)
5435            .unwrap()
5436            .iter()
5437            .all(|entry| !entry.trainable));
5438
5439        let choice = DerivedChoice::Present(parameter("choice.weight", 3));
5440        assert_eq!(
5441            validate_parameter_topology::<i32, _>(&choice).unwrap()[0]
5442                .id
5443                .as_str(),
5444            "choice.weight"
5445        );
5446        assert!(validate_parameter_topology::<i32, _>(&DerivedChoice::Empty)
5447            .unwrap()
5448            .is_empty());
5449    }
5450
5451    #[test]
5452    fn validation_rejects_duplicates_and_invalid_aliases() {
5453        let duplicate = vec![parameter("same.weight", 1), parameter("same.weight", 2)];
5454        assert!(matches!(
5455            validate_parameter_topology::<i32, _>(&duplicate),
5456            Err(ParameterTopologyError::DuplicateId(id)) if id.as_str() == "same.weight"
5457        ));
5458
5459        let alias = Parameter::new(
5460            ParameterSpec {
5461                id: ParameterId::new("alias.weight").unwrap(),
5462                trainable: true,
5463                alias_of: Some(ParameterId::new("missing.weight").unwrap()),
5464                group: None,
5465                linear_companion: None,
5466                linear_companion_of: None,
5467            },
5468            1,
5469        );
5470        assert!(matches!(
5471            validate_parameter_topology::<i32, _>(&alias),
5472            Err(ParameterTopologyError::MissingAliasDestination { .. })
5473        ));
5474    }
5475}
5476
5477#[cfg(test)]
5478mod fused_projection_layout_tests {
5479    use super::*;
5480
5481    #[test]
5482    fn component_major_layout_is_checked_and_stable() {
5483        let layout = FusedProjectionLayout::new([
5484            FusedProjectionSegment::new("query", 8).unwrap(),
5485            FusedProjectionSegment::new("key", 4).unwrap(),
5486            FusedProjectionSegment::new("value", 4).unwrap(),
5487        ])
5488        .unwrap();
5489        assert_eq!(layout.output_width(), 16);
5490        assert_eq!(
5491            layout
5492                .segments()
5493                .iter()
5494                .map(|segment| (segment.name(), segment.width()))
5495                .collect::<Vec<_>>(),
5496            [("query", 8), ("key", 4), ("value", 4)]
5497        );
5498        assert!(FusedProjectionLayout::new(Vec::new()).is_err());
5499        assert!(FusedProjectionLayout::new([
5500            FusedProjectionSegment::new("same", 1).unwrap(),
5501            FusedProjectionSegment::new("same", 1).unwrap(),
5502        ])
5503        .is_err());
5504        assert!(FusedProjectionSegment::new("", 1).is_err());
5505        assert!(FusedProjectionSegment::new("bad", 0).is_err());
5506    }
5507
5508    #[test]
5509    fn zero_sentinel_cannot_alias_an_embedding_row() {
5510        EmbeddingLookupPolicy::Strict.validate().unwrap();
5511        EmbeddingLookupPolicy::ZeroSentinel(-1).validate().unwrap();
5512        assert!(EmbeddingLookupPolicy::ZeroSentinel(0).validate().is_err());
5513    }
5514
5515    #[test]
5516    fn vocabulary_parallel_ownership_requires_exact_global_rows() {
5517        let range = VocabularyParallelRange {
5518            global_vocabulary: 5,
5519            local: 0..3,
5520        };
5521        range.validate_global_rows(5).unwrap();
5522        assert!(range.validate_global_rows(4).is_err());
5523        assert!(range.validate_global_rows(-1).is_err());
5524    }
5525}
5526
5527/// Generic rotary positional encoding configuration.
5528#[derive(Debug, Clone, Copy)]
5529pub struct Rope {
5530    dimensions: i32,
5531    traditional: bool,
5532    base: f32,
5533    scale: f32,
5534}
5535
5536impl Rope {
5537    /// Creates a rotary positional encoding.
5538    pub const fn new(dimensions: i32, traditional: bool, base: f32, scale: f32) -> Self {
5539        Self {
5540            dimensions,
5541            traditional,
5542            base,
5543            scale,
5544        }
5545    }
5546
5547    /// Applies rotary positional encoding through the selected backend.
5548    pub fn forward<T: Tensor>(
5549        &self,
5550        input: &T,
5551        offset: i32,
5552        context: &T::Context,
5553    ) -> Result<T, Error> {
5554        T::rope(
5555            input,
5556            self.dimensions,
5557            self.traditional,
5558            self.base,
5559            self.scale,
5560            offset,
5561            context,
5562        )
5563    }
5564}