SafeTensors Format

SafeTensors is the recommended format for production models in entrenar. It provides security, efficiency, and full HuggingFace Hub compatibility.

Why SafeTensors?

SafeTensors was developed by HuggingFace to address security concerns with Python's pickle format. Key benefits:

  • Security: No arbitrary code execution (pickle files can run malicious code)
  • Zero-copy loading: Memory-mapped tensor access without full deserialization
  • Cross-platform: Works consistently across Python, Rust, JavaScript
  • HuggingFace compatible: Direct upload/download from HuggingFace Hub

File Structure

SafeTensors files have a simple binary structure:

┌─────────────────────────────────────────┐
│ Header Length (8 bytes, little-endian)  │
├─────────────────────────────────────────┤
│ JSON Header (variable length)           │
│ - Tensor metadata (names, shapes, types)│
│ - Custom metadata (__metadata__ key)    │
├─────────────────────────────────────────┤
│ Tensor Data (contiguous binary)         │
│ - Aligned to 8-byte boundaries          │
│ - Ordered by dtype then name            │
└─────────────────────────────────────────┘

Saving to SafeTensors

#![allow(unused)]
fn main() {
use entrenar::io::{Model, ModelMetadata, save_model, SaveConfig, ModelFormat};
use entrenar::Tensor;

// Create model with parameters
let params = vec![
    ("model.embed_tokens.weight".to_string(),
     Tensor::from_vec(vec![0.1; 4096 * 768], false)),
    ("model.layers.0.self_attn.q_proj.weight".to_string(),
     Tensor::from_vec(vec![0.01; 768 * 768], false)),
];

let metadata = ModelMetadata::new("my-llm", "llama");
let model = Model::new(metadata, params);

// Save as SafeTensors
let config = SaveConfig::new(ModelFormat::SafeTensors);
save_model(&model, "model.safetensors", &config)?;
}

Loading from SafeTensors

Format is auto-detected from file extension:

#![allow(unused)]
fn main() {
use entrenar::io::load_model;

let model = load_model("model.safetensors")?;

// Access metadata
println!("Model: {}", model.metadata.name);
println!("Architecture: {}", model.metadata.architecture);

// Access tensors
for (name, tensor) in &model.parameters {
    println!("{}: {} elements", name, tensor.len());
}
}

Custom Metadata

SafeTensors supports custom metadata stored in the __metadata__ header field:

#![allow(unused)]
fn main() {
// When saving, metadata is automatically included:
// - name: model name
// - architecture: model architecture
// - version: model version

// For merge operations, additional metadata is added:
// - merge_method: TIES, DARE, SLERP, or Average
// - tensor_count: number of tensors
}

CLI Usage

Merge to SafeTensors

# Output format is detected from extension
entrenar merge model1.safetensors model2.safetensors \
    --method ties \
    --output merged.safetensors

Inspect SafeTensors

# Use entrenar-inspect crate
entrenar-inspect model.safetensors

Memory-Mapped Loading

For large models, use memory mapping to avoid loading entire file into RAM:

#![allow(unused)]
fn main() {
use memmap2::MmapOptions;
use safetensors::SafeTensors;

let file = std::fs::File::open("large_model.safetensors")?;
let mmap = unsafe { MmapOptions::new().map(&file)? };
let tensors = SafeTensors::deserialize(&mmap)?;

// Tensors are loaded on-demand from mmap
for name in tensors.names() {
    let tensor = tensors.tensor(name)?;
    // Process tensor...
}
}

HuggingFace Hub Integration

Models saved in SafeTensors format can be directly uploaded:

# Using HuggingFace CLI
huggingface-cli upload my-org/my-model ./model.safetensors

# Or programmatically
huggingface-cli repo create my-org/my-model
huggingface-cli upload my-org/my-model ./model.safetensors model.safetensors

And downloaded models load directly:

#![allow(unused)]
fn main() {
use entrenar::hf_pipeline::HfModelFetcher;
use entrenar::io::load_model;

let fetcher = HfModelFetcher::new()?;
let artifact = fetcher.download_model(
    "microsoft/codebert-base",
    Default::default()
)?;

let model = load_model(&artifact.path)?;
}

Performance

Model SizeSave TimeLoad TimeFile Size
100MB~100ms~50ms100MB
1GB~1s~500ms1GB
7GB~7s~3s7GB

Compare to JSON format:

Model SizeJSON SaveJSON LoadJSON Size
100MB~3s~2.5s~300MB
1GB~30s~25s~3GB

Error Handling

#![allow(unused)]
fn main() {
use entrenar::io::load_model;
use entrenar::Error;

match load_model("model.safetensors") {
    Ok(model) => {
        println!("Loaded {} tensors", model.parameters.len());
    }
    Err(Error::Serialization(msg)) => {
        // Invalid SafeTensors format
        eprintln!("Parse error: {}", msg);
    }
    Err(Error::Io(e)) => {
        // File not found, permission denied, etc.
        eprintln!("IO error: {}", e);
    }
    Err(e) => {
        eprintln!("Other error: {}", e);
    }
}
}

See Also