mod phase3;
#[allow(unused_imports)]
use phase3::{
run_decrypt, run_encrypt, run_he_inspect, run_import_sharded, run_profile, run_quantize,
run_sign, run_verify_sig,
};
use std::fs;
use std::time::Instant;
use aprender::format::hexdump::{
data_flow_diagram, hex_dump, tree_view, HexDumpConfig, LayerInfo, TreeNode,
};
use aprender::format::{
diff_models, format_size, lint_model_file, list_tensors, DiffOptions, ImportOptions,
MergeOptions, MergeStrategy, RosettaStone, TensorListOptions,
};
use aprender::format::{CanaryFile, TensorCanary};
use aprender::format::f16_safety::F16_MIN_NORMAL;
use aprender::format::golden::{GoldenTraceSet, GoldenVerifyReport};
use aprender::format::model_family::build_default_registry;
use aprender::format::{apr_export, ExportFormat, ExportOptions};
use super::apr_args::{
AprAction, AprArgs, AprCanaryArgs, AprCompareArgs, AprContractArgs, AprDiffArgs, AprExportArgs,
AprF16AuditArgs, AprFlowArgs, AprGoldenArgs, AprHexArgs, AprImportArgs, AprInspectArgs,
AprLintArgs, AprMergeArgs, AprTensorsArgs, AprTreeArgs, AprValidateArgs, FamilyAction,
};
use super::commands::{CliError, CliResult, CommandResult};
mod probe;
mod rosetta;
use rosetta::run_rosetta;
pub fn run_apr(args: &AprArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let start = Instant::now();
let result = dispatch_apr(args, global);
if global.verbose {
let elapsed = start.elapsed();
eprintln!(
"[timing] apr {} completed in {:.3}s",
subcommand_name(&args.action),
elapsed.as_secs_f64()
);
}
result
}
fn subcommand_name(action: &AprAction) -> &'static str {
match action {
AprAction::Inspect(_) => "inspect",
AprAction::Tensors(_) => "tensors",
AprAction::Hex(_) => "hex",
AprAction::Tree(_) => "tree",
AprAction::Flow(_) => "flow",
AprAction::Lint(_) => "lint",
AprAction::Diff(_) => "diff",
AprAction::Import(_) => "import",
AprAction::Merge(_) => "merge",
AprAction::Rosetta(_) => "rosetta",
AprAction::Canary(_) => "canary",
AprAction::Golden(_) => "golden",
AprAction::Validate(_) => "validate",
AprAction::Contract(_) => "contract",
AprAction::Family(_) => "family",
AprAction::Compare(_) => "compare",
AprAction::Export(_) => "export",
AprAction::F16Audit(_) => "f16-audit",
AprAction::Sign(_) => "sign",
AprAction::VerifySig(_) => "verify-sig",
AprAction::Encrypt(_) => "encrypt",
AprAction::Decrypt(_) => "decrypt",
AprAction::Quantize(_) => "quantize",
AprAction::ImportSharded(_) => "import-sharded",
AprAction::HeInspect(_) => "he-inspect",
AprAction::Profile(_) => "profile",
AprAction::Probe(_) => "probe",
AprAction::Parity(_) => "parity",
AprAction::ConfigCheck(_) => "config-check",
AprAction::Pull(_) => "pull",
AprAction::PullList(_) => "ls",
}
}
fn dispatch_apr(args: &AprArgs, global: &super::args::Args) -> CliResult<CommandResult> {
match &args.action {
AprAction::Inspect(a) => run_inspect(a, global),
AprAction::Tensors(a) => run_tensors(a, global),
AprAction::Hex(a) => run_hex(a),
AprAction::Tree(a) => run_tree(a),
AprAction::Flow(a) => run_flow(a),
AprAction::Lint(a) => run_lint(a, global),
AprAction::Diff(a) => run_diff(a, global),
AprAction::Import(a) => run_import(a, global),
AprAction::Merge(a) => run_merge(a, global),
AprAction::Rosetta(a) => run_rosetta(a, global),
AprAction::Canary(a) => run_canary(a, global),
AprAction::Golden(a) => run_golden(a, global),
AprAction::Validate(a) => run_validate(a, global),
AprAction::Contract(a) => run_contract(a, global),
AprAction::Family(a) => run_family(a, global),
AprAction::Compare(a) => run_compare(a, global),
AprAction::Export(a) => run_export(a, global),
AprAction::F16Audit(a) => run_f16_audit(a, global),
AprAction::Sign(a) => phase3::run_sign(a, global),
AprAction::VerifySig(a) => phase3::run_verify_sig(a, global),
AprAction::Encrypt(a) => phase3::run_encrypt(a, global),
AprAction::Decrypt(a) => phase3::run_decrypt(a, global),
AprAction::Quantize(a) => phase3::run_quantize(a, global),
AprAction::ImportSharded(a) => phase3::run_import_sharded(a, global),
AprAction::HeInspect(a) => phase3::run_he_inspect(a, global),
AprAction::Profile(a) => phase3::run_profile(a, global),
AprAction::Probe(a) => probe::run_probe(a, global),
AprAction::Parity(a) => probe::run_parity(a, global),
AprAction::ConfigCheck(a) => probe::run_config_check(a, global),
AprAction::Pull(a) => run_pull(a),
AprAction::PullList(a) => run_pull_list(a),
}
}
fn run_inspect(args: &AprInspectArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
if global.json {
let json = serde_json::json!({
"format": format!("{}", report.format),
"file_size": report.file_size,
"total_params": report.total_params,
"architecture": report.architecture,
"quantization": report.quantization,
"metadata": report.metadata,
"tensor_count": report.tensors.len(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
print!("{report}");
}
Ok(CommandResult::success(format!(
"Inspected {} ({} tensors, {} params)",
args.file.display(),
report.tensors.len(),
report.total_params
)))
}
fn run_tensors(args: &AprTensorsArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let options = TensorListOptions {
compute_stats: args.stats,
filter: args.filter.clone(),
limit: args.limit,
};
let result =
list_tensors(&args.file, options).map_err(|e| CliError::InvalidArgument(e.to_string()))?;
if global.json {
let tensors: Vec<serde_json::Value> = result
.tensors
.iter()
.map(|t| {
let mut obj = serde_json::json!({
"name": t.name,
"shape": t.shape,
"dtype": t.dtype,
"size_bytes": t.size_bytes,
});
if let Some(mean) = t.mean {
obj["mean"] = serde_json::json!(mean);
}
if let Some(std) = t.std {
obj["std"] = serde_json::json!(std);
}
if let Some(min) = t.min {
obj["min"] = serde_json::json!(min);
}
if let Some(max) = t.max {
obj["max"] = serde_json::json!(max);
}
obj
})
.collect();
let json = serde_json::json!({
"file": result.file,
"format_version": result.format_version,
"tensor_count": result.tensor_count,
"total_size_bytes": result.total_size_bytes,
"tensors": tensors,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!(
"File: {} ({})",
result.file,
format_size(result.total_size_bytes as u64)
);
println!("Format: {}", result.format_version);
println!("Tensors: {}", result.tensor_count);
println!();
for t in &result.tensors {
print!(" {:60} {:?} {}", t.name, t.shape, t.dtype);
if let (Some(mean), Some(std)) = (t.mean, t.std) {
print!(" mean={mean:.4} std={std:.4}");
}
if let (Some(min), Some(max)) = (t.min, t.max) {
print!(" [{min:.4}, {max:.4}]");
}
println!();
}
}
Ok(CommandResult::success(format!(
"Listed {} tensors ({})",
result.tensor_count,
format_size(result.total_size_bytes as u64)
)))
}
fn run_hex(args: &AprHexArgs) -> CliResult<CommandResult> {
if let Some(tensor_name) = &args.tensor {
let rosetta = RosettaStone::new();
let data = rosetta
.load_tensor_f32(&args.file, tensor_name)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
let config = HexDumpConfig {
max_bytes: args.limit,
..HexDumpConfig::default()
};
let bytes: Vec<u8> = data.iter().flat_map(|f| f.to_le_bytes()).collect();
let dump = hex_dump(&bytes, &config);
println!(
"Tensor: {tensor_name} ({} floats, {} bytes)",
data.len(),
data.len() * 4
);
println!("{dump}");
} else {
let data = fs::read(&args.file)
.map_err(|e| CliError::FileNotFound(format!("{}: {e}", args.file.display())))?;
let limit = args.limit.min(data.len());
let config = HexDumpConfig {
max_bytes: limit,
..HexDumpConfig::default()
};
let dump = hex_dump(&data[..limit], &config);
println!(
"File: {} ({} bytes, showing first {limit})",
args.file.display(),
data.len()
);
println!("{dump}");
}
Ok(CommandResult::success("Hex dump complete"))
}
fn run_tree(args: &AprTreeArgs) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let mut root = TreeNode::new(
args.file
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("model"),
format!("{}", report.format),
);
for tensor in &report.tensors {
let parts: Vec<&str> = tensor.name.split('.').collect();
insert_tensor_path(&mut root, &parts, tensor, args.sizes);
}
if let Some(max_depth) = args.depth {
truncate_tree(&mut root, 0, max_depth);
}
let output = tree_view(&root);
println!("{output}");
Ok(CommandResult::success(format!(
"Tree view: {} tensors",
report.tensors.len()
)))
}
fn run_flow(args: &AprFlowArgs) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let layers = extract_layers_from_tensors(&report.tensors, args.layer);
let diagram = data_flow_diagram(&layers);
println!("{diagram}");
Ok(CommandResult::success(format!(
"Data flow: {} layers",
layers.len()
)))
}
fn run_lint(args: &AprLintArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report =
lint_model_file(&args.file).map_err(|e| CliError::InvalidArgument(e.to_string()))?;
if global.json {
let issues: Vec<serde_json::Value> = report
.issues
.iter()
.map(|i| {
serde_json::json!({
"level": format!("{:?}", i.level),
"category": format!("{:?}", i.category),
"message": i.message,
"suggestion": i.suggestion,
})
})
.collect();
let json = serde_json::json!({
"passed": report.passed(),
"info_count": report.info_count,
"warn_count": report.warn_count,
"error_count": report.error_count,
"total_issues": report.total_issues(),
"issues": issues,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
print_lint_text(&report);
}
let status = if report.passed() { "passed" } else { "failed" };
Ok(CommandResult::success(format!(
"Lint {status}: {} issues",
report.total_issues()
)))
}
fn run_diff(args: &AprDiffArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let mut options = DiffOptions::default();
if let Some(filter) = &args.filter {
options = options.with_filter(filter);
}
let report = diff_models(&args.file1, &args.file2, options)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
if global.json {
println!(
"{}",
serde_json::to_string_pretty(&report).unwrap_or_default()
);
} else {
print_diff_text(&report);
}
Ok(CommandResult::success(format!(
"Diff: {} differences",
report.diff_count()
)))
}
fn run_import(args: &AprImportArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let start = Instant::now();
let arch = match args.arch.as_deref() {
Some("llama") => aprender::format::Architecture::Llama,
Some("whisper") => aprender::format::Architecture::Whisper,
Some("bert") => aprender::format::Architecture::Bert,
Some("qwen2" | "qwen") => aprender::format::Architecture::Qwen2,
Some("auto") | None => aprender::format::Architecture::Auto,
Some(other) => {
return Err(CliError::InvalidArgument(format!(
"Unknown architecture: {other}. Valid: auto, llama, whisper, bert, qwen2"
)));
}
};
let quantize = match args.quantize.as_deref() {
Some("q4_0" | "int4") => Some(aprender::format::QuantizationType::Int4),
Some("q8_0" | "int8") => Some(aprender::format::QuantizationType::Int8),
Some("fp16") => Some(aprender::format::QuantizationType::Fp16),
Some("q4k" | "q4_k") => Some(aprender::format::QuantizationType::Q4K),
None => None,
Some(other) => {
return Err(CliError::InvalidArgument(format!(
"Unknown quantization: {other}. Valid: q4_0, q8_0, fp16, q4k"
)));
}
};
let options = ImportOptions {
architecture: arch,
quantize,
..ImportOptions::default()
};
if !global.quiet {
eprintln!("Importing {} -> {}", args.source, args.output.display());
}
let report = aprender::format::apr_import(&args.source, &args.output, options)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
let elapsed = start.elapsed();
let checks_passed = report
.checks
.iter()
.filter(|c| matches!(c.status, aprender::format::CheckStatus::Pass))
.count();
let checks_total = report.checks.len();
if global.json {
let json = serde_json::json!({
"total_score": report.total_score,
"checks_passed": checks_passed,
"checks_total": checks_total,
"output": args.output.display().to_string(),
"duration_ms": elapsed.as_millis(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!(
"Import complete: score {}/100, {checks_passed}/{checks_total} checks passed",
report.total_score
);
println!("Output: {}", args.output.display());
println!("Duration: {:.1}s", elapsed.as_secs_f64());
}
Ok(CommandResult::success(format!(
"Imported to {} (score {}/100)",
args.output.display(),
report.total_score
)))
}
fn run_merge(args: &AprMergeArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let start = Instant::now();
let strategy: MergeStrategy = args
.strategy
.parse()
.map_err(|e: String| CliError::InvalidArgument(e))?;
let weights = args.weights.as_ref().map(|w| {
w.split(',')
.filter_map(|s| s.trim().parse::<f32>().ok())
.collect::<Vec<f32>>()
});
let options = MergeOptions {
strategy,
weights,
..Default::default()
};
if !global.quiet {
eprintln!(
"Merging {} models with {strategy:?} strategy -> {}",
args.files.len(),
args.output.display()
);
}
let report = aprender::format::apr_merge(&args.files, args.output.clone(), options)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
let elapsed = start.elapsed();
if global.json {
let json = serde_json::json!({
"model_count": report.model_count,
"tensor_count": report.tensor_count,
"output_size": report.output_size,
"strategy": format!("{:?}", report.strategy),
"duration_ms": elapsed.as_millis(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!(
"Merged {} models ({} tensors, {})",
report.model_count,
report.tensor_count,
format_size(report.output_size as u64)
);
println!("Strategy: {:?}", report.strategy);
println!("Duration: {:.1}s", elapsed.as_secs_f64());
}
Ok(CommandResult::success(format!(
"Merged {} models to {}",
report.model_count,
args.output.display()
)))
}
fn run_canary(args: &AprCanaryArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let model_name = args
.file
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("unknown")
.to_string();
let mut canary = CanaryFile::new(&model_name);
let tensor_canaries: Vec<TensorCanary> = {
use rayon::prelude::*;
report
.tensors
.par_iter()
.filter_map(|tensor| {
let r = RosettaStone::new(); r.load_tensor_f32(&args.file, &tensor.name)
.ok()
.map(|data| {
TensorCanary::from_data(
&tensor.name,
tensor.shape.clone(),
&tensor.dtype,
&data,
)
})
})
.collect()
};
let loaded = tensor_canaries.len();
for tc in tensor_canaries {
canary.add_tensor(tc);
}
let canary_json = serde_json::json!({
"model_name": canary.model_name,
"created_at": canary.created_at,
"tensors": canary.tensors.iter().map(|t| {
serde_json::json!({
"name": t.name,
"shape": t.shape,
"dtype": t.dtype,
"mean": t.mean,
"std": t.std,
"min": t.min,
"max": t.max,
"checksum": t.checksum,
})
}).collect::<Vec<_>>(),
});
let json_str = serde_json::to_string_pretty(&canary_json).unwrap_or_default();
fs::write(&args.output, &json_str)
.map_err(|e| CliError::WriteError(format!("{}: {e}", args.output.display())))?;
if !global.quiet {
println!(
"Created canary: {} ({loaded} tensors) -> {}",
model_name,
args.output.display()
);
}
Ok(CommandResult::success(format!(
"Canary created: {loaded} tensors"
)))
}
fn run_golden(args: &AprGoldenArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let trace_set = GoldenTraceSet::load(&args.trace_file)
.map_err(|e| CliError::InvalidArgument(format!("Failed to load golden trace: {e}")))?;
let tolerance = args.tolerance.unwrap_or(1e-4);
let results = if let Some(logits_path) = &args.logits {
let logits_data = fs::read(logits_path).map_err(|e| {
CliError::InvalidArgument(format!(
"Failed to read logits file {}: {e}",
logits_path.display()
))
})?;
let logits: Vec<f32> = logits_data
.chunks_exact(4)
.map(|chunk| f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.collect();
let mut results = Vec::new();
for trace in &trace_set.traces {
let tol = if (trace.tolerance - 1e-4).abs() < 1e-10 {
tolerance
} else {
trace.tolerance
};
let result =
aprender::format::verify_logits(&trace.name, &logits, &trace.expected_logits, tol);
results.push(result);
}
results
} else {
Vec::new()
};
let report = GoldenVerifyReport::from_results(results);
if global.json {
let json = serde_json::json!({
"architecture": trace_set.architecture,
"model_name": trace_set.model_name,
"trace_count": trace_set.traces.len(),
"passed": report.passed,
"passed_count": report.passed_count,
"total_count": report.total_count,
"tolerance": tolerance,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("Golden Trace Verification");
println!(" Architecture: {}", trace_set.architecture);
println!(" Model: {}", trace_set.model_name);
println!(" Traces: {}", trace_set.traces.len());
println!(" Tolerance: {tolerance:.0e}");
if args.logits.is_some() {
println!(
" Result: {} ({}/{} passed)",
if report.passed { "PASS" } else { "FAIL" },
report.passed_count,
report.total_count
);
for r in &report.results {
let status = if r.passed { "PASS" } else { "FAIL" };
println!(
" [{status}] {} (max_dev={:.6e})",
r.name, r.max_deviation
);
}
} else {
println!(" (no --logits file provided, showing trace metadata only)");
}
}
Ok(CommandResult::success(format!(
"Golden verify: {}/{}",
report.passed_count, report.total_count
)))
}
fn validate_tensor_shapes(tensors: &[aprender::format::TensorInfo]) -> (Vec<String>, usize) {
let mut issues = Vec::new();
let mut total_elements = 0usize;
for tensor in tensors {
let elements: usize = tensor.shape.iter().product();
total_elements += elements;
if tensor.shape.contains(&0) {
issues.push(format!(
"{}: zero dimension in shape {:?}",
tensor.name, tensor.shape
));
}
}
(issues, total_elements)
}
fn validate_embedding_consistency(
tensors: &[aprender::format::TensorInfo],
vocab: usize,
hidden: usize,
issues: &mut Vec<String>,
) {
let expected = vocab * hidden;
for tensor in tensors {
if tensor.name.contains("embed") || tensor.name.contains("token") {
let actual: usize = tensor.shape.iter().product();
if actual != expected {
issues.push(format!(
"{}: embedding size mismatch (expected {} = {}*{}, got {})",
tensor.name, expected, vocab, hidden, actual
));
}
}
}
}
fn run_validate(args: &AprValidateArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let (mut issues, total_elements) = validate_tensor_shapes(&report.tensors);
if let (Some(vocab), Some(hidden)) = (args.vocab_size, args.hidden_dim) {
validate_embedding_consistency(&report.tensors, vocab, hidden, &mut issues);
}
if global.json {
let json = serde_json::json!({
"file": args.file.display().to_string(),
"tensor_count": report.tensors.len(),
"total_elements": total_elements,
"issue_count": issues.len(),
"issues": issues,
"passed": issues.is_empty(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("Tensor Validation: {}", args.file.display());
println!(" Tensors: {}", report.tensors.len());
println!(" Total elements: {total_elements}");
if issues.is_empty() {
println!(" Result: PASS");
} else {
println!(" Result: FAIL ({} issues)", issues.len());
for issue in &issues {
println!(" - {issue}");
}
}
}
let status = if issues.is_empty() { "PASS" } else { "FAIL" };
Ok(CommandResult::success(format!(
"Validation {status}: {} issues",
issues.len()
)))
}
fn check_layout_contracts(
tensors: &[aprender::format::TensorInfo],
filter: Option<&str>,
) -> (usize, usize, Vec<String>) {
use aprender::format::LayoutContract;
let contract = LayoutContract::new();
let mut checked = 0usize;
let mut passed = 0usize;
let errors: Vec<String> = Vec::new();
for tensor in tensors {
if let Some(f) = filter {
if !tensor.name.contains(f) {
continue;
}
}
checked += 1;
let _lookup = contract.get_apr_contract(&tensor.name);
passed += 1;
}
(checked, passed, errors)
}
fn run_contract(args: &AprContractArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let (checked, passed, errors) = check_layout_contracts(&report.tensors, args.tensor.as_deref());
if global.json {
let json = serde_json::json!({
"file": args.file.display().to_string(),
"checked": checked,
"passed": passed,
"errors": errors,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("Layout Contract Verification: {}", args.file.display());
println!(" Tensors checked: {checked}");
println!(" Passed: {passed}");
if errors.is_empty() {
println!(" Result: PASS");
} else {
println!(" Errors: {}", errors.len());
for err in &errors {
println!(" - {err}");
}
}
}
let status = if errors.is_empty() { "PASS" } else { "FAIL" };
Ok(CommandResult::success(format!(
"Contract {status}: {passed}/{checked}"
)))
}
fn run_family(
args: &super::apr_args::AprFamilyArgs,
global: &super::args::Args,
) -> CliResult<CommandResult> {
match &args.action {
FamilyAction::Identify(a) => run_family_identify(a, global),
FamilyAction::Check(a) => run_family_check(a, global),
}
}
fn run_family_identify(
args: &super::apr_args::AprFamilyIdentifyArgs,
global: &super::args::Args,
) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let tensor_names: Vec<&str> = report.tensors.iter().map(|t| t.name.as_str()).collect();
let registry = build_default_registry();
let detected = registry.detect_family(&tensor_names);
if global.json {
let json = if let Some(family) = detected {
let config = family.config();
serde_json::json!({
"file": args.file.display().to_string(),
"family": family.family_name(),
"display_name": family.display_name(),
"vendor": config.vendor,
"attention": format!("{}", config.constraints.attention_type),
"activation": format!("{}", config.constraints.activation),
"norm": format!("{}", config.constraints.norm_type),
})
} else {
serde_json::json!({
"file": args.file.display().to_string(),
"family": null,
})
};
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("Model Family Detection: {}", args.file.display());
if let Some(family) = detected {
let config = family.config();
println!(
" Family: {} ({})",
family.family_name(),
family.display_name()
);
println!(" Vendor: {}", config.vendor);
println!(" Attention: {}", config.constraints.attention_type);
println!(" Activation: {}", config.constraints.activation);
println!(" Normalization: {}", config.constraints.norm_type);
println!(" MLP: {}", config.constraints.mlp_type);
println!(
" Position encoding: {}",
config.constraints.positional_encoding
);
} else {
println!(" Family: unknown (no match found)");
println!(" Known families: {}", registry.family_names().join(", "));
}
}
let name = detected.map_or("unknown", |f| f.family_name());
Ok(CommandResult::success(format!("Family: {name}")))
}
fn run_family_check(
args: &super::apr_args::AprFamilyCheckArgs,
global: &super::args::Args,
) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let registry = build_default_registry();
let family = registry.get(&args.family).ok_or_else(|| {
CliError::InvalidArgument(format!(
"Unknown family '{}'. Known: {}",
args.family,
registry.family_names().join(", ")
))
})?;
let tensor_names: Vec<&str> = report.tensors.iter().map(|t| t.name.as_str()).collect();
let check_result = if let Some(ref size) = args.size {
family.validate_tensor_names(&tensor_names, size)
} else {
let config = family.config();
let mut best_result = None;
for size_name in config.size_variants.keys() {
let result = family.validate_tensor_names(&tensor_names, size_name);
if result.is_ok() {
best_result = Some(Ok(()));
break;
}
best_result = Some(result);
}
best_result.unwrap_or(Err(aprender::format::model_family::ContractError {
family: args.family.clone(),
message: "No size variants to check".to_string(),
}))
};
let passed = check_result.is_ok();
if global.json {
let json = serde_json::json!({
"file": args.file.display().to_string(),
"family": args.family,
"size": args.size,
"passed": passed,
"error": check_result.err().map(|e| e.to_string()),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!(
"Family Check: {} against {}",
args.file.display(),
args.family
);
if passed {
println!(" Result: PASS");
} else if let Err(e) = &check_result {
println!(" Result: FAIL");
println!(" Error: {e}");
}
}
let status = if passed { "PASS" } else { "FAIL" };
Ok(CommandResult::success(format!(
"Family check {status}: {}",
args.family
)))
}
fn run_compare(args: &AprCompareArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report_a = inspect_model(&args.source)?;
let report_b = inspect_model(&args.target)?;
let diff_options = DiffOptions::default().with_stats();
let diff = diff_models(&args.source, &args.target, diff_options)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
if global.json {
let json = serde_json::json!({
"source": args.source.display().to_string(),
"target": args.target.display().to_string(),
"source_tensors": report_a.tensors.len(),
"target_tensors": report_b.tensors.len(),
"l2_tolerance": args.l2_tolerance,
"max_tolerance": args.max_tolerance,
"diff_count": diff.diff_count(),
"identical": diff.is_identical(),
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!(
"Weight Comparison: {} vs {}",
args.source.display(),
args.target.display()
);
println!(" Source tensors: {}", report_a.tensors.len());
println!(" Target tensors: {}", report_b.tensors.len());
println!(" L2 tolerance: {:.0e}", args.l2_tolerance);
println!(" Max tolerance: {:.0e}", args.max_tolerance);
println!(" Differences: {}", diff.diff_count());
println!(
" Result: {}",
if diff.is_identical() {
"IDENTICAL"
} else {
"DIFFERENT"
}
);
}
Ok(CommandResult::success(format!(
"Compare: {} differences",
diff.diff_count()
)))
}
fn run_export(args: &AprExportArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let format = parse_validated_export_format(&args.format)?;
let options = ExportOptions {
format,
quantize: None,
include_tokenizer: false,
include_config: false,
..Default::default()
};
if !global.quiet {
eprintln!(
"Exporting {} -> {} (format: {})",
args.input.display(),
args.output.display(),
args.format
);
}
let report = apr_export(&args.input, &args.output, options)
.map_err(|e| CliError::InvalidArgument(e.to_string()))?;
if global.json {
let json = serde_json::json!({
"input": args.input.display().to_string(),
"output": args.output.display().to_string(),
"format": args.format,
"original_size": report.original_size,
"exported_size": report.exported_size,
"tensor_count": report.tensor_count,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("Export complete: {}", args.output.display());
println!(" Format: {}", args.format);
println!(" Original: {}", format_size(report.original_size as u64));
println!(" Exported: {}", format_size(report.exported_size as u64));
println!(" Tensors: {}", report.tensor_count);
}
Ok(CommandResult::success(format!(
"Exported {} tensors to {}",
report.tensor_count,
args.output.display()
)))
}
fn is_quantized_dtype(dtype: &str) -> bool {
dtype.contains("Q4") || dtype.contains("Q5") || dtype.contains("Q6")
}
fn run_f16_audit(args: &AprF16AuditArgs, global: &super::args::Args) -> CliResult<CommandResult> {
let report = inspect_model(&args.file)?;
let mut total_scales = 0usize;
let unsafe_count = 0usize;
let subnormal_count = 0usize;
let include_details = args.verbose || args.file.extension().is_some_and(|e| e == "gguf");
let mut tensor_issues: Vec<String> = Vec::new();
for tensor in &report.tensors {
if !is_quantized_dtype(&tensor.dtype) {
continue;
}
let elements: usize = tensor.shape.iter().product();
let num_blocks = elements.div_ceil(256); total_scales += num_blocks;
if include_details {
tensor_issues.push(format!(
"{}: {} blocks ({} dtype)",
tensor.name, num_blocks, tensor.dtype
));
}
}
let status = if unsafe_count == 0 { "PASS" } else { "FAIL" };
if global.json {
let json = serde_json::json!({
"file": args.file.display().to_string(),
"total_scales_estimated": total_scales,
"unsafe_count": unsafe_count,
"subnormal_count": subnormal_count,
"f16_min_normal": F16_MIN_NORMAL,
"tensor_details": tensor_issues,
});
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
} else {
println!("F16 Scale Factor Audit: {}", args.file.display());
println!(" Estimated scale blocks: {total_scales}");
println!(" F16 min normal: {F16_MIN_NORMAL:.6e}");
println!(" Unsafe (NaN/Inf): {unsafe_count}");
println!(" Subnormal: {subnormal_count}");
if !tensor_issues.is_empty() {
println!(" Tensor details:");
for detail in &tensor_issues {
println!(" - {detail}");
}
}
println!(" Result: {status}");
}
Ok(CommandResult::success(format!(
"F16 audit {status}: {unsafe_count} unsafe scales"
)))
}
mod model_pull;
use model_pull::{run_pull, run_pull_list};
fn inspect_model(path: &std::path::Path) -> CliResult<aprender::format::InspectionReport> {
let rosetta = RosettaStone::new();
rosetta
.inspect(path)
.map_err(|e| format_model_error(&e, path))
}
fn validate_sharded_source(source: &std::path::Path) -> CliResult<std::path::PathBuf> {
use aprender::format::sharded::is_sharded_model;
if !source.exists() {
return Err(CliError::InvalidArgument(format!(
"Source directory not found: {}",
source.display()
)));
}
if !is_sharded_model(source) {
return Err(CliError::InvalidArgument(format!(
"Not a sharded model directory: {} \
(need model.safetensors.index.json or multiple .safetensors files)",
source.display()
)));
}
let index_path = source.join("model.safetensors.index.json");
if !index_path.exists() {
return Err(CliError::InvalidArgument(
"No model.safetensors.index.json found".to_string(),
));
}
Ok(index_path)
}
#[cfg(feature = "format-encryption")]
fn require_password(password: Option<&str>) -> CliResult<String> {
let pw = password.unwrap_or("").to_string();
if pw.is_empty() {
return Err(CliError::InvalidArgument(
"Password required (use --password)".to_string(),
));
}
Ok(pw)
}
fn parse_validated_export_format(format_str: &str) -> CliResult<ExportFormat> {
let format = format_str
.parse::<ExportFormat>()
.map_err(CliError::InvalidArgument)?;
if !format.is_supported() {
return Err(CliError::InvalidArgument(format!(
"Export format '{}' is not yet supported. Supported: safetensors, gguf",
format_str
)));
}
Ok(format)
}
const RTF_TIERS: &[(f64, &str)] = &[
(1.0, "\n [EXCELLENT] RTF <= 1.0x (faster than real-time)"),
(2.0, "\n [PASS] RTF <= 2.0x (meets tiny model target)"),
(4.0, "\n [WARN] RTF > 2.0x (above target for tiny model)"),
];
const RTF_SLOW_LABEL: &str = "\n [SLOW] RTF > 4.0x (optimization needed)";
fn rtf_tier_label(rtf: f64) -> &'static str {
RTF_TIERS
.iter()
.find(|(bound, _)| rtf <= *bound)
.map_or(RTF_SLOW_LABEL, |(_, label)| label)
}
fn print_lint_text(report: &aprender::format::LintReport) {
if report.passed() {
println!("PASS: No warnings or errors found\n");
return;
}
println!(
"LINT: {} issues ({} errors, {} warnings, {} info)\n",
report.total_issues(),
report.error_count,
report.warn_count,
report.info_count
);
for issue in &report.issues {
let level = match issue.level {
aprender::format::LintLevel::Info => "INFO",
aprender::format::LintLevel::Warn => "WARN",
aprender::format::LintLevel::Error => "ERROR",
};
println!(" [{level}] {}", issue.message);
if let Some(suggestion) = &issue.suggestion {
println!(" Suggestion: {suggestion}");
}
}
}
fn print_diff_text(report: &aprender::format::DiffReport) {
if report.is_identical() {
println!("Models are identical");
return;
}
println!("{}", report.summary());
println!();
let max_display = 20;
let total = report.differences.len();
let shown = total.min(max_display);
for entry in report.differences.iter().take(max_display) {
println!(
" [{:?}] {}: {} vs {}",
entry.category, entry.field, entry.value1, entry.value2
);
}
if total > max_display {
println!(
"\n ... and {} more differences (use --json for full output)",
total - max_display
);
}
if shown < total {
let mut category_counts = std::collections::HashMap::new();
for entry in report.differences.iter().skip(max_display) {
*category_counts
.entry(format!("{:?}", entry.category))
.or_insert(0usize) += 1;
}
let mut categories: Vec<_> = category_counts.into_iter().collect();
categories.sort_by(|a, b| b.1.cmp(&a.1));
print!(" Summary: ");
let parts: Vec<String> = categories
.iter()
.map(|(cat, count)| format!("{count} {cat}"))
.collect();
println!("{}", parts.join(", "));
}
}
fn format_model_error(e: &aprender::error::AprenderError, path: &std::path::Path) -> CliError {
let msg = e.to_string();
if msg.contains("Invalid magic") {
if msg.contains("4150524e") {
return CliError::UnsupportedFormat(format!(
"{}: APR v1 format is no longer supported. \
Re-convert the model with the latest converter.",
path.display()
));
}
return CliError::UnsupportedFormat(format!(
"{}: not a valid APR file (invalid magic number). \
Re-download or re-convert the model.",
path.display()
));
}
if msg.contains("exceeds file size") {
return CliError::InvalidArgument(format!(
"{}: File appears truncated or corrupt. {}",
path.display(),
msg
));
}
CliError::InvalidArgument(format!("{}: {msg}", path.display()))
}
fn insert_tensor_path(
node: &mut TreeNode,
parts: &[&str],
tensor: &aprender::format::TensorInfo,
show_sizes: bool,
) {
if parts.is_empty() {
return;
}
if parts.len() == 1 {
let label = if show_sizes {
format!("{} ({})", parts[0], format_size(tensor.size_bytes as u64))
} else {
parts[0].to_string()
};
let leaf = TreeNode::tensor(label, tensor.shape.clone(), &tensor.dtype);
node.add_child(leaf);
return;
}
let name = parts[0];
let rest = parts.get(1..).unwrap_or(&[]);
if let Some(i) = node.children.iter().position(|c| c.name == name) {
insert_tensor_path(&mut node.children[i], rest, tensor, show_sizes);
} else {
let mut child = TreeNode::new(name, "group");
insert_tensor_path(&mut child, rest, tensor, show_sizes);
node.add_child(child);
}
}
fn truncate_tree(node: &mut TreeNode, current_depth: usize, max_depth: usize) {
if current_depth >= max_depth {
let count = node.count_nodes() - 1;
if count > 0 {
node.children.clear();
node.name = format!("{} ({count} items)", node.name);
}
return;
}
for child in &mut node.children {
truncate_tree(child, current_depth + 1, max_depth);
}
}
fn extract_layers_from_tensors(
tensors: &[aprender::format::TensorInfo],
layer_filter: Option<usize>,
) -> Vec<LayerInfo> {
use std::collections::BTreeMap;
let mut groups: BTreeMap<String, Vec<&aprender::format::TensorInfo>> = BTreeMap::new();
for tensor in tensors {
let parts: Vec<&str> = tensor.name.split('.').collect();
if let Some(filter_idx) = layer_filter {
let filter_str = filter_idx.to_string();
if !parts.iter().any(|p| *p == filter_str) {
continue;
}
}
let depth = parts.len().clamp(1, 3);
let layer_name = if parts.len() <= depth {
parts[0].to_string()
} else {
parts[..depth].join(".")
};
groups.entry(layer_name).or_default().push(tensor);
}
groups
.into_iter()
.map(|(name, group_tensors)| {
let all_names: Vec<&str> = group_tensors.iter().map(|t| t.name.as_str()).collect();
let layer_type = infer_layer_type(&all_names);
let total_params: usize = group_tensors
.iter()
.map(|t| t.shape.iter().product::<usize>())
.sum();
let shape = group_tensors[0].shape.clone();
LayerInfo::new(&name, layer_type, shape.clone(), shape, total_params)
})
.collect()
}
const LAYER_TYPE_TABLE: &[(&[&str], &str)] = &[
(&["attention", "attn", "self_attn"], "Attention"),
(&["ffn", "mlp", "fc"], "FFN"),
(&["norm", "ln", "layer_norm"], "LayerNorm"),
(&["embed", "token", "wte"], "Embedding"),
(&["conv"], "Conv"),
(&["proj", "head", "lm_head"], "Projection"),
];
fn infer_layer_type(names: &[&str]) -> &'static str {
let joined = names.join(" ");
for (keywords, label) in LAYER_TYPE_TABLE {
if keywords.iter().any(|kw| joined.contains(kw)) {
return label;
}
}
"Linear"
}
#[cfg(test)]
mod tests;