use crate::schema::{ModelCapability, QuantScheme, Quantization};
use crate::tasks::generate::{parse_tool_calls, render_chat_prompt, GenerateRequest, ToolCall};
use crate::InferenceError;
pub trait TextDecoder: Send {
fn encode(&self, text: &str) -> Result<Vec<u32>, InferenceError>;
fn decode(&self, tokens: &[u32]) -> Result<String, InferenceError>;
fn forward(&mut self, tokens: &[u32], pos: usize) -> Result<Vec<f32>, InferenceError>;
fn eos_ids(&self) -> Vec<u32>;
fn context_length(&self) -> usize;
fn clear_kv_cache(&mut self);
fn begin_prompt(&mut self, prompt_tokens: &[u32]) -> usize {
let _ = prompt_tokens;
self.clear_kv_cache();
0
}
}
pub struct LocalGeneration {
pub text: String,
pub ttft_ms: Option<u64>,
pub stop_reason: Option<String>,
pub prompt_tokens: usize,
pub completion_tokens: usize,
}
pub enum DriveError {
Recoverable(InferenceError),
BackendCorrupted(InferenceError),
}
impl DriveError {
pub fn into_inner(self) -> InferenceError {
match self {
DriveError::Recoverable(e) | DriveError::BackendCorrupted(e) => e,
}
}
}
pub fn in_process_loader_available() -> bool {
#[cfg(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx)))]
{
crate::backend::swift_lm::is_available()
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx))))]
{
false
}
}
pub fn has_native_backend(model_type: &str) -> bool {
#[cfg(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx)))]
{
crate::backend::swift_lm::supports_model_type(model_type)
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx))))]
{
let _ = model_type;
false
}
}
pub const GGUF_MODEL_TYPES: &[&str] = &["qwen3", "qwen3_moe"];
pub fn gguf_backend_serves(model_type: &str) -> bool {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx))))]
{
GGUF_MODEL_TYPES.contains(&model_type)
}
#[cfg(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx)))]
{
let _ = model_type;
false
}
}
pub fn quantization_is_decodable(quantization: Option<&Quantization>) -> bool {
let Some(quantization) = quantization else {
return true;
};
match quantization.scheme {
QuantScheme::AffineGroupInt | QuantScheme::Unquantized => true,
QuantScheme::BlockScaledFloat => true,
QuantScheme::KQuantMixed | QuantScheme::RtnBlock => false,
QuantScheme::Unknown => false,
}
}
pub fn native_backend_serves(model_type: &str, quantization: Option<&Quantization>) -> bool {
has_native_backend(model_type) && quantization_is_decodable(quantization)
}
pub struct BackendDescriptor {
pub backend_name: &'static str,
pub model_types: &'static [&'static str],
}
pub trait LocalInferenceBackend: TextDecoder {
fn backend_name(&self) -> &'static str;
fn supports_capability(&self, cap: ModelCapability) -> bool;
fn render_prompt(&self, req: &GenerateRequest) -> Result<String, InferenceError> {
Ok(render_chat_prompt(req))
}
fn parse_tool_calls(&self, text: &str) -> (String, Vec<ToolCall>) {
parse_tool_calls(text)
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx)))]
fn read_model_type(model_dir: &std::path::Path) -> Result<String, InferenceError> {
let cfg_path = model_dir.join("config.json");
let raw = std::fs::read_to_string(&cfg_path).map_err(|e| {
InferenceError::InferenceFailed(format!("read {}: {e}", cfg_path.display()))
})?;
let cfg: serde_json::Value = serde_json::from_str(&raw).map_err(|e| {
InferenceError::InferenceFailed(format!("parse {}: {e}", cfg_path.display()))
})?;
Ok(cfg
.get("model_type")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_ascii_lowercase())
}
#[cfg(all(target_os = "macos", target_arch = "aarch64", not(car_skip_mlx)))]
pub fn describe_model_type(model_dir: &std::path::Path) -> String {
read_model_type(model_dir).unwrap_or_else(|_| "unknown".into())
}
#[cfg(test)]
mod native_admission_tests {
use super::*;
#[cfg(car_mlxlm_swift_built)]
#[test]
fn admission_follows_the_linked_loader_not_a_constant() {
assert!(
has_native_backend("glm4_moe_lite"),
"glm4_moe_lite: the linked loader registers it, so admission must allow it"
);
assert!(
!has_native_backend("not_a_real_architecture_9f3c"),
"the gate must be capable of refusing, or it is not a gate"
);
assert!(has_native_backend("Qwen3"), "the gate is case-insensitive");
assert_eq!(
has_native_backend("glm4_moe_lite"),
in_process_loader_available(),
"admission must follow the linked loader, not a list"
);
}
#[cfg(not(car_mlxlm_swift_built))]
#[test]
fn admission_refuses_everything_without_the_swift_stack() {
for t in ["qwen3", "gemma4_unified", "glm4_moe_lite", "nonsense_xyz"] {
assert!(
!has_native_backend(t),
"{t}: no Swift stack in this build, so nothing loads in-process"
);
}
}
#[test]
fn decodable_layouts_mirror_the_loader() {
let decodable = [
Quantization::from_mlx_config(Some(4), Some(64), None).unwrap(),
Quantization::from_mlx_config(Some(4), Some(64), Some("affine")).unwrap(),
Quantization::from_mlx_config(Some(8), Some(32), Some("mxfp8")).unwrap(),
Quantization::from_mlx_config(Some(4), Some(32), Some("mxfp4")).unwrap(),
Quantization::from_mlx_config(Some(4), Some(16), Some("nvfp4")).unwrap(),
Quantization::parse("bf16"),
];
for q in &decodable {
assert!(
quantization_is_decodable(Some(q)),
"loader decodes {q:?}, gate must admit it"
);
}
let refused = [
Quantization::parse("Q4_K_M"),
Quantization::parse("Q8_0"),
Quantization::parse("awq"),
];
for q in &refused {
assert!(
!quantization_is_decodable(Some(q)),
"loader cannot decode {q:?}, gate must refuse it"
);
}
assert!(quantization_is_decodable(None));
}
#[test]
fn block_scaled_float_is_decodable_at_any_width() {
for mode in ["mxfp4", "mxfp8", "nvfp4"] {
for bits in [4u8, 6, 8, 16] {
let q = Quantization::from_mlx_config(Some(bits), Some(32), Some(mode)).unwrap();
assert_eq!(q.scheme, QuantScheme::BlockScaledFloat, "{mode}/{bits}");
assert!(quantization_is_decodable(Some(&q)), "{mode}/{bits}");
}
}
assert!(!quantization_is_decodable(Some(&Quantization::parse(
"gptq"
))));
}
#[test]
fn architecture_and_layout_must_both_pass() {
let affine = Quantization::from_mlx_config(Some(4), Some(64), None).unwrap();
let undecodable = Quantization::parse("awq");
assert!(!native_backend_serves(
"not_a_real_architecture_9f3c",
Some(&affine)
));
assert!(!native_backend_serves(
"some_architecture_from_next_month",
None
));
let supported = has_native_backend("qwen3");
assert_eq!(native_backend_serves("qwen3", Some(&affine)), supported);
assert_eq!(native_backend_serves("qwen3", None), supported);
assert!(
!native_backend_serves("qwen3", Some(&undecodable)),
"an undecodable layout is refused on every platform"
);
}
}