use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ModelFormat {
Safetensors,
Q4,
Unknown,
}
pub fn detect_format(dir: &Path) -> ModelFormat {
if dir.join("model.safetensors").exists() || dir.join("model.safetensors.index.json").exists() {
return ModelFormat::Safetensors;
}
let has_q4_file = std::fs::read_dir(dir)
.ok()
.and_then(|mut entries| {
entries.find(|e| {
e.as_ref()
.ok()
.and_then(|e| e.file_name().to_str().map(|n| n.ends_with(".q4")))
.unwrap_or(false)
})
})
.is_some();
if has_q4_file {
ModelFormat::Q4
} else {
ModelFormat::Unknown
}
}
pub fn metal_gpu_required_message(dir: &Path) -> String {
format!(
"model directory '{}' is a native Q4 quantized checkpoint, which requires \
the Metal GPU forward pass. This binary was built without the `metal-gpu` \
feature. Rebuild with `--features \"f16 metal-gpu\"` (macOS only), or point \
--model at a safetensors directory instead.",
dir.display()
)
}
pub fn unrecognized_format_message(dir: &Path) -> String {
format!(
"'{}' is not a recognized model directory: no model.safetensors, \
model.safetensors.index.json, or *.q4 tensor files were found",
dir.display()
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn tempdir(name: &str) -> std::path::PathBuf {
let mut dir = std::env::temp_dir();
dir.push(format!(
"lattice-model-format-test-{name}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
));
fs::create_dir_all(&dir).expect("create tempdir");
dir
}
#[test]
fn detect_format_safetensors_file() {
let dir = tempdir("safetensors-file");
fs::write(dir.join("model.safetensors"), b"stub").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Safetensors);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_safetensors_index_only() {
let dir = tempdir("safetensors-index");
fs::write(dir.join("model.safetensors.index.json"), b"{}").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Safetensors);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_q4_dir() {
let dir = tempdir("q4");
fs::write(dir.join("model_layers_0_weight.q4"), b"stub").unwrap();
fs::write(dir.join("config.json"), b"{}").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Q4);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_prefers_safetensors_over_q4_files() {
let dir = tempdir("mixed");
fs::write(dir.join("model.safetensors"), b"stub").unwrap();
fs::write(dir.join("leftover.q4"), b"stub").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Safetensors);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_prefers_safetensors_index_over_q4_files() {
let dir = tempdir("mixed-index");
fs::write(dir.join("model.safetensors.index.json"), b"{}").unwrap();
fs::write(dir.join("leftover.q4"), b"stub").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Safetensors);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_empty_dir_is_unknown() {
let dir = tempdir("empty");
assert_eq!(detect_format(&dir), ModelFormat::Unknown);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_unrelated_files_is_unknown() {
let dir = tempdir("unrelated");
fs::write(dir.join("readme.txt"), b"hello").unwrap();
fs::write(dir.join("config.json"), b"{}").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Unknown);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn detect_format_unreadable_dir_is_unknown() {
let mut dir = std::env::temp_dir();
dir.push(format!(
"lattice-model-format-test-does-not-exist-{}",
std::process::id()
));
assert_eq!(detect_format(&dir), ModelFormat::Unknown);
}
#[test]
fn metal_gpu_required_message_mentions_rebuild_flags() {
let msg = metal_gpu_required_message(Path::new("/tmp/some-q4-dir"));
assert!(msg.contains("metal-gpu"));
assert!(msg.contains("--features"));
}
#[test]
fn unrecognized_format_message_mentions_expected_files() {
let msg = unrecognized_format_message(Path::new("/tmp/bogus"));
assert!(msg.contains("model.safetensors"));
assert!(msg.contains(".q4"));
}
#[cfg(not(feature = "metal-gpu"))]
#[test]
fn q4_dir_without_metal_gpu_feature_fails_closed() {
let dir = tempdir("q4-no-metal");
fs::write(dir.join("model_layers_0_weight.q4"), b"stub").unwrap();
assert_eq!(detect_format(&dir), ModelFormat::Q4);
let msg = metal_gpu_required_message(&dir);
assert!(msg.contains("metal-gpu"));
fs::remove_dir_all(&dir).ok();
}
}