`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]>
99 lines
3.1 KiB
Rust
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)
|
|
}
|
|
}
|