use crate::rvf_container::RvfBuilder;
const RVFS_MAGIC: u32 = 0x5256_4653;
const HF_QUANT_MAGIC: u32 = 0x7745_5735;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelFormat {
Rvf,
Safetensors,
HfQuantBin,
JsonlManifest,
Unknown,
}
impl ModelFormat {
pub fn label(self) -> &'static str {
match self {
ModelFormat::Rvf => "RVF binary container (RVFS)",
ModelFormat::Safetensors => "safetensors weight file",
ModelFormat::HfQuantBin => "HuggingFace quantized weight blob (model-q*.bin)",
ModelFormat::JsonlManifest => "JSONL manifest (model.rvf.jsonl)",
ModelFormat::Unknown => "unknown format",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ModelLoadError {
#[error(
"model file is {detected} — the --model loader needs an RVF binary container. \
Convert it once with `sensing-server --convert-model <in> --convert-out model.rvf`, \
then load the .rvf. (accepted by --model: RVF binary container; \
convertible: safetensors, model.rvf.jsonl)"
)]
NeedsConversion {
detected: &'static str,
},
#[error(
"model file is a HuggingFace quantized weight blob (magic 0x{magic:08X}); \
no reader for this quantization format ships in this build. Use the \
full-precision `model.safetensors` from the same HF repo and convert it \
with `sensing-server --convert-model model.safetensors --convert-out model.rvf`."
)]
UnsupportedQuant {
magic: u32,
},
#[error(
"model file is an unknown format (first bytes 0x{first_bytes:08X}); \
accepted: RVF binary container (RVFS, 0x52564653); convertible: \
safetensors, model.rvf.jsonl. ({detail})"
)]
Unknown {
first_bytes: u32,
detail: String,
},
#[error("failed to convert {format} to RVF: {detail}")]
ConversionFailed {
format: &'static str,
detail: String,
},
}
pub fn detect_format(data: &[u8], name: &str) -> ModelFormat {
let name = name.to_ascii_lowercase();
if leading_u32(data) == Some(RVFS_MAGIC) {
return ModelFormat::Rvf;
}
if name.ends_with(".safetensors") || looks_like_safetensors(data) {
return ModelFormat::Safetensors;
}
if leading_u32(data) == Some(HF_QUANT_MAGIC) || name.ends_with(".bin") || name.contains("-q") {
return ModelFormat::HfQuantBin;
}
if name.ends_with(".jsonl") || name.ends_with(".rvf.jsonl") || data.first() == Some(&b'{') {
return ModelFormat::JsonlManifest;
}
ModelFormat::Unknown
}
pub fn classify_load_failure(data: &[u8], name: &str, detail: &str) -> ModelLoadError {
match detect_format(data, name) {
ModelFormat::Rvf => ModelLoadError::Unknown {
first_bytes: leading_u32(data).unwrap_or(0),
detail: format!("RVFS magic present but container parse failed: {detail}"),
},
ModelFormat::Safetensors => ModelLoadError::NeedsConversion {
detected: ModelFormat::Safetensors.label(),
},
ModelFormat::JsonlManifest => ModelLoadError::NeedsConversion {
detected: ModelFormat::JsonlManifest.label(),
},
ModelFormat::HfQuantBin => ModelLoadError::UnsupportedQuant {
magic: leading_u32(data).unwrap_or(HF_QUANT_MAGIC),
},
ModelFormat::Unknown => ModelLoadError::Unknown {
first_bytes: leading_u32(data).unwrap_or(0),
detail: detail.to_string(),
},
}
}
pub fn safetensors_to_rvf(data: &[u8], model_id: &str) -> Result<Vec<u8>, ModelLoadError> {
let fail = |d: String| ModelLoadError::ConversionFailed {
format: ModelFormat::Safetensors.label(),
detail: d,
};
if data.len() < 8 {
return Err(fail("file shorter than the 8-byte safetensors length header".into()));
}
let header_len = u64::from_le_bytes(data[0..8].try_into().unwrap()) as usize;
let header_start: usize = 8;
let header_end = header_start
.checked_add(header_len)
.filter(|&e| e <= data.len())
.ok_or_else(|| fail(format!("declared header length {header_len} exceeds file size")))?;
let header: serde_json::Value = serde_json::from_slice(&data[header_start..header_end])
.map_err(|e| fail(format!("safetensors header is not valid JSON: {e}")))?;
let obj = header
.as_object()
.ok_or_else(|| fail("safetensors header is not a JSON object".into()))?;
let tensor_base = header_end;
let mut weights: Vec<f32> = Vec::new();
let mut tensor_names: Vec<String> = Vec::new();
let mut entries: Vec<(&String, &serde_json::Value)> = obj
.iter()
.filter(|(k, _)| k.as_str() != "__metadata__")
.collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (tname, tinfo) in entries {
let dtype = tinfo.get("dtype").and_then(|d| d.as_str()).unwrap_or("");
let offsets = tinfo
.get("data_offsets")
.and_then(|o| o.as_array())
.and_then(|a| {
Some((a.first()?.as_u64()? as usize, a.get(1)?.as_u64()? as usize))
});
let Some((start, end)) = offsets else { continue };
let abs_start = tensor_base.checked_add(start);
let abs_end = tensor_base.checked_add(end);
match (abs_start, abs_end) {
(Some(s), Some(e)) if e <= data.len() && s <= e => {
if dtype == "F32" {
let bytes = &data[s..e];
if bytes.len() % 4 == 0 {
for chunk in bytes.chunks_exact(4) {
weights.push(f32::from_le_bytes([
chunk[0], chunk[1], chunk[2], chunk[3],
]));
}
tensor_names.push(tname.clone());
}
}
}
_ => {
return Err(fail(format!(
"tensor `{tname}` data_offsets [{start}..{end}] out of bounds"
)));
}
}
}
if weights.is_empty() {
return Err(fail(
"no F32 tensors found to convert (the published weights may be quantized; \
use a full-precision safetensors export)"
.into(),
));
}
let mut builder = RvfBuilder::new();
builder.add_manifest(
model_id,
"converted-from-safetensors",
"RVF container converted from model.safetensors (issue #894)",
);
builder.add_weights(&weights);
builder.add_metadata(&serde_json::json!({
"source_format": "safetensors",
"converted_tensors": tensor_names,
"n_weights": weights.len(),
"note": "weights loaded; pose-decoder architecture may differ — see #894",
}));
Ok(builder.build())
}
pub fn jsonl_to_rvf(data: &[u8], model_id: &str) -> Result<Vec<u8>, ModelLoadError> {
let fail = |d: String| ModelLoadError::ConversionFailed {
format: ModelFormat::JsonlManifest.label(),
detail: d,
};
let text = std::str::from_utf8(data).map_err(|e| fail(format!("not valid UTF-8: {e}")))?;
let mut lines: Vec<serde_json::Value> = Vec::new();
let mut weights: Vec<f32> = Vec::new();
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let v: serde_json::Value = serde_json::from_str(line)
.map_err(|e| fail(format!("line is not valid JSON: {e}")))?;
if let Some(arr) = v.get("weights").and_then(|w| w.as_array()) {
for x in arr {
if let Some(f) = x.as_f64() {
weights.push(f as f32);
}
}
}
lines.push(v);
}
if lines.is_empty() {
return Err(fail("manifest contained no JSON lines".into()));
}
let mut builder = RvfBuilder::new();
builder.add_manifest(
model_id,
"converted-from-jsonl",
"RVF container converted from model.rvf.jsonl (issue #894)",
);
if !weights.is_empty() {
builder.add_weights(&weights);
}
builder.add_metadata(&serde_json::json!({
"source_format": "rvf.jsonl",
"n_lines": lines.len(),
"n_weights": weights.len(),
}));
Ok(builder.build())
}
pub fn convert_to_rvf(data: &[u8], name: &str, model_id: &str) -> Result<Vec<u8>, ModelLoadError> {
match detect_format(data, name) {
ModelFormat::Rvf => Ok(data.to_vec()), ModelFormat::Safetensors => safetensors_to_rvf(data, model_id),
ModelFormat::JsonlManifest => jsonl_to_rvf(data, model_id),
ModelFormat::HfQuantBin => Err(ModelLoadError::UnsupportedQuant {
magic: leading_u32(data).unwrap_or(HF_QUANT_MAGIC),
}),
ModelFormat::Unknown => Err(ModelLoadError::Unknown {
first_bytes: leading_u32(data).unwrap_or(0),
detail: "not a convertible model format".into(),
}),
}
}
fn leading_u32(data: &[u8]) -> Option<u32> {
data.get(0..4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
fn looks_like_safetensors(data: &[u8]) -> bool {
if data.len() < 9 || data[8] != b'{' {
return false;
}
let header_len = u64::from_le_bytes(data[0..8].try_into().unwrap());
header_len >= 2 && header_len <= 64 * 1024 * 1024
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rvf_pipeline::ProgressiveLoader;
fn make_safetensors(weights: &[f32]) -> Vec<u8> {
let n = weights.len();
let header = serde_json::json!({
"weight": {
"dtype": "F32",
"shape": [n],
"data_offsets": [0, n * 4],
}
});
let header_bytes = serde_json::to_vec(&header).unwrap();
let mut out = Vec::new();
out.extend_from_slice(&(header_bytes.len() as u64).to_le_bytes());
out.extend_from_slice(&header_bytes);
for &w in weights {
out.extend_from_slice(&w.to_le_bytes());
}
out
}
#[test]
fn detects_safetensors_by_magic_and_name() {
let st = make_safetensors(&[1.0, 2.0, 3.0]);
assert_eq!(detect_format(&st, "model.safetensors"), ModelFormat::Safetensors);
assert_eq!(detect_format(&st, ""), ModelFormat::Safetensors); }
#[test]
fn detects_hf_quant_magic() {
let data = [0x35u8, 0x57, 0x45, 0x77, 0xAA, 0xBB];
assert_eq!(leading_u32(&data), Some(HF_QUANT_MAGIC));
assert_eq!(detect_format(&data, "model-q4.bin"), ModelFormat::HfQuantBin);
assert_eq!(detect_format(&data, ""), ModelFormat::HfQuantBin); }
#[test]
fn detects_jsonl_and_rvf() {
assert_eq!(detect_format(b"{\"seg\":0}\n", "model.rvf.jsonl"), ModelFormat::JsonlManifest);
let rvfs = RVFS_MAGIC.to_le_bytes();
assert_eq!(detect_format(&rvfs, "model.rvf"), ModelFormat::Rvf);
}
#[test]
fn safetensors_converts_and_loads() {
let st = make_safetensors(&[1.0, 2.0, 3.0, 4.0]);
let rvf = safetensors_to_rvf(&st, "wifi-densepose-pretrained")
.expect("safetensors must convert to RVF");
assert_eq!(leading_u32(&rvf), Some(RVFS_MAGIC));
let mut loader = ProgressiveLoader::new(&rvf).expect("converted RVF must load");
let la = loader.load_layer_a().expect("Layer A");
assert_eq!(la.model_name, "wifi-densepose-pretrained");
let lc = loader.load_layer_c().expect("Layer C");
assert_eq!(lc.all_weights, vec![1.0, 2.0, 3.0, 4.0], "weights round-trip");
}
#[test]
fn hf_quant_classifies_to_actionable_error() {
let data = [0x35u8, 0x57, 0x45, 0x77];
let err = classify_load_failure(
&data,
"model-q4.bin",
"invalid magic at offset 0: expected 0x52564653, got 0x77455735",
);
assert!(matches!(err, ModelLoadError::UnsupportedQuant { magic } if magic == HF_QUANT_MAGIC));
let msg = err.to_string();
assert!(msg.contains("safetensors"), "must point at the loadable format: {msg}");
assert!(!msg.contains("invalid magic at offset"), "must not leak opaque magic: {msg}");
}
#[test]
fn safetensors_classifies_to_needs_conversion() {
let st = make_safetensors(&[1.0]);
let err = classify_load_failure(&st, "model.safetensors", "invalid magic …");
assert!(matches!(err, ModelLoadError::NeedsConversion { .. }));
let msg = err.to_string();
assert!(msg.contains("--convert-model"), "must give the convert command: {msg}");
}
#[test]
fn jsonl_converts_and_loads() {
let jsonl = b"{\"model_id\":\"x\"}\n{\"weights\":[1.0,2.0]}\n";
let rvf = jsonl_to_rvf(jsonl, "x").expect("jsonl converts");
let mut loader = ProgressiveLoader::new(&rvf).expect("converted jsonl loads");
let _ = loader.load_layer_a().expect("Layer A");
let lc = loader.load_layer_c().expect("Layer C");
assert_eq!(lc.all_weights, vec![1.0, 2.0]);
}
#[test]
fn convert_to_rvf_dispatches_and_rejects_quant() {
let st = make_safetensors(&[5.0]);
assert!(convert_to_rvf(&st, "model.safetensors", "m").is_ok());
let quant = [0x35u8, 0x57, 0x45, 0x77];
assert!(matches!(
convert_to_rvf(&quant, "model-q4.bin", "m"),
Err(ModelLoadError::UnsupportedQuant { .. })
));
}
}