open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-loader-gguf/examples/load_safetensors.rs
Claude fd25cb8b70 jouleclaw-loader-gguf: apply clippy's own suggestions, 81 -> 40
`cargo clippy --fix` only applies MachineApplicable suggestions, and most
of this crate's remaining lints carry MaybeIncorrect ones. Those are still
exact byte spans with exact replacement text; "maybe incorrect" means
clippy will not promise the semantics, not that the text is wrong. So
scripts/../apply-suggestions.py reads the JSON and applies them, and the
crate's 343 tests decide.

96 spans across 26 files: collapsible_if, redundant_guards,
needless_late_init, unnecessary_cast, manual_is_multiple_of,
manual_repeat_n, unnecessary_sort_by, manual_slice_size_calculation,
unused_mut, needless_lifetimes, needless_borrows_for_generic_args,
double_parens.

One suggestion class must never be applied this way, and it cost a
revert to learn: `needless_range_loop` emits a *template*, not a
replacement --

    for (i, <item>) in w.iter_mut().enumerate().take(total)

-- leaving both the binding name and the body's `w[i]` uses for a human.
Applied literally it writes `<item>` into the source, which does not
parse; four files failed to compile. They were reverted to HEAD and the
applier now refuses any replacement containing `<` and `>`.

The lesson that actually matters is the ordering: the first batch of 69
was verified green and then left uncommitted while a riskier batch ran on
top, so reverting the breakage could not be done without also losing
verified work. Commit verified progress before the next batch.

Verified: 0 errors, 343 tests pass.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-21 20:30:31 -04:00

99 lines
3.1 KiB
Rust

//! Real-model bring-up for safetensors files. Mirror of
//! `examples/load_model.rs` for the safetensors format.
//!
//! ```bash
//! cargo run --release --example load_safetensors -- path/to/model.safetensors
//! ```
use jouleclaw_loader_gguf::safetensors_reader::read_safetensors_file;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::time::Instant;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
eprintln!("usage: load_safetensors <path.safetensors>");
std::process::exit(1);
}
let path = PathBuf::from(&args[1]);
println!("model: {}", path.display());
let t0 = Instant::now();
let m = read_safetensors_file(&path).map_err(|e| format!("parse: {:?}", e))?;
let load_secs = t0.elapsed().as_secs_f64();
println!();
println!("=== load ===");
println!(" load_wall_clock_secs: {:.3}", load_secs);
println!(" tensor_count: {}", m.tensors.len());
println!(" metadata_keys: {}", m.metadata.len());
println!(" mmap_len: {} ({})", m.mmap.len(), humanize_bytes(m.mmap.len() as u64));
println!(" data_offset: {}", m.data_offset);
let total_data: u64 = m.tensors.iter().map(|t| t.data_len).sum();
println!();
println!("=== tensor data ===");
println!(" total_bytes: {} ({})", total_data, humanize_bytes(total_data));
// Dtype distribution.
let mut counts: BTreeMap<String, (u32, u64)> = BTreeMap::new();
for t in &m.tensors {
let key = format!("{:?}", t.dtype);
let e = counts.entry(key).or_insert((0, 0));
e.0 += 1;
e.1 += t.data_len;
}
println!();
println!("=== dtype distribution ===");
for (k, (c, b)) in &counts {
println!(" {:<10} count={:>5} size={}", k, c, humanize_bytes(*b));
}
// Top tensors by size.
let mut sorted: Vec<&_> = m.tensors.iter().collect();
sorted.sort_by_key(|a| std::cmp::Reverse(a.data_len));
println!();
println!("=== top 10 tensors by byte size ===");
for t in sorted.iter().take(10) {
println!(
" {:<60} {:>14} bytes ({})",
truncate(&t.name, 60),
t.data_len,
humanize_bytes(t.data_len),
);
}
// Metadata.
if !m.metadata.is_empty() {
println!();
println!("=== metadata ===");
for (k, v) in &m.metadata {
let v_display = if v.len() > 80 { format!("{}…", &v[..80]) } else { v.clone() };
println!(" {}: {}", k, v_display);
}
}
Ok(())
}
fn truncate(s: &str, max: usize) -> String {
if s.len() <= max {
s.to_string()
} else {
format!("…{}", &s[s.len() - max + 1..])
}
}
fn humanize_bytes(n: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if n >= GB {
format!("{:.2} GiB", n as f64 / GB as f64)
} else if n >= MB {
format!("{:.2} MiB", n as f64 / MB as f64)
} else if n >= KB {
format!("{:.2} KiB", n as f64 / KB as f64)
} else {
format!("{} B", n)
}
}