1use super::config::{SAM3_PATCH_GRID, Sam3VitConfig};
33use super::preprocess::{Sam3PreprocessWeights, assemble_patch_tokens, extract_preprocess_weights};
34use super::tensor::{gelu_tanh, layer_norm, linear, matmul, matmul_bt, softmax_rows};
35use anyhow::{Result, ensure};
36use rlx_core::weight_map::WeightMap;
37use rlx_flow::{GgufPackedLinear, GgufPackedParams};
38
39#[derive(Clone)]
40pub struct Sam3VitBlockWeights {
41 pub norm1_w: Vec<f32>,
42 pub norm1_b: Vec<f32>,
43 pub qkv_w_t: Vec<f32>,
44 pub qkv_b: Vec<f32>,
45 pub qkv_gguf_prefix: Option<String>,
47 pub proj_w_t: Vec<f32>,
48 pub proj_b: Vec<f32>,
49 pub proj_gguf_prefix: Option<String>,
50 pub norm2_w: Vec<f32>,
51 pub norm2_b: Vec<f32>,
52 pub mlp_fc1_w_t: Vec<f32>,
53 pub mlp_fc1_b: Vec<f32>,
54 pub mlp_fc1_gguf_prefix: Option<String>,
55 pub mlp_fc2_w_t: Vec<f32>,
56 pub mlp_fc2_b: Vec<f32>,
57 pub mlp_fc2_gguf_prefix: Option<String>,
58}
59
60#[derive(Clone)]
61pub struct Sam3VisionEncoderWeights {
62 pub pre: Sam3PreprocessWeights,
63 pub ln_pre_w: Vec<f32>,
64 pub ln_pre_b: Vec<f32>,
65 pub blocks: Vec<Sam3VitBlockWeights>,
66}
67
68pub struct Sam3VisionOutput {
69 pub tokens: Vec<f32>,
70 pub grid: usize,
71 pub dim: usize,
72}
73
74pub fn extract_vision_encoder_weights(
75 weights: &mut WeightMap,
76 cfg: &Sam3VitConfig,
77 gguf_packed: Option<&GgufPackedParams>,
78) -> Result<Sam3VisionEncoderWeights> {
79 let pre = extract_preprocess_weights(weights, cfg)?;
80 let e = cfg.embed_dim;
81 let (ln_pre_w, ln_pre_b) = take_layer_norm(weights, &prefixes("ln_pre"), e)?;
82 let hidden = (e as f64 * cfg.mlp_ratio) as usize;
83 let mut blocks = Vec::with_capacity(cfg.depth);
84 for i in 0..cfg.depth {
85 let p = format!("blocks.{i}");
86 let pref = prefixes(&p);
87 let (norm1_w, norm1_b) = take_layer_norm(weights, &prefixed(&pref, "norm1"), e)?;
88 let (qkv_w_t, qkv_gguf_prefix) =
89 take_linear_w_or_gguf(weights, gguf_packed, &prefixed(&pref, "attn.qkv"), e, 3 * e)?;
90 let qkv_b = take_linear_b(weights, &prefixed(&pref, "attn.qkv"), 3 * e)?;
91 let (proj_w_t, proj_gguf_prefix) =
92 take_linear_w_or_gguf(weights, gguf_packed, &prefixed(&pref, "attn.proj"), e, e)?;
93 let proj_b = take_linear_b(weights, &prefixed(&pref, "attn.proj"), e)?;
94 let (norm2_w, norm2_b) = take_layer_norm(weights, &prefixed(&pref, "norm2"), e)?;
95 let (mlp_fc1_w_t, mlp_fc1_gguf_prefix) = take_linear_w_any_or_gguf(
96 weights,
97 gguf_packed,
98 &pref,
99 &["mlp.fc1", "mlp.lin1"],
100 e,
101 hidden,
102 )?;
103 let mlp_fc1_b = take_linear_b_any(weights, &pref, &["mlp.fc1", "mlp.lin1"], hidden)?;
104 let (mlp_fc2_w_t, mlp_fc2_gguf_prefix) = take_linear_w_any_or_gguf(
105 weights,
106 gguf_packed,
107 &pref,
108 &["mlp.fc2", "mlp.lin2"],
109 hidden,
110 e,
111 )?;
112 let mlp_fc2_b = take_linear_b_any(weights, &pref, &["mlp.fc2", "mlp.lin2"], e)?;
113 blocks.push(Sam3VitBlockWeights {
114 norm1_w,
115 norm1_b,
116 qkv_w_t,
117 qkv_b,
118 qkv_gguf_prefix,
119 proj_w_t,
120 proj_b,
121 proj_gguf_prefix,
122 norm2_w,
123 norm2_b,
124 mlp_fc1_w_t,
125 mlp_fc1_b,
126 mlp_fc1_gguf_prefix,
127 mlp_fc2_w_t,
128 mlp_fc2_b,
129 mlp_fc2_gguf_prefix,
130 });
131 }
132 Ok(Sam3VisionEncoderWeights {
133 pre,
134 ln_pre_w,
135 ln_pre_b,
136 blocks,
137 })
138}
139
140pub fn forward_blocks_native(
144 weights: &Sam3VisionEncoderWeights,
145 gguf_packed: Option<&GgufPackedParams>,
146 cfg: &Sam3VitConfig,
147 tokens_in: &[f32],
148) -> Result<Vec<f32>> {
149 let e = cfg.embed_dim;
150 let grid = cfg.patch_grid();
151 let head_dim = e / cfg.num_heads;
152 ensure!(
153 head_dim * cfg.num_heads == e,
154 "embed_dim {e} not divisible by num_heads {}",
155 cfg.num_heads
156 );
157 ensure!(
158 tokens_in.len() == grid * grid * e,
159 "tokens_in expected {} got {}",
160 grid * grid * e,
161 tokens_in.len()
162 );
163 let rope_pt = if cfg.window_size > 0 {
164 cfg.window_size
165 } else {
166 grid
167 };
168 let global_set: std::collections::HashSet<usize> =
169 cfg.global_att_blocks.iter().copied().collect();
170 let rope_global = build_rope_freqs(head_dim, grid, grid, 10000.0, rope_pt as f32 / grid as f32);
171 let rope_window = build_rope_freqs(head_dim, cfg.window_size, cfg.window_size, 10000.0, 1.0);
172 let mut x = tokens_in.to_vec();
173 for (i, block) in weights.blocks.iter().enumerate() {
174 let is_global = global_set.contains(&i);
175 block_forward(
176 &mut x,
177 block,
178 gguf_packed,
179 cfg,
180 grid,
181 if is_global { 0 } else { cfg.window_size },
182 if is_global {
183 &rope_global
184 } else {
185 &rope_window
186 },
187 head_dim,
188 cfg.num_heads,
189 )?;
190 }
191 Ok(x)
192}
193
194pub fn encode_image_native(
195 weights: &Sam3VisionEncoderWeights,
196 gguf_packed: Option<&GgufPackedParams>,
197 cfg: &Sam3VitConfig,
198 image_nchw: &[f32],
199) -> Result<Sam3VisionOutput> {
200 let e = cfg.embed_dim;
201 let grid = cfg.patch_grid();
202 ensure!(
203 grid == SAM3_PATCH_GRID,
204 "SAM3 base grid must be {SAM3_PATCH_GRID}"
205 );
206 let head_dim = e / cfg.num_heads;
207 ensure!(
208 head_dim * cfg.num_heads == e,
209 "embed_dim {e} not divisible by num_heads {}",
210 cfg.num_heads
211 );
212 let rope_pt = if cfg.window_size > 0 {
213 cfg.window_size
214 } else {
215 grid
216 };
217
218 let mut x = assemble_patch_tokens(&weights.pre, image_nchw)?;
220 x = layer_norm(
221 &x,
222 &weights.ln_pre_w,
223 &weights.ln_pre_b,
224 e,
225 cfg.layer_norm_eps as f32,
226 )?;
227
228 let global_set: std::collections::HashSet<usize> =
229 cfg.global_att_blocks.iter().copied().collect();
230 let rope_global = build_rope_freqs(head_dim, grid, grid, 10000.0, rope_pt as f32 / grid as f32);
231 let rope_window = build_rope_freqs(head_dim, cfg.window_size, cfg.window_size, 10000.0, 1.0);
232
233 for (i, block) in weights.blocks.iter().enumerate() {
234 let is_global = global_set.contains(&i);
235 block_forward(
236 &mut x,
237 block,
238 gguf_packed,
239 cfg,
240 grid,
241 if is_global { 0 } else { cfg.window_size },
242 if is_global {
243 &rope_global
244 } else {
245 &rope_window
246 },
247 head_dim,
248 cfg.num_heads,
249 )?;
250 }
251 Ok(Sam3VisionOutput {
254 tokens: x,
255 grid,
256 dim: e,
257 })
258}
259
260fn build_rope_freqs(
264 head_dim: usize,
265 end_x: usize,
266 end_y: usize,
267 theta: f32,
268 scale_pos: f32,
269) -> Vec<f32> {
270 let half = head_dim / 2;
271 assert!(
272 head_dim.is_multiple_of(4),
273 "RoPE head_dim must be divisible by 4"
274 );
275 let pair_per_axis = head_dim / 4;
276 let mut freqs_per_pair = Vec::with_capacity(pair_per_axis);
277 for k in 0..pair_per_axis {
278 let exp = (4 * k) as f32 / head_dim as f32;
279 freqs_per_pair.push(1.0 / theta.powf(exp));
280 }
281 let l = end_x * end_y;
282 let mut out = vec![0f32; l * head_dim];
283 for pos in 0..l {
284 let t_x = (pos % end_x) as f32 * scale_pos;
285 let t_y = (pos / end_x) as f32 * scale_pos;
286 for k in 0..pair_per_axis {
287 let ang_x = t_x * freqs_per_pair[k];
288 let ang_y = t_y * freqs_per_pair[k];
289 out[pos * head_dim + 2 * k] = ang_x.cos();
290 out[pos * head_dim + 2 * k + 1] = ang_x.sin();
291 out[pos * head_dim + 2 * (k + pair_per_axis)] = ang_y.cos();
292 out[pos * head_dim + 2 * (k + pair_per_axis) + 1] = ang_y.sin();
293 }
294 }
295 let _ = half;
296 out
297}
298
299fn rope_apply_inplace(
303 qk: &mut [f32],
304 freqs_cis: &[f32],
305 rows: usize,
306 seq_len: usize,
307 head_dim: usize,
308) {
309 let pairs = head_dim / 2;
310 for r in 0..rows {
311 let l = r % seq_len;
312 let f = &freqs_cis[l * head_dim..(l + 1) * head_dim];
313 let v = &mut qk[r * head_dim..(r + 1) * head_dim];
314 for k in 0..pairs {
315 let vr = v[2 * k];
316 let vi = v[2 * k + 1];
317 let fr = f[2 * k];
318 let fi = f[2 * k + 1];
319 v[2 * k] = vr * fr - vi * fi;
320 v[2 * k + 1] = vr * fi + vi * fr;
321 }
322 }
323}
324
325fn linear_maybe_gguf(
328 x: &[f32],
329 m: usize,
330 k: usize,
331 w_t: &[f32],
332 gguf: Option<&GgufPackedLinear>,
333 n: usize,
334 b: &[f32],
335) -> Result<Vec<f32>> {
336 let mut out = vec![0f32; m * n];
337 if let Some(p) = gguf {
338 ensure!(
339 p.in_dim == k && p.out_dim == n,
340 "packed linear shape {k}x{n} vs gguf {}x{}",
341 p.in_dim,
342 p.out_dim
343 );
344 rlx_cpu::gguf_matmul::gguf_matmul_bt(x, &p.w_q, &mut out, m, k, n, p.scheme);
345 } else {
346 ensure!(
347 !w_t.is_empty(),
348 "linear: missing F32 weights and no GGUF packed entry"
349 );
350 return linear(x, m, k, w_t, n, b);
351 }
352 for row in 0..m {
353 for col in 0..n {
354 out[row * n + col] += b[col];
355 }
356 }
357 Ok(out)
358}
359
360fn packed_for_prefix<'a>(
361 packed: Option<&'a GgufPackedParams>,
362 prefix: Option<&String>,
363) -> Option<&'a GgufPackedLinear> {
364 let key = format!("{}.weight", prefix.as_ref()?);
365 packed?.get_linear(&key)
366}
367
368fn block_forward(
369 x: &mut [f32],
370 block: &Sam3VitBlockWeights,
371 gguf_packed: Option<&GgufPackedParams>,
372 cfg: &Sam3VitConfig,
373 grid: usize,
374 window_size: usize,
375 freqs_cis: &[f32],
376 head_dim: usize,
377 num_heads: usize,
378) -> Result<()> {
379 let e = cfg.embed_dim;
380 let n = grid * grid;
381 let eps = cfg.layer_norm_eps as f32;
382
383 let n1 = layer_norm(x, &block.norm1_w, &block.norm1_b, e, eps)?;
385 let qkv_gguf = packed_for_prefix(gguf_packed, block.qkv_gguf_prefix.as_ref());
386 let proj_gguf = packed_for_prefix(gguf_packed, block.proj_gguf_prefix.as_ref());
387 let attn_out = if window_size == 0 {
388 attention_native(
389 &n1,
390 1,
391 n,
392 &block.qkv_w_t,
393 qkv_gguf,
394 &block.qkv_b,
395 &block.proj_w_t,
396 proj_gguf,
397 &block.proj_b,
398 freqs_cis,
399 num_heads,
400 head_dim,
401 )?
402 } else {
403 attention_windowed(
404 &n1,
405 grid,
406 grid,
407 window_size,
408 e,
409 &block.qkv_w_t,
410 qkv_gguf,
411 &block.qkv_b,
412 &block.proj_w_t,
413 proj_gguf,
414 &block.proj_b,
415 freqs_cis,
416 num_heads,
417 head_dim,
418 )?
419 };
420 for i in 0..x.len() {
421 x[i] += attn_out[i];
422 }
423
424 let n2 = layer_norm(x, &block.norm2_w, &block.norm2_b, e, eps)?;
425 let hidden = block.mlp_fc1_b.len();
426 let fc1_gguf = packed_for_prefix(gguf_packed, block.mlp_fc1_gguf_prefix.as_ref());
427 let fc2_gguf = packed_for_prefix(gguf_packed, block.mlp_fc2_gguf_prefix.as_ref());
428 let mut mlp = linear_maybe_gguf(
429 &n2,
430 n,
431 e,
432 &block.mlp_fc1_w_t,
433 fc1_gguf,
434 hidden,
435 &block.mlp_fc1_b,
436 )?;
437 gelu_tanh(&mut mlp);
438 let ffn = linear_maybe_gguf(
439 &mlp,
440 n,
441 hidden,
442 &block.mlp_fc2_w_t,
443 fc2_gguf,
444 e,
445 &block.mlp_fc2_b,
446 )?;
447 for i in 0..x.len() {
448 x[i] += ffn[i];
449 }
450 Ok(())
451}
452
453fn attention_windowed(
454 x: &[f32],
455 h: usize,
456 w: usize,
457 ws: usize,
458 e: usize,
459 qkv_w_t: &[f32],
460 qkv_gguf: Option<&GgufPackedLinear>,
461 qkv_b: &[f32],
462 proj_w_t: &[f32],
463 proj_gguf: Option<&GgufPackedLinear>,
464 proj_b: &[f32],
465 freqs_cis: &[f32],
466 num_heads: usize,
467 head_dim: usize,
468) -> Result<Vec<f32>> {
469 let pad_h = (ws - h % ws) % ws;
470 let pad_w = (ws - w % ws) % ws;
471 let hp = h + pad_h;
472 let wp = w + pad_w;
473 let nh = hp / ws;
474 let nw = wp / ws;
475 let num_windows = nh * nw;
476 let win_len = ws * ws;
477
478 let mut win = vec![0f32; num_windows * win_len * e];
480 for y in 0..hp {
481 for xc in 0..wp {
482 let wy = y / ws;
483 let wx = xc / ws;
484 let ry = y % ws;
485 let rx = xc % ws;
486 let widx = wy * nw + wx;
487 let dst = ((widx * ws + ry) * ws + rx) * e;
488 if y < h && xc < w {
489 let src = (y * w + xc) * e;
490 win[dst..dst + e].copy_from_slice(&x[src..src + e]);
491 }
492 }
494 }
495
496 let attn = attention_native(
497 &win,
498 num_windows,
499 win_len,
500 qkv_w_t,
501 qkv_gguf,
502 qkv_b,
503 proj_w_t,
504 proj_gguf,
505 proj_b,
506 freqs_cis,
507 num_heads,
508 head_dim,
509 )?;
510
511 let mut out = vec![0f32; h * w * e];
514 for y in 0..h {
515 for xc in 0..w {
516 let wy = y / ws;
517 let wx = xc / ws;
518 let ry = y % ws;
519 let rx = xc % ws;
520 let widx = wy * nw + wx;
521 let src = ((widx * ws + ry) * ws + rx) * e;
522 let dst = (y * w + xc) * e;
523 out[dst..dst + e].copy_from_slice(&attn[src..src + e]);
524 }
525 }
526 Ok(out)
527}
528
529fn attention_native(
532 x: &[f32],
533 b: usize,
534 l: usize,
535 qkv_w_t: &[f32],
536 qkv_gguf: Option<&GgufPackedLinear>,
537 qkv_b: &[f32],
538 proj_w_t: &[f32],
539 proj_gguf: Option<&GgufPackedLinear>,
540 proj_b: &[f32],
541 freqs_cis: &[f32],
542 num_heads: usize,
543 head_dim: usize,
544) -> Result<Vec<f32>> {
545 let e = num_heads * head_dim;
546 let rows = b * l;
547 let qkv = linear_maybe_gguf(x, rows, e, qkv_w_t, qkv_gguf, 3 * e, qkv_b)?;
548
549 let bh = b * num_heads;
552 let mut q = vec![0f32; bh * l * head_dim];
553 let mut k = vec![0f32; bh * l * head_dim];
554 let mut v = vec![0f32; bh * l * head_dim];
555 for bi in 0..b {
556 for li in 0..l {
557 let src = (bi * l + li) * 3 * e;
558 for hd in 0..num_heads {
559 let qd_src = src + hd * head_dim;
560 let kd_src = src + e + hd * head_dim;
561 let vd_src = src + 2 * e + hd * head_dim;
562 let dst = ((bi * num_heads + hd) * l + li) * head_dim;
563 q[dst..dst + head_dim].copy_from_slice(&qkv[qd_src..qd_src + head_dim]);
564 k[dst..dst + head_dim].copy_from_slice(&qkv[kd_src..kd_src + head_dim]);
565 v[dst..dst + head_dim].copy_from_slice(&qkv[vd_src..vd_src + head_dim]);
566 }
567 }
568 }
569
570 rope_apply_inplace(&mut q, freqs_cis, bh * l, l, head_dim);
571 rope_apply_inplace(&mut k, freqs_cis, bh * l, l, head_dim);
572
573 let scale = 1.0f32 / (head_dim as f32).sqrt();
574 let mut attn_out = vec![0f32; bh * l * head_dim];
575 let mut scores = vec![0f32; l * l];
576
577 for bhi in 0..bh {
578 let q_h = &q[bhi * l * head_dim..(bhi + 1) * l * head_dim];
579 let k_h = &k[bhi * l * head_dim..(bhi + 1) * l * head_dim];
580 let v_h = &v[bhi * l * head_dim..(bhi + 1) * l * head_dim];
581 matmul_bt(q_h, k_h, &mut scores, l, head_dim, l, scale);
583 softmax_rows(&mut scores, l, l);
584 let out_h = &mut attn_out[bhi * l * head_dim..(bhi + 1) * l * head_dim];
586 matmul(&scores, v_h, out_h, l, l, head_dim);
587 }
588
589 let mut packed = vec![0f32; rows * e];
591 for bi in 0..b {
592 for li in 0..l {
593 for hd in 0..num_heads {
594 let src = ((bi * num_heads + hd) * l + li) * head_dim;
595 let dst = (bi * l + li) * e + hd * head_dim;
596 packed[dst..dst + head_dim].copy_from_slice(&attn_out[src..src + head_dim]);
597 }
598 }
599 }
600 linear_maybe_gguf(&packed, rows, e, proj_w_t, proj_gguf, e, proj_b)
601}
602
603fn prefixes(suffix: &str) -> Vec<String> {
604 [
605 "detector.backbone.vision_backbone.trunk",
606 "detector.backbone.visual.trunk",
607 "backbone.vision_backbone.trunk",
608 "backbone.visual.trunk",
609 "visual.trunk",
610 "trunk",
611 ]
612 .iter()
613 .map(|p| format!("{p}.{suffix}"))
614 .collect()
615}
616
617fn prefixed(prefixes: &[String], suffix: &str) -> Vec<String> {
618 prefixes.iter().map(|p| format!("{p}.{suffix}")).collect()
619}
620
621fn take_layer_norm(
622 weights: &mut WeightMap,
623 bases: &[String],
624 dim: usize,
625) -> Result<(Vec<f32>, Vec<f32>)> {
626 let w = take_shape(weights, &suffixes(bases, "weight"), &[dim])?;
627 let b = take_shape(weights, &suffixes(bases, "bias"), &[dim])?;
628 Ok((w, b))
629}
630
631fn take_linear_w_or_gguf(
632 weights: &mut WeightMap,
633 gguf_packed: Option<&GgufPackedParams>,
634 bases: &[String],
635 in_dim: usize,
636 out_dim: usize,
637) -> Result<(Vec<f32>, Option<String>)> {
638 let keys = suffixes(bases, "weight");
639 for key in &keys {
640 if weights.has(key) {
641 let w = take_linear_w(weights, bases, in_dim, out_dim)?;
642 return Ok((w, None));
643 }
644 if let Some(packed) = gguf_packed {
645 if let Some(prefix) = key.strip_suffix(".weight") {
646 if packed.get_linear(key).is_some() {
647 return Ok((Vec::new(), Some(prefix.to_string())));
648 }
649 }
650 }
651 }
652 anyhow::bail!("none of the SAM3 linear weight keys were found: {keys:?}")
653}
654
655fn take_linear_w_any_or_gguf(
656 weights: &mut WeightMap,
657 gguf_packed: Option<&GgufPackedParams>,
658 block_prefixes: &[String],
659 names: &[&str],
660 in_dim: usize,
661 out_dim: usize,
662) -> Result<(Vec<f32>, Option<String>)> {
663 let bases: Vec<String> = block_prefixes
664 .iter()
665 .flat_map(|p| names.iter().map(move |name| format!("{p}.{name}")))
666 .collect();
667 take_linear_w_or_gguf(weights, gguf_packed, &bases, in_dim, out_dim)
668}
669
670fn take_linear_w(
671 weights: &mut WeightMap,
672 bases: &[String],
673 in_dim: usize,
674 out_dim: usize,
675) -> Result<Vec<f32>> {
676 let keys = suffixes(bases, "weight");
677 for key in &keys {
678 if weights.has(key) {
679 let (data, shape) = weights.take_transposed(key)?;
680 ensure!(
681 shape == vec![in_dim, out_dim],
682 "{key} expected [{in_dim}, {out_dim}], got {shape:?}"
683 );
684 return Ok(data);
685 }
686 }
687 anyhow::bail!("none of the SAM3 linear weight keys were found: {keys:?}")
688}
689
690#[allow(dead_code)]
691fn take_linear_w_any(
692 weights: &mut WeightMap,
693 block_prefixes: &[String],
694 names: &[&str],
695 in_dim: usize,
696 out_dim: usize,
697) -> Result<Vec<f32>> {
698 let bases: Vec<String> = block_prefixes
699 .iter()
700 .flat_map(|p| names.iter().map(move |name| format!("{p}.{name}")))
701 .collect();
702 take_linear_w(weights, &bases, in_dim, out_dim)
703}
704
705fn take_linear_b(weights: &mut WeightMap, bases: &[String], dim: usize) -> Result<Vec<f32>> {
706 take_shape(weights, &suffixes(bases, "bias"), &[dim])
707}
708
709fn take_linear_b_any(
710 weights: &mut WeightMap,
711 block_prefixes: &[String],
712 names: &[&str],
713 dim: usize,
714) -> Result<Vec<f32>> {
715 let bases: Vec<String> = block_prefixes
716 .iter()
717 .flat_map(|p| names.iter().map(move |name| format!("{p}.{name}")))
718 .collect();
719 take_linear_b(weights, &bases, dim)
720}
721
722fn suffixes(bases: &[String], suffix: &str) -> Vec<String> {
723 bases.iter().map(|b| format!("{b}.{suffix}")).collect()
724}
725
726fn take_shape(weights: &mut WeightMap, keys: &[String], expected: &[usize]) -> Result<Vec<f32>> {
727 for key in keys {
728 if weights.has(key) {
729 let (data, shape) = weights.take(key)?;
730 ensure!(
731 shape == expected,
732 "{key} expected {expected:?}, got {shape:?}"
733 );
734 return Ok(data);
735 }
736 }
737 anyhow::bail!("none of the SAM3 weight keys were found: {keys:?}")
738}