Skip to content

Commit df13e32

Browse files
authored
Add FastVLM-0.5B (#136)
1 parent e5d4a92 commit df13e32

10 files changed

Lines changed: 354 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ usls = "latest-version"
112112
| [Moondream2](https://github.com/vikhyat/moondream/tree/main) | Open-Set Object Detection<br />Open-Set Keypoints Detection<br />Image Caption<br />Visual Question Answering | [demo](examples/moondream2) |
113113
| [OWLv2](https://huggingface.co/google/owlv2-base-patch16-ensemble) | Open-Set Object Detection | [demo](examples/owlv2) |
114114
| [SmolVLM(256M, 500M)](https://huggingface.co/HuggingFaceTB/SmolVLM-256M-Instruct) | Visual Question Answering | [demo](examples/smolvlm) |
115+
| [FastVLM(0.5B)](https://github.com/apple/ml-fastvlm) | Vision Language Models | [demo](examples/fastvlm) |
115116
| [RMBG(1.4, 2.0)](https://huggingface.co/briaai/RMBG-2.0) | Image Segmentation<br />Background Removal | [demo](examples/rmbg) |
116117
| [BEN2](https://huggingface.co/PramaLLC/BEN2) | Image Segmentation<br />Background Removal | [demo](examples/rmbg) |
117118
| [MediaPipe: Selfie-segmentation](https://ai.google.dev/edge/mediapipe/solutions/vision/image_segmenter) | Image Segmentation | [demo](examples/mediapipe-selfie-segmentation) |

examples/fastvlm/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Quick Start
2+
3+
```shell
4+
# single image
5+
cargo run -r --example fastvlm -- --source ./assets/bus.jpg
6+
7+
# batch inference
8+
cargo run -r --example fastvlm -- --prompt "Describe the image in detail." --max-tokens 512 --source ./assets/bus.jpg --source ./assets/dog.jpg --source ./assets/cat.png
9+
```

examples/fastvlm/main.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use anyhow::Result;
2+
use usls::{models::FastVLM, Config, DataLoader, Scale};
3+
4+
/// Example
5+
#[derive(argh::FromArgs)]
6+
struct Args {
7+
/// device
8+
#[argh(option, default = "String::from(\"cpu:0\")")]
9+
device: String,
10+
11+
/// source image
12+
#[argh(option, default = "vec![String::from(\"./assets/bus.jpg\")]")]
13+
source: Vec<String>,
14+
15+
/// promt
16+
#[argh(option, default = "String::from(\"Describe the image in detail.\")")]
17+
prompt: String,
18+
19+
/// scale
20+
#[argh(option, default = "String::from(\"0.5b\")")]
21+
scale: String,
22+
23+
/// max_tokens
24+
#[argh(option, default = "1024")]
25+
max_tokens: usize,
26+
27+
/// dtype
28+
#[argh(option, default = "String::from(\"fp16\")")]
29+
dtype: String,
30+
}
31+
32+
fn main() -> Result<()> {
33+
tracing_subscriber::fmt()
34+
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
35+
.with_timer(tracing_subscriber::fmt::time::ChronoLocal::rfc_3339())
36+
.init();
37+
let args: Args = argh::from_env();
38+
39+
// build model
40+
let config = match args.scale.parse()? {
41+
Scale::Billion(0.5) => Config::fastvlm_0_5b(),
42+
_ => unimplemented!(),
43+
}
44+
.with_device_all(args.device.parse()?)
45+
.with_dtype_all(args.dtype.parse()?)
46+
.with_batch_size_all(args.source.len())
47+
.with_max_tokens(args.max_tokens)
48+
.commit()?;
49+
let mut model = FastVLM::new(config)?;
50+
51+
// load images
52+
let xs = DataLoader::try_read_n(&args.source)?;
53+
54+
// run
55+
let ys = model.forward(&xs, &args.prompt)?;
56+
57+
for y in ys.iter() {
58+
if let Some(texts) = y.texts() {
59+
for text in texts {
60+
println!("\n[User]: {}\n[Assistant]:{:?}", args.prompt, text);
61+
}
62+
}
63+
}
64+
usls::perf(false);
65+
66+
Ok(())
67+
}

src/core/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub struct Config {
6262
pub db_binary_thresh: Option<f32>,
6363
pub sam_kind: Option<SamKind>,
6464
pub sam_low_res_mask: Option<bool>,
65+
pub max_tokens: Option<usize>,
6566
}
6667

6768
impl Default for Config {
@@ -75,6 +76,7 @@ impl Default for Config {
7576
text_confs: vec![0.25f32],
7677
apply_softmax: Some(false),
7778
num_classes: None,
79+
max_tokens: None,
7880
num_keypoints: None,
7981
num_masks: None,
8082
iou: None,

src/core/x.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ impl std::ops::Deref for X {
6464
}
6565
}
6666

67+
impl std::ops::DerefMut for X {
68+
fn deref_mut(&mut self) -> &mut Self::Target {
69+
&mut self.0
70+
}
71+
}
72+
6773
impl std::ops::Mul<f32> for X {
6874
type Output = Self;
6975

src/models/fastvlm/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# FastVLM: Efficient Vision Encoding for Vision Language Models
2+
3+
## Official Repository
4+
5+
The official repository can be found on:
6+
* [ml-fastvlm](https://github.com/apple/ml-fastvlm)
7+
8+
The onnx model can be found on:
9+
* [FastVLM-0.5B-ONNX](https://huggingface.co/onnx-community/FastVLM-0.5B-ONNX)
10+
11+
## Example
12+
13+
Refer to the [example](../../../examples/fastvlm)

src/models/fastvlm/config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Model configuration for `FastVLM`
2+
impl crate::Config {
3+
pub fn fastvlm() -> Self {
4+
Self::default()
5+
.with_name("fastvlm")
6+
.with_batch_size_all(1)
7+
.with_visual_ixx(0, 1, 3.into())
8+
.with_visual_ixx(0, 2, 1024.into())
9+
.with_visual_ixx(0, 3, 1024.into())
10+
.with_image_mean(&[0., 0., 0.])
11+
.with_image_std(&[1., 1., 1.])
12+
.with_normalize(true)
13+
.with_resize_filter("bilinear")
14+
.with_tokenizer_file("fastvlm/tokenizer.json")
15+
.with_tokenizer_config_file("fastvlm/tokenizer_config.json")
16+
.with_config_file("fastvlm/config.json")
17+
}
18+
19+
pub fn fastvlm_0_5b() -> Self {
20+
Self::fastvlm()
21+
.with_scale(crate::Scale::Billion(0.5))
22+
.with_visual_file("0.5b-vision-encoder.onnx")
23+
.with_textual_file("0.5b-embed-tokens.onnx")
24+
.with_textual_decoder_merged_file("0.5b-decoder-model-merged.onnx")
25+
}
26+
}

src/models/fastvlm/impl.rs

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
use aksr::Builder;
2+
use anyhow::Result;
3+
use ndarray::{s, Axis};
4+
use rayon::prelude::*;
5+
6+
use crate::{elapsed_module, Config, Engine, Image, LogitsSampler, Processor, Scale, X, Y};
7+
8+
#[derive(Debug, Builder)]
9+
pub struct FastVLM {
10+
vision: Engine,
11+
text_embed: Engine,
12+
decoder: Engine,
13+
scale: Scale,
14+
image_token: String,
15+
bos_token: String,
16+
eos_token: String,
17+
bos_token_id: u32,
18+
eos_token_id: u32,
19+
image_token_id: u32,
20+
max_length: usize,
21+
num_hidden_layers: usize,
22+
head_dim: usize,
23+
num_key_value_heads: usize,
24+
num_attention_heads: usize,
25+
hidden_size: usize,
26+
batch: usize,
27+
width: usize,
28+
height: usize,
29+
processor: Processor,
30+
}
31+
32+
impl FastVLM {
33+
pub fn new(config: Config) -> Result<Self> {
34+
let vision = Engine::try_from_config(&config.visual)?;
35+
let text_embed = Engine::try_from_config(&config.textual)?;
36+
let decoder = Engine::try_from_config(&config.textual_decoder_merged)?;
37+
let max_length = config.max_tokens.unwrap_or(1024);
38+
let image_token = "<image>".to_string();
39+
let image_token_id = 151646;
40+
let eos_token = "<|im_end|>".to_string();
41+
let eos_token_id = 151645;
42+
let bos_token = "<|im_start|>".to_string();
43+
let bos_token_id = 151644;
44+
let (num_hidden_layers, head_dim, num_key_value_heads, hidden_size, num_attention_heads) =
45+
match &config.scale {
46+
Some(Scale::Billion(0.5)) => (24, 64, 2, 896, 14),
47+
_ => unimplemented!(),
48+
};
49+
let scale = config
50+
.scale
51+
.clone()
52+
.ok_or_else(|| anyhow::anyhow!("Scale configuration is required for FastVLM model"))?;
53+
let (batch, height, width) = (
54+
vision.batch().opt(),
55+
vision.try_height().unwrap_or(&1024.into()).opt(),
56+
vision.try_width().unwrap_or(&1024.into()).opt(),
57+
);
58+
let processor = Processor::try_from_config(&config.processor)?
59+
.with_image_width(width as _)
60+
.with_image_height(height as _);
61+
62+
Ok(Self {
63+
vision,
64+
text_embed,
65+
decoder,
66+
scale,
67+
max_length,
68+
eos_token_id,
69+
bos_token_id,
70+
image_token,
71+
image_token_id,
72+
num_hidden_layers,
73+
head_dim,
74+
num_key_value_heads,
75+
num_attention_heads,
76+
hidden_size,
77+
bos_token,
78+
eos_token,
79+
batch,
80+
height,
81+
width,
82+
processor,
83+
})
84+
}
85+
86+
fn encode_images(&mut self, xs: &[Image]) -> Result<X> {
87+
let ys = self.processor.process_images(xs)?;
88+
self.batch = xs.len();
89+
let ys = self.vision.run(ys.into())?;
90+
Ok(ys[0].to_owned())
91+
}
92+
93+
pub fn forward(&mut self, images: &[Image], text: &str) -> Result<Vec<Y>> {
94+
let image_embeddings =
95+
elapsed_module!("FastVLM", "encode-images", self.encode_images(images)?);
96+
elapsed_module!(
97+
"FastVLM",
98+
"generate",
99+
self.generate(&image_embeddings, text)
100+
)
101+
}
102+
103+
fn generate(&mut self, image_embeddings: &X, text: &str) -> Result<Vec<Y>> {
104+
let image_seq_len = image_embeddings.dims()[1];
105+
106+
// prompt
107+
let prompt = format!("<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<image>\n{}<|im_end|>\n<|im_start|>assistant\n", text);
108+
let input_ids: Vec<f32> = self.processor.encode_text_ids(&prompt, true)?;
109+
110+
// inputs embeds
111+
let input_ids_x = X::from(input_ids.clone())
112+
.insert_axis(0)?
113+
.repeat(0, self.batch)?;
114+
let inputs_embeds0 = self.text_embed.run(input_ids_x.into())?[0].clone();
115+
let text_seq_len = inputs_embeds0.dims()[1];
116+
// total_sequence_length
117+
let mut seq_len = text_seq_len + image_seq_len;
118+
119+
// merge
120+
let mut inputs_embeds = X::zeros(&[self.batch, seq_len, self.hidden_size]);
121+
let pos = input_ids
122+
.iter()
123+
.position(|&x| x == self.image_token_id as f32)
124+
.unwrap_or(input_ids.len() / 2);
125+
inputs_embeds
126+
.slice_mut(s![.., ..pos, ..])
127+
.assign(&inputs_embeds0.slice(s![.., ..pos, ..]));
128+
inputs_embeds
129+
.slice_mut(s![.., pos..pos + image_seq_len, ..])
130+
.assign(&image_embeddings.slice(s![.., ..image_seq_len, ..]));
131+
if pos < text_seq_len {
132+
inputs_embeds
133+
.slice_mut(s![.., pos + image_seq_len.., ..])
134+
.assign(&inputs_embeds0.slice(s![.., pos.., ..]));
135+
}
136+
137+
// position ids
138+
let mut position_ids = X::from((0..seq_len).map(|x| x as f32).collect::<Vec<f32>>())
139+
.insert_axis(0)?
140+
.repeat(0, self.batch)?;
141+
142+
// past key_values
143+
let mut past_key_values =
144+
vec![
145+
X::zeros(&[self.batch, self.num_key_value_heads, 0, self.head_dim]);
146+
self.num_hidden_layers * 2
147+
];
148+
149+
// token ids
150+
let mut token_ids: Vec<Vec<u32>> = vec![vec![]; self.batch];
151+
let mut finished = vec![false; self.batch];
152+
let mut last_tokens: Vec<f32> = vec![0.; self.batch];
153+
154+
// decode
155+
let logits_sampler = LogitsSampler::new();
156+
for _ in 0..self.max_length {
157+
// inputs
158+
let attention_mask = X::ones(&[self.batch, seq_len]);
159+
let mut xs = vec![inputs_embeds.clone(), attention_mask, position_ids.clone()];
160+
for i in 0..self.num_hidden_layers {
161+
xs.push(past_key_values[i * 2].clone());
162+
xs.push(past_key_values[i * 2 + 1].clone());
163+
}
164+
165+
// decoder
166+
let decoder_outputs = self.decoder.run(xs.into())?;
167+
let logits = &decoder_outputs[0];
168+
past_key_values = (1..decoder_outputs.len())
169+
.step_by(2)
170+
.flat_map(|i| [i, i + 1])
171+
.map(|i| decoder_outputs[i].clone())
172+
.collect();
173+
174+
// decode each token for each batch·
175+
for (batch_idx, logit) in logits.axis_iter(Axis(0)).enumerate() {
176+
if !finished[batch_idx] {
177+
let token_id = logits_sampler.decode(
178+
&logit
179+
.slice(s![-1, ..])
180+
.into_owned()
181+
.into_raw_vec_and_offset()
182+
.0,
183+
)?;
184+
185+
// early return
186+
if token_id == self.eos_token_id {
187+
finished[batch_idx] = true;
188+
} else {
189+
token_ids[batch_idx].push(token_id);
190+
last_tokens[batch_idx] = token_id as f32;
191+
}
192+
}
193+
}
194+
195+
// all finished?
196+
if finished.iter().all(|&x| x) {
197+
break;
198+
}
199+
200+
// inputs embeds for next iteration
201+
let input_ids_x = X::from(last_tokens.clone()).insert_axis(1)?;
202+
inputs_embeds = self.text_embed.run(input_ids_x.into())?[0].clone();
203+
204+
// update position_ids for next iteration
205+
position_ids = X::from(
206+
position_ids
207+
.slice(s![.., -1..])
208+
.mapv(|x| x + 1.0)
209+
.into_owned()
210+
.into_dyn(),
211+
);
212+
seq_len += 1;
213+
}
214+
215+
// decode
216+
let texts = self.processor.decode_tokens_batch(&token_ids, true)?;
217+
let texts = texts
218+
.into_par_iter()
219+
.map(|x| Y::default().with_texts(&[x.into()]))
220+
.collect::<Vec<_>>();
221+
222+
Ok(texts)
223+
}
224+
}

src/models/fastvlm/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod config;
2+
mod r#impl;
3+
4+
pub use r#impl::FastVLM;

0 commit comments

Comments
 (0)