1use crate::model_config;
14use super::traits::*;
15use anyhow::Result;
16use serde::{Serialize, Deserialize};
17
18model_config!(CLIPConfig {
22 vocab_size: usize = 49408,
24 hidden_size: usize = 512, num_hidden_layers: usize = 12, context_length: usize = 77,
27 transformer_width: usize = 512,
28 transformer_heads: usize = 8,
29 transformer_layers: usize = 12,
30
31 image_resolution: usize = 224,
33 vision_layers: usize = 12,
34 vision_width: usize = 768,
35 vision_heads: usize = 12,
36 vision_patch_size: usize = 32,
37
38 embed_dim: usize = 512, projection_dim: usize = 512,
41
42 layer_norm_eps: f32 = 1e-5,
44 attention_dropout: f32 = 0.0,
45 initializer_range: f32 = 0.02,
46});
47
48impl CLIPConfig {
49 pub fn from_gguf_config(gguf: &crate::weight_loader_core::GGUFModelConfig) -> Self {
52 Self {
53 vocab_size: gguf.vocab_size,
54 hidden_size: gguf.hidden_size,
55 num_hidden_layers: gguf.num_hidden_layers,
56 transformer_width: gguf.hidden_size,
57 transformer_layers: gguf.num_hidden_layers,
58 transformer_heads: gguf.num_attention_heads,
59 vision_width: gguf.hidden_size, vision_layers: gguf.num_hidden_layers,
61 vision_heads: gguf.num_attention_heads,
62 ..Default::default()
63 }
64 }
65
66 pub fn vision_head_dim(&self) -> usize {
68 self.vision_width / self.vision_heads
69 }
70
71 pub fn text_head_dim(&self) -> usize {
73 self.transformer_width / self.transformer_heads
74 }
75
76 pub fn num_patches(&self) -> usize {
78 (self.image_resolution / self.vision_patch_size).pow(2)
79 }
80
81 pub fn num_positions(&self) -> usize {
83 self.num_patches() + 1
84 }
85}
86
87pub struct CLIPModelV2 {
89 config: CLIPConfig,
90 device: Device,
91 text_model: CLIPTextTransformer,
92 vision_model: CLIPVisionTransformer,
93 text_projection: Tensor,
94 visual_projection: Tensor,
95 logit_scale: Tensor,
96}
97
98pub struct CLIPTextTransformer {
100 embeddings: CLIPTextEmbeddings,
101 encoder: CLIPEncoder,
102 final_layer_norm: Tensor,
103 final_layer_norm_bias: Option<Tensor>,
104 config: CLIPConfig,
105 is_causal: bool,
106}
107
108pub struct CLIPVisionTransformer {
110 embeddings: CLIPVisionEmbeddings,
111 pre_layernorm: Tensor,
112 pre_layernorm_bias: Option<Tensor>,
113 encoder: CLIPEncoder,
114 post_layernorm: Tensor,
115 post_layernorm_bias: Option<Tensor>,
116 config: CLIPConfig,
117}
118
119pub struct CLIPTextEmbeddings {
121 token_embedding: Tensor,
122 position_embedding: Tensor,
123 config: CLIPConfig,
124}
125
126pub struct CLIPVisionEmbeddings {
128 patch_embedding: Tensor, class_embedding: Tensor, position_embedding: Tensor,
131 config: CLIPConfig,
132}
133
134pub struct CLIPEncoder {
136 layers: Vec<CLIPEncoderLayer>,
137 is_causal: bool,
138}
139
140pub struct CLIPEncoderLayer {
142 self_attn: CLIPAttention,
143 layer_norm1: Tensor,
144 layer_norm1_bias: Option<Tensor>,
145 mlp: CLIPMLP,
146 layer_norm2: Tensor,
147 layer_norm2_bias: Option<Tensor>,
148 hidden_size: usize,
149}
150
151pub struct CLIPAttention {
153 q_proj: Tensor,
154 k_proj: Tensor,
155 v_proj: Tensor,
156 out_proj: Tensor,
157 q_bias: Option<Tensor>,
158 k_bias: Option<Tensor>,
159 v_bias: Option<Tensor>,
160 out_bias: Option<Tensor>,
161 num_heads: usize,
162 head_dim: usize,
163 scale: f32,
164}
165
166pub struct CLIPMLP {
168 fc1: Tensor,
169 fc1_bias: Option<Tensor>,
170 fc2: Tensor,
171 fc2_bias: Option<Tensor>,
172 hidden_size: usize,
173 intermediate_size: usize,
174}
175
176impl Model for CLIPModelV2 {
177 type Config = CLIPConfig;
178
179 fn new(config: CLIPConfig) -> Result<Self> {
180 let device = Device::CPU;
181
182 let text_model = CLIPTextTransformer::new(&config, &device)?;
183 let vision_model = CLIPVisionTransformer::new(&config, &device)?;
184
185 let text_projection = ops_fn::zeros(
188 &[config.transformer_width, config.projection_dim],
189 DataType::Float32,
190 &device
191 )?;
192 let visual_projection = ops_fn::zeros(
193 &[config.vision_width, config.projection_dim],
194 DataType::Float32,
195 &device
196 )?;
197
198 let logit_scale_data = vec![config.projection_dim as f32 * 0.01]; let logit_scale = Tensor::from_f32_slice(&[2.6592], &[1], &device)?; Ok(Self {
203 config,
204 device,
205 text_model,
206 vision_model,
207 text_projection,
208 visual_projection,
209 logit_scale,
210 })
211 }
212
213 fn from_weights(config: CLIPConfig, weights: ModelWeights) -> Result<Self> {
214 let mut model = Self::new(config)?;
215
216 if let Some(w) = weights.get("text_projection.weight") {
218 model.text_projection = ops_fn::transpose(w)?;
219 } else if let Some(w) = weights.get("text_projection") {
220 model.text_projection = ops_fn::transpose(w)?;
222 }
223
224 if let Some(w) = weights.get("visual_projection.weight") {
225 model.visual_projection = ops_fn::transpose(w)?;
226 } else if let Some(w) = weights.get("visual_projection") {
227 model.visual_projection = ops_fn::transpose(w)?;
228 }
229
230 if let Some(w) = weights.get("logit_scale") {
231 model.logit_scale = w.clone();
232 }
233
234 model.text_model.load_weights(&weights)?;
235 model.vision_model.load_weights(&weights)?;
236
237 Ok(model)
238 }
239
240 fn forward(&self, inputs: &ModelInputs) -> Result<ModelOutputs> {
241 match inputs {
242 ModelInputs::Multimodal { input_ids, pixel_values, .. } => {
243 let text_features = self.encode_text_internal(input_ids)?;
245
246 let pixel_values = pixel_values.as_ref()
248 .ok_or_else(|| anyhow::anyhow!("CLIP multimodal forward requires pixel_values"))?;
249 let image_features = self.encode_image_internal(pixel_values)?;
250
251 let logit_scale = ops_fn::exp(&self.logit_scale)?;
253 let logit_scale_val = logit_scale.to_candle()?.to_vec1::<f32>()?[0];
254
255 let image_features_t = ops_fn::transpose(&image_features)?;
257 let logits_per_text = ops_fn::matmul(&text_features, &image_features_t)?;
258 let logits_per_text = ops_fn::scale(&logits_per_text, logit_scale_val)?;
259
260 let logits_per_image = ops_fn::transpose(&logits_per_text)?;
262
263 Ok(ModelOutputs::CLIP {
264 logits_per_text,
265 logits_per_image,
266 text_embeds: text_features,
267 image_embeds: image_features,
268 })
269 },
270 ModelInputs::Text { input_ids, .. } => {
271 let text_features = self.encode_text_internal(input_ids)?;
273
274 Ok(ModelOutputs::Embeddings {
275 embeddings: text_features.clone(),
276 pooled: Some(text_features),
277 })
278 },
279 ModelInputs::Image { pixel_values, .. } => {
280 let image_features = self.encode_image_internal(pixel_values)?;
282
283 Ok(ModelOutputs::Embeddings {
284 embeddings: image_features.clone(),
285 pooled: Some(image_features),
286 })
287 },
288 _ => Err(anyhow::anyhow!("CLIP expects text, image, or multimodal input")),
289 }
290 }
291
292 fn generate(&self, _prompt: &str, _config: &GenerationConfig) -> Result<String> {
293 Err(anyhow::anyhow!(
295 "CLIP is a contrastive model for computing image-text similarity. \
296 It does not support text generation. Use encode_text() and encode_image() \
297 to compute embeddings, then compare them for similarity."
298 ))
299 }
300
301 fn config(&self) -> &Self::Config {
302 &self.config
303 }
304
305 fn memory_requirements(&self) -> MemoryRequirements {
306 let text_embedding_params = self.config.vocab_size * self.config.transformer_width +
308 self.config.context_length * self.config.transformer_width;
309 let text_layer_params = self.config.transformer_layers * (
310 4 * self.config.transformer_width * self.config.transformer_width + 2 * self.config.transformer_width * (self.config.transformer_width * 4) );
313
314 let num_patches = self.config.num_patches();
316 let vision_embedding_params = 3 * self.config.vision_patch_size * self.config.vision_patch_size * self.config.vision_width +
317 self.config.vision_width + (num_patches + 1) * self.config.vision_width; let vision_layer_params = self.config.vision_layers * (
320 4 * self.config.vision_width * self.config.vision_width + 2 * self.config.vision_width * (self.config.vision_width * 4) );
323
324 let projection_params = self.config.transformer_width * self.config.projection_dim +
326 self.config.vision_width * self.config.projection_dim;
327
328 let total_params = text_embedding_params + text_layer_params +
329 vision_embedding_params + vision_layer_params +
330 projection_params;
331
332 let param_bytes = total_params * 4; MemoryRequirements {
335 gpu_memory: param_bytes,
336 cpu_memory: param_bytes / 4,
337 kv_cache_memory: 0, peak_memory: param_bytes * 2, }
340 }
341
342 fn to_device(&mut self, device: &Device) -> Result<()> {
343 self.device = device.clone();
344 self.text_projection = self.text_projection.to_device(device)?;
345 self.visual_projection = self.visual_projection.to_device(device)?;
346 self.logit_scale = self.logit_scale.to_device(device)?;
347 self.text_model.to_device(device)?;
348 self.vision_model.to_device(device)?;
349 Ok(())
350 }
351}
352
353impl CLIPModelV2 {
354 pub fn encode_text(&self, input_ids: &Tensor) -> Result<Tensor> {
356 self.encode_text_internal(input_ids)
357 }
358
359 pub fn encode_image(&self, pixel_values: &Tensor) -> Result<Tensor> {
361 self.encode_image_internal(pixel_values)
362 }
363
364 pub fn compute_similarity(&self, text_features: &Tensor, image_features: &Tensor) -> Result<Tensor> {
366 let logit_scale = ops_fn::exp(&self.logit_scale)?;
367 let logit_scale_val = logit_scale.to_candle()?.to_vec1::<f32>()?[0];
368
369 let image_features_t = ops_fn::transpose(image_features)?;
370 let similarity = ops_fn::matmul(text_features, &image_features_t)?;
371 ops_fn::scale(&similarity, logit_scale_val)
372 }
373
374 fn encode_text_internal(&self, input_ids: &Tensor) -> Result<Tensor> {
376 let hidden_states = self.text_model.forward(input_ids)?;
378
379 let pooled = self.pool_text_features(&hidden_states, input_ids)?;
382
383 let text_features = ops_fn::matmul(&pooled, &self.text_projection)?;
385
386 self.normalize_features(&text_features)
388 }
389
390 fn encode_image_internal(&self, pixel_values: &Tensor) -> Result<Tensor> {
392 let image_features = self.vision_model.forward(pixel_values)?;
394
395 let image_features = ops_fn::matmul(&image_features, &self.visual_projection)?;
397
398 self.normalize_features(&image_features)
400 }
401
402 fn pool_text_features(&self, hidden_states: &Tensor, input_ids: &Tensor) -> Result<Tensor> {
404 let candle_hidden = hidden_states.to_candle()?;
407 let shape = candle_hidden.dims();
408
409 if shape.len() == 3 {
410 let seq_len = shape[1];
412 let pooled = candle_hidden.narrow(1, seq_len - 1, 1)?.squeeze(1)?;
413 Ok(Tensor::from_candle(pooled))
414 } else if shape.len() == 2 {
415 Ok(hidden_states.clone())
417 } else {
418 Err(anyhow::anyhow!("Invalid hidden states shape: {:?}", shape))
419 }
420 }
421
422 fn normalize_features(&self, features: &Tensor) -> Result<Tensor> {
424 ops_fn::normalize(features, 2, -1)
425 }
426}
427
428impl CLIPTextTransformer {
433 fn new(config: &CLIPConfig, device: &Device) -> Result<Self> {
434 Ok(Self {
435 embeddings: CLIPTextEmbeddings::new(config, device)?,
436 encoder: CLIPEncoder::new_text(config, device)?,
437 final_layer_norm: ops_fn::zeros(&[config.transformer_width], DataType::Float32, device)?,
438 final_layer_norm_bias: None,
439 config: config.clone(),
440 is_causal: true,
441 })
442 }
443
444 fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
445 let hidden_states = self.embeddings.forward(input_ids)?;
447
448 let hidden_states = self.encoder.forward(&hidden_states, self.is_causal)?;
450
451 ops_fn::layer_norm(&hidden_states, &self.final_layer_norm, self.final_layer_norm_bias.as_ref(), self.config.layer_norm_eps)
453 }
454
455 fn load_weights(&mut self, weights: &ModelWeights) -> Result<()> {
456 if let Some(w) = weights.get("text_model.final_layer_norm.weight") {
457 self.final_layer_norm = w.clone();
458 }
459 if let Some(w) = weights.get("text_model.final_layer_norm.bias") {
460 self.final_layer_norm_bias = Some(w.clone());
461 }
462
463 self.embeddings.load_weights(weights)?;
464 self.encoder.load_weights(weights, "text_model")?;
465 Ok(())
466 }
467
468 fn to_device(&mut self, device: &Device) -> Result<()> {
469 self.final_layer_norm = self.final_layer_norm.to_device(device)?;
470 if let Some(ref mut b) = self.final_layer_norm_bias {
471 *b = b.to_device(device)?;
472 }
473 self.embeddings.to_device(device)?;
474 self.encoder.to_device(device)?;
475 Ok(())
476 }
477}
478
479impl CLIPVisionTransformer {
484 fn new(config: &CLIPConfig, device: &Device) -> Result<Self> {
485 Ok(Self {
486 embeddings: CLIPVisionEmbeddings::new(config, device)?,
487 pre_layernorm: ops_fn::zeros(&[config.vision_width], DataType::Float32, device)?,
488 pre_layernorm_bias: None,
489 encoder: CLIPEncoder::new_vision(config, device)?,
490 post_layernorm: ops_fn::zeros(&[config.vision_width], DataType::Float32, device)?,
491 post_layernorm_bias: None,
492 config: config.clone(),
493 })
494 }
495
496 fn forward(&self, pixel_values: &Tensor) -> Result<Tensor> {
497 let hidden_states = self.embeddings.forward(pixel_values)?;
499
500 let hidden_states = ops_fn::layer_norm(
502 &hidden_states,
503 &self.pre_layernorm,
504 self.pre_layernorm_bias.as_ref(),
505 self.config.layer_norm_eps
506 )?;
507
508 let hidden_states = self.encoder.forward(&hidden_states, false)?;
510
511 let candle_hidden = hidden_states.to_candle()?;
513 let cls_hidden = candle_hidden.narrow(1, 0, 1)?.squeeze(1)?;
514 let cls_hidden = Tensor::from_candle(cls_hidden);
515
516 ops_fn::layer_norm(
518 &cls_hidden,
519 &self.post_layernorm,
520 self.post_layernorm_bias.as_ref(),
521 self.config.layer_norm_eps
522 )
523 }
524
525 fn load_weights(&mut self, weights: &ModelWeights) -> Result<()> {
526 if let Some(w) = weights.get("vision_model.pre_layrnorm.weight") {
527 self.pre_layernorm = w.clone();
528 } else if let Some(w) = weights.get("vision_model.pre_layernorm.weight") {
529 self.pre_layernorm = w.clone();
530 }
531 if let Some(w) = weights.get("vision_model.pre_layrnorm.bias") {
532 self.pre_layernorm_bias = Some(w.clone());
533 } else if let Some(w) = weights.get("vision_model.pre_layernorm.bias") {
534 self.pre_layernorm_bias = Some(w.clone());
535 }
536
537 if let Some(w) = weights.get("vision_model.post_layernorm.weight") {
538 self.post_layernorm = w.clone();
539 }
540 if let Some(w) = weights.get("vision_model.post_layernorm.bias") {
541 self.post_layernorm_bias = Some(w.clone());
542 }
543
544 self.embeddings.load_weights(weights)?;
545 self.encoder.load_weights(weights, "vision_model")?;
546 Ok(())
547 }
548
549 fn to_device(&mut self, device: &Device) -> Result<()> {
550 self.pre_layernorm = self.pre_layernorm.to_device(device)?;
551 self.post_layernorm = self.post_layernorm.to_device(device)?;
552 if let Some(ref mut b) = self.pre_layernorm_bias {
553 *b = b.to_device(device)?;
554 }
555 if let Some(ref mut b) = self.post_layernorm_bias {
556 *b = b.to_device(device)?;
557 }
558 self.embeddings.to_device(device)?;
559 self.encoder.to_device(device)?;
560 Ok(())
561 }
562}
563
564impl CLIPTextEmbeddings {
569 fn new(config: &CLIPConfig, device: &Device) -> Result<Self> {
570 Ok(Self {
571 token_embedding: ops_fn::zeros(
572 &[config.vocab_size, config.transformer_width],
573 DataType::Float32,
574 device
575 )?,
576 position_embedding: ops_fn::zeros(
577 &[config.context_length, config.transformer_width],
578 DataType::Float32,
579 device
580 )?,
581 config: config.clone(),
582 })
583 }
584
585 fn forward(&self, input_ids: &Tensor) -> Result<Tensor> {
586 let token_embeds = ops_fn::embedding(input_ids, &self.token_embedding)?;
588
589 let input_shape = input_ids.shape();
591 let seq_len = if input_shape.len() >= 2 { input_shape[1] } else { input_shape[0] };
592
593 let pos_candle = self.position_embedding.to_candle()?;
595 let pos_slice = pos_candle.narrow(0, 0, seq_len)?;
596 let pos_embeds = Tensor::from_candle(pos_slice);
597
598 ops_fn::add(&token_embeds, &pos_embeds)
600 }
601
602 fn load_weights(&mut self, weights: &ModelWeights) -> Result<()> {
603 if let Some(w) = weights.get("text_model.embeddings.token_embedding.weight") {
604 self.token_embedding = w.clone();
605 }
606 if let Some(w) = weights.get("text_model.embeddings.position_embedding.weight") {
607 self.position_embedding = w.clone();
608 }
609 Ok(())
610 }
611
612 fn to_device(&mut self, device: &Device) -> Result<()> {
613 self.token_embedding = self.token_embedding.to_device(device)?;
614 self.position_embedding = self.position_embedding.to_device(device)?;
615 Ok(())
616 }
617}
618
619impl CLIPVisionEmbeddings {
620 fn new(config: &CLIPConfig, device: &Device) -> Result<Self> {
621 let num_positions = config.num_positions();
622
623 Ok(Self {
624 patch_embedding: ops_fn::zeros(
628 &[config.vision_width, 3, config.vision_patch_size, config.vision_patch_size],
629 DataType::Float32,
630 device
631 )?,
632 class_embedding: ops_fn::zeros(
633 &[config.vision_width],
634 DataType::Float32,
635 device
636 )?,
637 position_embedding: ops_fn::zeros(
638 &[num_positions, config.vision_width],
639 DataType::Float32,
640 device
641 )?,
642 config: config.clone(),
643 })
644 }
645
646 fn forward(&self, pixel_values: &Tensor) -> Result<Tensor> {
647 let pixel_candle = pixel_values.to_candle()?;
648 let shape = pixel_candle.dims();
649
650 let batch_size = shape[0];
652
653 let patch_candle = self.patch_embedding.to_candle()?;
656
657 let patch_embeds = pixel_candle.conv2d(
659 &patch_candle,
660 self.config.vision_patch_size, self.config.vision_patch_size, 1, 1, )?;
665
666 let patch_shape = patch_embeds.dims();
668 let num_patches = patch_shape[2] * patch_shape[3];
669 let patch_embeds = patch_embeds
670 .reshape(&[batch_size, self.config.vision_width, num_patches])?
671 .transpose(1, 2)?; let class_candle = self.class_embedding.to_candle()?;
675 let class_embeds = class_candle
676 .unsqueeze(0)? .unsqueeze(0)? .broadcast_as(&[batch_size, 1, self.config.vision_width])?;
679
680 let embeddings = candle_core::Tensor::cat(&[&class_embeds, &patch_embeds], 1)?;
681
682 let pos_candle = self.position_embedding.to_candle()?;
684 let seq_len = embeddings.dims()[1];
685 let pos_slice = pos_candle.narrow(0, 0, seq_len)?;
686 let embeddings = embeddings.broadcast_add(&pos_slice)?;
687
688 Ok(Tensor::from_candle(embeddings))
689 }
690
691 fn load_weights(&mut self, weights: &ModelWeights) -> Result<()> {
692 if let Some(w) = weights.get("vision_model.embeddings.patch_embedding.weight") {
693 self.patch_embedding = w.clone();
694 }
695 if let Some(w) = weights.get("vision_model.embeddings.class_embedding") {
696 self.class_embedding = w.clone();
697 }
698 if let Some(w) = weights.get("vision_model.embeddings.position_embedding.weight") {
699 self.position_embedding = w.clone();
700 }
701 Ok(())
702 }
703
704 fn to_device(&mut self, device: &Device) -> Result<()> {
705 self.patch_embedding = self.patch_embedding.to_device(device)?;
706 self.class_embedding = self.class_embedding.to_device(device)?;
707 self.position_embedding = self.position_embedding.to_device(device)?;
708 Ok(())
709 }
710}
711
712impl CLIPEncoder {
717 fn new_text(config: &CLIPConfig, device: &Device) -> Result<Self> {
718 let mut layers = Vec::new();
719 for _ in 0..config.transformer_layers {
720 layers.push(CLIPEncoderLayer::new(
721 config.transformer_width,
722 config.transformer_heads,
723 config.layer_norm_eps,
724 device,
725 )?);
726 }
727 Ok(Self { layers, is_causal: true })
728 }
729
730 fn new_vision(config: &CLIPConfig, device: &Device) -> Result<Self> {
731 let mut layers = Vec::new();
732 for _ in 0..config.vision_layers {
733 layers.push(CLIPEncoderLayer::new(
734 config.vision_width,
735 config.vision_heads,
736 config.layer_norm_eps,
737 device,
738 )?);
739 }
740 Ok(Self { layers, is_causal: false })
741 }
742
743 fn forward(&self, hidden_states: &Tensor, is_causal: bool) -> Result<Tensor> {
744 let mut hidden_states = hidden_states.clone();
745 for layer in &self.layers {
746 hidden_states = layer.forward(&hidden_states, is_causal)?;
747 }
748 Ok(hidden_states)
749 }
750
751 fn load_weights(&mut self, weights: &ModelWeights, prefix: &str) -> Result<()> {
752 for (i, layer) in self.layers.iter_mut().enumerate() {
753 layer.load_weights(weights, &format!("{}.encoder.layers.{}", prefix, i))?;
754 }
755 Ok(())
756 }
757
758 fn to_device(&mut self, device: &Device) -> Result<()> {
759 for layer in &mut self.layers {
760 layer.to_device(device)?;
761 }
762 Ok(())
763 }
764}
765
766impl CLIPEncoderLayer {
767 fn new(hidden_size: usize, num_heads: usize, layer_norm_eps: f32, device: &Device) -> Result<Self> {
768 let intermediate_size = hidden_size * 4;
769
770 Ok(Self {
771 self_attn: CLIPAttention::new(hidden_size, num_heads, device)?,
772 layer_norm1: ops_fn::zeros(&[hidden_size], DataType::Float32, device)?,
773 layer_norm1_bias: None,
774 mlp: CLIPMLP::new(hidden_size, intermediate_size, device)?,
775 layer_norm2: ops_fn::zeros(&[hidden_size], DataType::Float32, device)?,
776 layer_norm2_bias: None,
777 hidden_size,
778 })
779 }
780
781 fn forward(&self, hidden_states: &Tensor, is_causal: bool) -> Result<Tensor> {
782 let residual = hidden_states.clone();
784 let hidden_states = ops_fn::layer_norm(hidden_states, &self.layer_norm1, self.layer_norm1_bias.as_ref(), 1e-5)?;
785 let hidden_states = self.self_attn.forward(&hidden_states, is_causal)?;
786 let hidden_states = ops_fn::add(&residual, &hidden_states)?;
787
788 let residual = hidden_states.clone();
789 let hidden_states = ops_fn::layer_norm(&hidden_states, &self.layer_norm2, self.layer_norm2_bias.as_ref(), 1e-5)?;
790 let hidden_states = self.mlp.forward(&hidden_states)?;
791 ops_fn::add(&residual, &hidden_states)
792 }
793
794 fn load_weights(&mut self, weights: &ModelWeights, prefix: &str) -> Result<()> {
795 if let Some(w) = weights.get(&format!("{}.layer_norm1.weight", prefix)) {
796 self.layer_norm1 = w.clone();
797 }
798 if let Some(w) = weights.get(&format!("{}.layer_norm1.bias", prefix)) {
799 self.layer_norm1_bias = Some(w.clone());
800 }
801 if let Some(w) = weights.get(&format!("{}.layer_norm2.weight", prefix)) {
802 self.layer_norm2 = w.clone();
803 }
804 if let Some(w) = weights.get(&format!("{}.layer_norm2.bias", prefix)) {
805 self.layer_norm2_bias = Some(w.clone());
806 }
807 self.self_attn.load_weights(weights, &format!("{}.self_attn", prefix))?;
808 self.mlp.load_weights(weights, &format!("{}.mlp", prefix))?;
809 Ok(())
810 }
811
812 fn to_device(&mut self, device: &Device) -> Result<()> {
813 self.layer_norm1 = self.layer_norm1.to_device(device)?;
814 self.layer_norm2 = self.layer_norm2.to_device(device)?;
815 if let Some(ref mut b) = self.layer_norm1_bias {
816 *b = b.to_device(device)?;
817 }
818 if let Some(ref mut b) = self.layer_norm2_bias {
819 *b = b.to_device(device)?;
820 }
821 self.self_attn.to_device(device)?;
822 self.mlp.to_device(device)?;
823 Ok(())
824 }
825}
826
827impl CLIPAttention {
832 fn new(hidden_size: usize, num_heads: usize, device: &Device) -> Result<Self> {
833 let head_dim = hidden_size / num_heads;
834 let scale = 1.0 / (head_dim as f32).sqrt();
835
836 Ok(Self {
837 q_proj: ops_fn::zeros(&[hidden_size, hidden_size], DataType::Float32, device)?,
838 k_proj: ops_fn::zeros(&[hidden_size, hidden_size], DataType::Float32, device)?,
839 v_proj: ops_fn::zeros(&[hidden_size, hidden_size], DataType::Float32, device)?,
840 out_proj: ops_fn::zeros(&[hidden_size, hidden_size], DataType::Float32, device)?,
841 q_bias: None,
842 k_bias: None,
843 v_bias: None,
844 out_bias: None,
845 num_heads,
846 head_dim,
847 scale,
848 })
849 }
850
851 fn forward(&self, hidden_states: &Tensor, is_causal: bool) -> Result<Tensor> {
852 let shape = hidden_states.shape();
853 let (batch_size, seq_len, _) = if shape.len() == 3 {
854 (shape[0], shape[1], shape[2])
855 } else if shape.len() == 2 {
856 (1, shape[0], shape[1])
857 } else {
858 return Err(anyhow::anyhow!("Invalid hidden_states shape: {:?}", shape));
859 };
860
861 let query = ops_fn::matmul(hidden_states, &self.q_proj)?;
863 let key = ops_fn::matmul(hidden_states, &self.k_proj)?;
864 let value = ops_fn::matmul(hidden_states, &self.v_proj)?;
865
866 let query = if let Some(ref bias) = self.q_bias {
868 ops_fn::add(&query, bias)?
869 } else {
870 query
871 };
872 let key = if let Some(ref bias) = self.k_bias {
873 ops_fn::add(&key, bias)?
874 } else {
875 key
876 };
877 let value = if let Some(ref bias) = self.v_bias {
878 ops_fn::add(&value, bias)?
879 } else {
880 value
881 };
882
883 let q_candle = query.to_candle()?;
886 let k_candle = key.to_candle()?;
887 let v_candle = value.to_candle()?;
888
889 let q_reshaped = q_candle
890 .reshape(&[batch_size, seq_len, self.num_heads, self.head_dim])?
891 .transpose(1, 2)?;
892 let k_reshaped = k_candle
893 .reshape(&[batch_size, seq_len, self.num_heads, self.head_dim])?
894 .transpose(1, 2)?;
895 let v_reshaped = v_candle
896 .reshape(&[batch_size, seq_len, self.num_heads, self.head_dim])?
897 .transpose(1, 2)?;
898
899 let k_t = k_reshaped.transpose(2, 3)?;
901
902 let q_contiguous = q_reshaped.contiguous()?;
904 let k_contiguous = k_t.contiguous()?;
905
906 let scores = q_contiguous.matmul(&k_contiguous)?;
907 let scaled_scores = (scores * (self.scale as f64))?;
908
909 let masked_scores = if is_causal {
911 let device = scaled_scores.device();
912 let mut mask_data = vec![0.0f32; seq_len * seq_len];
913 for i in 0..seq_len {
914 for j in 0..seq_len {
915 if j > i {
916 mask_data[i * seq_len + j] = f32::NEG_INFINITY;
917 }
918 }
919 }
920 let causal_mask = candle_core::Tensor::from_vec(mask_data, &[1, 1, seq_len, seq_len], device)?;
921 scaled_scores.broadcast_add(&causal_mask)?
922 } else {
923 scaled_scores
924 };
925
926 let attention_weights = candle_nn::ops::softmax_last_dim(&masked_scores)?;
928
929 let v_contiguous = v_reshaped.contiguous()?;
931 let attn_output = attention_weights.matmul(&v_contiguous)?;
932
933 let attn_output = attn_output
935 .transpose(1, 2)?
936 .reshape(&[batch_size, seq_len, self.num_heads * self.head_dim])?;
937
938 let attn_output = Tensor::from_candle(attn_output);
939
940 let output = ops_fn::matmul(&attn_output, &self.out_proj)?;
942 if let Some(ref bias) = self.out_bias {
943 ops_fn::add(&output, bias)
944 } else {
945 Ok(output)
946 }
947 }
948
949 fn load_weights(&mut self, weights: &ModelWeights, prefix: &str) -> Result<()> {
950 if let Some(w) = weights.get(&format!("{}.q_proj.weight", prefix)) {
952 self.q_proj = ops_fn::transpose(w)?;
953 }
954 if let Some(w) = weights.get(&format!("{}.k_proj.weight", prefix)) {
955 self.k_proj = ops_fn::transpose(w)?;
956 }
957 if let Some(w) = weights.get(&format!("{}.v_proj.weight", prefix)) {
958 self.v_proj = ops_fn::transpose(w)?;
959 }
960 if let Some(w) = weights.get(&format!("{}.out_proj.weight", prefix)) {
961 self.out_proj = ops_fn::transpose(w)?;
962 }
963
964 if let Some(w) = weights.get(&format!("{}.q_proj.bias", prefix)) {
966 self.q_bias = Some(w.clone());
967 }
968 if let Some(w) = weights.get(&format!("{}.k_proj.bias", prefix)) {
969 self.k_bias = Some(w.clone());
970 }
971 if let Some(w) = weights.get(&format!("{}.v_proj.bias", prefix)) {
972 self.v_bias = Some(w.clone());
973 }
974 if let Some(w) = weights.get(&format!("{}.out_proj.bias", prefix)) {
975 self.out_bias = Some(w.clone());
976 }
977 Ok(())
978 }
979
980 fn to_device(&mut self, device: &Device) -> Result<()> {
981 self.q_proj = self.q_proj.to_device(device)?;
982 self.k_proj = self.k_proj.to_device(device)?;
983 self.v_proj = self.v_proj.to_device(device)?;
984 self.out_proj = self.out_proj.to_device(device)?;
985 if let Some(ref mut b) = self.q_bias { *b = b.to_device(device)?; }
986 if let Some(ref mut b) = self.k_bias { *b = b.to_device(device)?; }
987 if let Some(ref mut b) = self.v_bias { *b = b.to_device(device)?; }
988 if let Some(ref mut b) = self.out_bias { *b = b.to_device(device)?; }
989 Ok(())
990 }
991}
992
993impl CLIPMLP {
998 fn new(hidden_size: usize, intermediate_size: usize, device: &Device) -> Result<Self> {
999 Ok(Self {
1000 fc1: ops_fn::zeros(&[hidden_size, intermediate_size], DataType::Float32, device)?,
1001 fc1_bias: None,
1002 fc2: ops_fn::zeros(&[intermediate_size, hidden_size], DataType::Float32, device)?,
1003 fc2_bias: None,
1004 hidden_size,
1005 intermediate_size,
1006 })
1007 }
1008
1009 fn forward(&self, hidden_states: &Tensor) -> Result<Tensor> {
1010 let hidden_states = ops_fn::matmul(hidden_states, &self.fc1)?;
1012 let hidden_states = if let Some(ref bias) = self.fc1_bias {
1013 ops_fn::add(&hidden_states, bias)?
1014 } else {
1015 hidden_states
1016 };
1017
1018 let hidden_states = self.quick_gelu(&hidden_states)?;
1020
1021 let hidden_states = ops_fn::matmul(&hidden_states, &self.fc2)?;
1023 if let Some(ref bias) = self.fc2_bias {
1024 ops_fn::add(&hidden_states, bias)
1025 } else {
1026 Ok(hidden_states)
1027 }
1028 }
1029
1030 fn quick_gelu(&self, x: &Tensor) -> Result<Tensor> {
1032 let x_candle = x.to_candle()?;
1033 let scaled = (&x_candle * 1.702)?;
1035 let sigmoid = candle_nn::ops::sigmoid(&scaled)?;
1036 let result = (&x_candle * &sigmoid)?;
1037 Ok(Tensor::from_candle(result))
1038 }
1039
1040 fn load_weights(&mut self, weights: &ModelWeights, prefix: &str) -> Result<()> {
1041 if let Some(w) = weights.get(&format!("{}.fc1.weight", prefix)) {
1042 self.fc1 = ops_fn::transpose(w)?;
1043 }
1044 if let Some(w) = weights.get(&format!("{}.fc1.bias", prefix)) {
1045 self.fc1_bias = Some(w.clone());
1046 }
1047 if let Some(w) = weights.get(&format!("{}.fc2.weight", prefix)) {
1048 self.fc2 = ops_fn::transpose(w)?;
1049 }
1050 if let Some(w) = weights.get(&format!("{}.fc2.bias", prefix)) {
1051 self.fc2_bias = Some(w.clone());
1052 }
1053 Ok(())
1054 }
1055
1056 fn to_device(&mut self, device: &Device) -> Result<()> {
1057 self.fc1 = self.fc1.to_device(device)?;
1058 self.fc2 = self.fc2.to_device(device)?;
1059 if let Some(ref mut b) = self.fc1_bias { *b = b.to_device(device)?; }
1060 if let Some(ref mut b) = self.fc2_bias { *b = b.to_device(device)?; }
1061 Ok(())
1062 }
1063}
1064
1065#[cfg(test)]
1070mod tests {
1071 use super::*;
1072
1073 #[test]
1074 fn test_clip_config_creation() {
1075 let config = CLIPConfig::default();
1076 assert_eq!(config.vocab_size(), 49408);
1077 assert_eq!(config.hidden_size(), 512);
1078 assert_eq!(config.num_layers(), 12);
1079 assert_eq!(config.num_patches(), 49); assert_eq!(config.num_positions(), 50); }
1082
1083 #[test]
1084 fn test_clip_model_creation() {
1085 let config = CLIPConfig {
1086 vocab_size: 1000,
1087 hidden_size: 64,
1088 num_hidden_layers: 2,
1089 transformer_width: 64,
1090 transformer_layers: 2,
1091 transformer_heads: 4,
1092 vision_width: 64,
1093 vision_layers: 2,
1094 vision_heads: 4,
1095 vision_patch_size: 16,
1096 image_resolution: 64,
1097 projection_dim: 64,
1098 embed_dim: 64,
1099 ..Default::default()
1100 };
1101
1102 let model = CLIPModelV2::new(config).unwrap();
1103 assert_eq!(model.config().vocab_size(), 1000);
1104 }
1105
1106 #[test]
1107 fn test_clip_text_forward() {
1108 let config = CLIPConfig {
1109 vocab_size: 100,
1110 hidden_size: 32,
1111 num_hidden_layers: 1,
1112 transformer_width: 32,
1113 transformer_layers: 1,
1114 transformer_heads: 4,
1115 context_length: 16,
1116 vision_width: 32,
1117 vision_layers: 1,
1118 vision_heads: 4,
1119 vision_patch_size: 8,
1120 image_resolution: 32,
1121 projection_dim: 32,
1122 embed_dim: 32,
1123 ..Default::default()
1124 };
1125
1126 let model = CLIPModelV2::new(config).unwrap();
1127
1128 let input_ids = ops_fn::zeros(&[2, 8], DataType::Int64, &Device::CPU).unwrap();
1130 let inputs = ModelInputs::text(input_ids);
1131
1132 let outputs = model.forward(&inputs).unwrap();
1133 match outputs {
1134 ModelOutputs::Embeddings { embeddings, .. } => {
1135 assert_eq!(embeddings.shape()[0], 2); assert_eq!(embeddings.shape()[1], 32); }
1138 _ => panic!("Expected embeddings output"),
1139 }
1140 }
1141
1142 #[test]
1143 fn test_clip_generate_returns_error() {
1144 let config = CLIPConfig::default();
1145 let model = CLIPModelV2::new(config).unwrap();
1146 let gen_config = GenerationConfig::default();
1147
1148 let result = model.generate("test", &gen_config);
1149 assert!(result.is_err());
1150 assert!(result.unwrap_err().to_string().contains("contrastive model"));
1151 }
1152}