use crate::model::contract::{ContractViolation, Rendered};
pub use crate::embeddings::tokenizer_guard::PostProcessorTemplate;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug)]
pub struct ContractMismatch {
feature: &'static str,
expected: String,
actual: String,
}
impl ContractMismatch {
#[inline(always)]
pub const fn new(feature: &'static str, expected: String, actual: String) -> Self {
Self {
feature,
expected,
actual,
}
}
#[inline(always)]
pub const fn feature(&self) -> &'static str {
self.feature
}
#[inline(always)]
pub fn expected(&self) -> &str {
&self.expected
}
#[inline(always)]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug)]
pub struct OutputShape {
got: Vec<usize>,
expected: Vec<usize>,
}
impl OutputShape {
#[inline(always)]
pub const fn new(got: Vec<usize>, expected: Vec<usize>) -> Self {
Self { got, expected }
}
#[inline(always)]
pub fn got(&self) -> &[usize] {
&self.got
}
#[inline(always)]
pub fn expected(&self) -> &[usize] {
&self.expected
}
}
#[derive(Debug)]
pub struct ImageDimensions {
width: usize,
height: usize,
}
impl ImageDimensions {
#[inline(always)]
pub const fn new(width: usize, height: usize) -> Self {
Self { width, height }
}
#[inline(always)]
pub const fn width(&self) -> usize {
self.width
}
#[inline(always)]
pub const fn height(&self) -> usize {
self.height
}
}
#[derive(Debug)]
pub struct ImageDataLength {
got: usize,
expected: usize,
}
impl ImageDataLength {
#[inline(always)]
pub const fn new(got: usize, expected: usize) -> Self {
Self { got, expected }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
}
#[derive(Debug)]
pub struct PosEmbedLength {
got: usize,
expected: usize,
}
impl PosEmbedLength {
#[inline(always)]
pub const fn new(got: usize, expected: usize) -> Self {
Self { got, expected }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
}
#[derive(Debug)]
pub struct PatchCount {
got: usize,
max: usize,
}
impl PatchCount {
#[inline(always)]
pub const fn new(got: usize, max: usize) -> Self {
Self { got, max }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn max(&self) -> usize {
self.max
}
}
#[derive(Debug)]
pub struct EmbeddingDimMismatch {
expected: usize,
got: usize,
}
impl EmbeddingDimMismatch {
#[inline(always)]
pub const fn new(expected: usize, got: usize) -> Self {
Self { expected, got }
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
}
#[derive(Debug, thiserror::Error)]
#[error("failed to read the artifact tokenizer `{path}`: {source}")]
pub struct ArtifactTokenizerRead {
path: std::path::PathBuf,
#[source]
source: std::io::Error,
}
impl ArtifactTokenizerRead {
#[inline(always)]
pub const fn new(path: std::path::PathBuf, source: std::io::Error) -> Self {
Self { path, source }
}
#[inline(always)]
pub fn path(&self) -> &std::path::Path {
&self.path
}
#[inline(always)]
pub const fn source(&self) -> &std::io::Error {
&self.source
}
}
#[derive(Debug)]
pub struct ArtifactTokenizerIdentity {
path: std::path::PathBuf,
expected: &'static str,
actual: String,
}
impl ArtifactTokenizerIdentity {
#[inline(always)]
pub const fn new(path: std::path::PathBuf, expected: &'static str, actual: String) -> Self {
Self {
path,
expected,
actual,
}
}
#[inline(always)]
pub fn path(&self) -> &std::path::Path {
&self.path
}
#[inline(always)]
pub const fn expected(&self) -> &'static str {
self.expected
}
#[inline(always)]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug)]
pub struct TokenCount {
got: usize,
max: usize,
}
impl TokenCount {
#[inline(always)]
pub const fn new(got: usize, max: usize) -> Self {
Self { got, max }
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn max(&self) -> usize {
self.max
}
}
#[derive(Debug)]
pub struct SpecialTokenOverhead {
added: usize,
window: usize,
}
impl SpecialTokenOverhead {
#[inline(always)]
pub const fn new(added: usize, window: usize) -> Self {
Self { added, window }
}
#[inline(always)]
pub const fn added(&self) -> usize {
self.added
}
#[inline(always)]
pub const fn window(&self) -> usize {
self.window
}
}
#[derive(Debug)]
pub struct PreprocessedLength {
feature: &'static str,
got: usize,
expected: usize,
}
impl PreprocessedLength {
#[inline(always)]
pub const fn new(feature: &'static str, got: usize, expected: usize) -> Self {
Self {
feature,
got,
expected,
}
}
#[inline(always)]
pub const fn feature(&self) -> &'static str {
self.feature
}
#[inline(always)]
pub const fn got(&self) -> usize {
self.got
}
#[inline(always)]
pub const fn expected(&self) -> usize {
self.expected
}
}
#[derive(Debug)]
pub struct PreprocessedNonFinite {
feature: &'static str,
index: usize,
}
impl PreprocessedNonFinite {
#[inline(always)]
pub const fn new(feature: &'static str, index: usize) -> Self {
Self { feature, index }
}
#[inline(always)]
pub const fn feature(&self) -> &'static str {
self.feature
}
#[inline(always)]
pub const fn index(&self) -> usize {
self.index
}
}
#[derive(Debug)]
pub struct PreprocessedMaskValue {
index: usize,
value: f32,
}
impl PreprocessedMaskValue {
#[inline(always)]
pub const fn new(index: usize, value: f32) -> Self {
Self { index, value }
}
#[inline(always)]
pub const fn index(&self) -> usize {
self.index
}
#[inline(always)]
pub const fn value(&self) -> f32 {
self.value
}
}
#[derive(Debug)]
pub struct PreprocessedPadNonZero {
feature: &'static str,
index: usize,
}
impl PreprocessedPadNonZero {
#[inline(always)]
pub const fn new(feature: &'static str, index: usize) -> Self {
Self { feature, index }
}
#[inline(always)]
pub const fn feature(&self) -> &'static str {
self.feature
}
#[inline(always)]
pub const fn index(&self) -> usize {
self.index
}
}
#[derive(Debug)]
pub struct PatchBudgetMismatch {
input: usize,
model: usize,
}
impl PatchBudgetMismatch {
#[inline(always)]
pub const fn new(input: usize, model: usize) -> Self {
Self { input, model }
}
#[inline(always)]
pub const fn input(&self) -> usize {
self.input
}
#[inline(always)]
pub const fn model(&self) -> usize {
self.model
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("failed to load model: {0}")]
Load(#[from] crate::LoadError),
#[error("prediction failed: {0}")]
Prediction(#[from] crate::PredictionError),
#[error("tensor failed: {0}")]
Tensor(#[from] crate::TensorError),
#[error("model contract mismatch on `{}`: expected {}, got {}", .0.feature(), .0.expected(), .0.actual())]
ContractMismatch(ContractMismatch),
#[error(
"model declares a required input `{0}` that this door never supplies, so \
every prediction would fail"
)]
UnsatisfiableInput(String),
#[error(
"model declares the state buffer `{0}`, and this door predicts through the \
stateless API; a stateful graph needs an `MLState` on every prediction"
)]
UnsatisfiableState(String),
#[error("output shape mismatch: expected {:?}, got {:?}", .0.expected(), .0.got())]
OutputShape(OutputShape),
#[error("model output contains a non-finite value at index {0}")]
NonFiniteOutput(usize),
#[error(
"invalid image dimensions: {}×{} (zero, over the per-axis maximum, or size overflow)",
.0.width(),
.0.height()
)]
ImageDimensions(ImageDimensions),
#[error("image data length mismatch: expected {} bytes (w·h·3), got {}", .0.expected(), .0.got())]
ImageDataLength(ImageDataLength),
#[error("failed to read position-embedding grid: {0}")]
PosEmbedLoad(#[source] std::io::Error),
#[error("position-embedding grid length mismatch: expected {} bytes, got {}", .0.expected(), .0.got())]
PosEmbedLength(PosEmbedLength),
#[error("preprocessing produced {} patches, exceeding the {}-patch budget", .0.got(), .0.max())]
PatchCount(PatchCount),
#[error("image preprocessing failed to allocate a {0}-byte resize buffer")]
PreprocessAllocation(usize),
#[error("text input is empty")]
EmptyText,
#[error("embedding dimension mismatch: expected {}, got {}", .0.expected(), .0.got())]
EmbeddingDimMismatch(EmbeddingDimMismatch),
#[error("embedding contains a non-finite value at component {0}")]
NonFiniteEmbedding(usize),
#[error("embedding has zero magnitude and cannot be normalized")]
EmbeddingZero,
#[error("embedding is not unit-norm: |norm² − 1| = {0}")]
EmbeddingNotUnitNorm(f32),
#[error("failed to load tokenizer: {0}")]
TokenizerLoad(#[source] tokenizers::Error),
#[error(transparent)]
ArtifactTokenizerRead(#[from] ArtifactTokenizerRead),
#[error(
"artifact tokenizer `{}` is not the pinned Gemma tokenizer: expected sha-256 {}, got {}",
.0.path().display(),
.0.expected(),
.0.actual()
)]
ArtifactTokenizerIdentity(ArtifactTokenizerIdentity),
#[error("tokenizer is the build-time placeholder; stage the real Gemma tokenizer.json")]
TokenizerPlaceholder,
#[error("failed to configure tokenizer: {0}")]
TokenizerConfig(#[source] tokenizers::Error),
#[error(
"tokenizer post-processor adds {} special tokens, leaving no room for text in the {}-token window",
.0.added(),
.0.window()
)]
SpecialTokenOverhead(SpecialTokenOverhead),
#[error("tokenizer post-processor is inconsistent: {0}")]
PostProcessorTemplate(PostProcessorTemplate),
#[error("failed to tokenize text: {0}")]
Tokenize(#[source] tokenizers::Error),
#[error("tokenized input has {} tokens, exceeding the fixed {}-token window", .0.got(), .0.max())]
TokenCount(TokenCount),
#[error("token id {0} exceeds the model's int32 input range")]
TokenIdRange(u32),
#[error("invalid preprocessed patch budget {0} (zero, or tensor lengths overflow)")]
PreprocessedPatchBudget(usize),
#[error(
"preprocessed `{}` length mismatch: expected {}, got {}",
.0.feature(),
.0.expected(),
.0.got()
)]
PreprocessedLength(PreprocessedLength),
#[error("preprocessed `{}` contains a non-finite value at index {}", .0.feature(), .0.index())]
PreprocessedNonFinite(PreprocessedNonFinite),
#[error("preprocessed attention mask entry {} is {}, not exactly 0.0 or 1.0", .0.index(), .0.value())]
PreprocessedMaskValue(PreprocessedMaskValue),
#[error("preprocessed attention mask has a real (1.0) entry at index {0} after a pad")]
PreprocessedMaskOrder(usize),
#[error("preprocessed attention mask has no real (1.0) entries")]
PreprocessedMaskEmpty,
#[error(
"preprocessed `{}` has a nonzero value at index {} inside a padded row",
.0.feature(),
.0.index()
)]
PreprocessedPadNonZero(PreprocessedPadNonZero),
#[error(
"preprocessed patch budget {} does not match the model's resolved budget {}",
.0.input(),
.0.model()
)]
PatchBudgetMismatch(PatchBudgetMismatch),
}
#[cfg(test)]
mod tests;
pub(crate) fn contract_violation(violation: ContractViolation) -> Error {
match violation.rendered() {
Rendered::UnsatisfiableInput(name) => Error::UnsatisfiableInput(name),
Rendered::UnsatisfiableState(name) => Error::UnsatisfiableState(name),
Rendered::Feature(feature) => Error::ContractMismatch(ContractMismatch::new(
feature.feature(),
feature.clone().expected(),
feature.actual(),
)),
}
}