1use crate::model_config;
12use super::traits::*;
13use anyhow::Result;
14use serde::{Serialize, Deserialize};
15
16model_config!(JambaConfig {
18 vocab_size: usize = 65536,
19 hidden_size: usize = 4096,
20 intermediate_size: usize = 14336,
21 num_hidden_layers: usize = 32,
22 num_attention_heads: usize = 32,
23 num_key_value_heads: usize = 8,
24 hidden_act: String = "silu".to_string(),
25 max_position_embeddings: usize = 262144,
26 initializer_range: f32 = 0.02,
27 rms_norm_eps: f32 = 1e-6,
28 use_cache: bool = true,
29 pad_token_id: i64 = 0,
30 bos_token_id: i64 = 1,
31 eos_token_id: i64 = 2,
32 tie_word_embeddings: bool = false,
33 rope_theta: f32 = 10000.0,
34 attention_dropout: f32 = 0.0,
35 d_state: usize = 16,
37 d_conv: usize = 4,
38 expand: usize = 2,
39 num_experts: usize = 16,
41 num_experts_per_tok: usize = 2,
42 attn_layer_period: usize = 8, attn_layer_offset: usize = 4, use_mamba_preprocessing: bool = true,
46 mamba_d_inner: usize = 0, mamba_dt_rank: usize = 0, });
49
50impl JambaConfig {
51 pub fn from_gguf_config(gguf: &crate::weight_loader_core::GGUFModelConfig) -> Self {
53 Self {
54 vocab_size: gguf.vocab_size,
55 hidden_size: gguf.hidden_size,
56 intermediate_size: gguf.intermediate_size,
57 num_hidden_layers: gguf.num_hidden_layers,
58 num_attention_heads: gguf.num_attention_heads,
59 num_key_value_heads: gguf.num_key_value_heads,
60 rms_norm_eps: gguf.rms_norm_eps,
61 rope_theta: gguf.rope_theta,
62 max_position_embeddings: gguf.max_position_embeddings,
63 ..Default::default()
64 }
65 }
66
67 pub fn is_attention_layer(&self, layer_idx: usize) -> bool {
69 (layer_idx + self.attn_layer_offset) % self.attn_layer_period == 0
70 }
71
72 pub fn effective_mamba_d_inner(&self) -> usize {
74 if self.mamba_d_inner > 0 {
75 self.mamba_d_inner
76 } else {
77 self.hidden_size * self.expand
78 }
79 }
80
81 pub fn effective_mamba_dt_rank(&self) -> usize {
83 if self.mamba_dt_rank > 0 {
84 self.mamba_dt_rank
85 } else {
86 ((self.hidden_size as f32 / 16.0).ceil() as usize).max(1)
87 }
88 }
89}
90
91pub struct JambaModelV2 {
93 config: JambaConfig,
94 device: Device,
95 embed_tokens: Tensor,
96 layers: Vec<JambaLayer>,
97 norm: Tensor,
98 lm_head: Tensor,
99}
100
101pub enum JambaLayerType {
103 Mamba(JambaMambaBlock),
104 Attention(JambaAttentionBlock),
105}
106
107pub struct JambaLayer {
109 layer_type: JambaLayerType,
110 moe: JambaMoE,
111 input_layernorm: Tensor,
112 post_layernorm: Tensor,
113 config: JambaConfig,
114}
115
116pub struct JambaMambaBlock {
118 in_proj: Tensor,
119 conv1d_weight: Tensor,
120 conv1d_bias: Option<Tensor>,
121 x_proj: Tensor,
122 dt_proj: Tensor,
123 dt_proj_bias: Option<Tensor>,
124 a_log: Tensor,
125 d: Tensor,
126 out_proj: Tensor,
127 d_inner: usize,
128 d_state: usize,
129 d_conv: usize,
130 dt_rank: usize,
131}
132
133pub struct JambaAttentionBlock {
135 q_proj: Tensor,
136 k_proj: Tensor,
137 v_proj: Tensor,
138 o_proj: Tensor,
139 num_heads: usize,
140 num_key_value_heads: usize,
141 head_dim: usize,
142 scale: f32,
143}
144
145pub struct JambaMoE {
147 router: Tensor,
148 experts: Vec<JambaExpert>,
149 num_experts: usize,
150 num_experts_per_tok: usize,
151}
152
153pub struct JambaExpert {
155 gate_proj: Tensor,
156 up_proj: Tensor,
157 down_proj: Tensor,
158}
159
160impl Model for JambaModelV2 {
161 type Config = JambaConfig;
162
163 fn new(config: JambaConfig) -> Result<Self> {
164 let device = Device::CPU;
165
166 let embed_tokens = ops_fn::zeros(
167 &[config.vocab_size, config.hidden_size],
168 DataType::Float32,
169 &device
170 )?;
171
172 let norm = ops_fn::zeros(
173 &[config.hidden_size],
174 DataType::Float32,
175 &device
176 )?;
177
178 let lm_head = if config.tie_word_embeddings {
179 embed_tokens.clone()
180 } else {
181 ops_fn::zeros(
182 &[config.hidden_size, config.vocab_size],
183 DataType::Float32,
184 &device
185 )?
186 };
187
188 let mut layers = Vec::with_capacity(config.num_hidden_layers);
189 for i in 0..config.num_hidden_layers {
190 layers.push(JambaLayer::new(&config, i, &device)?);
191 }
192
193 Ok(Self {
194 config,
195 device,
196 embed_tokens,
197 layers,
198 norm,
199 lm_head,
200 })
201 }
202
203 fn from_weights(config: JambaConfig, weights: ModelWeights) -> Result<Self> {
204 let mut model = Self::new(config)?;
205
206 if let Some(w) = weights.get("model.embed_tokens.weight") {
207 model.embed_tokens = w.clone();
208 }
209
210 if let Some(w) = weights.get("model.norm.weight") {
211 model.norm = w.clone();
212 }
213
214 if let Some(w) = weights.get("lm_head.weight") {
215 model.lm_head = ops_fn::transpose(w)?;
216 }
217
218 for (i, layer) in model.layers.iter_mut().enumerate() {
219 layer.load_weights(&weights, i)?;
220 }
221
222 Ok(model)
223 }
224
225 fn forward(&self, inputs: &ModelInputs) -> Result<ModelOutputs> {
226 match inputs {
227 ModelInputs::Text { input_ids, attention_mask, .. } => {
228 let mut hidden_states = ops_fn::embedding(input_ids, &self.embed_tokens)?;
229
230 for layer in &self.layers {
231 hidden_states = layer.forward(
232 &hidden_states,
233 attention_mask.as_ref(),
234 )?;
235 }
236
237 hidden_states = ops_fn::rms_norm(&hidden_states, &self.norm, self.config.rms_norm_eps)?;
238 let logits = ops_fn::matmul(&hidden_states, &self.lm_head)?;
239
240 Ok(ModelOutputs::Logits {
241 logits,
242 hidden_states: None,
243 })
244 }
245 _ => Err(anyhow::anyhow!("Jamba model only supports text inputs")),
246 }
247 }
248
249 fn generate(&self, prompt: &str, config: &GenerationConfig) -> Result<String> {
250 use crate::tokenizer::Tokenizer;
251 use rand::Rng;
252
253 let tokenizer = Tokenizer::new();
254 let mut tokens: Vec<u32> = tokenizer.encode(prompt);
255
256 for _ in 0..config.max_new_tokens {
257 let input_ids = Tensor::from_i64_slice(
258 &tokens.iter().map(|&t| t as i64).collect::<Vec<_>>(),
259 &[1, tokens.len()],
260 &self.device
261 )?;
262
263 let inputs = ModelInputs::text(input_ids);
264 let outputs = self.forward(&inputs)?;
265
266 let logits = match outputs {
267 ModelOutputs::Logits { logits, .. } => logits,
268 _ => return Err(anyhow::anyhow!("Expected logits output")),
269 };
270
271 let logits_candle = logits.to_candle()?;
272 let shape = logits_candle.dims();
273 let last_logits = logits_candle
274 .narrow(1, shape[1] - 1, 1)?
275 .squeeze(0)?
276 .squeeze(0)?;
277
278 let logits_vec: Vec<f32> = last_logits.to_vec1()?;
279
280 let next_token = if config.do_sample && config.temperature > 0.0 {
281 let scaled: Vec<f32> = logits_vec.iter()
282 .map(|&x| x / config.temperature)
283 .collect();
284
285 let max_val = scaled.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
286 let exp_sum: f32 = scaled.iter().map(|&x| (x - max_val).exp()).sum();
287 let probs: Vec<f32> = scaled.iter()
288 .map(|&x| (x - max_val).exp() / exp_sum)
289 .collect();
290
291 let mut rng = rand::thread_rng();
292 let random_val: f32 = rng.gen();
293 let mut cumulative = 0.0;
294 let mut sampled = 0u32;
295
296 for (idx, &prob) in probs.iter().enumerate() {
297 cumulative += prob;
298 if random_val <= cumulative {
299 sampled = idx as u32;
300 break;
301 }
302 }
303 sampled
304 } else {
305 let mut max_idx = 0;
306 let mut max_val = logits_vec[0];
307 for (idx, &val) in logits_vec.iter().enumerate() {
308 if val > max_val {
309 max_val = val;
310 max_idx = idx;
311 }
312 }
313 max_idx as u32
314 };
315
316 if next_token == config.eos_token_id {
317 break;
318 }
319
320 tokens.push(next_token);
321 }
322
323 Ok(tokenizer.decode(&tokens))
324 }
325
326 fn config(&self) -> &Self::Config { &self.config }
327
328 fn memory_requirements(&self) -> MemoryRequirements {
329 let param_size = self.config.vocab_size * self.config.hidden_size +
330 self.config.num_hidden_layers * (
331 4 * self.config.hidden_size * self.config.hidden_size +
333 self.config.num_experts * 3 * self.config.hidden_size * self.config.intermediate_size
335 );
336 let param_size = param_size * 4;
337
338 MemoryRequirements {
339 gpu_memory: param_size,
340 cpu_memory: param_size / 4,
341 kv_cache_memory: param_size / 8,
342 peak_memory: param_size + param_size / 2,
343 }
344 }
345
346 fn to_device(&mut self, device: &Device) -> Result<()> {
347 self.device = device.clone();
348 self.embed_tokens = self.embed_tokens.to_device(device)?;
349 self.norm = self.norm.to_device(device)?;
350 self.lm_head = self.lm_head.to_device(device)?;
351 for layer in &mut self.layers {
352 layer.to_device(device)?;
353 }
354 Ok(())
355 }
356}
357
358impl JambaLayer {
359 fn new(config: &JambaConfig, layer_idx: usize, device: &Device) -> Result<Self> {
360 let layer_type = if config.is_attention_layer(layer_idx) {
361 JambaLayerType::Attention(JambaAttentionBlock::new(config, device)?)
362 } else {
363 JambaLayerType::Mamba(JambaMambaBlock::new(config, device)?)
364 };
365
366 let moe = JambaMoE::new(config, device)?;
367
368 let input_layernorm = ops_fn::zeros(&[config.hidden_size], DataType::Float32, device)?;
369 let post_layernorm = ops_fn::zeros(&[config.hidden_size], DataType::Float32, device)?;
370
371 Ok(Self {
372 layer_type,
373 moe,
374 input_layernorm,
375 post_layernorm,
376 config: config.clone(),
377 })
378 }
379
380 fn forward(&self, hidden_states: &Tensor, attention_mask: Option<&Tensor>) -> Result<Tensor> {
381 let residual = hidden_states.clone();
382
383 let normed = ops_fn::rms_norm(hidden_states, &self.input_layernorm, self.config.rms_norm_eps)?;
385
386 let mixed = match &self.layer_type {
388 JambaLayerType::Mamba(mamba) => mamba.forward(&normed)?,
389 JambaLayerType::Attention(attn) => attn.forward(&normed, attention_mask, self.config.rope_theta)?,
390 };
391
392 let hidden_states = ops_fn::add(&residual, &mixed)?;
394
395 let residual = hidden_states.clone();
397 let normed = ops_fn::rms_norm(&hidden_states, &self.post_layernorm, self.config.rms_norm_eps)?;
398
399 let moe_out = self.moe.forward(&normed)?;
401
402 ops_fn::add(&residual, &moe_out)
404 }
405
406 fn load_weights(&mut self, weights: &ModelWeights, layer_idx: usize) -> Result<()> {
407 let prefix = format!("model.layers.{}", layer_idx);
408
409 if let Some(w) = weights.get(&format!("{}.input_layernorm.weight", prefix)) {
410 self.input_layernorm = w.clone();
411 }
412
413 if let Some(w) = weights.get(&format!("{}.post_attention_layernorm.weight", prefix)) {
414 self.post_layernorm = w.clone();
415 }
416
417 match &mut self.layer_type {
419 JambaLayerType::Mamba(mamba) => mamba.load_weights(weights, layer_idx)?,
420 JambaLayerType::Attention(attn) => attn.load_weights(weights, layer_idx)?,
421 }
422
423 self.moe.load_weights(weights, layer_idx)?;
425
426 Ok(())
427 }
428
429 fn to_device(&mut self, device: &Device) -> Result<()> {
430 self.input_layernorm = self.input_layernorm.to_device(device)?;
431 self.post_layernorm = self.post_layernorm.to_device(device)?;
432
433 match &mut self.layer_type {
434 JambaLayerType::Mamba(mamba) => mamba.to_device(device)?,
435 JambaLayerType::Attention(attn) => attn.to_device(device)?,
436 }
437
438 self.moe.to_device(device)?;
439 Ok(())
440 }
441}
442
443impl JambaMambaBlock {
444 fn new(config: &JambaConfig, device: &Device) -> Result<Self> {
445 let d_inner = config.effective_mamba_d_inner();
446 let dt_rank = config.effective_mamba_dt_rank();
447 let d_state = config.d_state;
448 let d_conv = config.d_conv;
449
450 let in_proj = ops_fn::zeros(&[config.hidden_size, d_inner * 2], DataType::Float32, device)?;
451 let conv1d_weight = ops_fn::zeros(&[d_inner, d_conv], DataType::Float32, device)?;
452 let conv1d_bias = Some(ops_fn::zeros(&[d_inner], DataType::Float32, device)?);
453 let x_proj = ops_fn::zeros(&[d_inner, dt_rank + d_state * 2], DataType::Float32, device)?;
454 let dt_proj = ops_fn::zeros(&[dt_rank, d_inner], DataType::Float32, device)?;
455 let dt_proj_bias = Some(ops_fn::zeros(&[d_inner], DataType::Float32, device)?);
456 let a_log = ops_fn::zeros(&[d_inner, d_state], DataType::Float32, device)?;
457 let d = ops_fn::zeros(&[d_inner], DataType::Float32, device)?;
458 let out_proj = ops_fn::zeros(&[d_inner, config.hidden_size], DataType::Float32, device)?;
459
460 Ok(Self {
461 in_proj,
462 conv1d_weight,
463 conv1d_bias,
464 x_proj,
465 dt_proj,
466 dt_proj_bias,
467 a_log,
468 d,
469 out_proj,
470 d_inner,
471 d_state,
472 d_conv,
473 dt_rank,
474 })
475 }
476
477 fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
478 let shape = hidden_states.shape();
479 let (batch_size, seq_len, _) = (shape[0], shape[1], shape[2]);
480
481 let projected = ops_fn::matmul(hidden_states, &self.in_proj)?;
483
484 let proj_candle = projected.to_candle()?;
486 let x = proj_candle.narrow(2, 0, self.d_inner)?;
487 let z = proj_candle.narrow(2, self.d_inner, self.d_inner)?;
488
489 let x_conv = self.apply_conv1d(&Tensor::from_candle(x.clone()), batch_size, seq_len)?;
491
492 let x_act = ops_fn::silu(&x_conv)?;
494
495 let y = self.selective_scan(&x_act, batch_size, seq_len)?;
497
498 let z_act = ops_fn::silu(&Tensor::from_candle(z))?;
500 let gated = ops_fn::mul(&y, &z_act)?;
501
502 ops_fn::matmul(&gated, &self.out_proj)
504 }
505
506 fn apply_conv1d(&self, x: &Tensor, batch_size: usize, seq_len: usize) -> Result<Tensor> {
507 let x_candle = x.to_candle()?;
508 let w_candle = self.conv1d_weight.to_candle()?;
509
510 let pad_len = self.d_conv - 1;
512 let zeros = candle_core::Tensor::zeros(
513 &[batch_size, pad_len, self.d_inner],
514 x_candle.dtype(),
515 x_candle.device()
516 )?;
517
518 let x_padded = candle_core::Tensor::cat(&[&zeros, &x_candle], 1)?;
519
520 let mut outputs = Vec::new();
521 for i in 0..seq_len {
522 let window = x_padded.narrow(1, i, self.d_conv)?;
523 let window_t = window.transpose(1, 2)?;
524 let conv_out = window_t.broadcast_mul(&w_candle)?;
525 let summed = conv_out.sum(2)?;
526 outputs.push(summed);
527 }
528
529 let result = candle_core::Tensor::stack(&outputs, 1)?;
530
531 let result = if let Some(ref bias) = self.conv1d_bias {
532 let b_candle = bias.to_candle()?;
533 result.broadcast_add(&b_candle)?
534 } else {
535 result
536 };
537
538 Ok(Tensor::from_candle(result))
539 }
540
541 fn selective_scan(&self, x: &Tensor, batch_size: usize, seq_len: usize) -> Result<Tensor> {
542 let dbc = ops_fn::matmul(x, &self.x_proj)?;
544 let dbc_candle = dbc.to_candle()?;
545
546 let dt_raw = dbc_candle.narrow(2, 0, self.dt_rank)?;
547 let b = dbc_candle.narrow(2, self.dt_rank, self.d_state)?;
548 let c = dbc_candle.narrow(2, self.dt_rank + self.d_state, self.d_state)?;
549
550 let dt_proj_candle = self.dt_proj.to_candle()?;
552 let dt = dt_raw.broadcast_matmul(&dt_proj_candle)?;
553
554 let dt = if let Some(ref bias) = self.dt_proj_bias {
555 let b_candle = bias.to_candle()?;
556 dt.broadcast_add(&b_candle)?
557 } else {
558 dt
559 };
560
561 let dt = softplus(&dt)?;
562
563 let a_log_candle = self.a_log.to_candle()?;
565 let a = a_log_candle.exp()?.neg()?;
566
567 let x_candle = x.to_candle()?;
569 let d_candle = self.d.to_candle()?;
570
571 let mut h = candle_core::Tensor::zeros(
572 &[batch_size, self.d_inner, self.d_state],
573 candle_core::DType::F32,
574 x_candle.device()
575 )?;
576
577 let mut outputs = Vec::new();
578
579 for t in 0..seq_len {
580 let x_t = x_candle.narrow(1, t, 1)?.squeeze(1)?;
581 let dt_t = dt.narrow(1, t, 1)?.squeeze(1)?;
582 let b_t = b.narrow(1, t, 1)?.squeeze(1)?;
583 let c_t = c.narrow(1, t, 1)?.squeeze(1)?;
584
585 let dt_expanded = dt_t.unsqueeze(2)?;
586 let dt_a = dt_expanded.broadcast_mul(&a)?;
587 let a_bar = dt_a.exp()?;
588
589 let b_expanded = b_t.unsqueeze(1)?;
590 let dt_b = dt_expanded.broadcast_mul(&b_expanded)?;
591
592 let x_expanded = x_t.unsqueeze(2)?;
593
594 let ah = a_bar.mul(&h)?;
595 let bx = dt_b.mul(&x_expanded.broadcast_as(dt_b.dims())?)?;
596 h = ah.add(&bx)?;
597
598 let c_expanded = c_t.unsqueeze(1)?;
599 let y_state = h.mul(&c_expanded.broadcast_as(h.dims())?)?.sum(2)?;
600
601 let y_skip = x_t.broadcast_mul(&d_candle)?;
602 let y_t = y_state.add(&y_skip)?;
603
604 outputs.push(y_t);
605 }
606
607 let result = candle_core::Tensor::stack(&outputs, 1)?;
608 Ok(Tensor::from_candle(result))
609 }
610
611 fn load_weights(&mut self, weights: &ModelWeights, layer_idx: usize) -> Result<()> {
612 let prefix = format!("model.layers.{}.mamba", layer_idx);
613
614 if let Some(w) = weights.get(&format!("{}.in_proj.weight", prefix)) {
615 self.in_proj = ops_fn::transpose(w)?;
616 }
617
618 if let Some(w) = weights.get(&format!("{}.conv1d.weight", prefix)) {
619 let w_candle = w.to_candle()?;
620 let dims = w_candle.dims();
621 if dims.len() == 3 && dims[1] == 1 {
622 let reshaped = w_candle.squeeze(1)?;
623 self.conv1d_weight = Tensor::from_candle(reshaped);
624 } else {
625 self.conv1d_weight = w.clone();
626 }
627 }
628 if let Some(w) = weights.get(&format!("{}.conv1d.bias", prefix)) {
629 self.conv1d_bias = Some(w.clone());
630 }
631
632 if let Some(w) = weights.get(&format!("{}.x_proj.weight", prefix)) {
633 self.x_proj = ops_fn::transpose(w)?;
634 }
635
636 if let Some(w) = weights.get(&format!("{}.dt_proj.weight", prefix)) {
637 self.dt_proj = w.clone();
638 }
639 if let Some(w) = weights.get(&format!("{}.dt_proj.bias", prefix)) {
640 self.dt_proj_bias = Some(w.clone());
641 }
642
643 if let Some(w) = weights.get(&format!("{}.A_log", prefix)) {
644 self.a_log = w.clone();
645 }
646
647 if let Some(w) = weights.get(&format!("{}.D", prefix)) {
648 self.d = w.clone();
649 }
650
651 if let Some(w) = weights.get(&format!("{}.out_proj.weight", prefix)) {
652 self.out_proj = ops_fn::transpose(w)?;
653 }
654
655 Ok(())
656 }
657
658 fn to_device(&mut self, device: &Device) -> Result<()> {
659 self.in_proj = self.in_proj.to_device(device)?;
660 self.conv1d_weight = self.conv1d_weight.to_device(device)?;
661 if let Some(ref mut bias) = self.conv1d_bias {
662 *bias = bias.to_device(device)?;
663 }
664 self.x_proj = self.x_proj.to_device(device)?;
665 self.dt_proj = self.dt_proj.to_device(device)?;
666 if let Some(ref mut bias) = self.dt_proj_bias {
667 *bias = bias.to_device(device)?;
668 }
669 self.a_log = self.a_log.to_device(device)?;
670 self.d = self.d.to_device(device)?;
671 self.out_proj = self.out_proj.to_device(device)?;
672 Ok(())
673 }
674}
675
676impl JambaAttentionBlock {
677 fn new(config: &JambaConfig, device: &Device) -> Result<Self> {
678 let head_dim = config.hidden_size / config.num_attention_heads;
679 let num_heads = config.num_attention_heads;
680 let num_key_value_heads = config.num_key_value_heads;
681
682 let q_proj = ops_fn::zeros(&[config.hidden_size, num_heads * head_dim], DataType::Float32, device)?;
683 let k_proj = ops_fn::zeros(&[config.hidden_size, num_key_value_heads * head_dim], DataType::Float32, device)?;
684 let v_proj = ops_fn::zeros(&[config.hidden_size, num_key_value_heads * head_dim], DataType::Float32, device)?;
685 let o_proj = ops_fn::zeros(&[num_heads * head_dim, config.hidden_size], DataType::Float32, device)?;
686
687 Ok(Self {
688 q_proj,
689 k_proj,
690 v_proj,
691 o_proj,
692 num_heads,
693 num_key_value_heads,
694 head_dim,
695 scale: (head_dim as f32).powf(-0.5),
696 })
697 }
698
699 fn forward(&self, hidden_states: &Tensor, _attention_mask: Option<&Tensor>, rope_theta: f32) -> Result<Tensor> {
700 let shape = hidden_states.shape();
701 let (batch_size, seq_len, _) = (shape[0], shape[1], shape[2]);
702
703 let q = ops_fn::matmul(hidden_states, &self.q_proj)?;
705 let k = ops_fn::matmul(hidden_states, &self.k_proj)?;
706 let v = ops_fn::matmul(hidden_states, &self.v_proj)?;
707
708 let q_candle = q.to_candle()?;
710 let k_candle = k.to_candle()?;
711 let v_candle = v.to_candle()?;
712
713 let q_reshaped = q_candle
715 .reshape(&[batch_size, seq_len, self.num_heads, self.head_dim])?
716 .transpose(1, 2)?;
717
718 let k_reshaped = k_candle
719 .reshape(&[batch_size, seq_len, self.num_key_value_heads, self.head_dim])?
720 .transpose(1, 2)?;
721
722 let v_reshaped = v_candle
723 .reshape(&[batch_size, seq_len, self.num_key_value_heads, self.head_dim])?
724 .transpose(1, 2)?;
725
726 let (q_with_rope, k_with_rope) = apply_rope(&q_reshaped, &k_reshaped, seq_len, self.head_dim, rope_theta)?;
728
729 let num_groups = self.num_heads / self.num_key_value_heads;
731 let (k_expanded, v_expanded) = if num_groups > 1 {
732 let k_rep = k_with_rope
733 .unsqueeze(2)?
734 .broadcast_as(&[batch_size, self.num_key_value_heads, num_groups, seq_len, self.head_dim])?
735 .reshape(&[batch_size, self.num_heads, seq_len, self.head_dim])?;
736 let v_rep = v_reshaped
737 .unsqueeze(2)?
738 .broadcast_as(&[batch_size, self.num_key_value_heads, num_groups, seq_len, self.head_dim])?
739 .reshape(&[batch_size, self.num_heads, seq_len, self.head_dim])?;
740 (k_rep, v_rep)
741 } else {
742 (k_with_rope, v_reshaped)
743 };
744
745 let k_t = k_expanded.transpose(2, 3)?;
747 let q_contiguous = q_with_rope.contiguous()?;
748 let k_contiguous = k_t.contiguous()?;
749
750 let scores = q_contiguous.matmul(&k_contiguous)?;
751 let scaled_scores = (scores * (self.scale as f64))?;
752
753 let device = scaled_scores.device();
755 let causal_mask = {
756 let mut mask_data = vec![0.0f32; seq_len * seq_len];
757 for i in 0..seq_len {
758 for j in 0..seq_len {
759 if j > i {
760 mask_data[i * seq_len + j] = f32::NEG_INFINITY;
761 }
762 }
763 }
764 candle_core::Tensor::from_vec(mask_data, &[1, 1, seq_len, seq_len], device)?
765 };
766
767 let masked_scores = scaled_scores.broadcast_add(&causal_mask)?;
768 let attention_weights = candle_nn::ops::softmax_last_dim(&masked_scores)?;
769
770 let v_contiguous = v_expanded.contiguous()?;
772 let attn_output = attention_weights.matmul(&v_contiguous)?;
773
774 let attn_output = attn_output
776 .transpose(1, 2)?
777 .reshape(&[batch_size, seq_len, self.num_heads * self.head_dim])?;
778
779 let attn_output = Tensor::from_candle(attn_output);
780
781 ops_fn::matmul(&attn_output, &self.o_proj)
783 }
784
785 fn load_weights(&mut self, weights: &ModelWeights, layer_idx: usize) -> Result<()> {
786 let prefix = format!("model.layers.{}.self_attn", layer_idx);
787
788 if let Some(w) = weights.get(&format!("{}.q_proj.weight", prefix)) {
789 self.q_proj = ops_fn::transpose(w)?;
790 }
791 if let Some(w) = weights.get(&format!("{}.k_proj.weight", prefix)) {
792 self.k_proj = ops_fn::transpose(w)?;
793 }
794 if let Some(w) = weights.get(&format!("{}.v_proj.weight", prefix)) {
795 self.v_proj = ops_fn::transpose(w)?;
796 }
797 if let Some(w) = weights.get(&format!("{}.o_proj.weight", prefix)) {
798 self.o_proj = ops_fn::transpose(w)?;
799 }
800
801 Ok(())
802 }
803
804 fn to_device(&mut self, device: &Device) -> Result<()> {
805 self.q_proj = self.q_proj.to_device(device)?;
806 self.k_proj = self.k_proj.to_device(device)?;
807 self.v_proj = self.v_proj.to_device(device)?;
808 self.o_proj = self.o_proj.to_device(device)?;
809 Ok(())
810 }
811}
812
813impl JambaMoE {
814 fn new(config: &JambaConfig, device: &Device) -> Result<Self> {
815 let router = ops_fn::zeros(
816 &[config.hidden_size, config.num_experts],
817 DataType::Float32,
818 device
819 )?;
820
821 let mut experts = Vec::with_capacity(config.num_experts);
822 for _ in 0..config.num_experts {
823 experts.push(JambaExpert::new(config, device)?);
824 }
825
826 Ok(Self {
827 router,
828 experts,
829 num_experts: config.num_experts,
830 num_experts_per_tok: config.num_experts_per_tok,
831 })
832 }
833
834 fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
835 let shape = hidden_states.shape();
836 let (batch_size, seq_len, hidden_size) = (shape[0], shape[1], shape[2]);
837
838 let router_logits = ops_fn::matmul(hidden_states, &self.router)?;
840
841 let (topk_weights, topk_indices) = ops_fn::topk(&router_logits, self.num_experts_per_tok, -1)?;
843 let routing_weights = ops_fn::softmax(&topk_weights, -1)?;
844
845 let hidden_flat = hidden_states.reshape(&[batch_size * seq_len, hidden_size])?;
847
848 let all_indices: Vec<i64> = topk_indices.to_candle()?.flatten_all()?.to_vec1()?;
850 let all_weights: Vec<f32> = routing_weights.to_candle()?.flatten_all()?.to_vec1()?;
851 let num_tokens = batch_size * seq_len;
852 let k = self.num_experts_per_tok;
853
854 let mut output_candle = candle_core::Tensor::zeros(
856 &[num_tokens, hidden_size],
857 candle_core::DType::F32,
858 &candle_core::Device::Cpu
859 )?;
860
861 for tok_idx in 0..num_tokens {
863 let tok_hidden = hidden_flat.to_candle()?.narrow(0, tok_idx, 1)?;
864 let mut tok_output = candle_core::Tensor::zeros(
865 &[1, hidden_size],
866 candle_core::DType::F32,
867 &candle_core::Device::Cpu
868 )?;
869
870 let start = tok_idx * k;
871 for j in 0..k {
872 let expert_idx = all_indices[start + j] as usize;
873 let weight = all_weights[start + j];
874
875 if expert_idx < self.experts.len() {
876 let expert_out = self.experts[expert_idx].forward(&Tensor::from_candle(tok_hidden.clone()))?;
877 let weighted = expert_out.to_candle()?.affine(weight as f64, 0.0)?;
878 tok_output = tok_output.add(&weighted)?;
879 }
880 }
881
882 output_candle = output_candle.slice_assign(&[tok_idx..tok_idx+1, 0..hidden_size], &tok_output)?;
883 }
884
885 let output = Tensor::from_candle(output_candle);
887 output.reshape(&[batch_size, seq_len, hidden_size])
888 }
889
890 fn load_weights(&mut self, weights: &ModelWeights, layer_idx: usize) -> Result<()> {
891 let prefix = format!("model.layers.{}.block_sparse_moe", layer_idx);
892
893 if let Some(w) = weights.get(&format!("{}.gate.weight", prefix)) {
894 self.router = ops_fn::transpose(w)?;
895 }
896
897 for (i, expert) in self.experts.iter_mut().enumerate() {
898 expert.load_weights(weights, layer_idx, i)?;
899 }
900
901 Ok(())
902 }
903
904 fn to_device(&mut self, device: &Device) -> Result<()> {
905 self.router = self.router.to_device(device)?;
906 for expert in &mut self.experts {
907 expert.to_device(device)?;
908 }
909 Ok(())
910 }
911}
912
913impl JambaExpert {
914 fn new(config: &JambaConfig, device: &Device) -> Result<Self> {
915 let gate_proj = ops_fn::zeros(&[config.hidden_size, config.intermediate_size], DataType::Float32, device)?;
916 let up_proj = ops_fn::zeros(&[config.hidden_size, config.intermediate_size], DataType::Float32, device)?;
917 let down_proj = ops_fn::zeros(&[config.intermediate_size, config.hidden_size], DataType::Float32, device)?;
918
919 Ok(Self { gate_proj, up_proj, down_proj })
920 }
921
922 fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
923 let gate = ops_fn::matmul(hidden_states, &self.gate_proj)?;
924 let gate = ops_fn::silu(&gate)?;
925 let up = ops_fn::matmul(hidden_states, &self.up_proj)?;
926 let hidden = ops_fn::mul(&gate, &up)?;
927 ops_fn::matmul(&hidden, &self.down_proj)
928 }
929
930 fn load_weights(&mut self, weights: &ModelWeights, layer_idx: usize, expert_idx: usize) -> Result<()> {
931 let prefix = format!("model.layers.{}.block_sparse_moe.experts.{}", layer_idx, expert_idx);
932
933 if let Some(w) = weights.get(&format!("{}.w1.weight", prefix)) {
934 self.gate_proj = ops_fn::transpose(w)?;
935 }
936 if let Some(w) = weights.get(&format!("{}.w3.weight", prefix)) {
937 self.up_proj = ops_fn::transpose(w)?;
938 }
939 if let Some(w) = weights.get(&format!("{}.w2.weight", prefix)) {
940 self.down_proj = ops_fn::transpose(w)?;
941 }
942
943 Ok(())
944 }
945
946 fn to_device(&mut self, device: &Device) -> Result<()> {
947 self.gate_proj = self.gate_proj.to_device(device)?;
948 self.up_proj = self.up_proj.to_device(device)?;
949 self.down_proj = self.down_proj.to_device(device)?;
950 Ok(())
951 }
952}
953
954fn softplus(x: &candle_core::Tensor) -> Result<candle_core::Tensor> {
956 let one = candle_core::Tensor::ones(x.dims(), x.dtype(), x.device())?;
957 let exp_x = x.exp()?;
958 let one_plus_exp = one.add(&exp_x)?;
959 Ok(one_plus_exp.log()?)
960}
961
962fn apply_rope(
964 q: &candle_core::Tensor,
965 k: &candle_core::Tensor,
966 seq_len: usize,
967 head_dim: usize,
968 rope_theta: f32,
969) -> Result<(candle_core::Tensor, candle_core::Tensor)> {
970 let device = q.device();
971 let half_dim = head_dim / 2;
972
973 let positions: Vec<f32> = (0..seq_len).map(|i| i as f32).collect();
975 let positions = candle_core::Tensor::from_vec(positions, &[seq_len, 1], device)?;
976
977 let freq_indices: Vec<f32> = (0..half_dim).map(|i| i as f32).collect();
979 let freq_indices = candle_core::Tensor::from_vec(freq_indices, &[1, half_dim], device)?;
980
981 let power = (freq_indices * (2.0 / head_dim as f64))?;
983 let theta_tensor = candle_core::Tensor::from_vec(vec![rope_theta], &[1], device)?
984 .broadcast_as(&[1, half_dim])?;
985 let freqs = theta_tensor.pow(&power)?;
986 let inv_freqs = (freqs.recip())?;
987
988 let angles = positions.broadcast_mul(&inv_freqs)?;
990
991 let cos = angles.cos()?;
993 let sin = angles.sin()?;
994
995 let cos = cos.reshape(&[1, 1, seq_len, half_dim])?;
997 let sin = sin.reshape(&[1, 1, seq_len, half_dim])?;
998
999 let q_shape = q.dims();
1001 let q_first_half = q.narrow(3, 0, half_dim)?;
1002 let q_second_half = q.narrow(3, half_dim, half_dim)?;
1003
1004 let cos_q = cos.broadcast_as(&[q_shape[0], q_shape[1], seq_len, half_dim])?;
1005 let sin_q = sin.broadcast_as(&[q_shape[0], q_shape[1], seq_len, half_dim])?;
1006
1007 let q_rotated_first = (q_first_half.broadcast_mul(&cos_q)?
1008 .sub(&q_second_half.broadcast_mul(&sin_q)?))?;
1009 let q_rotated_second = (q_first_half.broadcast_mul(&sin_q)?
1010 .add(&q_second_half.broadcast_mul(&cos_q)?))?;
1011
1012 let q_rotated = candle_core::Tensor::cat(&[&q_rotated_first, &q_rotated_second], 3)?;
1013
1014 let k_shape = k.dims();
1016 let k_first_half = k.narrow(3, 0, half_dim)?;
1017 let k_second_half = k.narrow(3, half_dim, half_dim)?;
1018
1019 let cos_k = cos.broadcast_as(&[k_shape[0], k_shape[1], seq_len, half_dim])?;
1020 let sin_k = sin.broadcast_as(&[k_shape[0], k_shape[1], seq_len, half_dim])?;
1021
1022 let k_rotated_first = (k_first_half.broadcast_mul(&cos_k)?
1023 .sub(&k_second_half.broadcast_mul(&sin_k)?))?;
1024 let k_rotated_second = (k_first_half.broadcast_mul(&sin_k)?
1025 .add(&k_second_half.broadcast_mul(&cos_k)?))?;
1026
1027 let k_rotated = candle_core::Tensor::cat(&[&k_rotated_first, &k_rotated_second], 3)?;
1028
1029 Ok((q_rotated, k_rotated))
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035
1036 #[test]
1037 fn test_jamba_config() {
1038 let config = JambaConfig::default();
1039 assert_eq!(config.vocab_size, 65536);
1040 assert_eq!(config.hidden_size, 4096);
1041 assert_eq!(config.num_experts, 16);
1042 assert_eq!(config.attn_layer_period, 8);
1043 }
1044
1045 #[test]
1046 fn test_jamba_layer_type_selection() {
1047 let config = JambaConfig {
1048 attn_layer_period: 4,
1049 attn_layer_offset: 2,
1050 ..Default::default()
1051 };
1052
1053 assert!(config.is_attention_layer(2));
1058 assert!(!config.is_attention_layer(3));
1059 assert!(config.is_attention_layer(6));
1060 }
1061
1062 #[test]
1063 fn test_jamba_model_creation() {
1064 let config = JambaConfig {
1065 vocab_size: 1000,
1066 hidden_size: 128,
1067 intermediate_size: 512,
1068 num_hidden_layers: 4,
1069 num_attention_heads: 4,
1070 num_key_value_heads: 2,
1071 num_experts: 4,
1072 num_experts_per_tok: 2,
1073 d_state: 8,
1074 d_conv: 4,
1075 attn_layer_period: 2,
1076 attn_layer_offset: 1,
1077 ..Default::default()
1078 };
1079
1080 let model = JambaModelV2::new(config).unwrap();
1081 assert_eq!(model.config().vocab_size(), 1000);
1082 assert_eq!(model.config().hidden_size(), 128);
1083 assert_eq!(model.config().num_layers(), 4);
1084 }
1085
1086 #[test]
1087 fn test_jamba_forward_pass() {
1088 let config = JambaConfig {
1089 vocab_size: 100,
1090 hidden_size: 64,
1091 intermediate_size: 256,
1092 num_hidden_layers: 2,
1093 num_attention_heads: 4,
1094 num_key_value_heads: 2,
1095 num_experts: 2,
1096 num_experts_per_tok: 1,
1097 d_state: 8,
1098 d_conv: 4,
1099 expand: 2,
1100 attn_layer_period: 2,
1101 attn_layer_offset: 1,
1102 ..Default::default()
1103 };
1104
1105 let model = JambaModelV2::new(config).unwrap();
1106 let input_ids = ops_fn::zeros(&[1, 4], DataType::Int64, &Device::CPU).unwrap();
1107 let inputs = ModelInputs::text(input_ids);
1108
1109 let outputs = model.forward(&inputs).unwrap();
1110 match outputs {
1111 ModelOutputs::Logits { logits, .. } => {
1112 assert_eq!(logits.shape(), &[1, 4, 100]);
1113 }
1114 _ => panic!("Expected logits output"),
1115 }
1116 }
1117}