use std::collections::HashMap;
use std::path::Path;
use anyhow::Context;
use crate::config::ModelConfig;
use super::graph::{build_reve_graph, ReveSpec};
use super::pos_embed;
use super::weights::{apply_params, build_params, load_safetensors, ParamMap};
#[derive(Clone, Debug)]
pub struct ReveOutput {
pub output: Vec<f32>,
pub shape: Vec<usize>,
pub n_channels: usize,
}
#[derive(Clone, Debug)]
pub struct EncodingResult {
pub outputs: Vec<ReveOutput>,
pub ms_load: f64,
pub ms_encode: f64,
}
pub struct ReveEncoder {
pub model_cfg: ModelConfig,
pub device: rlx::Device,
params: ParamMap,
cls_query_token: Option<Vec<f32>>,
session: rlx::Session,
cache: HashMap<usize, rlx::CompiledGraph>,
}
impl ReveEncoder {
pub fn load(
config_path: &Path,
weights_path: &Path,
device: rlx::Device,
) -> anyhow::Result<(Self, f64)> {
let cfg_str = std::fs::read_to_string(config_path)
.with_context(|| format!("config: {}", config_path.display()))?;
let hf_val: serde_json::Value = serde_json::from_str(&cfg_str)?;
let mut model_cfg: ModelConfig = serde_json::from_value(
hf_val.get("model").cloned().unwrap_or(hf_val.clone()),
)
.context("parsing model config")?;
let t = std::time::Instant::now();
let mut raw = load_safetensors(
weights_path.to_str().context("weights path not valid UTF-8")?,
)?;
if !model_cfg.attention_pooling && raw.contains_key("cls_query_token") {
model_cfg.attention_pooling = true;
}
if model_cfg.n_outputs == 0 {
let bias_key = if model_cfg.attention_pooling {
"final_layer.1.bias"
} else {
"final_layer.2.bias"
};
if let Some(p) = raw.get(bias_key) {
anyhow::ensure!(p.shape.len() == 1, "{bias_key} must be 1-D");
model_cfg.n_outputs = p.shape[0];
} else {
model_cfg.n_outputs = 0;
}
}
let mut params = build_params(&mut raw, &model_cfg)?;
let cls_query_token = if model_cfg.attention_pooling {
let p = params
.remove("cls_query_token")
.ok_or_else(|| anyhow::anyhow!("missing weight key: cls_query_token"))?;
anyhow::ensure!(
p.shape == vec![1, 1, model_cfg.embed_dim],
"cls_query_token shape mismatch: {:?}",
p.shape
);
Some(p.data)
} else {
None
};
super::prepare_device(device);
let session = rlx::Session::new(device);
let ms = t.elapsed().as_secs_f64() * 1000.0;
Ok((
Self {
model_cfg,
device,
params,
cls_query_token,
session,
cache: HashMap::new(),
},
ms,
))
}
pub fn describe(&self) -> String {
let c = &self.model_cfg;
format!(
"REVE (RLX, dev={:?}) embed_dim={} depth={} heads={} head_dim={} patch={} outputs={}",
self.device, c.embed_dim, c.depth, c.heads, c.head_dim, c.patch_size, c.n_outputs,
)
}
pub fn params(&self) -> &super::weights::ParamMap { &self.params }
pub fn n_patches(&self) -> usize {
let c = &self.model_cfg;
let step = c.patch_size - c.patch_overlap;
if c.n_times == 0 {
0
} else {
(c.n_times - c.patch_size) / step + 1
}
}
fn spec(&self, b: usize) -> ReveSpec {
let c = &self.model_cfg;
let n_patches = self.n_patches();
ReveSpec {
b,
s: c.n_chans * n_patches,
patch_size: c.patch_size,
embed_dim: c.embed_dim,
n_outputs: c.n_outputs,
depth: c.depth,
heads: c.heads,
head_dim: c.head_dim,
mlp_dim: c.mlp_dim(),
use_geglu: c.use_geglu,
freqs: c.freqs,
attention_pooling: c.attention_pooling,
}
}
fn compiled_for(&mut self, b: usize, s: usize) -> &mut rlx::CompiledGraph {
let key = b * 0x10_0000 + s;
if !self.cache.contains_key(&key) {
let mut spec = self.spec(b);
spec.s = s;
let graph = build_reve_graph(&spec);
let mut compiled = self.session.compile(graph);
apply_params(&mut compiled, &self.params);
self.cache.insert(key, compiled);
}
self.cache.get_mut(&key).expect("just inserted")
}
fn normalize(signal: &mut [f32], n_channels: usize, n_times: usize) {
for c in 0..n_channels {
let row = &mut signal[c * n_times..(c + 1) * n_times];
let mean = row.iter().copied().sum::<f32>() / (n_times as f32);
let mut var = 0.0f32;
for &v in row.iter() {
let d = v - mean;
var += d * d;
}
var /= n_times as f32;
let std = (var + 1e-8).sqrt();
let inv = 1.0 / std;
for v in row.iter_mut() {
let z = (*v - mean) * inv;
*v = z.clamp(-15.0, 15.0);
}
}
}
pub fn prep_inputs(
&self,
mut signal: Vec<f32>,
positions_xyz: &[f32],
n_channels: usize,
n_times: usize,
) -> anyhow::Result<(Vec<f32>, Vec<f32>)> {
let c = &self.model_cfg;
if c.n_chans != 0 {
anyhow::ensure!(
n_channels == c.n_chans,
"n_channels mismatch: got {n_channels}, cfg {}",
c.n_chans
);
}
if c.n_times != 0 {
anyhow::ensure!(
n_times == c.n_times,
"n_times mismatch: got {n_times}, cfg {}",
c.n_times
);
}
anyhow::ensure!(positions_xyz.len() == n_channels * 3, "positions_xyz len mismatch");
anyhow::ensure!(signal.len() == n_channels * n_times, "signal len mismatch");
Self::normalize(&mut signal, n_channels, n_times);
let step = c.patch_size - c.patch_overlap;
anyhow::ensure!(
n_times >= c.patch_size,
"n_times ({n_times}) < patch_size ({})",
c.patch_size
);
let n_patches = (n_times - c.patch_size) / step + 1;
let s = n_channels * n_patches;
let mut patches = vec![0f32; s * c.patch_size];
let mut pos4 = vec![0f32; s * 4];
for ch in 0..n_channels {
let x = positions_xyz[ch * 3 + 0];
let y = positions_xyz[ch * 3 + 1];
let z = positions_xyz[ch * 3 + 2];
let row = &signal[ch * n_times..(ch + 1) * n_times];
for p in 0..n_patches {
let start = p * step;
let dst_tok = ch * n_patches + p;
let dst_patch = dst_tok * c.patch_size;
patches[dst_patch..dst_patch + c.patch_size]
.copy_from_slice(&row[start..start + c.patch_size]);
let dst_pos = dst_tok * 4;
pos4[dst_pos + 0] = x;
pos4[dst_pos + 1] = y;
pos4[dst_pos + 2] = z;
pos4[dst_pos + 3] = p as f32;
}
}
Ok((patches, pos4))
}
pub fn run_at_layer(
&mut self,
signal: Vec<f32>,
positions_xyz: Vec<f32>,
n_channels: usize,
n_times: usize,
layer_end: usize,
) -> anyhow::Result<ReveOutput> {
let (patches, pos4) = self.prep_inputs(signal, &positions_xyz, n_channels, n_times)?;
let s = pos4.len() / 4;
let d = self.model_cfg.embed_dim;
let pos_embed = pos_embed::precompute_pos_embed(&pos4, s, d, &self.params);
let depth = self.model_cfg.depth;
let layer_end = layer_end.min(depth);
let key = 0x8000_0000usize.wrapping_add(layer_end * 0x10_0000 + s);
if !self.cache.contains_key(&key) {
let mut spec = self.spec(1);
spec.s = s;
let graph = super::graph::build_reve_graph_range(&spec, 0, layer_end, false);
let mut compiled = self.session.compile(graph);
super::weights::apply_params(&mut compiled, &self.params);
self.cache.insert(key, compiled);
}
let compiled = self.cache.get_mut(&key).expect("just inserted");
let outs = compiled.run(&[("patches", &patches), ("pos_embed", &pos_embed)]);
let output = outs
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("reve graph produced no output"))?;
Ok(ReveOutput {
output,
shape: vec![s, d],
n_channels,
})
}
pub fn run_one(
&mut self,
signal: Vec<f32>,
positions_xyz: Vec<f32>,
n_channels: usize,
n_times: usize,
) -> anyhow::Result<ReveOutput> {
let (patches, pos4) = self.prep_inputs(signal, &positions_xyz, n_channels, n_times)?;
let s = pos4.len() / 4;
let d = self.model_cfg.embed_dim;
let pos_embed = pos_embed::precompute_pos_embed(&pos4, s, d, &self.params);
let attention_pooling = self.model_cfg.attention_pooling;
let cls_q = self.cls_query_token.clone();
let compiled = self.compiled_for(1, s);
let outs = if attention_pooling {
let q = cls_q.as_ref().expect("cls token loaded");
compiled.run(&[
("patches", &patches),
("pos_embed", &pos_embed),
("cls_q", q),
])
} else {
compiled.run(&[("patches", &patches), ("pos_embed", &pos_embed)])
};
let output = outs
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("reve graph produced no output"))?;
Ok(ReveOutput {
output,
shape: if self.model_cfg.n_outputs == 0 {
vec![self.model_cfg.embed_dim]
} else {
vec![self.model_cfg.n_outputs]
},
n_channels,
})
}
}