makepad/libs/ai/models/common/src/sharded.rs
Admin 7f59912916 libs/ai: one AI stack, replacing libs/ggml, llama, mlx, cuda, tts, voice2 and pbr_paint
The model code was spread across eight crates that had grown into each other:
ggml and cuda and mlx each owned part of a tensor runtime, llama and tts and
voice2 each owned part of a model, and libs/diffusion owned everything else.
They are now one tree with an explicit shape:

  libs/ai/cuda     — kernels and launch surface
  libs/ai/metal    — Metal shaders and the shim
  libs/ai/llm      — the language-model runtime (sessions, lanes, contexts,
                     the CUDA and Metal executors, the compiled Metal path)
  libs/ai/models/  — common, flux, h3, music, paint, speech, stems, vision

libs/diffusion is not deleted but demoted: what remains is the VALIDATOR
crate — several dozen `*_validate.rs` oracles that check a native
implementation against a reference, which is where they belong now that the
implementations live next door.

The functional work inside the move is mostly in the LLM runtime: N lanes that
draft while one verify batch serves all of them, per-slot prefill over a shared
folded attention arena, speculation that survives batching, and a scheduler
that reports rather than publishes. And in the CUDA build: a machine without
usable CUDA must still LINK (and say so), the default kernel arch is the
building machine's GPU, `NO_CUDA` forces the stub even where the toolkit
exists, and kernels compile in parallel with progress.

libs/video_flow is new here: classical optical flow estimation and the `mkfl`
motion-field payload — a flow field measured from a clip without a model,
which is what drives free-rate bounce-looping playback and the uprez/tween
enhance pipe.
2026-08-23 01:34:35 +02:00

160 lines
6.4 KiB
Rust

//! Plain sharded-safetensors-directory reader: every `*.safetensors` in a
//! dir (headers only, no index.json needed) or one single `.safetensors`
//! file, streamed per-tensor (never a whole-model host copy). Tensor names
//! are used as-is (canonical == file-local spelling) — family crates that
//! need name canonicalization (e.g. H3's video-VAE repack remap) or
//! quantized backends (GGUF/NVFP4) wrap this type as one variant of their
//! own weight-source enum instead of using it directly.
//!
//! This is the primary-split fallback for lane T6a (/aiarch.md §1): H3's
//! `H3ShardedWeights` (libs/diffusion/src/h3.rs) also does sharded
//! safetensors reading, but its struct is entangled with GGUF/NVFP4 variant
//! payloads that are H3-private, so it was left as-is rather than rebuilt
//! on top of this type. `Music3Shards` (libs/diffusion/src/music3_weights.rs)
//! only entangles a GGUF variant the same way, and its plain-safetensors
//! variant is a near-verbatim duplicate of this loader, so it was
//! re-pointed to wrap `ShardedSafetensors` instead of carrying its own copy.
use crate::error::{DiffusionError, Result};
use makepad_ai_loader::{MlxDType, MlxSafetensorsHeader};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub struct ShardedSafetensors {
pub dir: PathBuf,
shards: Vec<MlxSafetensorsHeader>,
map: HashMap<String, (usize, String)>,
}
impl ShardedSafetensors {
/// Open a safetensors weight source: either every `*.safetensors` in a
/// dir (headers only, no index.json needed) or one single `.safetensors`
/// file. `label` is used only in error messages (e.g. "h3 weights",
/// "music3 weights") so callers keep their existing diagnostics.
pub fn load(dir: impl AsRef<Path>, label: &str) -> Result<Self> {
let dir = dir.as_ref().to_path_buf();
let mut files: Vec<PathBuf> = if dir.is_file() {
vec![dir.clone()]
} else {
std::fs::read_dir(&dir)
.map_err(|err| {
DiffusionError::model(format!("{label} {}: {err}", dir.display()))
})?
.filter_map(|entry| entry.ok().map(|e| e.path()))
.filter(|path| {
path.extension()
.map(|ext| ext == "safetensors")
.unwrap_or(false)
})
.collect()
};
files.sort();
if files.is_empty() {
return Err(DiffusionError::model(format!(
"{label} {} holds no safetensors",
dir.display()
)));
}
let mut shards = Vec::with_capacity(files.len());
let mut map = HashMap::new();
for (index, path) in files.iter().enumerate() {
let header = MlxSafetensorsHeader::load(path)
.map_err(|err| DiffusionError::model(format!("{}: {err}", path.display())))?;
for name in header.tensors.keys() {
map.insert(name.clone(), (index, name.clone()));
}
shards.push(header);
}
let dir = if dir.is_file() {
dir.parent().map(|p| p.to_path_buf()).unwrap_or(dir)
} else {
dir
};
Ok(Self { dir, shards, map })
}
fn shard_for(&self, name: &str, label: &str) -> Result<(&MlxSafetensorsHeader, &str)> {
let (index, file_name) = self.map.get(name).ok_or_else(|| {
DiffusionError::model(format!(
"{label} tensor '{name}' not found in {}",
self.dir.display()
))
})?;
Ok((&self.shards[*index], file_name.as_str()))
}
pub fn has_tensor(&self, name: &str) -> bool {
self.map.contains_key(name)
}
/// Raw tensor payload as stored (safetensors BF16/F32 stream).
pub fn tensor_bytes(&self, name: &str, label: &str) -> Result<Vec<u8>> {
let (shard, file_name) = self.shard_for(name, label)?;
shard
.read_tensor_bytes(file_name)
.map_err(|err| DiffusionError::model(format!("{label} tensor '{name}': {err}")))
}
/// Contiguous rank-2 rows as stored (BF16/F32 bytes).
pub fn tensor_row_range_bytes(
&self,
name: &str,
row0: u64,
nrows: u64,
label: &str,
) -> Result<Vec<u8>> {
let (shard, file_name) = self.shard_for(name, label)?;
let mut out = Vec::new();
for row in row0..row0.saturating_add(nrows) {
let bytes = shard.read_rank2_row_bytes(file_name, row).map_err(|err| {
DiffusionError::model(format!(
"{label} tensor '{name}' rows {row0}+{nrows}: {err}"
))
})?;
if out.is_empty() {
out.reserve((nrows as usize).saturating_mul(bytes.len()));
}
out.extend_from_slice(&bytes);
}
Ok(out)
}
pub fn tensor_row_f32(&self, name: &str, row: u64, label: &str) -> Result<Vec<f32>> {
let (shard, file_name) = self.shard_for(name, label)?;
let entry = shard
.tensor(file_name)
.ok_or_else(|| DiffusionError::model(format!("{label} tensor '{name}' missing entry")))?;
let bytes = shard.read_rank2_row_bytes(file_name, row).map_err(|err| {
DiffusionError::model(format!("{label} tensor '{name}' row {row}: {err}"))
})?;
bytes_to_f32(&bytes, entry.dtype, name, label)
}
pub fn tensor_f32(&self, name: &str, label: &str) -> Result<Vec<f32>> {
let (shard, file_name) = self.shard_for(name, label)?;
let entry = shard
.tensor(file_name)
.ok_or_else(|| DiffusionError::model(format!("{label} tensor '{name}' missing entry")))?;
let bytes = self.tensor_bytes(name, label)?;
bytes_to_f32(&bytes, entry.dtype, name, label)
}
}
fn bytes_to_f32(bytes: &[u8], dtype: MlxDType, name: &str, label: &str) -> Result<Vec<f32>> {
match dtype {
MlxDType::F32 => Ok(bytes
.chunks_exact(4)
.map(|chunk| f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.collect()),
MlxDType::BF16 => Ok(bytes
.chunks_exact(2)
.map(|chunk| {
let word = u16::from_le_bytes([chunk[0], chunk[1]]);
f32::from_bits((word as u32) << 16)
})
.collect()),
other => Err(DiffusionError::model(format!(
"{label} tensor '{name}': unsupported dtype {other:?}"
))),
}
}