fn extract_extended_model_paths(command: &ExtendedCommands) -> Vec<PathBuf> {
match command {
ExtendedCommands::Probar { file, .. }
| ExtendedCommands::CompareHf { file, .. }
| ExtendedCommands::Chat { file, .. }
| ExtendedCommands::Bench { file, .. }
| ExtendedCommands::Eval { file, .. }
| ExtendedCommands::Profile { file, .. } => vec![file.clone()],
ExtendedCommands::Cbtop { model_path, .. } => model_path.iter().cloned().collect(),
ExtendedCommands::Tools(ToolCommands::Rosetta { action }) => match action {
RosettaCommands::Convert { source, .. }
| RosettaCommands::Chain { source, .. }
| RosettaCommands::Verify { source, .. } => vec![source.clone()],
RosettaCommands::CompareInference {
model_a, model_b, ..
} => {
vec![model_a.clone(), model_b.clone()]
}
_ => vec![],
},
_ => vec![],
}
}
fn extract_model_paths(command: &Commands) -> Vec<PathBuf> {
match command {
Commands::Run { source, .. } => {
let path = PathBuf::from(source);
if path.exists() {
vec![path]
} else {
vec![]
}
}
Commands::Export { plan: true, .. } => vec![],
Commands::Export { file, .. } => file.iter().cloned().collect(),
Commands::Serve { .. } => vec![],
Commands::Trace { file, .. }
| Commands::Convert { file, .. }
| Commands::Check { file, .. } => vec![file.clone()],
Commands::Merge { plan: true, .. } => vec![],
Commands::Merge { files, .. } => files.clone(),
Commands::Quantize { file, .. } => vec![file.clone()],
Commands::ModelOps(ModelOpsCommands::Prune { file, .. }) => vec![file.clone()],
Commands::ModelOps(ModelOpsCommands::Distill { teacher, .. }) => {
teacher.iter().cloned().collect()
}
#[cfg(feature = "training")]
Commands::ModelOps(ModelOpsCommands::Finetune { file, .. }) => {
file.iter().cloned().collect()
}
Commands::Tui { file, .. } => file.iter().cloned().collect(),
Commands::Import { source, .. } => {
let path = PathBuf::from(source);
if path.exists() {
vec![path]
} else {
vec![]
}
}
Commands::Extended(ref ext) => extract_extended_model_paths(ext),
_ => vec![],
}
}
fn validate_model_contract(paths: &[PathBuf]) -> Result<(), CliError> {
let rosetta = aprender::format::rosetta::RosettaStone::new();
for path in paths {
if !path.exists() {
continue; }
if path.to_string_lossy().ends_with(".safetensors.index.json") {
validate_shard_index(path)?;
continue;
}
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_lowercase();
if !matches!(ext.as_str(), "gguf" | "safetensors" | "apr") {
continue;
}
validate_single_model_metadata(&rosetta, path)?;
}
Ok(())
}
fn validate_shard_index(path: &Path) -> Result<(), CliError> {
let Some(parent) = path.parent() else {
return Ok(());
};
let manifest_path = parent.join(".apr-manifest.json");
if manifest_path.exists() {
validate_shard_manifest(&manifest_path, parent)?;
}
Ok(())
}
fn validate_single_model_metadata(
rosetta: &aprender::format::rosetta::RosettaStone,
path: &Path,
) -> Result<(), CliError> {
let report = rosetta.validate_metadata_only(path).map_err(|e| {
CliError::ValidationFailed(format!(
"Contract validation failed for {}: {e}",
path.display()
))
})?;
if !report.is_valid {
return Err(CliError::ValidationFailed(format!(
"PMAT-237 CONTRACT VIOLATION: {} has 0 tensors (corrupt file?). \
Use 'apr qa {}' for details. Use --skip-contract to bypass.",
path.display(),
path.display(),
)));
}
Ok(())
}
#[allow(dead_code)]
fn validate_single_model(
rosetta: &aprender::format::rosetta::RosettaStone,
path: &Path,
) -> Result<(), CliError> {
let report = rosetta.validate(path).map_err(|e| {
CliError::ValidationFailed(format!(
"Contract validation failed for {}: {e}",
path.display()
))
})?;
if !report.is_valid {
let violation_count: usize = report.tensors.iter().map(|t| t.failures.len()).sum();
return Err(CliError::ValidationFailed(format!(
"PMAT-237 CONTRACT VIOLATION: {} has {} violations in {} tensors. \
Use 'apr qa {}' for details. Use --skip-contract to bypass.",
path.display(),
violation_count,
report.failed_tensor_count,
path.display(),
)));
}
Ok(())
}
fn validate_shard_manifest(
manifest_path: &std::path::Path,
cache_dir: &std::path::Path,
) -> Result<(), CliError> {
let manifest_str = std::fs::read_to_string(manifest_path).map_err(|e| {
CliError::ValidationFailed(format!(
"Failed to read manifest {}: {e}",
manifest_path.display()
))
})?;
let manifest: commands::pull::ShardManifest =
serde_json::from_str(&manifest_str).map_err(|e| {
CliError::ValidationFailed(format!(
"Failed to parse manifest {}: {e}",
manifest_path.display()
))
})?;
for (filename, checksum) in &manifest.files {
let file_path = cache_dir.join(filename);
if !file_path.exists() {
return Err(CliError::ValidationFailed(format!(
"Shard '{}' is missing. Re-run 'apr pull --force' to re-download.",
filename
)));
}
let actual_size = std::fs::metadata(&file_path)
.map(|m| m.len())
.map_err(|e| {
CliError::ValidationFailed(format!("Failed to stat shard '{}': {e}", filename))
})?;
if actual_size != checksum.size {
return Err(CliError::ValidationFailed(format!(
"Shard '{}' size mismatch: expected {} bytes, got {} bytes \
(file may be truncated). Re-run 'apr pull --force' to re-download.",
filename, checksum.size, actual_size
)));
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn dispatch_cbtop(
model: Option<&str>,
attach: Option<&str>,
model_path: Option<&Path>,
headless: bool,
json: bool,
output: Option<&Path>,
ci: bool,
throughput: Option<f64>,
brick_score: Option<u32>,
warmup: usize,
iterations: usize,
speculative: bool,
speculation_k: usize,
draft_model: Option<&Path>,
concurrent: usize,
simulated: bool,
) -> Result<(), CliError> {
let (resolved_model, resolved_model_path) = if let Some(m) = model {
let path = std::path::Path::new(m);
let is_gguf = path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("gguf"));
if is_gguf || path.exists() {
(
Some(
path.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(m)
.to_string(),
),
Some(PathBuf::from(m)),
)
} else {
(Some(m.to_string()), model_path.map(PathBuf::from))
}
} else {
(None, model_path.map(PathBuf::from))
};
cbtop::run(cbtop::CbtopConfig {
model: resolved_model,
attach: attach.map(String::from),
model_path: resolved_model_path,
headless,
json,
output: output.map(PathBuf::from),
ci,
throughput_threshold: throughput,
brick_score_threshold: brick_score,
warmup,
iterations,
speculative,
speculation_k,
draft_model_path: draft_model.map(PathBuf::from),
concurrent,
simulated,
})
}
#[allow(clippy::too_many_arguments)]
fn dispatch_showcase(
auto_verify: bool,
step: Option<&str>,
tier: &str,
model_dir: &Path,
baseline: &str,
zram: bool,
runs: usize,
gpu: bool,
json: bool,
verbose: bool,
quiet: bool,
) -> Result<(), CliError> {
let step = step.and_then(|s| match s {
"import" => Some(showcase::ShowcaseStep::Import),
"gguf" => Some(showcase::ShowcaseStep::GgufInference),
"convert" => Some(showcase::ShowcaseStep::Convert),
"apr" => Some(showcase::ShowcaseStep::AprInference),
"bench" => Some(showcase::ShowcaseStep::Benchmark),
"chat" => Some(showcase::ShowcaseStep::Chat),
"visualize" => Some(showcase::ShowcaseStep::Visualize),
"zram" => Some(showcase::ShowcaseStep::ZramDemo),
"cuda" => Some(showcase::ShowcaseStep::CudaDemo),
"brick" => Some(showcase::ShowcaseStep::BrickDemo),
"all" => Some(showcase::ShowcaseStep::All),
_ => None,
});
let tier = match tier {
"tiny" => showcase::ModelTier::Tiny,
"small" => showcase::ModelTier::Small,
"medium" => showcase::ModelTier::Medium,
"large" => showcase::ModelTier::Large,
_ => showcase::ModelTier::Small,
};
let baselines: Vec<showcase::Baseline> = baseline
.split(',')
.filter_map(|b| match b.trim() {
"llama-cpp" => Some(showcase::Baseline::LlamaCpp),
"ollama" => Some(showcase::Baseline::Ollama),
_ => None,
})
.collect();
let export_format = if json {
showcase::ExportFormat::Json
} else {
showcase::ExportFormat::None
};
let config = showcase::ShowcaseConfig {
tier,
model: tier.model_path().to_string(),
quant: "Q4_K_M".to_string(),
model_dir: model_dir.to_path_buf(),
auto_verify,
step,
baselines,
zram,
bench_runs: runs,
export_format,
export_path: None,
gpu,
verbose,
quiet,
};
showcase::run(&config)
}
#[allow(clippy::too_many_arguments)]
fn dispatch_profile(
file: &Path,
granular: bool,
format: &str,
focus: Option<&str>,
detect_naive: bool,
threshold: f64,
compare_hf: Option<&str>,
energy: bool,
perf_grade: bool,
callgraph: bool,
fail_on_naive: bool,
output: Option<&Path>,
ci: bool,
assert_throughput: Option<f64>,
assert_p99: Option<f64>,
assert_p50: Option<f64>,
warmup: usize,
measure: usize,
tokens: usize,
ollama: bool,
no_gpu: bool,
compare: Option<&Path>,
) -> Result<(), CliError> {
let output_format = format.parse().unwrap_or(profile::OutputFormat::Human);
if ci || assert_throughput.is_some() || assert_p99.is_some() || assert_p50.is_some() {
let assertions = profile::CiAssertions {
min_throughput: assert_throughput,
max_p99_ms: assert_p99,
max_p50_ms: assert_p50,
max_memory_mb: None,
};
match profile::run_ci(file, output_format, &assertions, warmup, measure) {
Ok(true) => Ok(()),
Ok(false) => Err(CliError::ValidationFailed(
"CI profile assertions failed".to_string(),
)),
Err(e) => Err(e),
}
} else if let Some(compare_path) = compare {
#[cfg(feature = "inference")]
{
profile::run_cross_format_comparison(file, compare_path, warmup, measure, tokens, no_gpu)
}
#[cfg(not(feature = "inference"))]
{
let _ = (compare_path, tokens, no_gpu);
Err(CliError::ValidationFailed(
"Cross-format comparison requires the 'inference' feature".to_string(),
))
}
} else {
let profile_focus = focus
.and_then(|f| f.parse().ok())
.unwrap_or(profile::ProfileFocus::All);
profile::run(
file,
granular,
output_format,
profile_focus,
detect_naive,
threshold,
compare_hf,
energy,
perf_grade,
callgraph,
fail_on_naive,
output,
tokens,
ollama,
no_gpu,
)
}
}