use std::path::{Path, PathBuf};
use crate::{
CompileError, LoadError, PredictionError, TensorError,
model::contract::{ContractViolation, Rendered},
};
use super::{
artifacts::{ArtifactLabel, Part, tree::MAX_TREE_ENTRIES},
audio::AudioPrecision,
text::TextFunction,
vision::{VisionBudget, VisionBudgets},
};
pub use crate::embeddings::long_text::{
ContentlessInputOverBudget, InputTooLarge, WindowOverBudget,
};
pub use crate::embeddings::image::{ImageDataLength, ImageDimensions};
pub use windit::WinditError;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Artifact {
TextBundle,
AudioBundle(AudioPrecision),
VisionBundle,
TokenTable,
PositionTable,
TokenizerImage,
TokenizerSource,
Config,
Stamp,
}
impl Artifact {
#[inline]
pub const fn name(&self) -> &'static str {
match self {
Self::TextBundle => "EmbeddingGemma2Text",
Self::AudioBundle(precision) => precision.package_stem(),
Self::VisionBundle => "EmbeddingGemma2Vision",
Self::TokenTable => "embeddings.bf16",
Self::PositionTable => "position_embeddings.f16",
Self::TokenizerImage => "tokenizer.v<format>-<12 hex>.gemmabpe",
Self::TokenizerSource => "tokenizer.json",
Self::Config => "config.json",
Self::Stamp => "coremlit-embeddinggemma2.stamp.json",
}
}
#[inline]
pub const fn is_bundle(&self) -> bool {
matches!(
self,
Self::TextBundle | Self::AudioBundle(_) | Self::VisionBundle
)
}
}
impl core::fmt::Display for Artifact {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.is_bundle() {
write!(f, "the {} bundle", self.name())
} else {
write!(f, "`{}`", self.name())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Function {
Text(TextFunction),
Vision(VisionBudget),
}
impl Function {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Text(function) => function.as_str(),
Self::Vision(budget) => budget.as_str(),
}
}
}
impl core::fmt::Display for Function {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
struct InFunction(Option<Function>);
impl core::fmt::Display for InFunction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.0 {
Some(function) => write!(f, " (function `{function}`)"),
None => Ok(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum AllocationSite {
BatchOutput,
WindowList,
TokenIds,
TokenOffsets,
TokenizerScratch,
PieceTable,
PackLayout,
GatherRows,
ImagePlanes,
AudioPlan,
ImageBuild,
StampDigests,
ArtifactRead,
SourceParse,
}
impl AllocationSite {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::BatchOutput => "batch_output",
Self::WindowList => "window_list",
Self::TokenIds => "token_ids",
Self::TokenOffsets => "token_offsets",
Self::TokenizerScratch => "tokenizer_scratch",
Self::PieceTable => "piece_table",
Self::PackLayout => "pack_layout",
Self::GatherRows => "gather_rows",
Self::ImagePlanes => "image_planes",
Self::AudioPlan => "audio_plan",
Self::ImageBuild => "image_build",
Self::StampDigests => "stamp_digests",
Self::ArtifactRead => "artifact_read",
Self::SourceParse => "source_parse",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConfigField(&'static str);
impl ConfigField {
#[inline]
pub(crate) const fn new(path: &'static str) -> Self {
Self(path)
}
#[inline]
pub const fn as_str(&self) -> &'static str {
self.0
}
}
impl core::fmt::Display for ConfigField {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.0.is_empty() {
f.write_str("(top level)")
} else {
f.write_str(self.0)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum StampField {
Document,
Format,
Kit,
CoremlitVersion,
OsBuild,
Files,
Bundles,
TokenizerImage,
}
impl StampField {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Document => "document",
Self::Format => "format",
Self::Kit => "kit",
Self::CoremlitVersion => "coremlit",
Self::OsBuild => "os_build",
Self::Files => "files",
Self::Bundles => "bundles",
Self::TokenizerImage => "tokenizer_image",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum ImageField {
Length,
Magic,
FormatVersion,
HeaderSize,
Section,
Count,
SourceDigest,
ImageDigest,
Sentinel,
}
impl ImageField {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Length => "length",
Self::Magic => "magic",
Self::FormatVersion => "format_version",
Self::HeaderSize => "header_size",
Self::Section => "section",
Self::Count => "count",
Self::SourceDigest => "source_digest",
Self::ImageDigest => "image_digest",
Self::Sentinel => "sentinel",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum SourceField {
Document,
Normalizer,
PreTokenizer,
Model,
Vocabulary,
Merges,
PostProcessor,
AddedTokens,
ByteFallback,
}
impl SourceField {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Document => "document",
Self::Normalizer => "normalizer",
Self::PreTokenizer => "pre_tokenizer",
Self::Model => "model",
Self::Vocabulary => "vocabulary",
Self::Merges => "merges",
Self::PostProcessor => "post_processor",
Self::AddedTokens => "added_tokens",
Self::ByteFallback => "byte_fallback",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum IoOperation {
Read,
Write,
Copy,
Rename,
Sync,
Stat,
Lock,
Remove,
}
impl IoOperation {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::Copy => "copy",
Self::Rename => "rename",
Self::Sync => "sync",
Self::Stat => "stat",
Self::Lock => "lock",
Self::Remove => "remove",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, derive_more::Display)]
#[display("{}", self.as_str())]
#[non_exhaustive]
pub enum TraversalDefect {
BrokenLink,
Cycle,
TooManyEntries,
}
impl TraversalDefect {
#[inline]
pub const fn as_str(&self) -> &'static str {
match self {
Self::BrokenLink => "broken_link",
Self::Cycle => "cycle",
Self::TooManyEntries => "too_many_entries",
}
}
}
pub(crate) const EXCERPT_BYTES: usize = 64;
struct Excerpt {
out: String,
budget: usize,
cut: bool,
}
impl Excerpt {
fn within(budget: usize) -> Self {
Self {
out: String::with_capacity(budget + '…'.len_utf8()),
budget,
cut: false,
}
}
fn finish(mut self) -> String {
if self.cut {
self.out.push('…');
}
self.out
}
}
impl core::fmt::Write for Excerpt {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if self.cut {
return Err(core::fmt::Error);
}
let room = self.budget - self.out.len();
if s.len() <= room {
self.out.push_str(s);
return Ok(());
}
let mut end = room;
while !s.is_char_boundary(end) {
end -= 1;
}
self.out.push_str(&s[..end]);
self.cut = true;
Err(core::fmt::Error)
}
}
fn excerpt_within(text: impl core::fmt::Display, budget: usize) -> String {
let mut kept = Excerpt::within(budget);
let _ = core::fmt::write(&mut kept, format_args!("{text}"));
kept.finish()
}
pub(crate) fn excerpt(text: impl core::fmt::Display) -> String {
excerpt_within(text, EXCERPT_BYTES)
}
pub(crate) fn excerpt_pair(a: &str, b: &str) -> (String, String) {
let half = EXCERPT_BYTES / 2;
let (for_a, for_b) = if a.len() <= half {
(half, EXCERPT_BYTES - a.len())
} else if b.len() <= half {
(EXCERPT_BYTES - b.len(), half)
} else {
(half, EXCERPT_BYTES - half)
};
(excerpt_within(a, for_a), excerpt_within(b, for_b))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TableCell {
table: Option<u8>,
row: u32,
column: u32,
}
impl TableCell {
#[inline]
pub const fn token(row: u32, column: u32) -> Self {
Self {
table: None,
row,
column,
}
}
#[inline]
pub const fn position(table: u8, row: u32, column: u32) -> Self {
Self {
table: Some(table),
row,
column,
}
}
#[inline]
pub const fn table(&self) -> Option<u8> {
self.table
}
#[inline]
pub const fn row(&self) -> u32 {
self.row
}
#[inline]
pub const fn column(&self) -> u32 {
self.column
}
}
impl core::fmt::Display for TableCell {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if let Some(table) = self.table {
write!(f, "table {table}, ")?;
}
write!(f, "row {}, column {}", self.row, self.column)
}
}
#[derive(Debug, thiserror::Error)]
#[error("Core ML refused to load {artifact}{}: {source}", InFunction(*.function))]
pub struct LoadRefusal {
artifact: Artifact,
function: Option<Function>,
#[source]
source: LoadError,
}
impl LoadRefusal {
#[inline]
pub const fn new(artifact: Artifact, function: Option<Function>, source: LoadError) -> Self {
Self {
artifact,
function,
source,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn function(&self) -> Option<Function> {
self.function
}
#[inline]
pub const fn load_error(&self) -> &LoadError {
&self.source
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"{artifact}{} does not keep the kit's contract at feature `{feature}`: expected {expected}, got {actual}",
InFunction(*.function)
)]
pub struct ContractRefusal {
artifact: Artifact,
function: Option<Function>,
feature: String,
expected: String,
actual: String,
}
impl ContractRefusal {
pub(crate) fn of(
artifact: Artifact,
function: Option<Function>,
violation: ContractViolation,
) -> Error {
let (feature, expected, actual) = match violation.rendered() {
Rendered::Feature(feature) => (
feature.feature().to_owned(),
feature.clone().expected(),
feature.actual(),
),
Rendered::UnsatisfiableInput(name) => (
name,
"no required input the kit does not send".to_owned(),
"a required input".to_owned(),
),
Rendered::UnsatisfiableState(name) => {
(name, "no state".to_owned(), "a state buffer".to_owned())
}
};
Error::Contract(Self::new(artifact, function, feature, expected, actual))
}
#[inline]
pub const fn new(
artifact: Artifact,
function: Option<Function>,
feature: String,
expected: String,
actual: String,
) -> Self {
Self {
artifact,
function,
feature,
expected,
actual,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn function(&self) -> Option<Function> {
self.function
}
#[inline]
pub fn feature(&self) -> &str {
&self.feature
}
#[inline]
pub fn expected(&self) -> &str {
&self.expected
}
#[inline]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"config.json disagrees with the kit's compiled contract at `{field}`: expected {expected}, got {actual}"
)]
pub struct ConfigMismatch {
field: ConfigField,
expected: String,
actual: String,
}
impl ConfigMismatch {
#[inline]
pub const fn new(field: ConfigField, expected: String, actual: String) -> Self {
Self {
field,
expected,
actual,
}
}
#[inline]
pub const fn field(&self) -> ConfigField {
self.field
}
#[inline]
pub fn expected(&self) -> &str {
&self.expected
}
#[inline]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error("could not read {artifact} at `{}`: {source}", .path.display())]
pub struct ReadFailure {
artifact: Artifact,
path: PathBuf,
#[source]
source: std::io::Error,
}
impl ReadFailure {
#[inline]
pub const fn new(artifact: Artifact, path: PathBuf, source: std::io::Error) -> Self {
Self {
artifact,
path,
source,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub const fn io_error(&self) -> &std::io::Error {
&self.source
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"the stamp in `{}` lists nothing for {artifact}: run `compile_artifacts` over the directory first",
.dir.display()
)]
pub struct Unstamped {
artifact: Artifact,
dir: PathBuf,
}
impl Unstamped {
#[inline]
pub const fn new(artifact: Artifact, dir: PathBuf) -> Self {
Self { artifact, dir }
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub fn dir(&self) -> &Path {
&self.dir
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"{artifact} has changed since it was stamped: the stamp recorded {recorded} bytes and the file has {actual}; run `compile_artifacts` over the directory again"
)]
pub struct StaleStamp {
artifact: Artifact,
recorded: u64,
actual: u64,
}
impl StaleStamp {
#[inline]
pub const fn new(artifact: Artifact, recorded: u64, actual: u64) -> Self {
Self {
artifact,
recorded,
actual,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn recorded(&self) -> u64 {
self.recorded
}
#[inline]
pub const fn actual(&self) -> u64 {
self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error("the stamp is malformed at `{field}`: {detail}")]
pub struct StampDefect {
field: StampField,
detail: String,
}
impl StampDefect {
#[inline]
pub const fn new(field: StampField, detail: String) -> Self {
Self { field, detail }
}
#[inline]
pub const fn field(&self) -> StampField {
self.field
}
#[inline]
pub fn detail(&self) -> &str {
&self.detail
}
}
#[derive(Debug, thiserror::Error)]
#[error("{artifact} is {actual} bytes; the model's table is {expected}")]
pub struct TableShape {
artifact: Artifact,
expected: u64,
actual: u64,
}
impl TableShape {
#[inline]
pub const fn new(artifact: Artifact, expected: u64, actual: u64) -> Self {
Self {
artifact,
expected,
actual,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn expected(&self) -> u64 {
self.expected
}
#[inline]
pub const fn actual(&self) -> u64 {
self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"{artifact} differs from its stamp at {cell}: the stamp recorded {expected:#06x}, the file holds {actual:#06x}"
)]
pub struct TableIdentity {
artifact: Artifact,
cell: TableCell,
expected: u16,
actual: u16,
}
impl TableIdentity {
#[inline]
pub const fn new(artifact: Artifact, cell: TableCell, expected: u16, actual: u16) -> Self {
Self {
artifact,
cell,
expected,
actual,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn cell(&self) -> TableCell {
self.cell
}
#[inline]
pub const fn expected(&self) -> u16 {
self.expected
}
#[inline]
pub const fn actual(&self) -> u16 {
self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error("the tokenizer image's {field} is wrong: expected {expected}, got {actual}")]
pub struct TokenizerImageDefect {
field: ImageField,
expected: String,
actual: String,
}
impl TokenizerImageDefect {
#[inline]
pub const fn new(field: ImageField, expected: String, actual: String) -> Self {
Self {
field,
expected,
actual,
}
}
#[inline]
pub const fn field(&self) -> ImageField {
self.field
}
#[inline]
pub fn expected(&self) -> &str {
&self.expected
}
#[inline]
pub fn actual(&self) -> &str {
&self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[error("tokenizer.json is outside the configuration the kit implements at `{field}`: {detail}")]
pub struct TokenizerSourceDefect {
field: SourceField,
detail: String,
}
impl TokenizerSourceDefect {
#[inline]
pub const fn new(field: SourceField, detail: String) -> Self {
Self { field, detail }
}
#[inline]
pub const fn field(&self) -> SourceField {
self.field
}
#[inline]
pub fn detail(&self) -> &str {
&self.detail
}
}
#[derive(Debug, thiserror::Error)]
#[error("Core ML could not compile {artifact}: {source}")]
pub struct CompileFailure {
artifact: Artifact,
#[source]
source: CompileError,
}
impl CompileFailure {
#[inline]
pub const fn new(artifact: Artifact, source: CompileError) -> Self {
Self { artifact, source }
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn compile_error(&self) -> &CompileError {
&self.source
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"{artifact} has more than one source in the directory ({}): remove all but one; none is chosen",
Candidates(.candidates)
)]
pub struct AmbiguousArtifact {
artifact: Artifact,
candidates: Vec<PathBuf>,
}
impl AmbiguousArtifact {
#[inline]
pub const fn new(artifact: Artifact, candidates: Vec<PathBuf>) -> Self {
Self {
artifact,
candidates,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub fn candidates(&self) -> &[PathBuf] {
&self.candidates
}
}
struct Candidates<'a>(&'a [PathBuf]);
impl core::fmt::Display for Candidates<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for (index, path) in self.0.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
write!(f, "`{}`", path.display())?;
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
#[error("could not {operation} `{}`: {source}", .path.display())]
pub struct IoFailure {
operation: IoOperation,
path: PathBuf,
#[source]
source: std::io::Error,
}
impl IoFailure {
#[inline]
pub const fn new(operation: IoOperation, path: PathBuf, source: std::io::Error) -> Self {
Self {
operation,
path,
source,
}
}
#[inline]
pub const fn operation(&self) -> IoOperation {
self.operation
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub const fn io_error(&self) -> &std::io::Error {
&self.source
}
}
#[derive(Debug)]
pub struct TraversalRefusal {
defect: TraversalDefect,
path: PathBuf,
}
impl TraversalRefusal {
#[inline]
pub const fn new(defect: TraversalDefect, path: PathBuf) -> Self {
Self { defect, path }
}
#[inline]
pub const fn defect(&self) -> TraversalDefect {
self.defect
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
}
impl core::fmt::Display for TraversalRefusal {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let path = self.path.display();
match self.defect {
TraversalDefect::BrokenLink => write!(
f,
"`{path}` is a broken link: the kit follows every link in a model directory, and this one leads to nothing"
),
TraversalDefect::Cycle => write!(
f,
"`{path}` leads back into a directory that holds it: a directory cycle"
),
TraversalDefect::TooManyEntries => write!(
f,
"`{path}` holds more than {MAX_TREE_ENTRIES} files and directories, the most one walked tree may hold"
),
}
}
}
impl std::error::Error for TraversalRefusal {}
#[derive(Debug, thiserror::Error)]
#[error(
"the prompt's framing takes {framing} ids of the {window}-id window, leaving none for the text"
)]
pub struct PromptFillsWindow {
window: usize,
framing: usize,
}
impl PromptFillsWindow {
#[inline]
pub const fn new(window: usize, framing: usize) -> Self {
Self { window, framing }
}
#[inline]
pub const fn window(&self) -> usize {
self.window
}
#[inline]
pub const fn framing(&self) -> usize {
self.framing
}
}
#[derive(Debug, thiserror::Error)]
#[error("the image resize failed: {source}")]
pub struct ResizeFailure {
#[source]
source: pixon::sinker::MixedSinkerError,
}
impl ResizeFailure {
#[inline]
pub const fn new(source: pixon::sinker::MixedSinkerError) -> Self {
Self { source }
}
#[inline]
pub const fn pixon_error(&self) -> &pixon::sinker::MixedSinkerError {
&self.source
}
}
#[derive(Debug, thiserror::Error)]
#[error("an audio window of {samples} samples completes no mel frame: it needs at least {min}")]
pub struct AudioTooShort {
samples: usize,
min: usize,
}
impl AudioTooShort {
#[inline]
pub const fn new(samples: usize, min: usize) -> Self {
Self { samples, min }
}
#[inline]
pub const fn samples(&self) -> usize {
self.samples
}
#[inline]
pub const fn min(&self) -> usize {
self.min
}
}
#[derive(Debug, thiserror::Error)]
#[error("an audio window holds at most {max} samples, got {samples}")]
pub struct AudioWindowTooLong {
samples: usize,
max: usize,
}
impl AudioWindowTooLong {
#[inline]
pub const fn new(samples: usize, max: usize) -> Self {
Self { samples, max }
}
#[inline]
pub const fn samples(&self) -> usize {
self.samples
}
#[inline]
pub const fn max(&self) -> usize {
self.max
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"vision budget {budget} is not among the loaded budgets {loaded}; a budget is never loaded on the side"
)]
pub struct BudgetNotLoaded {
budget: VisionBudget,
loaded: VisionBudgets,
}
impl BudgetNotLoaded {
#[inline]
pub const fn new(budget: VisionBudget, loaded: VisionBudgets) -> Self {
Self { budget, loaded }
}
#[inline]
pub const fn budget(&self) -> VisionBudget {
self.budget
}
#[inline]
pub const fn loaded(&self) -> VisionBudgets {
self.loaded
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"{artifact}{} produced a non-finite value at index {index} of a row it was asked for",
InFunction(*.function)
)]
pub struct NonFiniteOutput {
artifact: Artifact,
function: Option<Function>,
index: usize,
}
impl NonFiniteOutput {
#[inline]
pub const fn new(artifact: Artifact, function: Option<Function>, index: usize) -> Self {
Self {
artifact,
function,
index,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn function(&self) -> Option<Function> {
self.function
}
#[inline]
pub const fn index(&self) -> usize {
self.index
}
}
#[derive(Debug, thiserror::Error)]
#[error("Core ML's prediction failed in {artifact}{}: {source}", InFunction(*.function))]
pub struct PredictionFailure {
artifact: Artifact,
function: Option<Function>,
#[source]
source: PredictionError,
}
impl PredictionFailure {
#[inline]
pub const fn new(
artifact: Artifact,
function: Option<Function>,
source: PredictionError,
) -> Self {
Self {
artifact,
function,
source,
}
}
#[inline]
pub const fn artifact(&self) -> Artifact {
self.artifact
}
#[inline]
pub const fn function(&self) -> Option<Function> {
self.function
}
#[inline]
pub const fn prediction_error(&self) -> &PredictionError {
&self.source
}
}
#[derive(Debug, thiserror::Error)]
#[error("could not reserve {bytes} bytes for {site}")]
pub struct Allocation {
site: AllocationSite,
bytes: usize,
}
impl Allocation {
#[inline]
pub const fn new(site: AllocationSite, bytes: usize) -> Self {
Self { site, bytes }
}
#[inline]
pub const fn site(&self) -> AllocationSite {
self.site
}
#[inline]
pub const fn bytes(&self) -> usize {
self.bytes
}
}
#[derive(Debug, thiserror::Error)]
#[error("the {part} part's artifacts are {actual}, but the deployment expects {expected}")]
pub struct ArtifactIdentity {
part: Part,
expected: ArtifactLabel,
actual: ArtifactLabel,
}
impl ArtifactIdentity {
#[inline]
pub const fn new(part: Part, expected: ArtifactLabel, actual: ArtifactLabel) -> Self {
Self {
part,
expected,
actual,
}
}
#[inline]
pub const fn part(&self) -> Part {
self.part
}
#[inline]
pub const fn expected(&self) -> ArtifactLabel {
self.expected
}
#[inline]
pub const fn actual(&self) -> ArtifactLabel {
self.actual
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(
"EmbeddingGemma 2 needs macOS 15 or later: its packages are Core ML specification 9 and load one function at a time"
)]
UnsupportedOs,
#[error(transparent)]
Load(LoadRefusal),
#[error(transparent)]
Contract(ContractRefusal),
#[error(transparent)]
Config(ConfigMismatch),
#[error(transparent)]
Read(ReadFailure),
#[error(transparent)]
Unstamped(Unstamped),
#[error(transparent)]
StaleStamp(StaleStamp),
#[error(transparent)]
Stamp(StampDefect),
#[error(transparent)]
TableShape(TableShape),
#[error(transparent)]
TableIdentity(TableIdentity),
#[error(transparent)]
TokenizerImage(TokenizerImageDefect),
#[error(transparent)]
TokenizerSource(TokenizerSourceDefect),
#[error(transparent)]
Compile(CompileFailure),
#[error(transparent)]
AmbiguousArtifact(AmbiguousArtifact),
#[error(transparent)]
Io(IoFailure),
#[error(transparent)]
Traversal(TraversalRefusal),
#[error("text input is empty")]
EmptyText,
#[error(
"text input is {} bytes, exceeding the configured {}-byte limit",
.0.got(),
.0.max()
)]
InputTooLarge(InputTooLarge),
#[error(
"embed_long window budget {} exceeds the model's fixed {}-token input window",
.0.window(),
.0.max()
)]
WindowOverBudget(WindowOverBudget),
#[error(transparent)]
PromptFillsWindow(PromptFillsWindow),
#[error(
"contentless text at bytes {}..{} measures {} tokens, exceeding the model's fixed {}-token window",
.0.start(),
.0.end(),
.0.tokens(),
.0.max()
)]
ContentlessInputOverBudget(ContentlessInputOverBudget),
#[error("windowed-sequence processing failed: {0}")]
Windowing(#[from] WinditError),
#[error(transparent)]
AudioTooShort(AudioTooShort),
#[error(transparent)]
AudioWindowTooLong(AudioWindowTooLong),
#[error(
"invalid image dimensions: {}×{} (a zero side, a side over the per-axis maximum, or both target sides rounding to zero)",
.0.width(),
.0.height()
)]
ImageDimensions(ImageDimensions),
#[error(
"image data length mismatch: expected {} bytes (w·h·3), got {}",
.0.expected(),
.0.got()
)]
ImageBuffer(ImageDataLength),
#[error(transparent)]
Resize(ResizeFailure),
#[error(transparent)]
BudgetNotLoaded(BudgetNotLoaded),
#[error("the {0} lane is off: the options enable no {0} part, so the facade loaded none")]
PartDisabled(Part),
#[error(transparent)]
NonFiniteOutput(NonFiniteOutput),
#[error(transparent)]
Prediction(PredictionFailure),
#[error("tensor failed: {0}")]
Tensor(#[from] TensorError),
#[error(transparent)]
Allocation(Allocation),
#[error(transparent)]
ArtifactIdentity(ArtifactIdentity),
}
impl From<crate::embeddings::long_text::Error> for Error {
fn from(e: crate::embeddings::long_text::Error) -> Self {
use crate::embeddings::long_text::Error as LongText;
match e {
LongText::Windowing(e) => Self::Windowing(e),
LongText::WindowOverBudget(e) => Self::WindowOverBudget(e),
LongText::InputTooLarge(e) => Self::InputTooLarge(e),
LongText::ContentlessInputOverBudget(e) => Self::ContentlessInputOverBudget(e),
}
}
}
#[cfg(test)]
mod tests;