1use candle::{Context, DType, Result, Tensor, D};
9use candle_nn::{
10 batch_norm, conv2d, conv2d_no_bias, linear, linear_no_bias, ops::sigmoid, ops::softmax,
11 BatchNorm, Conv2d, Conv2dConfig, Func, VarBuilder,
12};
13
14#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
15pub struct Config {
16 pub exp_ratio: usize,
17 pub in_channels: usize,
18 pub blocks: [usize; 4],
19 pub attn: bool,
20 pub lkc_use_act: bool,
21}
22
23impl Config {
24 pub fn t8() -> Self {
25 Self {
26 exp_ratio: 3,
27 in_channels: 48,
28 blocks: [2, 2, 4, 2],
29 attn: false,
30 lkc_use_act: false,
31 }
32 }
33
34 pub fn t12() -> Self {
35 Self {
36 exp_ratio: 3,
37 in_channels: 64,
38 blocks: [2, 2, 6, 2],
39 attn: false,
40 lkc_use_act: false,
41 }
42 }
43 pub fn s12() -> Self {
44 Self {
45 exp_ratio: 4,
46 in_channels: 64,
47 blocks: [2, 2, 6, 2],
48 attn: false,
49 lkc_use_act: false,
50 }
51 }
52 pub fn sa12() -> Self {
53 Self {
54 exp_ratio: 4,
55 in_channels: 64,
56 blocks: [2, 2, 6, 2],
57 attn: true,
58 lkc_use_act: false,
59 }
60 }
61 pub fn sa24() -> Self {
62 Self {
63 exp_ratio: 4,
64 in_channels: 64,
65 blocks: [4, 4, 12, 4],
66 attn: true,
67 lkc_use_act: false,
68 }
69 }
70 pub fn sa36() -> Self {
71 Self {
72 exp_ratio: 4,
73 in_channels: 64,
74 blocks: [6, 6, 18, 6],
75 attn: true,
76 lkc_use_act: false,
77 }
78 }
79 pub fn ma36() -> Self {
80 Self {
81 exp_ratio: 4,
82 in_channels: 76,
83 blocks: [6, 6, 18, 6],
84 attn: true,
85 lkc_use_act: false,
86 }
87 }
88
89 pub fn mci0() -> Self {
91 Self {
92 exp_ratio: 3,
93 in_channels: 64,
94 blocks: [2, 6, 10, 2],
95 attn: true,
96 lkc_use_act: true,
97 }
98 }
99 pub fn mci1() -> Self {
100 Self {
101 exp_ratio: 3,
102 in_channels: 64,
103 blocks: [4, 12, 20, 4],
104 attn: true,
105 lkc_use_act: true,
106 }
107 }
108 pub fn mci2() -> Self {
109 Self {
110 exp_ratio: 3,
111 in_channels: 80,
112 blocks: [4, 12, 24, 4],
113 attn: true,
114 lkc_use_act: true,
115 }
116 }
117}
118
119fn conv_norm(
120 in_channels: usize,
121 out_channels: usize,
122 kernel: usize,
123 stride: usize,
124 vb: VarBuilder,
125) -> Result<Func<'static>> {
126 let conv2d_cfg = Conv2dConfig {
127 stride,
128 padding: kernel / 2,
129 groups: in_channels,
130 ..Default::default()
131 };
132
133 let bn = batch_norm(out_channels, 1e-5, vb.pp("bn"))?;
134 let conv = conv2d_no_bias(in_channels, out_channels, kernel, conv2d_cfg, vb.pp("conv"))?;
135 let conv = conv.absorb_bn(&bn)?;
136 Ok(Func::new(move |xs| {
137 let xs = xs.apply(&conv)?;
138 Ok(xs)
139 }))
140}
141
142fn conv_mlp(dim: usize, exp_ratio: usize, vb: VarBuilder) -> Result<Func<'static>> {
143 let conv2d_cfg = Conv2dConfig {
144 ..Default::default()
145 };
146
147 let conv = conv_norm(dim, dim, 7, 1, vb.pp("conv"))?;
148 let fc1 = conv2d(dim, dim * exp_ratio, 1, conv2d_cfg, vb.pp("fc1"))?;
149 let fc2 = conv2d(dim * exp_ratio, dim, 1, conv2d_cfg, vb.pp("fc2"))?;
150
151 Ok(Func::new(move |xs| {
152 let xs = xs.apply(&conv)?.apply(&fc1)?.gelu_erf()?.apply(&fc2)?;
153 Ok(xs)
154 }))
155}
156
157fn squeeze_and_excitation(
158 in_channels: usize,
159 squeeze_channels: usize,
160 vb: VarBuilder,
161) -> Result<Func<'static>> {
162 let conv2d_cfg = Conv2dConfig {
163 ..Default::default()
164 };
165 let fc1 = conv2d(in_channels, squeeze_channels, 1, conv2d_cfg, vb.pp("fc1"))?;
166 let fc2 = conv2d(squeeze_channels, in_channels, 1, conv2d_cfg, vb.pp("fc2"))?;
167
168 Ok(Func::new(move |xs| {
169 let residual = xs;
170 let xs = xs.mean_keepdim(D::Minus2)?.mean_keepdim(D::Minus1)?;
171 let xs = sigmoid(&xs.apply(&fc1)?.relu()?.apply(&fc2)?)?;
172
173 residual.broadcast_mul(&xs)
174 }))
175}
176
177fn fuse_conv_bn(weights: &Tensor, bn: BatchNorm) -> Result<(Tensor, Tensor)> {
181 let (gamma, beta) = bn.weight_and_bias().context("no weight-bias")?;
182 let mu = bn.running_mean();
183 let sigma = (bn.running_var() + bn.eps())?.sqrt();
184 let gps = (gamma / sigma)?;
185 let bias = (beta - mu * &gps)?;
186 let weights = weights.broadcast_mul(&gps.reshape(((), 1, 1, 1))?)?;
187
188 Ok((weights, bias))
189}
190
191fn mobileone_block(
192 in_channels: usize,
193 out_channels: usize,
194 kernel: usize,
195 stride: usize,
196 group_size: usize,
197 use_act: bool,
198 vb: VarBuilder,
199) -> Result<Func<'static>> {
200 let groups = in_channels.checked_div(group_size).unwrap_or(1);
201
202 let padding = kernel / 2;
203 let conv2d_cfg = Conv2dConfig {
204 stride,
205 groups,
206 padding,
207 ..Default::default()
208 };
209
210 let mut w = Tensor::zeros(
211 (out_channels, in_channels / groups, kernel, kernel),
212 DType::F32,
213 vb.device(),
214 )?;
215 let dim = out_channels;
216
217 let mut b = Tensor::zeros(dim, DType::F32, vb.device())?;
218
219 let conv_kxk_bn = batch_norm(dim, 1e-5, vb.pp("conv_kxk.0.bn"));
220 let conv_kxk = conv2d_no_bias(
221 in_channels,
222 out_channels,
223 kernel,
224 conv2d_cfg,
225 vb.pp("conv_kxk.0.conv"),
226 );
227
228 if let (Ok(conv), Ok(bn)) = (conv_kxk, conv_kxk_bn) {
229 let (wk, bk) = fuse_conv_bn(conv.weight(), bn)?;
230 w = (w + wk)?;
231 b = (b + bk)?;
232 };
233
234 let conv_scale_bn = batch_norm(dim, 1e-5, vb.pp("conv_scale.bn"));
235 let conv_scale = conv2d_no_bias(
236 in_channels,
237 out_channels,
238 1,
239 conv2d_cfg,
240 vb.pp("conv_scale.conv"),
241 );
242
243 if let (Ok(conv), Ok(bn)) = (conv_scale, conv_scale_bn) {
244 let (ws, bs) = fuse_conv_bn(conv.weight(), bn)?;
245 let ws = ws
247 .pad_with_zeros(D::Minus1, 1, 1)?
248 .pad_with_zeros(D::Minus2, 1, 1)?;
249
250 w = (w + ws)?;
251 b = (b + bs)?;
252 };
253
254 let se = squeeze_and_excitation(out_channels, out_channels / 16, vb.pp("se"));
255
256 let identity_bn = batch_norm(dim, 1e-5, vb.pp("identity"));
258
259 if let Ok(id_bn) = identity_bn {
260 let mut weights: Vec<f32> = vec![0.0; w.elem_count()];
261 let id = in_channels / groups;
262 for i in 0..in_channels {
264 if kernel > 1 {
265 weights[i * kernel * kernel + 4] = 1.0;
266 } else {
267 weights[i * (id + 1)] = 1.0;
268 }
269 }
270
271 let weights = &Tensor::from_vec(weights, w.shape(), w.device())?;
272 let (wi, bi) = fuse_conv_bn(weights, id_bn)?;
273
274 w = (w + wi)?;
275 b = (b + bi)?;
276 };
277 let reparam_conv = Conv2d::new(w, Some(b), conv2d_cfg);
278
279 Ok(Func::new(move |xs| {
280 let mut xs = xs.apply(&reparam_conv)?;
281 if let Ok(f) = &se {
282 xs = xs.apply(f)?;
283 }
284 if use_act {
285 xs = xs.gelu_erf()?;
286 };
287 Ok(xs)
288 }))
289}
290
291fn repmixer(dim: usize, kernel: usize, vb: VarBuilder) -> Result<Func<'static>> {
292 let gamma = vb.get((dim, 1, 1), "layer_scale.gamma")?;
293 let norm = mobileone_block(dim, dim, kernel, 1, 1, false, vb.pp("norm"))?;
294 let mixer = mobileone_block(dim, dim, kernel, 1, 1, false, vb.pp("mixer"))?;
295
296 Ok(Func::new(move |xs| {
297 let residual = xs.clone();
298 let xs = (xs.apply(&mixer)? - xs.apply(&norm)?)?;
299 let xs = xs.broadcast_mul(&gamma.reshape((1, (), 1, 1))?)?;
300 let xs = (xs + residual)?;
301 Ok(xs)
302 }))
303}
304
305fn repmixer_block(dim: usize, exp_ratio: usize, vb: VarBuilder) -> Result<Func<'static>> {
306 let gamma = vb.get((dim, 1, 1), "layer_scale.gamma")?;
307 let token_mixer = repmixer(dim, 3, vb.pp("token_mixer"))?;
308 let mlp = conv_mlp(dim, exp_ratio, vb.pp("mlp"))?;
309
310 Ok(Func::new(move |xs| {
311 let residual = xs.apply(&token_mixer)?;
312 let mut xs = residual.apply(&mlp)?;
313 xs = xs.broadcast_mul(&gamma.reshape((1, (), 1, 1))?)?;
314 let xs = (xs + residual)?;
315 Ok(xs)
316 }))
317}
318
319fn positional_encoding(dim: usize, vb: VarBuilder) -> Result<Func<'static>> {
320 let conv2d_cfg = Conv2dConfig {
321 stride: 1,
322 padding: 3,
323 groups: dim,
324 ..Default::default()
325 };
326
327 let conv = conv2d(dim, dim, 7, conv2d_cfg, vb.pp("pos_enc"))?;
328
329 Ok(Func::new(move |xs| {
330 let xs = (xs + xs.apply(&conv)?)?;
331 Ok(xs)
332 }))
333}
334
335fn attention(dim: usize, vb: VarBuilder) -> Result<Func<'static>> {
336 let qkv = linear_no_bias(dim, dim * 3, vb.pp("qkv"))?;
337 let proj = linear(dim, dim, vb.pp("proj"))?;
338 let head_dim = 32;
339 let num_heads = dim / head_dim;
340 let scale = (head_dim as f64).powf(-0.5);
341
342 Ok(Func::new(move |xs| {
343 let xs = xs.clone();
344 let (b, c, h, w) = xs.dims4()?;
345 let n = h * w;
346 let xs = xs.flatten_from(2)?.transpose(D::Minus1, D::Minus2)?;
347 let qkv = xs
348 .apply(&qkv)?
349 .reshape((b, n, 3, num_heads, head_dim))?
350 .permute((2, 0, 3, 1, 4))?;
351
352 let q = qkv.get(0)?;
353 let k = qkv.get(1)?;
354 let v = qkv.get(2)?;
355
356 let q = (q * scale)?;
357
358 let att = q.matmul(&k.transpose(D::Minus2, D::Minus1)?)?;
359 let att = softmax(&att, D::Minus1)?;
360 let xs = att.matmul(&v)?;
361
362 let xs = xs.transpose(1, 2)?.reshape((b, n, c))?;
363 let xs = xs.apply(&proj)?;
364 let xs = xs.transpose(D::Minus1, D::Minus2)?.reshape((b, c, h, w))?;
365
366 Ok(xs)
367 }))
368}
369
370fn attention_block(dim: usize, exp_ratio: usize, vb: VarBuilder) -> Result<Func<'static>> {
371 let gamma1 = vb.get((dim, 1, 1), "layer_scale_1.gamma")?;
372 let gamma2 = vb.get((dim, 1, 1), "layer_scale_2.gamma")?;
373 let norm = batch_norm(dim, 1e-5, vb.pp("norm"))?;
374 let token_mixer = attention(dim, vb.pp("token_mixer"))?;
375 let mlp = conv_mlp(dim, exp_ratio, vb.pp("mlp"))?;
376
377 Ok(Func::new(move |xs| {
378 let xs = xs.clone();
379 let xs = (&xs
380 + &xs
381 .apply_t(&norm, false)?
382 .apply(&token_mixer)?
383 .broadcast_mul(&gamma1.reshape((1, (), 1, 1))?)?)?;
384
385 let xs = (&xs
386 + &xs
387 .apply(&mlp)?
388 .broadcast_mul(&gamma2.reshape((1, (), 1, 1))?)?)?;
389
390 Ok(xs)
391 }))
392}
393
394fn fastvit_stage(cfg: &Config, idx: usize, vb: VarBuilder) -> Result<Func<'static>> {
395 let nblocks = cfg.blocks[idx];
396 let mut blocks = Vec::with_capacity(nblocks);
397
398 let dim = cfg.in_channels << idx;
399 let downsample = fastvit_patch_embed(dim / 2, dim, cfg.lkc_use_act, vb.pp("downsample"));
400 for block_idx in 0..nblocks {
401 let block = if cfg.attn && idx == 3 {
402 attention_block(dim, cfg.exp_ratio, vb.pp(format!("blocks.{block_idx}")))?
403 } else {
404 repmixer_block(dim, cfg.exp_ratio, vb.pp(format!("blocks.{block_idx}")))?
405 };
406 blocks.push(block);
407 }
408 let pos_emb = positional_encoding(dim, vb.pp("pos_emb"));
409
410 Ok(Func::new(move |xs| {
411 let mut xs = xs.clone();
412 if let Ok(ds) = &downsample {
413 xs = xs.apply(ds)?;
414 }
415 if let Ok(pos) = &pos_emb {
416 xs = xs.apply(pos)?;
417 }
418 for block in blocks.iter() {
419 xs = xs.apply(block)?;
420 }
421 Ok(xs)
422 }))
423}
424
425fn fastvit_patch_embed(
426 in_channels: usize,
427 out_channels: usize,
428 use_act: bool,
429 vb: VarBuilder,
430) -> Result<Func<'static>> {
431 let lk = conv_norm(in_channels, out_channels, 7, 2, vb.pp("proj.0.large_conv"))?;
432 let sk = conv_norm(in_channels, out_channels, 3, 2, vb.pp("proj.0.small_conv"))?;
433 let se = squeeze_and_excitation(out_channels, out_channels / 4, vb.pp("proj.0.se"));
434 let mb = mobileone_block(out_channels, out_channels, 1, 1, 0, true, vb.pp("proj.1"))?;
435
436 Ok(Func::new(move |xs| {
437 let mut xs = (xs.apply(&lk)? + xs.apply(&sk)?)?;
438 if let Ok(f) = &se {
439 xs = xs.apply(f)?;
440 }
441 if use_act {
442 xs = xs.gelu_erf()?;
443 };
444 let xs = xs.apply(&mb)?;
445 Ok(xs)
446 }))
447}
448
449fn fastvit_stem(in_channels: usize, out_channels: usize, vb: VarBuilder) -> Result<Func<'static>> {
450 let mb0 = mobileone_block(in_channels, out_channels, 3, 2, 0, true, vb.pp(0))?;
451 let mb1 = mobileone_block(out_channels, out_channels, 3, 2, 1, true, vb.pp(1))?;
452 let mb2 = mobileone_block(out_channels, out_channels, 1, 1, 0, true, vb.pp(2))?;
453 Ok(Func::new(move |xs| {
454 let xs = xs.apply(&mb0)?.apply(&mb1)?.apply(&mb2)?;
455 Ok(xs)
456 }))
457}
458
459fn fastvit_model(cfg: &Config, nclasses: Option<usize>, vb: VarBuilder) -> Result<Func<'static>> {
461 let cls = match nclasses {
462 None => None,
463 Some(nclasses) => {
464 let linear = linear(cfg.in_channels * 16, nclasses, vb.pp("head.fc"))?;
465 Some(linear)
466 }
467 };
468
469 let stem = fastvit_stem(3, cfg.in_channels, vb.pp("stem"))?;
470 let final_conv = mobileone_block(
471 cfg.in_channels * 8,
472 cfg.in_channels * 16,
473 3,
474 1,
475 1,
476 true,
477 vb.pp("final_conv"),
478 )?;
479
480 let vb = vb.pp("stages");
481 let stage1 = fastvit_stage(cfg, 0, vb.pp(0))?;
482 let stage2 = fastvit_stage(cfg, 1, vb.pp(1))?;
483 let stage3 = fastvit_stage(cfg, 2, vb.pp(2))?;
484 let stage4 = fastvit_stage(cfg, 3, vb.pp(3))?;
485
486 Ok(Func::new(move |xs| {
487 let xs = xs
488 .apply(&stem)?
489 .apply(&stage1)?
490 .apply(&stage2)?
491 .apply(&stage3)?
492 .apply(&stage4)?
493 .apply(&final_conv)?;
494 match &cls {
495 None => Ok(xs),
496 Some(cls) => xs.mean(D::Minus2)?.mean(D::Minus1)?.apply(cls),
497 }
498 }))
499}
500
501pub fn fastvit(cfg: &Config, nclasses: usize, vb: VarBuilder) -> Result<Func<'static>> {
502 fastvit_model(cfg, Some(nclasses), vb)
503}
504
505pub fn fastvit_no_final_layer(cfg: &Config, vb: VarBuilder) -> Result<Func<'static>> {
506 fastvit_model(cfg, None, vb)
507}