Skip to content

Commit a3a4bf4

Browse files
authored
Add mobileclip and jina-clip-v2 (#106)
1 parent 027c628 commit a3a4bf4

9 files changed

Lines changed: 190 additions & 37 deletions

File tree

.github/workflows/rust-ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121

2222
- name: Install dependencies
2323
run: |
24-
DEBIAN_FRONTEND=noninteractive apt-get update
24+
DEBIAN_FRONTEND=noninteractive apt-get update --fix-missing
2525
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential ca-certificates clang curl pkg-config protobuf-compiler
2626
2727
- name: Setup Rust
@@ -46,7 +46,7 @@ jobs:
4646

4747
- name: Install dependencies
4848
run: |
49-
DEBIAN_FRONTEND=noninteractive apt-get update
49+
DEBIAN_FRONTEND=noninteractive apt-get update --fix-missing
5050
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential ca-certificates clang curl pkg-config protobuf-compiler
5151
5252
- name: Setup Rust
@@ -66,7 +66,7 @@ jobs:
6666

6767
- name: Install dependencies
6868
run: |
69-
DEBIAN_FRONTEND=noninteractive apt-get update
69+
DEBIAN_FRONTEND=noninteractive apt-get update --fix-missing
7070
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential ca-certificates clang curl pkg-config protobuf-compiler
7171
7272
- name: Setup Rust
@@ -92,11 +92,11 @@ jobs:
9292

9393
- name: Install dependencies
9494
run: |
95-
DEBIAN_FRONTEND=noninteractive apt-get update
95+
DEBIAN_FRONTEND=noninteractive apt-get update --fix-missing
9696
DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential ca-certificates clang curl pkg-config protobuf-compiler
9797
9898
- name: Setup Rust
9999
uses: dtolnay/rust-toolchain@stable
100100

101101
- name: Build
102-
run: cargo build --all-features
102+
run: cargo build --all-features

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
- **YOLO Models**: [YOLOv5](https://github.com/ultralytics/yolov5), [YOLOv6](https://github.com/meituan/YOLOv6), [YOLOv7](https://github.com/WongKinYiu/yolov7), [YOLOv8](https://github.com/ultralytics/ultralytics), [YOLOv9](https://github.com/WongKinYiu/yolov9), [YOLOv10](https://github.com/THU-MIG/yolov10), [YOLO11](https://github.com/ultralytics/ultralytics), [YOLOv12](https://github.com/sunsmarterjie/yolov12)
6363
- **SAM Models**: [SAM](https://github.com/facebookresearch/segment-anything), [SAM2](https://github.com/facebookresearch/segment-anything-2), [MobileSAM](https://github.com/ChaoningZhang/MobileSAM), [EdgeSAM](https://github.com/chongzhou96/EdgeSAM), [SAM-HQ](https://github.com/SysCV/sam-hq), [FastSAM](https://github.com/CASIA-IVA-Lab/FastSAM)
6464
- **Vision Models**: [RT-DETR](https://arxiv.org/abs/2304.08069), [RTMO](https://github.com/open-mmlab/mmpose/tree/main/projects/rtmo), [Depth-Anything](https://github.com/LiheYoung/Depth-Anything), [DINOv2](https://github.com/facebookresearch/dinov2), [MODNet](https://github.com/ZHKKKe/MODNet), [Sapiens](https://arxiv.org/abs/2408.12569), [DepthPro](https://github.com/apple/ml-depth-pro), [FastViT](https://github.com/apple/ml-fastvit), [BEiT](https://github.com/microsoft/unilm/tree/master/beit), [MobileOne](https://github.com/apple/ml-mobileone)
65-
- **Vision-Language Models**: [CLIP](https://github.com/openai/CLIP), [jina-clip-v1](https://huggingface.co/jinaai/jina-clip-v1), [BLIP](https://arxiv.org/abs/2201.12086), [GroundingDINO](https://github.com/IDEA-Research/GroundingDINO), [YOLO-World](https://github.com/AILab-CVC/YOLO-World), [Florence2](https://arxiv.org/abs/2311.06242), [Moondream2](https://github.com/vikhyat/moondream/tree/main)
65+
- **Vision-Language Models**: [CLIP](https://github.com/openai/CLIP), [jina-clip-v1-v2](https://huggingface.co/jinaai/jina-clip-v1), [BLIP](https://arxiv.org/abs/2201.12086), [GroundingDINO](https://github.com/IDEA-Research/GroundingDINO), [YOLO-World](https://github.com/AILab-CVC/YOLO-World), [Florence2](https://arxiv.org/abs/2311.06242), [Moondream2](https://github.com/vikhyat/moondream/tree/main)
6666
- **OCR-Related Models**: [FAST](https://github.com/czczup/FAST), [DB(PaddleOCR-Det)](https://arxiv.org/abs/1911.08947), [SVTR(PaddleOCR-Rec)](https://arxiv.org/abs/2205.00159), [SLANet](https://paddlepaddle.github.io/PaddleOCR/latest/algorithm/table_recognition/algorithm_table_slanet.html), [TrOCR](https://huggingface.co/microsoft/trocr-base-printed), [DocLayout-YOLO](https://github.com/opendatalab/DocLayout-YOLO)
6767

6868
<details>
@@ -100,6 +100,8 @@
100100
| [GroundingDINO](https://github.com/IDEA-Research/GroundingDINO) | Open-Set Detection With Language | [demo](examples/grounding-dino) |||| | |
101101
| [CLIP](https://github.com/openai/CLIP) | Vision-Language Embedding | [demo](examples/clip) ||||||
102102
| [jina-clip-v1](https://huggingface.co/jinaai/jina-clip-v1) | Vision-Language Embedding | [demo](examples/clip) ||||||
103+
| [jina-clip-v2](https://huggingface.co/jinaai/jina-clip-v2) | Vision-Language Embedding | [demo](examples/clip) ||||||
104+
| [mobileclip](https://github.com/apple/ml-mobileclip) | Vision-Language Embedding | [demo](examples/clip) ||||||
103105
| [BLIP](https://github.com/salesforce/BLIP) | Image Captioning | [demo](examples/blip) ||||||
104106
| [DB(PaddleOCR-Det)](https://arxiv.org/abs/1911.08947) | Text Detection | [demo](examples/db) ||||||
105107
| [FAST](https://github.com/czczup/FAST) | Text Detection | [demo](examples/fast) ||||||

examples/clip/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ cargo run -r -F cuda --example clip -- --device cuda:0
99
## Results
1010

1111
```shell
12-
(99.9675%) ./examples/clip/images/carrot.jpg => Some carrots
13-
(99.93718%) ./examples/clip/images/doll.jpg => There is a doll with red hair and a clock on a table
14-
(100.0%) ./examples/clip/images/drink.jpg => Some people holding wine glasses in a restaurant
12+
[99.999428%] (examples/clip/images/carrot.jpg) <=> (A picture of some carrots.)
13+
[100.000000%] (examples/clip/images/doll.jpg) <=> (There is a doll with red hair and a clock on a table.)
14+
[99.990738%] (examples/clip/images/drink.jpg) <=> (Some people holding wine glasses in a restaurant.)
1515
```

examples/clip/main.rs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
use anyhow::Result;
2-
use usls::{models::Clip, Config, DataLoader, Ops};
2+
use ndarray::Axis;
3+
use usls::{models::Clip, Config, DataLoader};
34

45
#[derive(argh::FromArgs)]
56
/// CLIP Example
67
struct Args {
78
/// device
89
#[argh(option, default = "String::from(\"cpu:0\")")]
910
device: String,
11+
12+
/// dtype
13+
#[argh(option, default = "String::from(\"fp16\")")]
14+
dtype: String,
1015
}
1116

1217
fn main() -> Result<()> {
@@ -17,45 +22,52 @@ fn main() -> Result<()> {
1722
let args: Args = argh::from_env();
1823

1924
// build model
20-
let config = Config::jina_clip_v1()
25+
let config = Config::mobileclip_s0()
26+
// mobileclip_blt()
27+
// clip_vit_b16()
28+
// clip_vit_l14()
29+
// clip_vit_b32()
30+
// jina_clip_v1()
31+
// jina_clip_v2()
32+
.with_dtype_all(args.dtype.as_str().try_into()?)
2133
.with_device_all(args.device.as_str().try_into()?)
2234
.commit()?;
2335
let mut model = Clip::new(config)?;
2436

2537
// texts
2638
let texts = vec![
27-
"A photo of a dinosaur",
28-
"A photo of a cat",
29-
"A photo of a dog",
30-
"Some carrots",
31-
"There are some playing cards on a striped table cloth",
32-
"There is a doll with red hair and a clock on a table",
33-
"Some people holding wine glasses in a restaurant",
39+
"A photo of a dinosaur.",
40+
"A photo of a cat.",
41+
"A photo of a dog.",
42+
"A picture of some carrots.",
43+
"There are some playing cards on a striped table cloth.",
44+
"There is a doll with red hair and a clock on a table.",
45+
"Some people holding wine glasses in a restaurant.",
3446
];
35-
let feats_text = model.encode_texts(&texts)?; // [n, ndim]
47+
let feats_text = model.encode_texts(&texts)?.norm(1)?;
3648

3749
// load images
3850
let dl = DataLoader::new("./examples/clip/images")?.build()?;
3951

4052
// run
41-
for images in dl {
42-
let feats_image = model.encode_images(&images)?;
53+
for images in &dl {
54+
let feats_image = model.encode_images(&images)?.norm(1)?;
4355

4456
// use image to query texts
45-
let matrix = Ops::dot2(&feats_image, &feats_text)?;
57+
let matrix = (feats_image * 100.).dot2(&feats_text)?.softmax(1)?;
4658

47-
for i in 0..images.len() {
48-
let probs = &matrix[i];
49-
let (id, &score) = probs
59+
// Process each image's matching scores
60+
for (i, row) in matrix.axis_iter(Axis(0)).enumerate() {
61+
let (id, &score) = row
5062
.iter()
5163
.enumerate()
52-
.reduce(|max, x| if x.1 > max.1 { x } else { max })
64+
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
5365
.unwrap();
5466

5567
println!(
56-
"({:?}%) {:?} => {} ",
68+
"[{:.6}%] ({}) <=> ({})",
5769
score * 100.0,
58-
images[i].source(),
70+
images[i].source().unwrap().display(),
5971
&texts[id]
6072
);
6173
}

src/inference/x.rs

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use image::DynamicImage;
3-
use ndarray::{Array, Dim, IntoDimension, IxDyn, IxDynImpl};
3+
use ndarray::{Array, Dim, IntoDimension, Ix2, IxDyn, IxDynImpl};
4+
// use std::ops::Mul;
45

56
use crate::{Ops, ResizeMode};
67

@@ -64,9 +65,39 @@ impl std::ops::Deref for X {
6465
}
6566
}
6667

67-
impl X {
68-
// TODO: Add some slice and index method
68+
impl std::ops::Mul<f32> for X {
69+
type Output = Self;
70+
71+
fn mul(self, other: f32) -> Self::Output {
72+
Self(self.0 * other)
73+
}
74+
}
75+
76+
impl std::ops::Div<f32> for X {
77+
type Output = Self;
78+
79+
fn div(self, other: f32) -> Self::Output {
80+
Self(self.0 / other)
81+
}
82+
}
83+
84+
impl std::ops::Add<f32> for X {
85+
type Output = Self;
6986

87+
fn add(self, other: f32) -> Self::Output {
88+
Self(self.0 + other)
89+
}
90+
}
91+
92+
impl std::ops::Sub<f32> for X {
93+
type Output = Self;
94+
95+
fn sub(self, other: f32) -> Self::Output {
96+
Self(self.0 - other)
97+
}
98+
}
99+
100+
impl X {
70101
pub fn zeros(shape: &[usize]) -> Self {
71102
Self::from(Array::zeros(Dim(IxDynImpl::from(shape.to_vec()))))
72103
}
@@ -187,6 +218,36 @@ impl X {
187218
Ok(self)
188219
}
189220

221+
pub fn dot2(&self, other: &Self) -> Result<Self> {
222+
// Check dimensions
223+
if self.ndim() != 2 || other.ndim() != 2 {
224+
anyhow::bail!(
225+
"dot2 requires 2D matrices, got {}D and {}D",
226+
self.ndim(),
227+
other.ndim()
228+
);
229+
}
230+
231+
let a = self.0.as_standard_layout().into_dimensionality::<Ix2>()?;
232+
let b = other.0.as_standard_layout().into_dimensionality::<Ix2>()?;
233+
234+
// Check compatibility
235+
if a.shape()[1] != b.shape()[1] {
236+
anyhow::bail!(
237+
"Incompatible dimensions for dot2: {:?} and {:?}",
238+
a.shape(),
239+
b.shape()
240+
);
241+
}
242+
243+
Ok(a.dot(&b.t()).into_dyn().into())
244+
}
245+
246+
pub fn softmax(mut self, d: usize) -> Result<Self> {
247+
self.0 = Ops::softmax(self.0, d)?;
248+
Ok(self)
249+
}
250+
190251
pub fn unsigned(mut self) -> Self {
191252
self.0.par_mapv_inplace(|x| if x < 0.0 { 0.0 } else { x });
192253
self

src/models/clip/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ CLIP (Contrastive Language-Image Pre-Training) is a neural network trained on a
44

55
## Official Repository
66

7-
The official repository can be found on: [GitHub](https://github.com/openai/CLIP)
7+
The official repository can be found on:
8+
- [CLIP](https://github.com/openai/CLIP)
9+
- [jina-clip-v1](https://huggingface.co/jinaai/jina-clip-v1)
10+
- [jina-clip-v2](https://huggingface.co/jinaai/jina-clip-v2)
11+
- [mobileclip](https://github.com/apple/ml-mobileclip)
812

913

1014
## Example

src/models/clip/config.rs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,22 +36,76 @@ impl crate::Config {
3636

3737
pub fn jina_clip() -> Self {
3838
Self::default()
39-
.with_name("jina-clip-v1")
4039
.with_batch_size_all(1)
4140
.with_visual_ixx(0, 1, 3.into())
4241
.with_visual_ixx(0, 2, 224.into())
4342
.with_visual_ixx(0, 3, 224.into())
4443
.with_image_mean(&[0.48145466, 0.4578275, 0.40821073])
4544
.with_image_std(&[0.26862954, 0.2613026, 0.2757771])
45+
.with_visual_file("visual.onnx")
46+
.with_textual_file("textual.onnx")
47+
}
48+
49+
pub fn jina_clip_v1() -> Self {
50+
Self::jina_clip()
51+
.with_name("jina-clip-v1")
4652
.with_tokenizer_file("jina-clip-v1/tokenizer.json")
4753
.with_tokenizer_config_file("jina-clip-v1/tokenizer_config.json")
4854
.with_special_tokens_map_file("jina-clip-v1/special_tokens_map.json")
4955
.with_config_file("jina-clip-v1/config.json")
5056
}
5157

52-
pub fn jina_clip_v1() -> Self {
58+
pub fn jina_clip_v2() -> Self {
5359
Self::jina_clip()
54-
.with_visual_file("visual.onnx")
55-
.with_textual_file("textual.onnx")
60+
.with_name("jina-clip-v2")
61+
.with_visual_ixx(0, 2, 512.into())
62+
.with_visual_ixx(0, 3, 512.into())
63+
.with_tokenizer_file("jina-clip-v2/tokenizer.json")
64+
.with_tokenizer_config_file("jina-clip-v2/tokenizer_config.json")
65+
.with_special_tokens_map_file("jina-clip-v2/special_tokens_map.json")
66+
.with_config_file("jina-clip-v2/config.json")
67+
}
68+
69+
pub fn mobileclip() -> Self {
70+
Self::default()
71+
.with_name("mobileclip")
72+
.with_batch_size_all(1)
73+
.with_visual_ixx(0, 1, 3.into())
74+
.with_visual_ixx(0, 2, 224.into())
75+
.with_visual_ixx(0, 3, 224.into())
76+
.with_model_max_length(77)
77+
.with_tokenizer_file("clip/tokenizer.json")
78+
.with_tokenizer_config_file("clip/tokenizer_config.json")
79+
.with_special_tokens_map_file("clip/special_tokens_map.json")
80+
}
81+
82+
pub fn mobileclip_s0() -> Self {
83+
Self::mobileclip()
84+
.with_textual_file("s0-textual.onnx")
85+
.with_visual_file("s0-visual.onnx")
86+
}
87+
88+
pub fn mobileclip_s1() -> Self {
89+
Self::mobileclip()
90+
.with_textual_file("s1-textual.onnx")
91+
.with_visual_file("s1-visual.onnx")
92+
}
93+
94+
pub fn mobileclip_s2() -> Self {
95+
Self::mobileclip()
96+
.with_textual_file("s2-textual.onnx")
97+
.with_visual_file("s2-visual.onnx")
98+
}
99+
100+
pub fn mobileclip_b() -> Self {
101+
Self::mobileclip()
102+
.with_textual_file("b-textual.onnx")
103+
.with_visual_file("b-visual.onnx")
104+
}
105+
106+
pub fn mobileclip_blt() -> Self {
107+
Self::mobileclip()
108+
.with_textual_file("blt-textual.onnx")
109+
.with_visual_file("blt-visual.onnx")
56110
}
57111
}

src/models/clip/impl.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ impl Clip {
5454
let xs = elapsed!("textual-preprocess", self.ts, {
5555
let encodings: Vec<f32> = self
5656
.processor
57-
.encode_texts_ids(xs, false)?
57+
.encode_texts_ids(xs, true)?
5858
.into_iter()
5959
.flatten()
6060
.collect();
6161

6262
let x: X = Array2::from_shape_vec((xs.len(), encodings.len() / xs.len()), encodings)?
6363
.into_dyn()
6464
.into();
65+
6566
x
6667
});
6768
let xs = elapsed!("textual-inference", self.ts, {

src/utils/ops.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,25 @@ impl Ops<'_> {
189189
Ok(xs / std_)
190190
}
191191

192+
pub fn softmax(xs: Array<f32, IxDyn>, d: usize) -> Result<Array<f32, IxDyn>> {
193+
if xs.shape().len() <= d {
194+
anyhow::bail!(
195+
"`softmax`: Specified axis {} exceeds the maximum dimension length {}.",
196+
d,
197+
xs.shape().len()
198+
);
199+
}
200+
let max_vals = xs
201+
.map_axis(Axis(d), |view| {
202+
view.fold(f32::NEG_INFINITY, |a, &b| a.max(b))
203+
})
204+
.insert_axis(Axis(d));
205+
let exps = (&xs - &max_vals).mapv(f32::exp);
206+
let sums = exps.sum_axis(Axis(d)).insert_axis(Axis(d));
207+
208+
Ok(exps / sums)
209+
}
210+
192211
pub fn scale_wh(w0: f32, h0: f32, w1: f32, h1: f32) -> (f32, f32, f32) {
193212
let r = (w1 / w0).min(h1 / h0);
194213
(r, (w0 * r).round(), (h0 * r).round())

0 commit comments

Comments
 (0)