1use super::config::Sam3VitConfig;
26use super::packed_gguf::packed_linear;
27use super::preprocess::assemble_patch_tokens;
28use super::tensor::layer_norm;
29use super::vision_encoder::{Sam3VisionEncoderWeights, Sam3VisionOutput, Sam3VitBlockWeights};
30use anyhow::{Result, ensure};
31use rlx_flow::CompileProfile;
32use rlx_flow::{GgufPackedLinear, GgufPackedParams};
33use rlx_ir::hir::{HirGraphExt, HirModule, HirMut, HirNodeId};
34use rlx_ir::op::MaskKind;
35use rlx_ir::{DType, Op, Shape};
36use rlx_runtime::{CompiledGraph, Device};
37use std::collections::{HashMap, HashSet};
38
39const ROPE_THETA: f32 = 10_000.0;
40
41pub struct Sam3VisionEncoderHirParts {
43 pub hir: HirModule,
44 pub params: HashMap<String, Vec<f32>>,
45 pub typed_params: Vec<(String, Vec<u8>, DType)>,
46}
47
48pub struct Sam3CompiledVisionEncoder {
50 pub compiled: CompiledGraph,
51 pub batch: usize,
52 pub grid: usize,
53 pub embed_dim: usize,
54}
55
56impl Sam3CompiledVisionEncoder {
57 pub fn new(
58 weights: &Sam3VisionEncoderWeights,
59 cfg: &Sam3VitConfig,
60 batch: usize,
61 device: Device,
62 ) -> Result<Self> {
63 Self::new_with_profile(weights, cfg, batch, device, &CompileProfile::sam3())
64 }
65
66 pub fn new_with_profile(
67 weights: &Sam3VisionEncoderWeights,
68 cfg: &Sam3VitConfig,
69 batch: usize,
70 device: Device,
71 profile: &CompileProfile,
72 ) -> Result<Self> {
73 Self::new_with_profile_and_gguf(weights, cfg, batch, device, profile, None)
74 }
75
76 pub fn new_with_profile_and_gguf(
77 weights: &Sam3VisionEncoderWeights,
78 cfg: &Sam3VitConfig,
79 batch: usize,
80 device: Device,
81 profile: &CompileProfile,
82 gguf_packed: Option<&GgufPackedParams>,
83 ) -> Result<Self> {
84 let parts = build_vision_encoder_hir(weights, cfg, batch, gguf_packed)?;
85 let mut compiled =
86 rlx_core::flow_bridge::compile_hir_with_profile(device, parts.hir, profile)?;
87 rlx_core::flow_util::attach_built_params(&mut compiled, parts.params, &parts.typed_params);
88 Ok(Self {
89 compiled,
90 batch,
91 grid: cfg.patch_grid(),
92 embed_dim: cfg.embed_dim,
93 })
94 }
95
96 pub fn run_tokens(&mut self, tokens: &[f32]) -> Result<Vec<f32>> {
99 let expected = self.batch * self.grid * self.grid * self.embed_dim;
100 ensure!(
101 tokens.len() == expected,
102 "vision encoder expects {expected} tokens, got {}",
103 tokens.len()
104 );
105 let outputs = self.compiled.run(&[("tokens", tokens)]);
106 outputs
107 .into_iter()
108 .next()
109 .ok_or_else(|| anyhow::anyhow!("vision encoder graph produced no outputs"))
110 }
111}
112
113pub fn encode_image_ir_on_with_profile(
116 weights: &Sam3VisionEncoderWeights,
117 gguf_packed: Option<&GgufPackedParams>,
118 cfg: &Sam3VitConfig,
119 image_nchw: &[f32],
120 device: Device,
121 profile: &CompileProfile,
122) -> Result<Sam3VisionOutput> {
123 let mut compiled = Sam3CompiledVisionEncoder::new_with_profile_and_gguf(
124 weights,
125 cfg,
126 1,
127 device,
128 profile,
129 gguf_packed,
130 )?;
131 let tokens_in = host_preroll(weights, cfg, image_nchw)?;
132 let out = compiled.run_tokens(&tokens_in)?;
133 Ok(Sam3VisionOutput {
134 tokens: out,
135 grid: cfg.patch_grid(),
136 dim: cfg.embed_dim,
137 })
138}
139
140pub fn encode_image_ir_on(
141 weights: &Sam3VisionEncoderWeights,
142 gguf_packed: Option<&GgufPackedParams>,
143 cfg: &Sam3VitConfig,
144 image_nchw: &[f32],
145 device: Device,
146) -> Result<Sam3VisionOutput> {
147 encode_image_ir_on_with_profile(
148 weights,
149 gguf_packed,
150 cfg,
151 image_nchw,
152 device,
153 &CompileProfile::sam3(),
154 )
155}
156
157pub fn host_preroll(
159 weights: &Sam3VisionEncoderWeights,
160 cfg: &Sam3VitConfig,
161 image_nchw: &[f32],
162) -> Result<Vec<f32>> {
163 let mut x = assemble_patch_tokens(&weights.pre, image_nchw)?;
164 x = layer_norm(
165 &x,
166 &weights.ln_pre_w,
167 &weights.ln_pre_b,
168 cfg.embed_dim,
169 cfg.layer_norm_eps as f32,
170 )?;
171 Ok(x)
172}
173
174pub fn build_vision_encoder_hir(
175 weights: &Sam3VisionEncoderWeights,
176 cfg: &Sam3VitConfig,
177 batch: usize,
178 gguf_packed: Option<&GgufPackedParams>,
179) -> Result<Sam3VisionEncoderHirParts> {
180 let e = cfg.embed_dim;
181 let grid = cfg.patch_grid();
182 let seq = grid * grid;
183 let nh = cfg.num_heads;
184 let dh = e / nh;
185 ensure!(
186 dh * nh == e,
187 "embed_dim {e} not divisible by num_heads {nh}"
188 );
189 ensure!(dh.is_multiple_of(4), "head_dim must be divisible by 4");
190 let ws = cfg.window_size;
191 ensure!(
192 ws > 0 && grid.is_multiple_of(ws),
193 "vision IR currently assumes window_size>0 and divides grid (got ws={ws}, grid={grid})"
194 );
195 let nw_h = grid / ws;
196 let nw_w = grid / ws;
197 let num_windows = nw_h * nw_w;
198 let win_len = ws * ws;
199 let hidden = (e as f64 * cfg.mlp_ratio) as usize;
200
201 let mut hir = HirModule::new("sam3_vision_encoder");
202 let mut g = HirMut::new(&mut hir);
203 let mut params: HashMap<String, Vec<f32>> = HashMap::new();
204 let mut typed_params: Vec<(String, Vec<u8>, DType)> = Vec::new();
205 let mut gguf_cache: HashMap<String, HirNodeId> = HashMap::new();
206 let f = DType::F32;
207
208 let tokens = g.input("tokens", Shape::new(&[batch, seq, e], f));
209
210 let scale_global = ws as f32 / grid as f32;
213 let (cos_w_x_v, sin_w_x_v, cos_w_y_v, sin_w_y_v) =
214 rope_quarter_tables(dh, ws, ws, ROPE_THETA, 1.0);
215 let (cos_g_x_v, sin_g_x_v, cos_g_y_v, sin_g_y_v) =
216 rope_quarter_tables(dh, grid, grid, ROPE_THETA, scale_global);
217
218 let quarter = dh / 4;
219 let cos_w_x = param_2d(
220 &mut g,
221 &mut params,
222 "rope.win.cos_x",
223 &cos_w_x_v,
224 win_len,
225 quarter,
226 );
227 let sin_w_x = param_2d(
228 &mut g,
229 &mut params,
230 "rope.win.sin_x",
231 &sin_w_x_v,
232 win_len,
233 quarter,
234 );
235 let cos_w_y = param_2d(
236 &mut g,
237 &mut params,
238 "rope.win.cos_y",
239 &cos_w_y_v,
240 win_len,
241 quarter,
242 );
243 let sin_w_y = param_2d(
244 &mut g,
245 &mut params,
246 "rope.win.sin_y",
247 &sin_w_y_v,
248 win_len,
249 quarter,
250 );
251 let cos_g_x = param_2d(
252 &mut g,
253 &mut params,
254 "rope.glob.cos_x",
255 &cos_g_x_v,
256 seq,
257 quarter,
258 );
259 let sin_g_x = param_2d(
260 &mut g,
261 &mut params,
262 "rope.glob.sin_x",
263 &sin_g_x_v,
264 seq,
265 quarter,
266 );
267 let cos_g_y = param_2d(
268 &mut g,
269 &mut params,
270 "rope.glob.cos_y",
271 &cos_g_y_v,
272 seq,
273 quarter,
274 );
275 let sin_g_y = param_2d(
276 &mut g,
277 &mut params,
278 "rope.glob.sin_y",
279 &sin_g_y_v,
280 seq,
281 quarter,
282 );
283
284 let global_set: HashSet<usize> = cfg.global_att_blocks.iter().copied().collect();
285
286 let mut x = tokens;
287 for (li, block) in weights.blocks.iter().enumerate() {
288 let is_global = global_set.contains(&li);
289 let (cos_x, sin_x, cos_y, sin_y) = if is_global {
290 (cos_g_x, sin_g_x, cos_g_y, sin_g_y)
291 } else {
292 (cos_w_x, sin_w_x, cos_w_y, sin_w_y)
293 };
294 x = emit_block(
295 &mut g,
296 &mut params,
297 &mut typed_params,
298 &mut gguf_cache,
299 gguf_packed,
300 li,
301 block,
302 x,
303 batch,
304 seq,
305 grid,
306 ws,
307 nw_h,
308 nw_w,
309 num_windows,
310 win_len,
311 e,
312 nh,
313 dh,
314 hidden,
315 cfg.layer_norm_eps as f32,
316 is_global,
317 cos_x,
318 sin_x,
319 cos_y,
320 sin_y,
321 )?;
322 }
323 g.set_outputs(vec![x]);
324 Ok(Sam3VisionEncoderHirParts {
325 hir,
326 params,
327 typed_params,
328 })
329}
330
331#[allow(clippy::too_many_arguments)]
332fn emit_block(
333 g: &mut HirMut<'_>,
334 params: &mut HashMap<String, Vec<f32>>,
335 typed_params: &mut Vec<(String, Vec<u8>, DType)>,
336 gguf_cache: &mut HashMap<String, HirNodeId>,
337 gguf_packed: Option<&GgufPackedParams>,
338 li: usize,
339 block: &Sam3VitBlockWeights,
340 x: HirNodeId,
341 batch: usize,
342 seq: usize,
343 grid: usize,
344 ws: usize,
345 nw_h: usize,
346 nw_w: usize,
347 num_windows: usize,
348 win_len: usize,
349 e: usize,
350 nh: usize,
351 dh: usize,
352 hidden: usize,
353 eps: f32,
354 is_global: bool,
355 cos_x: HirNodeId,
356 sin_x: HirNodeId,
357 cos_y: HirNodeId,
358 sin_y: HirNodeId,
359) -> Result<HirNodeId> {
360 let f = DType::F32;
361 let n1w = param_1d(g, params, &format!("b{li}.norm1.w"), &block.norm1_w, e);
362 let n1b = param_1d(g, params, &format!("b{li}.norm1.b"), &block.norm1_b, e);
363 let n1 = g.ln(x, n1w, n1b, eps);
364
365 let qkv = linear_or_gguf(
368 g,
369 params,
370 typed_params,
371 gguf_cache,
372 gguf_packed,
373 block.qkv_gguf_prefix.as_deref(),
374 &format!("b{li}.qkv"),
375 n1,
376 &block.qkv_w_t,
377 &block.qkv_b,
378 e,
379 3 * e,
380 )?;
381 let q_flat = g.narrow_(qkv, 2, 0, e);
382 let k_flat = g.narrow_(qkv, 2, e, e);
383 let v_flat = g.narrow_(qkv, 2, 2 * e, e);
384
385 let (q_eff, k_eff, v_eff, eff_batch, eff_seq) = if is_global {
386 (q_flat, k_flat, v_flat, batch, seq)
387 } else {
388 let q_w = window_partition(g, q_flat, batch, ws, nw_h, nw_w, e);
389 let k_w = window_partition(g, k_flat, batch, ws, nw_h, nw_w, e);
390 let v_w = window_partition(g, v_flat, batch, ws, nw_h, nw_w, e);
391 (q_w, k_w, v_w, batch * num_windows, win_len)
392 };
393
394 let q_rot = rope_2d_decomposed(
395 g, q_eff, eff_batch, eff_seq, nh, dh, cos_x, sin_x, cos_y, sin_y,
396 );
397 let k_rot = rope_2d_decomposed(
398 g, k_eff, eff_batch, eff_seq, nh, dh, cos_x, sin_x, cos_y, sin_y,
399 );
400
401 let attn = g.attention_kind(
402 q_rot,
403 k_rot,
404 v_eff,
405 nh,
406 dh,
407 MaskKind::None,
408 Shape::new(&[eff_batch, eff_seq, e], f),
409 );
410
411 let attn_full = if is_global {
412 attn
413 } else {
414 window_unpartition(g, attn, batch, grid, ws, nw_h, nw_w, e)
415 };
416
417 let proj = linear_or_gguf(
418 g,
419 params,
420 typed_params,
421 gguf_cache,
422 gguf_packed,
423 block.proj_gguf_prefix.as_deref(),
424 &format!("b{li}.proj"),
425 attn_full,
426 &block.proj_w_t,
427 &block.proj_b,
428 e,
429 e,
430 )?;
431 let x = g.add(x, proj);
432
433 let n2w = param_1d(g, params, &format!("b{li}.norm2.w"), &block.norm2_w, e);
434 let n2b = param_1d(g, params, &format!("b{li}.norm2.b"), &block.norm2_b, e);
435 let n2 = g.ln(x, n2w, n2b, eps);
436 let fc1 = linear_or_gguf(
437 g,
438 params,
439 typed_params,
440 gguf_cache,
441 gguf_packed,
442 block.mlp_fc1_gguf_prefix.as_deref(),
443 &format!("b{li}.mlp1"),
444 n2,
445 &block.mlp_fc1_w_t,
446 &block.mlp_fc1_b,
447 e,
448 hidden,
449 )?;
450 let act = g.gelu_approx(fc1);
451 let fc2 = linear_or_gguf(
452 g,
453 params,
454 typed_params,
455 gguf_cache,
456 gguf_packed,
457 block.mlp_fc2_gguf_prefix.as_deref(),
458 &format!("b{li}.mlp2"),
459 act,
460 &block.mlp_fc2_w_t,
461 &block.mlp_fc2_b,
462 hidden,
463 e,
464 )?;
465 Ok(g.add(x, fc2))
466}
467
468fn window_partition(
472 g: &mut HirMut<'_>,
473 x: HirNodeId,
474 batch: usize,
475 ws: usize,
476 nw_h: usize,
477 nw_w: usize,
478 e: usize,
479) -> HirNodeId {
480 let v = g.reshape_(
481 x,
482 vec![
483 batch as i64,
484 nw_h as i64,
485 ws as i64,
486 nw_w as i64,
487 ws as i64,
488 e as i64,
489 ],
490 );
491 let t = g.transpose_(v, vec![0, 1, 3, 2, 4, 5]);
492 g.reshape_(
493 t,
494 vec![(batch * nw_h * nw_w) as i64, (ws * ws) as i64, e as i64],
495 )
496}
497
498fn window_unpartition(
499 g: &mut HirMut<'_>,
500 x: HirNodeId,
501 batch: usize,
502 grid: usize,
503 ws: usize,
504 nw_h: usize,
505 nw_w: usize,
506 e: usize,
507) -> HirNodeId {
508 let v = g.reshape_(
509 x,
510 vec![
511 batch as i64,
512 nw_h as i64,
513 nw_w as i64,
514 ws as i64,
515 ws as i64,
516 e as i64,
517 ],
518 );
519 let t = g.transpose_(v, vec![0, 1, 3, 2, 4, 5]);
520 g.reshape_(t, vec![batch as i64, (grid * grid) as i64, e as i64])
521}
522
523#[allow(clippy::too_many_arguments)]
536fn rope_2d_decomposed(
537 g: &mut HirMut<'_>,
538 x: HirNodeId,
539 batch: usize,
540 seq: usize,
541 nh: usize,
542 dh: usize,
543 cos_x: HirNodeId,
544 sin_x: HirNodeId,
545 cos_y: HirNodeId,
546 sin_y: HirNodeId,
547) -> HirNodeId {
548 let half = dh / 2;
549 let quarter = dh / 4;
550
551 let x4 = g.reshape_(x, vec![batch as i64, seq as i64, nh as i64, dh as i64]);
552 let x_xh = g.narrow_(x4, 3, 0, half);
553 let x_yh = g.narrow_(x4, 3, half, half);
554
555 let xh_rot = pairwise_rope_half(g, x_xh, batch, seq, nh, quarter, cos_x, sin_x);
556 let yh_rot = pairwise_rope_half(g, x_yh, batch, seq, nh, quarter, cos_y, sin_y);
557
558 let cat = g.concat_(vec![xh_rot, yh_rot], 3);
559 g.reshape_(cat, vec![batch as i64, seq as i64, (nh * dh) as i64])
560}
561
562#[allow(clippy::too_many_arguments)]
568fn pairwise_rope_half(
569 g: &mut HirMut<'_>,
570 x: HirNodeId, batch: usize,
572 seq: usize,
573 nh: usize,
574 quarter: usize,
575 cos: HirNodeId, sin: HirNodeId, ) -> HirNodeId {
578 let pairs = g.reshape_(
580 x,
581 vec![batch as i64, seq as i64, nh as i64, quarter as i64, 2],
582 );
583 let x_r5 = g.narrow_(pairs, 4, 0, 1);
584 let x_i5 = g.narrow_(pairs, 4, 1, 1);
585 let x_r = g.reshape_(
587 x_r5,
588 vec![batch as i64, seq as i64, nh as i64, quarter as i64],
589 );
590 let x_i = g.reshape_(
591 x_i5,
592 vec![batch as i64, seq as i64, nh as i64, quarter as i64],
593 );
594
595 let cos_b = g.reshape_(cos, vec![1, seq as i64, 1, quarter as i64]);
597 let sin_b = g.reshape_(sin, vec![1, seq as i64, 1, quarter as i64]);
598
599 let rc = g.mul(x_r, cos_b);
600 let is_ = g.mul(x_i, sin_b);
601 let rs = g.mul(x_r, sin_b);
602 let ic = g.mul(x_i, cos_b);
603 let out_r = g.sub(rc, is_);
604 let out_i = g.add(rs, ic);
605
606 let out_r5 = g.reshape_(
608 out_r,
609 vec![batch as i64, seq as i64, nh as i64, quarter as i64, 1],
610 );
611 let out_i5 = g.reshape_(
612 out_i,
613 vec![batch as i64, seq as i64, nh as i64, quarter as i64, 1],
614 );
615 let pairs_out = g.concat_(vec![out_r5, out_i5], 4);
616 g.reshape_(
617 pairs_out,
618 vec![batch as i64, seq as i64, nh as i64, (2 * quarter) as i64],
619 )
620}
621
622#[allow(clippy::too_many_arguments)]
627fn linear_or_gguf(
628 g: &mut HirMut<'_>,
629 params: &mut HashMap<String, Vec<f32>>,
630 typed_params: &mut Vec<(String, Vec<u8>, DType)>,
631 gguf_cache: &mut HashMap<String, HirNodeId>,
632 gguf_packed: Option<&GgufPackedParams>,
633 gguf_prefix: Option<&str>,
634 ir_stem: &str,
635 input: HirNodeId,
636 w_t: &[f32],
637 bias: &[f32],
638 in_dim: usize,
639 out_dim: usize,
640) -> Result<HirNodeId> {
641 if let Some(p) = gguf_prefix
642 .and_then(|pref| gguf_packed.map(|gp| (gp, format!("{pref}.weight"))))
643 .and_then(|(gp, key)| packed_linear(gp, &key))
644 {
645 return linear_gguf_bias(
646 g,
647 params,
648 typed_params,
649 gguf_cache,
650 ir_stem,
651 p,
652 input,
653 bias,
654 in_dim,
655 out_dim,
656 );
657 }
658 ensure!(
659 !w_t.is_empty(),
660 "{ir_stem}: missing F32 weight and no GGUF packed entry"
661 );
662 Ok(fused_linear(
663 g, params, ir_stem, input, w_t, bias, in_dim, out_dim,
664 ))
665}
666
667fn fused_linear(
668 g: &mut HirMut<'_>,
669 params: &mut HashMap<String, Vec<f32>>,
670 ir_stem: &str,
671 input: HirNodeId,
672 w_t: &[f32],
673 bias: &[f32],
674 in_dim: usize,
675 out_dim: usize,
676) -> HirNodeId {
677 let f = DType::F32;
678 let w_name = format!("{ir_stem}.w");
679 let b_name = format!("{ir_stem}.b");
680 let w_id = g.param(&w_name, Shape::new(&[in_dim, out_dim], f));
681 params.insert(w_name, w_t.to_vec());
682 let b_id = g.param(&b_name, Shape::new(&[out_dim], f));
683 params.insert(b_name, bias.to_vec());
684 let cur_shape = g.shape(input);
685 let mut out_dims: Vec<usize> = cur_shape.dims().iter().map(|d| d.unwrap_static()).collect();
686 *out_dims.last_mut().unwrap() = out_dim;
687 g.add_node(
688 Op::FusedMatMulBiasAct { activation: None },
689 vec![input, w_id, b_id],
690 Shape::new(&out_dims, f),
691 )
692}
693
694#[allow(clippy::too_many_arguments)]
695fn linear_gguf_bias(
696 g: &mut HirMut<'_>,
697 params: &mut HashMap<String, Vec<f32>>,
698 typed_params: &mut Vec<(String, Vec<u8>, DType)>,
699 gguf_cache: &mut HashMap<String, HirNodeId>,
700 ir_stem: &str,
701 p: &GgufPackedLinear,
702 input: HirNodeId,
703 bias: &[f32],
704 in_dim: usize,
705 out_dim: usize,
706) -> Result<HirNodeId> {
707 ensure!(
708 p.in_dim == in_dim && p.out_dim == out_dim,
709 "{ir_stem}: packed linear shape {}x{} vs {in_dim}x{out_dim}",
710 p.in_dim,
711 p.out_dim
712 );
713 let w_name = format!("{ir_stem}.w");
714 let w_id = if let Some(&id) = gguf_cache.get(&w_name) {
715 id
716 } else {
717 let id = g.param(&w_name, Shape::new(&[p.w_q.len()], DType::U8));
718 typed_params.push((w_name.clone(), p.w_q.clone(), DType::U8));
719 gguf_cache.insert(w_name, id);
720 id
721 };
722 let cur = g.shape(input);
723 let mut dims: Vec<usize> = cur.dims().iter().map(|d| d.unwrap_static()).collect();
724 *dims.last_mut().unwrap() = out_dim;
725 let out_shape = Shape::new(&dims, DType::F32);
726 let mm = g.add_node(
727 Op::DequantMatMul { scheme: p.scheme },
728 vec![input, w_id],
729 out_shape,
730 );
731 Ok(add_f32_bias(g, params, &format!("{ir_stem}.b"), mm, bias))
732}
733
734fn add_f32_bias(
735 g: &mut HirMut<'_>,
736 params: &mut HashMap<String, Vec<f32>>,
737 name: &str,
738 input: HirNodeId,
739 bias: &[f32],
740) -> HirNodeId {
741 if bias.iter().all(|&v| v == 0.0) {
742 return input;
743 }
744 let b_id = g.param(name, Shape::new(&[bias.len()], DType::F32));
745 params.insert(name.to_string(), bias.to_vec());
746 g.add(input, b_id)
747}
748
749fn param_1d(
750 g: &mut HirMut<'_>,
751 params: &mut HashMap<String, Vec<f32>>,
752 name: &str,
753 data: &[f32],
754 n: usize,
755) -> HirNodeId {
756 let id = g.param(name, Shape::new(&[n], DType::F32));
757 params.insert(name.to_string(), data.to_vec());
758 id
759}
760
761fn param_2d(
762 g: &mut HirMut<'_>,
763 params: &mut HashMap<String, Vec<f32>>,
764 name: &str,
765 data: &[f32],
766 rows: usize,
767 cols: usize,
768) -> HirNodeId {
769 let id = g.param(name, Shape::new(&[rows, cols], DType::F32));
770 params.insert(name.to_string(), data.to_vec());
771 id
772}
773
774fn rope_quarter_tables(
779 head_dim: usize,
780 end_x: usize,
781 end_y: usize,
782 theta: f32,
783 scale_pos: f32,
784) -> (Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>) {
785 assert!(head_dim.is_multiple_of(4));
786 let pair_per_axis = head_dim / 4;
787 let mut freqs_per_pair = Vec::with_capacity(pair_per_axis);
788 for k in 0..pair_per_axis {
789 let exp = (4 * k) as f32 / head_dim as f32;
790 freqs_per_pair.push(1.0 / theta.powf(exp));
791 }
792 let seq = end_x * end_y;
793 let q = pair_per_axis;
794 let mut cos_x = vec![0f32; seq * q];
795 let mut sin_x = vec![0f32; seq * q];
796 let mut cos_y = vec![0f32; seq * q];
797 let mut sin_y = vec![0f32; seq * q];
798 for pos in 0..seq {
799 let t_x = (pos % end_x) as f32 * scale_pos;
800 let t_y = (pos / end_x) as f32 * scale_pos;
801 for k in 0..q {
802 let ang_x = t_x * freqs_per_pair[k];
803 let ang_y = t_y * freqs_per_pair[k];
804 cos_x[pos * q + k] = ang_x.cos();
805 sin_x[pos * q + k] = ang_x.sin();
806 cos_y[pos * q + k] = ang_y.cos();
807 sin_y[pos * q + k] = ang_y.sin();
808 }
809 }
810 (cos_x, sin_x, cos_y, sin_y)
811}