1use std::collections::HashMap;
4use std::path::Path;
5
6use anyhow::Context;
7
8use crate::config::ModelConfig;
9use super::graph::{build_reve_graph, ReveSpec};
10use super::pos_embed;
11use super::weights::{apply_params, build_params, load_safetensors, ParamMap};
12
13#[derive(Clone, Debug)]
15pub struct ReveOutput {
16 pub output: Vec<f32>,
18 pub shape: Vec<usize>,
20 pub n_channels: usize,
21}
22
23#[derive(Clone, Debug)]
25pub struct EncodingResult {
26 pub outputs: Vec<ReveOutput>,
27 pub ms_load: f64,
28 pub ms_encode: f64,
29}
30
31pub struct ReveEncoder {
32 pub model_cfg: ModelConfig,
33 pub device: rlx::Device,
34
35 params: ParamMap,
36 cls_query_token: Option<Vec<f32>>,
37
38 session: rlx::Session,
39 cache: HashMap<usize, rlx::CompiledGraph>,
40}
41
42impl ReveEncoder {
43 pub fn load(
44 config_path: &Path,
45 weights_path: &Path,
46 device: rlx::Device,
47 ) -> anyhow::Result<(Self, f64)> {
48 let cfg_str = std::fs::read_to_string(config_path)
49 .with_context(|| format!("config: {}", config_path.display()))?;
50 let hf_val: serde_json::Value = serde_json::from_str(&cfg_str)?;
51 let mut model_cfg: ModelConfig = serde_json::from_value(
52 hf_val.get("model").cloned().unwrap_or(hf_val.clone()),
53 )
54 .context("parsing model config")?;
55
56 let t = std::time::Instant::now();
57 let mut raw = load_safetensors(
58 weights_path.to_str().context("weights path not valid UTF-8")?,
59 )?;
60
61 if !model_cfg.attention_pooling && raw.contains_key("cls_query_token") {
62 model_cfg.attention_pooling = true;
63 }
64 if model_cfg.n_outputs == 0 {
65 let bias_key = if model_cfg.attention_pooling {
66 "final_layer.1.bias"
67 } else {
68 "final_layer.2.bias"
69 };
70 if let Some(p) = raw.get(bias_key) {
71 anyhow::ensure!(p.shape.len() == 1, "{bias_key} must be 1-D");
72 model_cfg.n_outputs = p.shape[0];
73 } else {
74 model_cfg.n_outputs = 0;
75 }
76 }
77
78 let mut params = build_params(&mut raw, &model_cfg)?;
79
80 let cls_query_token = if model_cfg.attention_pooling {
81 let p = params
82 .remove("cls_query_token")
83 .ok_or_else(|| anyhow::anyhow!("missing weight key: cls_query_token"))?;
84 anyhow::ensure!(
85 p.shape == vec![1, 1, model_cfg.embed_dim],
86 "cls_query_token shape mismatch: {:?}",
87 p.shape
88 );
89 Some(p.data)
90 } else {
91 None
92 };
93
94 super::prepare_device(device);
95 let session = rlx::Session::new(device);
96 let ms = t.elapsed().as_secs_f64() * 1000.0;
97 Ok((
98 Self {
99 model_cfg,
100 device,
101 params,
102 cls_query_token,
103 session,
104 cache: HashMap::new(),
105 },
106 ms,
107 ))
108 }
109
110 pub fn describe(&self) -> String {
111 let c = &self.model_cfg;
112 format!(
113 "REVE (RLX, dev={:?}) embed_dim={} depth={} heads={} head_dim={} patch={} outputs={}",
114 self.device, c.embed_dim, c.depth, c.heads, c.head_dim, c.patch_size, c.n_outputs,
115 )
116 }
117
118 pub fn params(&self) -> &super::weights::ParamMap { &self.params }
119 pub fn n_patches(&self) -> usize {
120 let c = &self.model_cfg;
121 let step = c.patch_size - c.patch_overlap;
122 if c.n_times == 0 {
123 0
124 } else {
125 (c.n_times - c.patch_size) / step + 1
126 }
127 }
128
129 fn spec(&self, b: usize) -> ReveSpec {
130 let c = &self.model_cfg;
131 let n_patches = self.n_patches();
132 ReveSpec {
133 b,
134 s: c.n_chans * n_patches,
135 patch_size: c.patch_size,
136 embed_dim: c.embed_dim,
137 n_outputs: c.n_outputs,
138 depth: c.depth,
139 heads: c.heads,
140 head_dim: c.head_dim,
141 mlp_dim: c.mlp_dim(),
142 use_geglu: c.use_geglu,
143 freqs: c.freqs,
144 attention_pooling: c.attention_pooling,
145 }
146 }
147
148 fn compiled_for(&mut self, b: usize, s: usize) -> &mut rlx::CompiledGraph {
149 let key = b * 0x10_0000 + s;
150 if !self.cache.contains_key(&key) {
151 let mut spec = self.spec(b);
152 spec.s = s;
153 let graph = build_reve_graph(&spec);
154 let mut compiled = self.session.compile(graph);
155 apply_params(&mut compiled, &self.params);
156 self.cache.insert(key, compiled);
157 }
158 self.cache.get_mut(&key).expect("just inserted")
159 }
160
161 fn normalize(signal: &mut [f32], n_channels: usize, n_times: usize) {
163 for c in 0..n_channels {
164 let row = &mut signal[c * n_times..(c + 1) * n_times];
165 let mean = row.iter().copied().sum::<f32>() / (n_times as f32);
166 let mut var = 0.0f32;
167 for &v in row.iter() {
168 let d = v - mean;
169 var += d * d;
170 }
171 var /= n_times as f32;
172 let std = (var + 1e-8).sqrt();
173 let inv = 1.0 / std;
174 for v in row.iter_mut() {
175 let z = (*v - mean) * inv;
176 *v = z.clamp(-15.0, 15.0);
177 }
178 }
179 }
180
181 pub fn prep_inputs(
187 &self,
188 mut signal: Vec<f32>,
189 positions_xyz: &[f32],
190 n_channels: usize,
191 n_times: usize,
192 ) -> anyhow::Result<(Vec<f32>, Vec<f32>)> {
193 let c = &self.model_cfg;
194 if c.n_chans != 0 {
195 anyhow::ensure!(
196 n_channels == c.n_chans,
197 "n_channels mismatch: got {n_channels}, cfg {}",
198 c.n_chans
199 );
200 }
201 if c.n_times != 0 {
202 anyhow::ensure!(
203 n_times == c.n_times,
204 "n_times mismatch: got {n_times}, cfg {}",
205 c.n_times
206 );
207 }
208 anyhow::ensure!(positions_xyz.len() == n_channels * 3, "positions_xyz len mismatch");
209 anyhow::ensure!(signal.len() == n_channels * n_times, "signal len mismatch");
210
211 Self::normalize(&mut signal, n_channels, n_times);
212
213 let step = c.patch_size - c.patch_overlap;
214 anyhow::ensure!(
215 n_times >= c.patch_size,
216 "n_times ({n_times}) < patch_size ({})",
217 c.patch_size
218 );
219 let n_patches = (n_times - c.patch_size) / step + 1;
220 let s = n_channels * n_patches;
221
222 let mut patches = vec![0f32; s * c.patch_size];
223 let mut pos4 = vec![0f32; s * 4];
224
225 for ch in 0..n_channels {
226 let x = positions_xyz[ch * 3 + 0];
227 let y = positions_xyz[ch * 3 + 1];
228 let z = positions_xyz[ch * 3 + 2];
229 let row = &signal[ch * n_times..(ch + 1) * n_times];
230 for p in 0..n_patches {
231 let start = p * step;
232 let dst_tok = ch * n_patches + p;
233 let dst_patch = dst_tok * c.patch_size;
234 patches[dst_patch..dst_patch + c.patch_size]
235 .copy_from_slice(&row[start..start + c.patch_size]);
236
237 let dst_pos = dst_tok * 4;
238 pos4[dst_pos + 0] = x;
239 pos4[dst_pos + 1] = y;
240 pos4[dst_pos + 2] = z;
241 pos4[dst_pos + 3] = p as f32;
242 }
243 }
244
245 Ok((patches, pos4))
246 }
247
248 pub fn run_at_layer(
257 &mut self,
258 signal: Vec<f32>,
259 positions_xyz: Vec<f32>,
260 n_channels: usize,
261 n_times: usize,
262 layer_end: usize,
263 ) -> anyhow::Result<ReveOutput> {
264 let (patches, pos4) = self.prep_inputs(signal, &positions_xyz, n_channels, n_times)?;
265 let s = pos4.len() / 4;
266 let d = self.model_cfg.embed_dim;
267 let pos_embed = pos_embed::precompute_pos_embed(&pos4, s, d, &self.params);
268 let depth = self.model_cfg.depth;
269 let layer_end = layer_end.min(depth);
270 let key = 0x8000_0000usize.wrapping_add(layer_end * 0x10_0000 + s);
272 if !self.cache.contains_key(&key) {
273 let mut spec = self.spec(1);
274 spec.s = s;
275 let graph = super::graph::build_reve_graph_range(&spec, 0, layer_end, false);
276 let mut compiled = self.session.compile(graph);
277 super::weights::apply_params(&mut compiled, &self.params);
278 self.cache.insert(key, compiled);
279 }
280 let compiled = self.cache.get_mut(&key).expect("just inserted");
281 let outs = compiled.run(&[("patches", &patches), ("pos_embed", &pos_embed)]);
282 let output = outs
283 .into_iter()
284 .next()
285 .ok_or_else(|| anyhow::anyhow!("reve graph produced no output"))?;
286 Ok(ReveOutput {
287 output,
288 shape: vec![s, d],
289 n_channels,
290 })
291 }
292
293 pub fn run_one(
294 &mut self,
295 signal: Vec<f32>,
296 positions_xyz: Vec<f32>,
297 n_channels: usize,
298 n_times: usize,
299 ) -> anyhow::Result<ReveOutput> {
300 let (patches, pos4) = self.prep_inputs(signal, &positions_xyz, n_channels, n_times)?;
301 let s = pos4.len() / 4;
302 let d = self.model_cfg.embed_dim;
303 let pos_embed = pos_embed::precompute_pos_embed(&pos4, s, d, &self.params);
304 let attention_pooling = self.model_cfg.attention_pooling;
305 let cls_q = self.cls_query_token.clone();
306 let compiled = self.compiled_for(1, s);
307
308 let outs = if attention_pooling {
309 let q = cls_q.as_ref().expect("cls token loaded");
310 compiled.run(&[
311 ("patches", &patches),
312 ("pos_embed", &pos_embed),
313 ("cls_q", q),
314 ])
315 } else {
316 compiled.run(&[("patches", &patches), ("pos_embed", &pos_embed)])
317 };
318
319 let output = outs
320 .into_iter()
321 .next()
322 .ok_or_else(|| anyhow::anyhow!("reve graph produced no output"))?;
323
324 Ok(ReveOutput {
325 output,
326 shape: if self.model_cfg.n_outputs == 0 {
327 vec![self.model_cfg.embed_dim]
328 } else {
329 vec![self.model_cfg.n_outputs]
330 },
331 n_channels,
332 })
333 }
334}