Mayo commited on
Commit
9dae136
·
unverified ·
1 Parent(s): 68f4799

perf: prefer bf16

Browse files
koharu-ml/src/aot_inpainting/mod.rs CHANGED
@@ -47,6 +47,7 @@ pub struct AotInpainting {
47
  model: AotGenerator,
48
  config: AotInpaintingConfig,
49
  device: Device,
 
50
  }
51
 
52
  #[derive(Debug, Clone, Deserialize)]
@@ -113,17 +114,22 @@ impl AotInpainting {
113
  cpu: bool,
114
  ) -> Result<Self> {
115
  let device = device(cpu)?;
 
116
  let config = loading::read_json::<AotInpaintingConfig>(config_path.as_ref())
117
  .with_context(|| format!("failed to parse {}", config_path.as_ref().display()))?;
118
  config.validate()?;
119
- let model = loading::load_mmaped_safetensors_path(weights_path.as_ref(), &device, |vb| {
120
- AotGenerator::load(&vb, &config.spec())
121
- })?;
 
 
 
122
 
123
  Ok(Self {
124
  model,
125
  config,
126
  device,
 
127
  })
128
  }
129
 
@@ -199,7 +205,7 @@ impl AotInpainting {
199
  &self.device,
200
  )?
201
  .permute((0, 3, 1, 2))?
202
- .to_dtype(DType::F32)?
203
  / 127.5)?;
204
  let image_tensor = (image_tensor - 1.0)?;
205
 
@@ -209,7 +215,7 @@ impl AotInpainting {
209
  &self.device,
210
  )?
211
  .permute((0, 3, 1, 2))?
212
- .to_dtype(DType::F32)?;
213
  let mask_tensor = (mask_tensor / 255.0)?;
214
  let mask_inv = (Tensor::ones_like(&mask_tensor)? - &mask_tensor)?;
215
  let mask_inv_rgb = mask_inv.broadcast_as((1, 3, h as usize, w as usize))?;
@@ -220,7 +226,10 @@ impl AotInpainting {
220
  }
221
 
222
  fn postprocess(&self, output: &Tensor) -> Result<RgbImage> {
223
- let output = output.to_device(&Device::Cpu)?.squeeze(0)?;
 
 
 
224
  let (channels, height, width) = output.dims3()?;
225
  if channels != 3 {
226
  bail!("expected 3 output channels, got {channels}");
 
47
  model: AotGenerator,
48
  config: AotInpaintingConfig,
49
  device: Device,
50
+ dtype: DType,
51
  }
52
 
53
  #[derive(Debug, Clone, Deserialize)]
 
114
  cpu: bool,
115
  ) -> Result<Self> {
116
  let device = device(cpu)?;
117
+ let dtype = loading::model_dtype(&device);
118
  let config = loading::read_json::<AotInpaintingConfig>(config_path.as_ref())
119
  .with_context(|| format!("failed to parse {}", config_path.as_ref().display()))?;
120
  config.validate()?;
121
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
122
+ weights_path.as_ref(),
123
+ &device,
124
+ dtype,
125
+ |vb| AotGenerator::load(&vb, &config.spec()),
126
+ )?;
127
 
128
  Ok(Self {
129
  model,
130
  config,
131
  device,
132
+ dtype,
133
  })
134
  }
135
 
 
205
  &self.device,
206
  )?
207
  .permute((0, 3, 1, 2))?
208
+ .to_dtype(self.dtype)?
209
  / 127.5)?;
210
  let image_tensor = (image_tensor - 1.0)?;
211
 
 
215
  &self.device,
216
  )?
217
  .permute((0, 3, 1, 2))?
218
+ .to_dtype(self.dtype)?;
219
  let mask_tensor = (mask_tensor / 255.0)?;
220
  let mask_inv = (Tensor::ones_like(&mask_tensor)? - &mask_tensor)?;
221
  let mask_inv_rgb = mask_inv.broadcast_as((1, 3, h as usize, w as usize))?;
 
226
  }
227
 
228
  fn postprocess(&self, output: &Tensor) -> Result<RgbImage> {
229
+ let output = output
230
+ .to_dtype(DType::F32)?
231
+ .to_device(&Device::Cpu)?
232
+ .squeeze(0)?;
233
  let (channels, height, width) = output.dims3()?;
234
  if channels != 3 {
235
  bail!("expected 3 output channels, got {channels}");
koharu-ml/src/aot_inpainting/model.rs CHANGED
@@ -262,6 +262,8 @@ fn relu_nf(xs: &Tensor) -> candle_core::Result<Tensor> {
262
  }
263
 
264
  fn my_layer_norm(xs: &Tensor) -> candle_core::Result<Tensor> {
 
 
265
  let (batch, channels, height, width) = xs.dims4()?;
266
  let flat = xs.flatten_from(2)?;
267
  let mean = flat.mean_keepdim(2)?;
@@ -269,7 +271,9 @@ fn my_layer_norm(xs: &Tensor) -> candle_core::Result<Tensor> {
269
  let normalized = ((flat.broadcast_sub(&mean)? * 2.0)?)
270
  .broadcast_div(&std)?
271
  .broadcast_sub(&Tensor::ones_like(&flat)?)?;
272
- (normalized * 5.0)?.reshape((batch, channels, height, width))
 
 
273
  }
274
 
275
  fn load_plain_conv2d(
@@ -342,6 +346,9 @@ fn load_scaled_ws_transpose_conv2d(
342
  }
343
 
344
  fn standardize_conv2d_weight(weight: Tensor, gain: Tensor) -> candle_core::Result<Tensor> {
 
 
 
345
  let (out_channels, in_channels, kernel_h, kernel_w) = weight.dims4()?;
346
  let flat = weight.flatten_from(1)?;
347
  let fan_in = flat.dim(1)? as f64;
@@ -356,18 +363,19 @@ fn standardize_conv2d_weight(weight: Tensor, gain: Tensor) -> candle_core::Resul
356
  let scale = variance.maximum(&eps)?.sqrt()?.recip()?;
357
  let scale = scale.broadcast_mul(&gain.reshape((out_channels, 1))?)?;
358
  let shift = mean.broadcast_mul(&scale)?;
359
- flat.broadcast_mul(&scale)?.broadcast_sub(&shift)?.reshape((
360
- out_channels,
361
- in_channels,
362
- kernel_h,
363
- kernel_w,
364
- ))
365
  }
366
 
367
  fn standardize_transpose_conv2d_weight(
368
  weight: Tensor,
369
  gain: Tensor,
370
  ) -> candle_core::Result<Tensor> {
 
 
 
371
  let (in_channels, out_channels, kernel_h, kernel_w) = weight.dims4()?;
372
  let flat = weight.flatten_from(1)?;
373
  let fan_in = flat.dim(1)? as f64;
@@ -382,12 +390,10 @@ fn standardize_transpose_conv2d_weight(
382
  let scale = variance.maximum(&eps)?.sqrt()?.recip()?;
383
  let scale = scale.broadcast_mul(&gain.reshape((in_channels, 1))?)?;
384
  let shift = mean.broadcast_mul(&scale)?;
385
- flat.broadcast_mul(&scale)?.broadcast_sub(&shift)?.reshape((
386
- in_channels,
387
- out_channels,
388
- kernel_h,
389
- kernel_w,
390
- ))
391
  }
392
 
393
  fn reflect_pad2d(xs: &Tensor, pad: usize) -> candle_core::Result<Tensor> {
 
262
  }
263
 
264
  fn my_layer_norm(xs: &Tensor) -> candle_core::Result<Tensor> {
265
+ let dtype = xs.dtype();
266
+ let xs = xs.to_dtype(candle_core::DType::F32)?;
267
  let (batch, channels, height, width) = xs.dims4()?;
268
  let flat = xs.flatten_from(2)?;
269
  let mean = flat.mean_keepdim(2)?;
 
271
  let normalized = ((flat.broadcast_sub(&mean)? * 2.0)?)
272
  .broadcast_div(&std)?
273
  .broadcast_sub(&Tensor::ones_like(&flat)?)?;
274
+ (normalized * 5.0)?
275
+ .reshape((batch, channels, height, width))?
276
+ .to_dtype(dtype)
277
  }
278
 
279
  fn load_plain_conv2d(
 
346
  }
347
 
348
  fn standardize_conv2d_weight(weight: Tensor, gain: Tensor) -> candle_core::Result<Tensor> {
349
+ let dtype = weight.dtype();
350
+ let weight = weight.to_dtype(candle_core::DType::F32)?;
351
+ let gain = gain.to_dtype(candle_core::DType::F32)?;
352
  let (out_channels, in_channels, kernel_h, kernel_w) = weight.dims4()?;
353
  let flat = weight.flatten_from(1)?;
354
  let fan_in = flat.dim(1)? as f64;
 
363
  let scale = variance.maximum(&eps)?.sqrt()?.recip()?;
364
  let scale = scale.broadcast_mul(&gain.reshape((out_channels, 1))?)?;
365
  let shift = mean.broadcast_mul(&scale)?;
366
+ flat.broadcast_mul(&scale)?
367
+ .broadcast_sub(&shift)?
368
+ .reshape((out_channels, in_channels, kernel_h, kernel_w))?
369
+ .to_dtype(dtype)
 
 
370
  }
371
 
372
  fn standardize_transpose_conv2d_weight(
373
  weight: Tensor,
374
  gain: Tensor,
375
  ) -> candle_core::Result<Tensor> {
376
+ let dtype = weight.dtype();
377
+ let weight = weight.to_dtype(candle_core::DType::F32)?;
378
+ let gain = gain.to_dtype(candle_core::DType::F32)?;
379
  let (in_channels, out_channels, kernel_h, kernel_w) = weight.dims4()?;
380
  let flat = weight.flatten_from(1)?;
381
  let fan_in = flat.dim(1)? as f64;
 
390
  let scale = variance.maximum(&eps)?.sqrt()?.recip()?;
391
  let scale = scale.broadcast_mul(&gain.reshape((in_channels, 1))?)?;
392
  let shift = mean.broadcast_mul(&scale)?;
393
+ flat.broadcast_mul(&scale)?
394
+ .broadcast_sub(&shift)?
395
+ .reshape((in_channels, out_channels, kernel_h, kernel_w))?
396
+ .to_dtype(dtype)
 
 
397
  }
398
 
399
  fn reflect_pad2d(xs: &Tensor, pad: usize) -> candle_core::Result<Tensor> {
koharu-ml/src/comic_text_bubble_detector/mod.rs CHANGED
@@ -3,6 +3,7 @@ mod model;
3
  use std::{collections::BTreeMap, time::Instant};
4
 
5
  use anyhow::{Context, Result, bail};
 
6
  use image::{DynamicImage, GenericImageView, imageops::FilterType};
7
  use koharu_runtime::RuntimeManager;
8
  use serde::{Deserialize, Serialize};
@@ -44,12 +45,14 @@ pub struct ComicTextBubbleDetector {
44
  config: RTDetrV2Config,
45
  preprocessor: RTDetrImageProcessorConfig,
46
  device: Device,
 
47
  slicer: ImageSlicer,
48
  }
49
 
50
  impl ComicTextBubbleDetector {
51
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
52
  let device = device(cpu)?;
 
53
  let downloads = runtime.downloads();
54
  let config_path = downloads.huggingface_model(HF_REPO, "config.json").await?;
55
  let preprocessor_path = downloads
@@ -64,15 +67,19 @@ impl ComicTextBubbleDetector {
64
  config.validate()?;
65
  let preprocessor = loading::read_json::<RTDetrImageProcessorConfig>(&preprocessor_path)
66
  .with_context(|| format!("failed to parse {}", preprocessor_path.display()))?;
67
- let model = loading::load_mmaped_safetensors_path(&weights_path, &device, |vb| {
68
- RTDetrV2ForObjectDetection::load(vb, &config)
69
- })?;
 
 
 
70
 
71
  Ok(Self {
72
  model,
73
  config,
74
  preprocessor,
75
  device,
 
76
  slicer: ImageSlicer::default(),
77
  })
78
  }
@@ -121,7 +128,7 @@ impl ComicTextBubbleDetector {
121
  image: &DynamicImage,
122
  threshold: f32,
123
  ) -> Result<Vec<ComicTextBubbleRegion>> {
124
- let pixel_values = preprocess_image(image, &self.preprocessor, &self.device)?;
125
  let outputs = self.model.forward(&pixel_values)?;
126
  post_process_object_detection(&self.config, &outputs, image.dimensions(), threshold)
127
  }
@@ -365,6 +372,7 @@ fn preprocess_image(
365
  image: &DynamicImage,
366
  preprocessor: &RTDetrImageProcessorConfig,
367
  device: &Device,
 
368
  ) -> Result<candle_core::Tensor> {
369
  let target_h = preprocessor.size.height;
370
  let target_w = preprocessor.size.width;
@@ -378,15 +386,17 @@ fn preprocess_image(
378
  candle_core::Tensor::from_vec(rgb.into_raw(), (1, target_h, target_w, 3), &Device::Cpu)?
379
  .to_device(device)?
380
  .permute((0, 3, 1, 2))?
381
- .to_dtype(candle_core::DType::F32)?;
382
  let tensor = if preprocessor.do_rescale {
383
  tensor.affine(preprocessor.rescale_factor as f64, 0.0)?
384
  } else {
385
  tensor
386
  };
387
  if preprocessor.do_normalize {
388
- let mean = candle_core::Tensor::from_slice(&preprocessor.image_mean, (1, 3, 1, 1), device)?;
389
- let std = candle_core::Tensor::from_slice(&preprocessor.image_std, (1, 3, 1, 1), device)?;
 
 
390
  Ok(tensor.broadcast_sub(&mean)?.broadcast_div(&std)?)
391
  } else {
392
  Ok(tensor)
@@ -399,8 +409,14 @@ fn post_process_object_detection(
399
  target_size: (u32, u32),
400
  threshold: f32,
401
  ) -> Result<Vec<ComicTextBubbleRegion>> {
402
- let logits = outputs.logits.to_device(&Device::Cpu)?;
403
- let pred_boxes = outputs.pred_boxes.to_device(&Device::Cpu)?;
 
 
 
 
 
 
404
  let (batch_size, num_queries, num_classes) = logits.dims3()?;
405
  if batch_size != 1 {
406
  bail!("only single-image inference is supported, got batch_size={batch_size}");
 
3
  use std::{collections::BTreeMap, time::Instant};
4
 
5
  use anyhow::{Context, Result, bail};
6
+ use candle_core::DType;
7
  use image::{DynamicImage, GenericImageView, imageops::FilterType};
8
  use koharu_runtime::RuntimeManager;
9
  use serde::{Deserialize, Serialize};
 
45
  config: RTDetrV2Config,
46
  preprocessor: RTDetrImageProcessorConfig,
47
  device: Device,
48
+ dtype: DType,
49
  slicer: ImageSlicer,
50
  }
51
 
52
  impl ComicTextBubbleDetector {
53
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
54
  let device = device(cpu)?;
55
+ let dtype = loading::model_dtype(&device);
56
  let downloads = runtime.downloads();
57
  let config_path = downloads.huggingface_model(HF_REPO, "config.json").await?;
58
  let preprocessor_path = downloads
 
67
  config.validate()?;
68
  let preprocessor = loading::read_json::<RTDetrImageProcessorConfig>(&preprocessor_path)
69
  .with_context(|| format!("failed to parse {}", preprocessor_path.display()))?;
70
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
71
+ &weights_path,
72
+ &device,
73
+ dtype,
74
+ |vb| RTDetrV2ForObjectDetection::load(vb, &config),
75
+ )?;
76
 
77
  Ok(Self {
78
  model,
79
  config,
80
  preprocessor,
81
  device,
82
+ dtype,
83
  slicer: ImageSlicer::default(),
84
  })
85
  }
 
128
  image: &DynamicImage,
129
  threshold: f32,
130
  ) -> Result<Vec<ComicTextBubbleRegion>> {
131
+ let pixel_values = preprocess_image(image, &self.preprocessor, &self.device, self.dtype)?;
132
  let outputs = self.model.forward(&pixel_values)?;
133
  post_process_object_detection(&self.config, &outputs, image.dimensions(), threshold)
134
  }
 
372
  image: &DynamicImage,
373
  preprocessor: &RTDetrImageProcessorConfig,
374
  device: &Device,
375
+ dtype: DType,
376
  ) -> Result<candle_core::Tensor> {
377
  let target_h = preprocessor.size.height;
378
  let target_w = preprocessor.size.width;
 
386
  candle_core::Tensor::from_vec(rgb.into_raw(), (1, target_h, target_w, 3), &Device::Cpu)?
387
  .to_device(device)?
388
  .permute((0, 3, 1, 2))?
389
+ .to_dtype(dtype)?;
390
  let tensor = if preprocessor.do_rescale {
391
  tensor.affine(preprocessor.rescale_factor as f64, 0.0)?
392
  } else {
393
  tensor
394
  };
395
  if preprocessor.do_normalize {
396
+ let mean = candle_core::Tensor::from_slice(&preprocessor.image_mean, (1, 3, 1, 1), device)?
397
+ .to_dtype(dtype)?;
398
+ let std = candle_core::Tensor::from_slice(&preprocessor.image_std, (1, 3, 1, 1), device)?
399
+ .to_dtype(dtype)?;
400
  Ok(tensor.broadcast_sub(&mean)?.broadcast_div(&std)?)
401
  } else {
402
  Ok(tensor)
 
409
  target_size: (u32, u32),
410
  threshold: f32,
411
  ) -> Result<Vec<ComicTextBubbleRegion>> {
412
+ let logits = outputs
413
+ .logits
414
+ .to_dtype(DType::F32)?
415
+ .to_device(&Device::Cpu)?;
416
+ let pred_boxes = outputs
417
+ .pred_boxes
418
+ .to_dtype(DType::F32)?
419
+ .to_device(&Device::Cpu)?;
420
  let (batch_size, num_queries, num_classes) = logits.dims3()?;
421
  if batch_size != 1 {
422
  bail!("only single-image inference is supported, got batch_size={batch_size}");
koharu-ml/src/comic_text_bubble_detector/model.rs CHANGED
@@ -95,6 +95,15 @@ fn load_layer_norm(vb: VarBuilder, hidden_size: usize, eps: f64) -> Result<Layer
95
  Ok(layer_norm(hidden_size, eps, vb)?)
96
  }
97
 
 
 
 
 
 
 
 
 
 
98
  fn pad_all_sides_one(xs: &Tensor) -> candle_core::Result<Tensor> {
99
  xs.pad_with_zeros(2, 1, 1)?.pad_with_zeros(3, 1, 1)
100
  }
@@ -539,7 +548,8 @@ impl RTDetrV2MultiheadAttention {
539
  ) -> Result<Tensor> {
540
  let (batch_size, sequence_length, hidden_size) = hidden_states.dims3()?;
541
  let query_key_input = match position_embeddings {
542
- Some(position_embeddings) => hidden_states.broadcast_add(position_embeddings)?,
 
543
  None => hidden_states.clone(),
544
  };
545
  let shape = (
@@ -570,7 +580,7 @@ impl RTDetrV2MultiheadAttention {
570
  let mut attention_scores =
571
  query_states.matmul(&key_states.transpose(2, 3)?.contiguous()?)?;
572
  attention_scores = (attention_scores * self.scaling)?;
573
- let attention_probs = softmax(&attention_scores, D::Minus1)?;
574
  let context = attention_probs.matmul(&value_states)?;
575
  let context = context.transpose(1, 2)?.contiguous()?.reshape((
576
  batch_size,
@@ -985,11 +995,10 @@ impl RTDetrV2HybridEncoder {
985
  let src_flatten = feature_maps[*encoder_index]
986
  .flatten_from(2)?
987
  .transpose(1, 2)?;
988
- let pos_embed = self.position_embedding.forward(
989
- width,
990
- height,
991
- feature_maps[*encoder_index].device(),
992
- )?;
993
  let encoded = self.encoder[index].forward(&src_flatten, &pos_embed)?;
994
  feature_maps[*encoder_index] = encoded.transpose(1, 2)?.reshape((
995
  batch_size,
@@ -1088,7 +1097,8 @@ impl RTDetrV2MultiscaleDeformableAttention {
1088
  spatial_shapes_list: &[(usize, usize)],
1089
  ) -> Result<Tensor> {
1090
  let hidden_states = match position_embeddings {
1091
- Some(position_embeddings) => hidden_states.broadcast_add(position_embeddings)?,
 
1092
  None => hidden_states.clone(),
1093
  };
1094
  let (batch_size, num_queries, _) = hidden_states.dims3()?;
@@ -1119,7 +1129,7 @@ impl RTDetrV2MultiscaleDeformableAttention {
1119
  self.n_points,
1120
  2,
1121
  ))?;
1122
- let attention_weights = softmax(
1123
  &self.attention_weights.forward(&hidden_states)?.reshape((
1124
  batch_size,
1125
  num_queries,
@@ -1346,9 +1356,10 @@ impl RTDetrV2Decoder {
1346
  )?;
1347
 
1348
  let delta = self.bbox_embed[index].forward(&hidden_states)?;
1349
- reference_points = inverse_sigmoid_to_sigmoid(
1350
- &delta.broadcast_add(&inverse_sigmoid_tensor(&reference_points)?)?,
1351
- )?;
 
1352
  logits = Some(self.class_embed[index].forward(&hidden_states)?);
1353
  pred_boxes = Some(reference_points.clone());
1354
  }
@@ -1619,8 +1630,12 @@ fn batch_gather_rows(tensor: &Tensor, indices: &Tensor) -> Result<Tensor> {
1619
  }
1620
 
1621
  fn inverse_sigmoid_tensor(tensor: &Tensor) -> Result<Tensor> {
 
1622
  let tensor = tensor.to_dtype(DType::F32)?.clamp(1e-5, 1.0 - 1e-5)?;
1623
- Ok(tensor.broadcast_div(&tensor.affine(-1.0, 1.0)?)?.log()?)
 
 
 
1624
  }
1625
 
1626
  fn inverse_sigmoid_to_sigmoid(tensor: &Tensor) -> Result<Tensor> {
@@ -1637,6 +1652,9 @@ fn multi_scale_deformable_attention(
1637
  offset_scale: f64,
1638
  ) -> Result<Tensor> {
1639
  let (batch_size, sequence_length, num_heads, head_dim) = value.dims4()?;
 
 
 
1640
  let [_, num_queries, _, num_levels, num_points, _] =
1641
  <[usize; 6]>::try_from(sampling_offsets.dims().to_vec()).map_err(|_| {
1642
  anyhow::anyhow!(
 
95
  Ok(layer_norm(hidden_size, eps, vb)?)
96
  }
97
 
98
+ fn softmax_f32(xs: &Tensor, dim: D) -> Result<Tensor> {
99
+ let dtype = xs.dtype();
100
+ if dtype == DType::F32 {
101
+ Ok(softmax(xs, dim)?)
102
+ } else {
103
+ Ok(softmax(&xs.to_dtype(DType::F32)?, dim)?.to_dtype(dtype)?)
104
+ }
105
+ }
106
+
107
  fn pad_all_sides_one(xs: &Tensor) -> candle_core::Result<Tensor> {
108
  xs.pad_with_zeros(2, 1, 1)?.pad_with_zeros(3, 1, 1)
109
  }
 
548
  ) -> Result<Tensor> {
549
  let (batch_size, sequence_length, hidden_size) = hidden_states.dims3()?;
550
  let query_key_input = match position_embeddings {
551
+ Some(position_embeddings) => hidden_states
552
+ .broadcast_add(&position_embeddings.to_dtype(hidden_states.dtype())?)?,
553
  None => hidden_states.clone(),
554
  };
555
  let shape = (
 
580
  let mut attention_scores =
581
  query_states.matmul(&key_states.transpose(2, 3)?.contiguous()?)?;
582
  attention_scores = (attention_scores * self.scaling)?;
583
+ let attention_probs = softmax_f32(&attention_scores, D::Minus1)?;
584
  let context = attention_probs.matmul(&value_states)?;
585
  let context = context.transpose(1, 2)?.contiguous()?.reshape((
586
  batch_size,
 
995
  let src_flatten = feature_maps[*encoder_index]
996
  .flatten_from(2)?
997
  .transpose(1, 2)?;
998
+ let pos_embed = self
999
+ .position_embedding
1000
+ .forward(width, height, feature_maps[*encoder_index].device())?
1001
+ .to_dtype(src_flatten.dtype())?;
 
1002
  let encoded = self.encoder[index].forward(&src_flatten, &pos_embed)?;
1003
  feature_maps[*encoder_index] = encoded.transpose(1, 2)?.reshape((
1004
  batch_size,
 
1097
  spatial_shapes_list: &[(usize, usize)],
1098
  ) -> Result<Tensor> {
1099
  let hidden_states = match position_embeddings {
1100
+ Some(position_embeddings) => hidden_states
1101
+ .broadcast_add(&position_embeddings.to_dtype(hidden_states.dtype())?)?,
1102
  None => hidden_states.clone(),
1103
  };
1104
  let (batch_size, num_queries, _) = hidden_states.dims3()?;
 
1129
  self.n_points,
1130
  2,
1131
  ))?;
1132
+ let attention_weights = softmax_f32(
1133
  &self.attention_weights.forward(&hidden_states)?.reshape((
1134
  batch_size,
1135
  num_queries,
 
1356
  )?;
1357
 
1358
  let delta = self.bbox_embed[index].forward(&hidden_states)?;
1359
+ let reference_points_unact =
1360
+ inverse_sigmoid_tensor(&reference_points)?.to_dtype(delta.dtype())?;
1361
+ reference_points =
1362
+ inverse_sigmoid_to_sigmoid(&delta.broadcast_add(&reference_points_unact)?)?;
1363
  logits = Some(self.class_embed[index].forward(&hidden_states)?);
1364
  pred_boxes = Some(reference_points.clone());
1365
  }
 
1630
  }
1631
 
1632
  fn inverse_sigmoid_tensor(tensor: &Tensor) -> Result<Tensor> {
1633
+ let dtype = tensor.dtype();
1634
  let tensor = tensor.to_dtype(DType::F32)?.clamp(1e-5, 1.0 - 1e-5)?;
1635
+ Ok(tensor
1636
+ .broadcast_div(&tensor.affine(-1.0, 1.0)?)?
1637
+ .log()?
1638
+ .to_dtype(dtype)?)
1639
  }
1640
 
1641
  fn inverse_sigmoid_to_sigmoid(tensor: &Tensor) -> Result<Tensor> {
 
1652
  offset_scale: f64,
1653
  ) -> Result<Tensor> {
1654
  let (batch_size, sequence_length, num_heads, head_dim) = value.dims4()?;
1655
+ let reference_points = reference_points.to_dtype(DType::F32)?;
1656
+ let sampling_offsets = sampling_offsets.to_dtype(DType::F32)?;
1657
+ let attention_weights = attention_weights.to_dtype(DType::F32)?;
1658
  let [_, num_queries, _, num_levels, num_points, _] =
1659
  <[usize; 6]>::try_from(sampling_offsets.dims().to_vec()).map_err(|_| {
1660
  anyhow::anyhow!(
koharu-ml/src/comic_text_detector/mod.rs CHANGED
@@ -57,6 +57,7 @@ pub struct ComicTextDetector {
57
  unet: unet::UNet,
58
  dbnet: Option<dbnet::DbNet>,
59
  device: Device,
 
60
  }
61
 
62
  impl ComicTextDetector {
@@ -77,24 +78,32 @@ impl ComicTextDetector {
77
  load_dbnet: bool,
78
  ) -> anyhow::Result<Self> {
79
  let device = device(cpu)?;
 
80
  let downloads = runtime.downloads();
81
  let yolo_path = downloads
82
  .huggingface_model(HF_REPO, "yolo-v5.safetensors")
83
  .await?;
84
- let yolo = loading::load_mmaped_safetensors_path(&yolo_path, &device, |vb| {
85
- yolo_v5::YoloV5::load(vb, 2, 3)
86
- })?;
 
87
  let unet_path = downloads
88
  .huggingface_model(HF_REPO, "unet.safetensors")
89
  .await?;
90
- let unet = loading::load_mmaped_safetensors_path(&unet_path, &device, unet::UNet::load)?;
 
 
 
 
 
91
  let dbnet = if load_dbnet {
92
  let dbnet_path = downloads
93
  .huggingface_model(HF_REPO, "dbnet.safetensors")
94
  .await?;
95
- Some(loading::load_mmaped_safetensors_path(
96
  &dbnet_path,
97
  &device,
 
98
  dbnet::DbNet::load,
99
  )?)
100
  } else {
@@ -106,13 +115,14 @@ impl ComicTextDetector {
106
  unet,
107
  dbnet,
108
  device,
 
109
  })
110
  }
111
 
112
  #[instrument(level = "debug", skip_all)]
113
  pub fn inference(&self, image: &DynamicImage) -> anyhow::Result<ComicTextDetection> {
114
  let original_dimensions = image.dimensions();
115
- let (image_tensor, resized_dimensions) = preprocess(image, &self.device)?;
116
  let (predictions, mask, shrink_threshold) = self.forward(&image_tensor)?;
117
 
118
  let bboxes = postprocess_yolo(&predictions, original_dimensions, resized_dimensions)?;
@@ -145,7 +155,7 @@ impl ComicTextDetector {
145
  #[instrument(level = "debug", skip_all)]
146
  pub fn inference_segmentation(&self, image: &DynamicImage) -> anyhow::Result<GrayImage> {
147
  let original_dimensions = image.dimensions();
148
- let (image_tensor, resized_dimensions) = preprocess(image, &self.device)?;
149
  let mask = self.forward_mask(&image_tensor)?;
150
  postprocess_unet_mask(&mask, original_dimensions, resized_dimensions)
151
  }
@@ -184,7 +194,11 @@ impl ComicTextDetector {
184
  }
185
 
186
  #[instrument(level = "debug", skip_all)]
187
- fn preprocess(image: &DynamicImage, device: &Device) -> anyhow::Result<(Tensor, (u32, u32))> {
 
 
 
 
188
  let (orig_w, orig_h) = image.dimensions();
189
  let image_size = match device {
190
  Device::Cpu => CPU_DETECT_SIZE,
@@ -208,7 +222,7 @@ fn preprocess(image: &DynamicImage, device: &Device) -> anyhow::Result<(Tensor,
208
  .pad_with_zeros(3, 0, image_size as usize - w)?
209
  * (1. / 255.))?;
210
 
211
- Ok((tensor, (width, height)))
212
  }
213
 
214
  #[instrument(level = "debug", skip(predictions))]
@@ -217,7 +231,7 @@ fn postprocess_yolo(
217
  original_dimensions: (u32, u32),
218
  resized_dimensions: (u32, u32),
219
  ) -> anyhow::Result<Vec<Bbox<usize>>> {
220
- let predictions = predictions.squeeze(0)?;
221
  let (_, num_outputs) = predictions.dims2()?;
222
  if num_outputs < 6 {
223
  bail!("invalid prediction shape: expected at least 6 outputs, got {num_outputs}");
@@ -272,9 +286,9 @@ fn postprocess_mask(
272
  resized_dimensions: (u32, u32),
273
  ) -> anyhow::Result<GrayImage> {
274
  let shrink_and_thresh = shrink_thresh.squeeze(0)?;
275
- let shrink = shrink_and_thresh.i(0)?;
276
- let thresh = shrink_and_thresh.i(1)?;
277
- let unet_mask = mask.squeeze(0)?;
278
 
279
  let (_, h_db, w_db) = shrink_and_thresh.dims3()?;
280
  let (_, h_unet, w_unet) = unet_mask.dims3()?;
@@ -334,7 +348,11 @@ fn tensor_channel_to_gray_resized(
334
  height: u32,
335
  ) -> anyhow::Result<GrayImage> {
336
  let (th, tw) = tensor.dims2()?;
337
- let values: Vec<f32> = tensor.to_device(&Device::Cpu)?.flatten_all()?.to_vec1()?;
 
 
 
 
338
  let pixels: Vec<u8> = values
339
  .iter()
340
  .map(|&v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
 
57
  unet: unet::UNet,
58
  dbnet: Option<dbnet::DbNet>,
59
  device: Device,
60
+ dtype: DType,
61
  }
62
 
63
  impl ComicTextDetector {
 
78
  load_dbnet: bool,
79
  ) -> anyhow::Result<Self> {
80
  let device = device(cpu)?;
81
+ let dtype = loading::model_dtype(&device);
82
  let downloads = runtime.downloads();
83
  let yolo_path = downloads
84
  .huggingface_model(HF_REPO, "yolo-v5.safetensors")
85
  .await?;
86
+ let yolo =
87
+ loading::load_mmaped_safetensors_path_with_dtype(&yolo_path, &device, dtype, |vb| {
88
+ yolo_v5::YoloV5::load(vb, 2, 3)
89
+ })?;
90
  let unet_path = downloads
91
  .huggingface_model(HF_REPO, "unet.safetensors")
92
  .await?;
93
+ let unet = loading::load_mmaped_safetensors_path_with_dtype(
94
+ &unet_path,
95
+ &device,
96
+ dtype,
97
+ unet::UNet::load,
98
+ )?;
99
  let dbnet = if load_dbnet {
100
  let dbnet_path = downloads
101
  .huggingface_model(HF_REPO, "dbnet.safetensors")
102
  .await?;
103
+ Some(loading::load_mmaped_safetensors_path_with_dtype(
104
  &dbnet_path,
105
  &device,
106
+ dtype,
107
  dbnet::DbNet::load,
108
  )?)
109
  } else {
 
115
  unet,
116
  dbnet,
117
  device,
118
+ dtype,
119
  })
120
  }
121
 
122
  #[instrument(level = "debug", skip_all)]
123
  pub fn inference(&self, image: &DynamicImage) -> anyhow::Result<ComicTextDetection> {
124
  let original_dimensions = image.dimensions();
125
+ let (image_tensor, resized_dimensions) = preprocess(image, &self.device, self.dtype)?;
126
  let (predictions, mask, shrink_threshold) = self.forward(&image_tensor)?;
127
 
128
  let bboxes = postprocess_yolo(&predictions, original_dimensions, resized_dimensions)?;
 
155
  #[instrument(level = "debug", skip_all)]
156
  pub fn inference_segmentation(&self, image: &DynamicImage) -> anyhow::Result<GrayImage> {
157
  let original_dimensions = image.dimensions();
158
+ let (image_tensor, resized_dimensions) = preprocess(image, &self.device, self.dtype)?;
159
  let mask = self.forward_mask(&image_tensor)?;
160
  postprocess_unet_mask(&mask, original_dimensions, resized_dimensions)
161
  }
 
194
  }
195
 
196
  #[instrument(level = "debug", skip_all)]
197
+ fn preprocess(
198
+ image: &DynamicImage,
199
+ device: &Device,
200
+ dtype: DType,
201
+ ) -> anyhow::Result<(Tensor, (u32, u32))> {
202
  let (orig_w, orig_h) = image.dimensions();
203
  let image_size = match device {
204
  Device::Cpu => CPU_DETECT_SIZE,
 
222
  .pad_with_zeros(3, 0, image_size as usize - w)?
223
  * (1. / 255.))?;
224
 
225
+ Ok((tensor.to_dtype(dtype)?, (width, height)))
226
  }
227
 
228
  #[instrument(level = "debug", skip(predictions))]
 
231
  original_dimensions: (u32, u32),
232
  resized_dimensions: (u32, u32),
233
  ) -> anyhow::Result<Vec<Bbox<usize>>> {
234
+ let predictions = predictions.squeeze(0)?.to_dtype(DType::F32)?;
235
  let (_, num_outputs) = predictions.dims2()?;
236
  if num_outputs < 6 {
237
  bail!("invalid prediction shape: expected at least 6 outputs, got {num_outputs}");
 
286
  resized_dimensions: (u32, u32),
287
  ) -> anyhow::Result<GrayImage> {
288
  let shrink_and_thresh = shrink_thresh.squeeze(0)?;
289
+ let shrink = shrink_and_thresh.i(0)?.to_dtype(DType::F32)?;
290
+ let thresh = shrink_and_thresh.i(1)?.to_dtype(DType::F32)?;
291
+ let unet_mask = mask.squeeze(0)?.to_dtype(DType::F32)?;
292
 
293
  let (_, h_db, w_db) = shrink_and_thresh.dims3()?;
294
  let (_, h_unet, w_unet) = unet_mask.dims3()?;
 
348
  height: u32,
349
  ) -> anyhow::Result<GrayImage> {
350
  let (th, tw) = tensor.dims2()?;
351
+ let values: Vec<f32> = tensor
352
+ .to_dtype(DType::F32)?
353
+ .to_device(&Device::Cpu)?
354
+ .flatten_all()?
355
+ .to_vec1()?;
356
  let pixels: Vec<u8> = values
357
  .iter()
358
  .map(|&v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
koharu-ml/src/comic_text_detector/yolo_v5.rs CHANGED
@@ -1,4 +1,4 @@
1
- use candle_core::{DType, Device, IndexOp, Result, Tensor};
2
  use candle_nn::{
3
  BatchNorm, Conv2d, Conv2dConfig, Module, ModuleT, VarBuilder, batch_norm, conv2d,
4
  conv2d_no_bias,
@@ -338,8 +338,9 @@ impl YoloV3Head {
338
  ny: usize,
339
  dev: &Device,
340
  ) -> Result<(Tensor, Tensor)> {
341
- let gx = Tensor::arange(0, nx as u32, dev)?.to_dtype(DType::F32)?;
342
- let gy = Tensor::arange(0, ny as u32, dev)?.to_dtype(DType::F32)?;
 
343
 
344
  let gx = gx.reshape((1, 1, 1, nx))?.repeat((1, 1, ny, 1))?;
345
  let gy = gy.reshape((1, 1, ny, 1))?.repeat((1, 1, 1, nx))?;
 
1
+ use candle_core::{Device, IndexOp, Result, Tensor};
2
  use candle_nn::{
3
  BatchNorm, Conv2d, Conv2dConfig, Module, ModuleT, VarBuilder, batch_norm, conv2d,
4
  conv2d_no_bias,
 
338
  ny: usize,
339
  dev: &Device,
340
  ) -> Result<(Tensor, Tensor)> {
341
+ let dtype = self.anchors.dtype();
342
+ let gx = Tensor::arange(0, nx as u32, dev)?.to_dtype(dtype)?;
343
+ let gy = Tensor::arange(0, ny as u32, dev)?.to_dtype(dtype)?;
344
 
345
  let gx = gx.reshape((1, 1, 1, nx))?.repeat((1, 1, ny, 1))?;
346
  let gy = gy.reshape((1, 1, ny, 1))?.repeat((1, 1, 1, nx))?;
koharu-ml/src/flux2_klein/mod.rs CHANGED
@@ -147,16 +147,18 @@ impl Flux2Klein {
147
  );
148
  }
149
  let prompt_embedder = Flux2PromptEmbedder::new(&model_device);
150
- let vae =
151
- loading::load_mmaped_safetensors_path(&paths.vae_safetensors, &model_device, |vb| {
152
- Flux2Vae::new(vb)
153
- })
154
- .with_context(|| {
155
- format!(
156
- "failed to load Flux2 VAE from {}",
157
- paths.vae_safetensors.display()
158
- )
159
- })?;
 
 
160
 
161
  Ok(Self {
162
  transformer,
@@ -454,7 +456,8 @@ impl Flux2Klein {
454
  }
455
 
456
  fn encode_image_latents(&self, image: &RgbImage) -> Result<Tensor> {
457
- let image_tensor = image_to_tensor(image, &self.device)?;
 
458
  let latents = self.vae.encode_patchified_normalized(&image_tensor)?;
459
  drop(image_tensor);
460
  Ok(latents.to_dtype(DType::F32)?)
@@ -466,7 +469,8 @@ impl Flux2Klein {
466
  packed_h: usize,
467
  packed_w: usize,
468
  ) -> Result<RgbImage> {
469
- let patchified = unpack_latents(&packed_latents, packed_h, packed_w)?;
 
470
  drop(packed_latents);
471
  let decoded = self.vae.decode_patchified_normalized(&patchified)?;
472
  drop(patchified);
@@ -500,6 +504,14 @@ fn transformer_dtype(device: &Device) -> DType {
500
  DType::F32
501
  }
502
 
 
 
 
 
 
 
 
 
503
  fn release_cuda_temporary_memory(device: &Device) -> Result<()> {
504
  device.synchronize()?;
505
 
 
147
  );
148
  }
149
  let prompt_embedder = Flux2PromptEmbedder::new(&model_device);
150
+ let vae = loading::load_mmaped_safetensors_path_with_dtype(
151
+ &paths.vae_safetensors,
152
+ &model_device,
153
+ vae_dtype(&model_device),
154
+ |vb| Flux2Vae::new(vb),
155
+ )
156
+ .with_context(|| {
157
+ format!(
158
+ "failed to load Flux2 VAE from {}",
159
+ paths.vae_safetensors.display()
160
+ )
161
+ })?;
162
 
163
  Ok(Self {
164
  transformer,
 
456
  }
457
 
458
  fn encode_image_latents(&self, image: &RgbImage) -> Result<Tensor> {
459
+ let image_tensor =
460
+ image_to_tensor(image, &self.device)?.to_dtype(vae_dtype(&self.device))?;
461
  let latents = self.vae.encode_patchified_normalized(&image_tensor)?;
462
  drop(image_tensor);
463
  Ok(latents.to_dtype(DType::F32)?)
 
469
  packed_h: usize,
470
  packed_w: usize,
471
  ) -> Result<RgbImage> {
472
+ let patchified = unpack_latents(&packed_latents, packed_h, packed_w)?
473
+ .to_dtype(vae_dtype(&self.device))?;
474
  drop(packed_latents);
475
  let decoded = self.vae.decode_patchified_normalized(&patchified)?;
476
  drop(patchified);
 
504
  DType::F32
505
  }
506
 
507
+ fn vae_dtype(device: &Device) -> DType {
508
+ if device.is_cuda() {
509
+ return DType::BF16;
510
+ }
511
+
512
+ DType::F32
513
+ }
514
+
515
  fn release_cuda_temporary_memory(device: &Device) -> Result<()> {
516
  device.synchronize()?;
517
 
koharu-ml/src/flux2_klein/vae.rs CHANGED
@@ -54,7 +54,11 @@ fn scaled_dot_product_attention(q: &Tensor, k: &Tensor, v: &Tensor) -> Result<Te
54
  let len = chunk_size.min(seq_len - start);
55
  let q_chunk = q.narrow(2, start, len)?;
56
  let attn_weights = (q_chunk.matmul(&k_t)? * scale)?;
57
- chunks.push(candle_nn::ops::softmax_last_dim(&attn_weights)?.matmul(&v)?);
 
 
 
 
58
  }
59
  Tensor::cat(&chunks, 2)
60
  }
 
54
  let len = chunk_size.min(seq_len - start);
55
  let q_chunk = q.narrow(2, start, len)?;
56
  let attn_weights = (q_chunk.matmul(&k_t)? * scale)?;
57
+ let attn_dtype = attn_weights.dtype();
58
+ let attn_weights =
59
+ candle_nn::ops::softmax_last_dim(&attn_weights.to_dtype(candle_core::DType::F32)?)?
60
+ .to_dtype(attn_dtype)?;
61
+ chunks.push(attn_weights.matmul(&v)?);
62
  }
63
  Tensor::cat(&chunks, 2)
64
  }
koharu-ml/src/font_detector/mod.rs CHANGED
@@ -37,6 +37,7 @@ pub struct FontDetector {
37
  model: models::Model,
38
  labels: FontLabels,
39
  device: Device,
 
40
  }
41
 
42
  impl FontDetector {
@@ -50,18 +51,23 @@ impl FontDetector {
50
  kind: ModelKind,
51
  ) -> Result<Self> {
52
  let device = device(cpu)?;
 
53
  let downloads = runtime.downloads();
54
  let weights_path = downloads
55
  .huggingface_model(HF_REPO, "yuzumarker-font-detection.safetensors")
56
  .await?;
57
- let model = loading::load_mmaped_safetensors_path(&weights_path, &device, move |vb| {
58
- models::Model::load(vb.pp("model._orig_mod.model"), kind)
59
- })?;
 
 
 
60
  let labels = FontLabels::load(runtime).await?;
61
 
62
  Ok(Self {
63
  model,
64
  device,
 
65
  labels,
66
  })
67
  }
@@ -84,11 +90,17 @@ impl FontDetector {
84
  .collect::<Vec<_>>()
85
  .into_iter()
86
  .collect::<Result<Vec<_>>>()?;
87
- let batch = Tensor::stack(&processed, 0)?.to_device(&self.device)?;
 
 
88
  let preprocess_elapsed = preprocess_started.elapsed();
89
 
90
  let forward_started = Instant::now();
91
- let logits = self.model.forward(&batch, false)?.to_device(&Device::Cpu)?;
 
 
 
 
92
  let forward_elapsed = forward_started.elapsed();
93
 
94
  let postprocess_started = Instant::now();
 
37
  model: models::Model,
38
  labels: FontLabels,
39
  device: Device,
40
+ dtype: DType,
41
  }
42
 
43
  impl FontDetector {
 
51
  kind: ModelKind,
52
  ) -> Result<Self> {
53
  let device = device(cpu)?;
54
+ let dtype = loading::model_dtype(&device);
55
  let downloads = runtime.downloads();
56
  let weights_path = downloads
57
  .huggingface_model(HF_REPO, "yuzumarker-font-detection.safetensors")
58
  .await?;
59
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
60
+ &weights_path,
61
+ &device,
62
+ dtype,
63
+ move |vb| models::Model::load(vb.pp("model._orig_mod.model"), kind),
64
+ )?;
65
  let labels = FontLabels::load(runtime).await?;
66
 
67
  Ok(Self {
68
  model,
69
  device,
70
+ dtype,
71
  labels,
72
  })
73
  }
 
90
  .collect::<Vec<_>>()
91
  .into_iter()
92
  .collect::<Result<Vec<_>>>()?;
93
+ let batch = Tensor::stack(&processed, 0)?
94
+ .to_device(&self.device)?
95
+ .to_dtype(self.dtype)?;
96
  let preprocess_elapsed = preprocess_started.elapsed();
97
 
98
  let forward_started = Instant::now();
99
+ let logits = self
100
+ .model
101
+ .forward(&batch, false)?
102
+ .to_dtype(DType::F32)?
103
+ .to_device(&Device::Cpu)?;
104
  let forward_elapsed = forward_started.elapsed();
105
 
106
  let postprocess_started = Instant::now();
koharu-ml/src/font_detector/models.rs CHANGED
@@ -1,5 +1,5 @@
1
  use anyhow::Result;
2
- use candle_core::{DType, Module, ModuleT, Tensor};
3
  use candle_nn::{BatchNorm, Conv2d, Conv2dConfig, Linear, VarBuilder};
4
  use clap::ValueEnum;
5
 
@@ -68,7 +68,8 @@ impl Model {
68
  // For models that only output font logits (e.g., DeepFont), pad zeros for direction/regression.
69
  if dim == FONT_COUNT {
70
  let device = logits.device();
71
- let zeros = Tensor::zeros((logits.dim(0)?, REGRESSION_DIM + 2), DType::F32, device)?;
 
72
  return Tensor::cat(&[logits, zeros], 1);
73
  }
74
 
 
1
  use anyhow::Result;
2
+ use candle_core::{Module, ModuleT, Tensor};
3
  use candle_nn::{BatchNorm, Conv2d, Conv2dConfig, Linear, VarBuilder};
4
  use clap::ValueEnum;
5
 
 
68
  // For models that only output font logits (e.g., DeepFont), pad zeros for direction/regression.
69
  if dim == FONT_COUNT {
70
  let device = logits.device();
71
+ let zeros =
72
+ Tensor::zeros((logits.dim(0)?, REGRESSION_DIM + 2), logits.dtype(), device)?;
73
  return Tensor::cat(&[logits, zeros], 1);
74
  }
75
 
koharu-ml/src/loading.rs CHANGED
@@ -6,6 +6,14 @@ use candle_core::{DType, Device};
6
  use candle_nn::VarBuilder;
7
  use serde::de::DeserializeOwned;
8
 
 
 
 
 
 
 
 
 
9
  pub async fn resolve_manifest_path<F>(manifest: F) -> Result<PathBuf>
10
  where
11
  F: Future<Output = Result<PathBuf>>,
@@ -36,7 +44,20 @@ where
36
  Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
37
  E: Into<anyhow::Error>,
38
  {
39
- let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[weights], DType::F32, device)? };
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  build(vb).map_err(Into::into)
41
  }
42
 
@@ -59,13 +80,26 @@ pub fn load_buffered_safetensors_path<T, Build, E>(
59
  device: &Device,
60
  build: Build,
61
  ) -> Result<T>
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  where
63
  Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
64
  E: Into<anyhow::Error>,
65
  {
66
  let data =
67
  std::fs::read(weights).with_context(|| format!("failed to read {}", weights.display()))?;
68
- let vb = VarBuilder::from_buffered_safetensors(data, DType::F32, device)?;
69
  build(vb).map_err(Into::into)
70
  }
71
 
 
6
  use candle_nn::VarBuilder;
7
  use serde::de::DeserializeOwned;
8
 
9
+ pub fn model_dtype(device: &Device) -> DType {
10
+ if device.is_cuda() {
11
+ DType::BF16
12
+ } else {
13
+ DType::F32
14
+ }
15
+ }
16
+
17
  pub async fn resolve_manifest_path<F>(manifest: F) -> Result<PathBuf>
18
  where
19
  F: Future<Output = Result<PathBuf>>,
 
44
  Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
45
  E: Into<anyhow::Error>,
46
  {
47
+ load_mmaped_safetensors_path_with_dtype(weights, device, DType::F32, build)
48
+ }
49
+
50
+ pub fn load_mmaped_safetensors_path_with_dtype<T, Build, E>(
51
+ weights: &Path,
52
+ device: &Device,
53
+ dtype: DType,
54
+ build: Build,
55
+ ) -> Result<T>
56
+ where
57
+ Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
58
+ E: Into<anyhow::Error>,
59
+ {
60
+ let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[weights], dtype, device)? };
61
  build(vb).map_err(Into::into)
62
  }
63
 
 
80
  device: &Device,
81
  build: Build,
82
  ) -> Result<T>
83
+ where
84
+ Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
85
+ E: Into<anyhow::Error>,
86
+ {
87
+ load_buffered_safetensors_path_with_dtype(weights, device, DType::F32, build)
88
+ }
89
+
90
+ pub fn load_buffered_safetensors_path_with_dtype<T, Build, E>(
91
+ weights: &Path,
92
+ device: &Device,
93
+ dtype: DType,
94
+ build: Build,
95
+ ) -> Result<T>
96
  where
97
  Build: FnOnce(VarBuilder) -> std::result::Result<T, E>,
98
  E: Into<anyhow::Error>,
99
  {
100
  let data =
101
  std::fs::read(weights).with_context(|| format!("failed to read {}", weights.display()))?;
102
+ let vb = VarBuilder::from_buffered_safetensors(data, dtype, device)?;
103
  build(vb).map_err(Into::into)
104
  }
105
 
koharu-ml/src/manga_ocr/bert.rs CHANGED
@@ -175,6 +175,15 @@ impl Module for Dropout {
175
  }
176
  }
177
 
 
 
 
 
 
 
 
 
 
178
  struct BertSelfAttention {
179
  query: Linear,
180
  key: Linear,
@@ -233,7 +242,7 @@ impl BertSelfAttention {
233
  if let Some(mask) = attention_mask {
234
  attention_scores = attention_scores.broadcast_add(mask)?;
235
  }
236
- let attention_probs = candle_nn::ops::softmax(&attention_scores, D::Minus1)?;
237
  let attention_probs = self.dropout.forward(&attention_probs)?;
238
  let context_layer = attention_probs.matmul(&value)?;
239
  context_layer.transpose(1, 2)?.contiguous()?.reshape((
 
175
  }
176
  }
177
 
178
+ fn softmax_f32(xs: &Tensor, dim: D) -> candle_core::Result<Tensor> {
179
+ let dtype = xs.dtype();
180
+ if dtype == DType::F32 {
181
+ candle_nn::ops::softmax(xs, dim)
182
+ } else {
183
+ candle_nn::ops::softmax(&xs.to_dtype(DType::F32)?, dim)?.to_dtype(dtype)
184
+ }
185
+ }
186
+
187
  struct BertSelfAttention {
188
  query: Linear,
189
  key: Linear,
 
242
  if let Some(mask) = attention_mask {
243
  attention_scores = attention_scores.broadcast_add(mask)?;
244
  }
245
+ let attention_probs = softmax_f32(&attention_scores, D::Minus1)?;
246
  let attention_probs = self.dropout.forward(&attention_probs)?;
247
  let context_layer = attention_probs.matmul(&value)?;
248
  context_layer.transpose(1, 2)?.contiguous()?.reshape((
koharu-ml/src/manga_ocr/mod.rs CHANGED
@@ -27,11 +27,23 @@ pub struct MangaOcr {
27
  tokenizer: Tokenizer,
28
  preprocessor: PreprocessorConfig,
29
  device: Device,
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
 
32
  impl MangaOcr {
33
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
34
  let device = device(cpu)?;
 
35
  let hf = runtime.downloads();
36
  let config_path = hf.huggingface_model(HF_REPO, "config.json").await?;
37
  let preprocessor_path = hf
@@ -49,15 +61,19 @@ impl MangaOcr {
49
  let tokenizer = load_tokenizer(None, &vocab_path, &special_tokens_path)?;
50
  let model_device = device.clone();
51
  let weights = hf.huggingface_model(HF_REPO, "model.safetensors").await?;
52
- let model = loading::load_mmaped_safetensors_path(&weights, &device, move |vb| {
53
- VisionEncoderDecoder::from_config(config, vb, model_device.clone())
54
- })?;
 
 
 
55
 
56
  Ok(Self {
57
  model,
58
  tokenizer,
59
  preprocessor,
60
  device,
 
61
  })
62
  }
63
 
@@ -67,15 +83,16 @@ impl MangaOcr {
67
  return Ok(Vec::new());
68
  }
69
 
70
- let pixel_values = preprocess_images(
71
- images,
72
- self.preprocessor.size,
73
- &self.preprocessor.image_mean,
74
- &self.preprocessor.image_std,
75
- self.preprocessor.do_resize,
76
- self.preprocessor.do_normalize,
77
- &self.device,
78
- )?;
 
79
  let token_ids = self.forward(&pixel_values)?;
80
  let texts = token_ids
81
  .into_iter()
@@ -96,24 +113,11 @@ impl MangaOcr {
96
  #[instrument(level = "debug", skip_all)]
97
  fn preprocess_images(
98
  images: &[image::DynamicImage],
99
- image_size: u32,
100
- image_mean: &[f32; 3],
101
- image_std: &[f32; 3],
102
- do_resize: bool,
103
- do_normalize: bool,
104
- device: &Device,
105
  ) -> Result<Tensor> {
106
  let mut batch = Vec::with_capacity(images.len());
107
  for image in images {
108
- let processed = preprocess_single_image(
109
- image,
110
- image_size,
111
- image_mean,
112
- image_std,
113
- do_resize,
114
- do_normalize,
115
- device,
116
- )?;
117
  batch.push(processed);
118
  }
119
 
@@ -123,16 +127,11 @@ fn preprocess_images(
123
  #[instrument(level = "debug", skip_all)]
124
  fn preprocess_single_image(
125
  image: &image::DynamicImage,
126
- image_size: u32,
127
- image_mean: &[f32; 3],
128
- image_std: &[f32; 3],
129
- do_resize: bool,
130
- do_normalize: bool,
131
- device: &Device,
132
  ) -> Result<Tensor> {
133
  let (orig_w, orig_h) = image.dimensions();
134
- let (width, height) = if do_resize {
135
- (image_size as usize, image_size as usize)
136
  } else {
137
  (orig_w as usize, orig_h as usize)
138
  };
@@ -140,44 +139,44 @@ fn preprocess_single_image(
140
  let tensor = Tensor::from_vec(
141
  image.grayscale().to_rgb8().into_raw(),
142
  (1, orig_h as usize, orig_w as usize, 3),
143
- device,
144
  )?
145
  .permute((0, 3, 1, 2))?
146
  .to_dtype(DType::F32)?;
147
 
148
- let tensor = if do_resize {
149
  tensor.interpolate2d(height, width)?
150
  } else {
151
  tensor
152
  };
153
 
154
  let tensor = (tensor * (1.0 / 255.0))?;
155
- let tensor = if do_normalize {
156
  let std = [
157
- if image_std[0] == 0.0 {
158
  1.0
159
  } else {
160
- image_std[0]
161
  },
162
- if image_std[1] == 0.0 {
163
  1.0
164
  } else {
165
- image_std[1]
166
  },
167
- if image_std[2] == 0.0 {
168
  1.0
169
  } else {
170
- image_std[2]
171
  },
172
  ];
173
- let mean_t = Tensor::from_slice(image_mean, (1, 3, 1, 1), device)?;
174
- let std_t = Tensor::from_slice(&std, (1, 3, 1, 1), device)?;
175
  tensor.broadcast_sub(&mean_t)?.broadcast_div(&std_t)?
176
  } else {
177
  tensor
178
  };
179
 
180
- Ok(tensor)
181
  }
182
 
183
  #[instrument(level = "debug", skip_all)]
 
27
  tokenizer: Tokenizer,
28
  preprocessor: PreprocessorConfig,
29
  device: Device,
30
+ dtype: DType,
31
+ }
32
+
33
+ struct ImagePreprocessOptions<'a> {
34
+ image_size: u32,
35
+ image_mean: &'a [f32; 3],
36
+ image_std: &'a [f32; 3],
37
+ do_resize: bool,
38
+ do_normalize: bool,
39
+ device: &'a Device,
40
+ dtype: DType,
41
  }
42
 
43
  impl MangaOcr {
44
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
45
  let device = device(cpu)?;
46
+ let dtype = loading::model_dtype(&device);
47
  let hf = runtime.downloads();
48
  let config_path = hf.huggingface_model(HF_REPO, "config.json").await?;
49
  let preprocessor_path = hf
 
61
  let tokenizer = load_tokenizer(None, &vocab_path, &special_tokens_path)?;
62
  let model_device = device.clone();
63
  let weights = hf.huggingface_model(HF_REPO, "model.safetensors").await?;
64
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
65
+ &weights,
66
+ &device,
67
+ dtype,
68
+ move |vb| VisionEncoderDecoder::from_config(config, vb, model_device.clone()),
69
+ )?;
70
 
71
  Ok(Self {
72
  model,
73
  tokenizer,
74
  preprocessor,
75
  device,
76
+ dtype,
77
  })
78
  }
79
 
 
83
  return Ok(Vec::new());
84
  }
85
 
86
+ let options = ImagePreprocessOptions {
87
+ image_size: self.preprocessor.size,
88
+ image_mean: &self.preprocessor.image_mean,
89
+ image_std: &self.preprocessor.image_std,
90
+ do_resize: self.preprocessor.do_resize,
91
+ do_normalize: self.preprocessor.do_normalize,
92
+ device: &self.device,
93
+ dtype: self.dtype,
94
+ };
95
+ let pixel_values = preprocess_images(images, &options)?;
96
  let token_ids = self.forward(&pixel_values)?;
97
  let texts = token_ids
98
  .into_iter()
 
113
  #[instrument(level = "debug", skip_all)]
114
  fn preprocess_images(
115
  images: &[image::DynamicImage],
116
+ options: &ImagePreprocessOptions<'_>,
 
 
 
 
 
117
  ) -> Result<Tensor> {
118
  let mut batch = Vec::with_capacity(images.len());
119
  for image in images {
120
+ let processed = preprocess_single_image(image, options)?;
 
 
 
 
 
 
 
 
121
  batch.push(processed);
122
  }
123
 
 
127
  #[instrument(level = "debug", skip_all)]
128
  fn preprocess_single_image(
129
  image: &image::DynamicImage,
130
+ options: &ImagePreprocessOptions<'_>,
 
 
 
 
 
131
  ) -> Result<Tensor> {
132
  let (orig_w, orig_h) = image.dimensions();
133
+ let (width, height) = if options.do_resize {
134
+ (options.image_size as usize, options.image_size as usize)
135
  } else {
136
  (orig_w as usize, orig_h as usize)
137
  };
 
139
  let tensor = Tensor::from_vec(
140
  image.grayscale().to_rgb8().into_raw(),
141
  (1, orig_h as usize, orig_w as usize, 3),
142
+ options.device,
143
  )?
144
  .permute((0, 3, 1, 2))?
145
  .to_dtype(DType::F32)?;
146
 
147
+ let tensor = if options.do_resize {
148
  tensor.interpolate2d(height, width)?
149
  } else {
150
  tensor
151
  };
152
 
153
  let tensor = (tensor * (1.0 / 255.0))?;
154
+ let tensor = if options.do_normalize {
155
  let std = [
156
+ if options.image_std[0] == 0.0 {
157
  1.0
158
  } else {
159
+ options.image_std[0]
160
  },
161
+ if options.image_std[1] == 0.0 {
162
  1.0
163
  } else {
164
+ options.image_std[1]
165
  },
166
+ if options.image_std[2] == 0.0 {
167
  1.0
168
  } else {
169
+ options.image_std[2]
170
  },
171
  ];
172
+ let mean_t = Tensor::from_slice(options.image_mean, (1, 3, 1, 1), options.device)?;
173
+ let std_t = Tensor::from_slice(&std, (1, 3, 1, 1), options.device)?;
174
  tensor.broadcast_sub(&mean_t)?.broadcast_div(&std_t)?
175
  } else {
176
  tensor
177
  };
178
 
179
+ Ok(tensor.to_dtype(options.dtype)?)
180
  }
181
 
182
  #[instrument(level = "debug", skip_all)]
koharu-ml/src/manga_ocr/model.rs CHANGED
@@ -106,7 +106,7 @@ impl VisionEncoderDecoder {
106
  }
107
 
108
  let last_idx = seq_lengths[batch_idx].saturating_sub(1);
109
- let last_logits = logits.i((batch_idx, last_idx, ..))?;
110
  let next_id = sampler.sample(&last_logits)?;
111
  seq.push(next_id);
112
  if next_id == self.eos_token_id {
 
106
  }
107
 
108
  let last_idx = seq_lengths[batch_idx].saturating_sub(1);
109
+ let last_logits = logits.i((batch_idx, last_idx, ..))?.to_dtype(DType::F32)?;
110
  let next_id = sampler.sample(&last_logits)?;
111
  seq.push(next_id);
112
  if next_id == self.eos_token_id {
koharu-ml/src/manga_text_segmentation_2025/mod.rs CHANGED
@@ -24,6 +24,7 @@ const CPU_MAX_PIXELS: u64 = 1_280 * 1_280;
24
  pub struct MangaTextSegmentation {
25
  model: model::MangaTextSegmentationModel,
26
  device: Device,
 
27
  mean: Tensor,
28
  std: Tensor,
29
  }
@@ -44,17 +45,19 @@ impl MangaTextSegmentation {
44
 
45
  pub fn load_from_path(path: impl AsRef<Path>, cpu: bool) -> Result<Self> {
46
  let device = device(cpu)?;
47
- let model = loading::load_mmaped_safetensors_path(
 
48
  path.as_ref(),
49
  &device,
 
50
  model::MangaTextSegmentationModel::load,
51
  )?;
52
- let mean =
53
- Tensor::from_slice(&IMAGENET_MEAN, (1, 3, 1, 1), &device)?.to_dtype(DType::F32)?;
54
- let std = Tensor::from_slice(&IMAGENET_STD, (1, 3, 1, 1), &device)?.to_dtype(DType::F32)?;
55
  Ok(Self {
56
  model,
57
  device,
 
58
  mean,
59
  std,
60
  })
@@ -92,6 +95,7 @@ impl MangaTextSegmentation {
92
  } else {
93
  probabilities
94
  }
 
95
  .to_device(&Device::Cpu)?;
96
  let values = probabilities.flatten_all()?.to_vec1::<f32>()?;
97
  tracing::info!(
@@ -138,7 +142,7 @@ impl MangaTextSegmentation {
138
  &self.device,
139
  )?
140
  .permute((0, 3, 1, 2))?
141
- .to_dtype(DType::F32)?;
142
  let tensor = (tensor * (1.0 / 255.0))?
143
  .broadcast_sub(&self.mean)?
144
  .broadcast_div(&self.std)?;
 
24
  pub struct MangaTextSegmentation {
25
  model: model::MangaTextSegmentationModel,
26
  device: Device,
27
+ dtype: DType,
28
  mean: Tensor,
29
  std: Tensor,
30
  }
 
45
 
46
  pub fn load_from_path(path: impl AsRef<Path>, cpu: bool) -> Result<Self> {
47
  let device = device(cpu)?;
48
+ let dtype = loading::model_dtype(&device);
49
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
50
  path.as_ref(),
51
  &device,
52
+ dtype,
53
  model::MangaTextSegmentationModel::load,
54
  )?;
55
+ let mean = Tensor::from_slice(&IMAGENET_MEAN, (1, 3, 1, 1), &device)?.to_dtype(dtype)?;
56
+ let std = Tensor::from_slice(&IMAGENET_STD, (1, 3, 1, 1), &device)?.to_dtype(dtype)?;
 
57
  Ok(Self {
58
  model,
59
  device,
60
+ dtype,
61
  mean,
62
  std,
63
  })
 
95
  } else {
96
  probabilities
97
  }
98
+ .to_dtype(DType::F32)?
99
  .to_device(&Device::Cpu)?;
100
  let values = probabilities.flatten_all()?.to_vec1::<f32>()?;
101
  tracing::info!(
 
142
  &self.device,
143
  )?
144
  .permute((0, 3, 1, 2))?
145
+ .to_dtype(self.dtype)?;
146
  let tensor = (tensor * (1.0 / 255.0))?
147
  .broadcast_sub(&self.mean)?
148
  .broadcast_div(&self.std)?;
koharu-ml/src/mit48px_ocr/mod.rs CHANGED
@@ -75,6 +75,7 @@ pub struct Mit48pxOcr {
75
  config: Mit48pxConfig,
76
  dictionary: Vec<String>,
77
  device: Device,
 
78
  }
79
 
80
  impl Mit48pxOcr {
@@ -102,18 +103,20 @@ impl Mit48pxOcr {
102
 
103
  fn load_from_files(files: ModelFiles, cpu: bool) -> Result<Self> {
104
  let device = device(cpu)?;
 
105
  let config: Mit48pxConfig =
106
  loading::read_json(&files.config).context("failed to parse mit48px config")?;
107
  let dictionary = read_dictionary(&files.dictionary)?;
108
  let data = std::fs::read(&files.weights)
109
  .with_context(|| format!("failed to read {}", files.weights.display()))?;
110
- let vb = VarBuilder::from_buffered_safetensors(data, DType::F32, &device)?;
111
  let model = Mit48pxModel::new(config.clone(), dictionary.len(), vb, device.clone())?;
112
  Ok(Self {
113
  model,
114
  config,
115
  dictionary,
116
  device,
 
117
  })
118
  }
119
 
@@ -125,7 +128,7 @@ impl Mit48pxOcr {
125
 
126
  let mut predictions = Vec::with_capacity(regions.len());
127
  for chunk in regions.chunks(OCR_CHUNK_SIZE) {
128
- let batch = preprocess_regions(chunk, &self.config, &self.device)?;
129
  let raw = self.model.infer_batch(&batch.tensor, &batch.widths)?;
130
  for prediction in raw {
131
  predictions.push(self.decode_prediction(prediction));
@@ -278,6 +281,7 @@ fn preprocess_regions(
278
  regions: &[DynamicImage],
279
  config: &Mit48pxConfig,
280
  device: &Device,
 
281
  ) -> Result<PreparedBatch> {
282
  let mut resized = Vec::<RgbImage>::with_capacity(regions.len());
283
  let mut widths = Vec::with_capacity(regions.len());
@@ -310,8 +314,9 @@ fn preprocess_regions(
310
  }
311
  }
312
 
313
- let tensor =
314
- Tensor::from_vec(flat, (resized.len(), height, width, 3), device)?.permute((0, 3, 1, 2))?;
 
315
  Ok(PreparedBatch { tensor, widths })
316
  }
317
 
@@ -397,7 +402,12 @@ mod tests {
397
  fn preprocessing_resizes_to_48px_and_matches_ballonstranslator_width_padding()
398
  -> anyhow::Result<()> {
399
  let image = DynamicImage::ImageRgb8(RgbImage::from_pixel(25, 10, image::Rgb([255, 0, 0])));
400
- let batch = preprocess_regions(&[image], &test_config(), &candle_core::Device::Cpu)?;
 
 
 
 
 
401
  assert_eq!(batch.widths, vec![120]);
402
  assert_eq!(batch.tensor.dims(), &[1, 3, 48, 127]);
403
  Ok(())
 
75
  config: Mit48pxConfig,
76
  dictionary: Vec<String>,
77
  device: Device,
78
+ dtype: DType,
79
  }
80
 
81
  impl Mit48pxOcr {
 
103
 
104
  fn load_from_files(files: ModelFiles, cpu: bool) -> Result<Self> {
105
  let device = device(cpu)?;
106
+ let dtype = loading::model_dtype(&device);
107
  let config: Mit48pxConfig =
108
  loading::read_json(&files.config).context("failed to parse mit48px config")?;
109
  let dictionary = read_dictionary(&files.dictionary)?;
110
  let data = std::fs::read(&files.weights)
111
  .with_context(|| format!("failed to read {}", files.weights.display()))?;
112
+ let vb = VarBuilder::from_buffered_safetensors(data, dtype, &device)?;
113
  let model = Mit48pxModel::new(config.clone(), dictionary.len(), vb, device.clone())?;
114
  Ok(Self {
115
  model,
116
  config,
117
  dictionary,
118
  device,
119
+ dtype,
120
  })
121
  }
122
 
 
128
 
129
  let mut predictions = Vec::with_capacity(regions.len());
130
  for chunk in regions.chunks(OCR_CHUNK_SIZE) {
131
+ let batch = preprocess_regions(chunk, &self.config, &self.device, self.dtype)?;
132
  let raw = self.model.infer_batch(&batch.tensor, &batch.widths)?;
133
  for prediction in raw {
134
  predictions.push(self.decode_prediction(prediction));
 
281
  regions: &[DynamicImage],
282
  config: &Mit48pxConfig,
283
  device: &Device,
284
+ dtype: DType,
285
  ) -> Result<PreparedBatch> {
286
  let mut resized = Vec::<RgbImage>::with_capacity(regions.len());
287
  let mut widths = Vec::with_capacity(regions.len());
 
314
  }
315
  }
316
 
317
+ let tensor = Tensor::from_vec(flat, (resized.len(), height, width, 3), device)?
318
+ .permute((0, 3, 1, 2))?
319
+ .to_dtype(dtype)?;
320
  Ok(PreparedBatch { tensor, widths })
321
  }
322
 
 
402
  fn preprocessing_resizes_to_48px_and_matches_ballonstranslator_width_padding()
403
  -> anyhow::Result<()> {
404
  let image = DynamicImage::ImageRgb8(RgbImage::from_pixel(25, 10, image::Rgb([255, 0, 0])));
405
+ let batch = preprocess_regions(
406
+ &[image],
407
+ &test_config(),
408
+ &candle_core::Device::Cpu,
409
+ candle_core::DType::F32,
410
+ )?;
411
  assert_eq!(batch.widths, vec![120]);
412
  assert_eq!(batch.tensor.dims(), &[1, 3, 48, 127]);
413
  Ok(())
koharu-ml/src/mit48px_ocr/model.rs CHANGED
@@ -35,6 +35,7 @@ pub(crate) struct Mit48pxModel {
35
  color_pred_fg_ind: Linear,
36
  color_pred_bg_ind: Linear,
37
  device: Device,
 
38
  }
39
 
40
  #[derive(Clone)]
@@ -46,7 +47,7 @@ struct Hypothesis {
46
  }
47
 
48
  fn topk_last_dim(tensor: &Tensor, topk: usize) -> Result<TopkOutput> {
49
- let rows = tensor.to_vec2::<f32>()?;
50
  let mut values = Vec::with_capacity(rows.len());
51
  let mut indices = Vec::with_capacity(rows.len());
52
 
@@ -93,10 +94,11 @@ impl Hypothesis {
93
  decoder_layers: usize,
94
  embd_dim: usize,
95
  device: &Device,
 
96
  ) -> Result<Self> {
97
  let mut cached_activations = Vec::with_capacity(decoder_layers + 1);
98
  for _ in 0..=decoder_layers {
99
- cached_activations.push(Tensor::zeros((1, 0, embd_dim), DType::F32, device)?);
100
  }
101
  Ok(Self {
102
  sample_index,
@@ -160,6 +162,7 @@ impl Mit48pxModel {
160
  vb: VarBuilder,
161
  device: Device,
162
  ) -> Result<Self> {
 
163
  let backbone = ConvNextFeatureExtractor::new(vb.pp("backbone"))?;
164
  let encoders = (0..config.encoder_layers)
165
  .map(|index| TransformerEncoderLayer::new(vb.pp(format!("encoders.{index}"))))
@@ -190,6 +193,7 @@ impl Mit48pxModel {
190
  color_pred_fg_ind,
191
  color_pred_bg_ind,
192
  device,
 
193
  })
194
  }
195
 
@@ -216,6 +220,7 @@ impl Mit48pxModel {
216
  self.decoders.len(),
217
  self.config.embd_dim,
218
  &self.device,
 
219
  )
220
  })
221
  .collect::<Result<Vec<_>>>()?;
@@ -400,7 +405,7 @@ impl Mit48pxModel {
400
  fn next_token_candidates(&self, decoded: &Tensor, beam_size: usize) -> Result<TopkOutput> {
401
  let pred_feats = self.pred1.forward(decoded)?.gelu_erf()?;
402
  let logits = self.pred.forward(&pred_feats)?;
403
- let log_probs = candle_nn::ops::log_softmax(&logits, D::Minus1)?;
404
  topk_last_dim(&log_probs, beam_size)
405
  }
406
 
@@ -410,6 +415,7 @@ impl Mit48pxModel {
410
  let fg_colors = self
411
  .color_pred_fg
412
  .forward(&color_feats)?
 
413
  .squeeze(0)?
414
  .to_vec2::<f32>()?
415
  .into_iter()
@@ -418,6 +424,7 @@ impl Mit48pxModel {
418
  let bg_colors = self
419
  .color_pred_bg
420
  .forward(&color_feats)?
 
421
  .squeeze(0)?
422
  .to_vec2::<f32>()?
423
  .into_iter()
@@ -426,6 +433,7 @@ impl Mit48pxModel {
426
  let fg_indicators = self
427
  .color_pred_fg_ind
428
  .forward(&color_feats)?
 
429
  .squeeze(0)?
430
  .to_vec2::<f32>()?
431
  .into_iter()
@@ -434,6 +442,7 @@ impl Mit48pxModel {
434
  let bg_indicators = self
435
  .color_pred_bg_ind
436
  .forward(&color_feats)?
 
437
  .squeeze(0)?
438
  .to_vec2::<f32>()?
439
  .into_iter()
@@ -871,7 +880,8 @@ impl XposMultiheadAttention {
871
  f32::NEG_INFINITY,
872
  attn_weights_4d.shape().dims(),
873
  attn_weights_4d.device(),
874
- )?;
 
875
  attn_weights = mask.where_cond(&neg_inf, &attn_weights_4d)?.reshape((
876
  batch * self.num_heads,
877
  tgt_len,
@@ -879,7 +889,9 @@ impl XposMultiheadAttention {
879
  ))?;
880
  }
881
 
882
- let attn_weights = candle_nn::ops::softmax_last_dim(&attn_weights)?;
 
 
883
  let attn = attn_weights
884
  .matmul(&v)?
885
  .reshape((batch, self.num_heads, tgt_len, self.head_dim))?
 
35
  color_pred_fg_ind: Linear,
36
  color_pred_bg_ind: Linear,
37
  device: Device,
38
+ dtype: DType,
39
  }
40
 
41
  #[derive(Clone)]
 
47
  }
48
 
49
  fn topk_last_dim(tensor: &Tensor, topk: usize) -> Result<TopkOutput> {
50
+ let rows = tensor.to_dtype(DType::F32)?.to_vec2::<f32>()?;
51
  let mut values = Vec::with_capacity(rows.len());
52
  let mut indices = Vec::with_capacity(rows.len());
53
 
 
94
  decoder_layers: usize,
95
  embd_dim: usize,
96
  device: &Device,
97
+ dtype: DType,
98
  ) -> Result<Self> {
99
  let mut cached_activations = Vec::with_capacity(decoder_layers + 1);
100
  for _ in 0..=decoder_layers {
101
+ cached_activations.push(Tensor::zeros((1, 0, embd_dim), dtype, device)?);
102
  }
103
  Ok(Self {
104
  sample_index,
 
162
  vb: VarBuilder,
163
  device: Device,
164
  ) -> Result<Self> {
165
+ let dtype = vb.dtype();
166
  let backbone = ConvNextFeatureExtractor::new(vb.pp("backbone"))?;
167
  let encoders = (0..config.encoder_layers)
168
  .map(|index| TransformerEncoderLayer::new(vb.pp(format!("encoders.{index}"))))
 
193
  color_pred_fg_ind,
194
  color_pred_bg_ind,
195
  device,
196
+ dtype,
197
  })
198
  }
199
 
 
220
  self.decoders.len(),
221
  self.config.embd_dim,
222
  &self.device,
223
+ self.dtype,
224
  )
225
  })
226
  .collect::<Result<Vec<_>>>()?;
 
405
  fn next_token_candidates(&self, decoded: &Tensor, beam_size: usize) -> Result<TopkOutput> {
406
  let pred_feats = self.pred1.forward(decoded)?.gelu_erf()?;
407
  let logits = self.pred.forward(&pred_feats)?;
408
+ let log_probs = candle_nn::ops::log_softmax(&logits.to_dtype(DType::F32)?, D::Minus1)?;
409
  topk_last_dim(&log_probs, beam_size)
410
  }
411
 
 
415
  let fg_colors = self
416
  .color_pred_fg
417
  .forward(&color_feats)?
418
+ .to_dtype(DType::F32)?
419
  .squeeze(0)?
420
  .to_vec2::<f32>()?
421
  .into_iter()
 
424
  let bg_colors = self
425
  .color_pred_bg
426
  .forward(&color_feats)?
427
+ .to_dtype(DType::F32)?
428
  .squeeze(0)?
429
  .to_vec2::<f32>()?
430
  .into_iter()
 
433
  let fg_indicators = self
434
  .color_pred_fg_ind
435
  .forward(&color_feats)?
436
+ .to_dtype(DType::F32)?
437
  .squeeze(0)?
438
  .to_vec2::<f32>()?
439
  .into_iter()
 
442
  let bg_indicators = self
443
  .color_pred_bg_ind
444
  .forward(&color_feats)?
445
+ .to_dtype(DType::F32)?
446
  .squeeze(0)?
447
  .to_vec2::<f32>()?
448
  .into_iter()
 
880
  f32::NEG_INFINITY,
881
  attn_weights_4d.shape().dims(),
882
  attn_weights_4d.device(),
883
+ )?
884
+ .to_dtype(attn_weights_4d.dtype())?;
885
  attn_weights = mask.where_cond(&neg_inf, &attn_weights_4d)?.reshape((
886
  batch * self.num_heads,
887
  tgt_len,
 
889
  ))?;
890
  }
891
 
892
+ let attn_dtype = attn_weights.dtype();
893
+ let attn_weights = candle_nn::ops::softmax_last_dim(&attn_weights.to_dtype(DType::F32)?)?
894
+ .to_dtype(attn_dtype)?;
895
  let attn = attn_weights
896
  .matmul(&v)?
897
  .reshape((batch, self.num_heads, tgt_len, self.head_dim))?
koharu-ml/src/pp_doclayout_v3/mod.rs CHANGED
@@ -46,6 +46,7 @@ pub struct PPDocLayoutV3 {
46
  config: PPDocLayoutV3Config,
47
  preprocessor: PPDocLayoutV3PreprocessorConfig,
48
  device: Device,
 
49
  mean: Tensor,
50
  std: Tensor,
51
  }
@@ -53,6 +54,7 @@ pub struct PPDocLayoutV3 {
53
  impl PPDocLayoutV3 {
54
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
55
  let device = device(cpu)?;
 
56
  let downloads = runtime.downloads();
57
  let config_path = downloads.huggingface_model(HF_REPO, "config.json").await?;
58
  let preprocessor_path = downloads
@@ -63,22 +65,26 @@ impl PPDocLayoutV3 {
63
  let preprocessor =
64
  loading::read_json::<PPDocLayoutV3PreprocessorConfig>(&preprocessor_path)
65
  .with_context(|| format!("failed to load {}", preprocessor_path.display()))?;
66
- let mean = Tensor::from_slice(&preprocessor.image_mean, (1, 3, 1, 1), &device)?
67
- .to_dtype(DType::F32)?;
68
- let std = Tensor::from_slice(&preprocessor.image_std, (1, 3, 1, 1), &device)?
69
- .to_dtype(DType::F32)?;
70
  let weights_path = downloads
71
  .huggingface_model(HF_REPO, "model.safetensors")
72
  .await?;
73
- let model = loading::load_mmaped_safetensors_path(&weights_path, &device, |vb| {
74
- PPDocLayoutV3ForObjectDetection::load(vb, &config, &device)
75
- })?;
 
 
 
76
 
77
  Ok(Self {
78
  model,
79
  config,
80
  preprocessor,
81
  device,
 
82
  mean,
83
  std,
84
  })
@@ -116,6 +122,7 @@ impl PPDocLayoutV3 {
116
  images,
117
  &self.preprocessor,
118
  &self.device,
 
119
  &self.mean,
120
  &self.std,
121
  )?;
@@ -376,6 +383,7 @@ fn preprocess_images(
376
  images: &[DynamicImage],
377
  preprocessor: &PPDocLayoutV3PreprocessorConfig,
378
  device: &Device,
 
379
  mean: &Tensor,
380
  std: &Tensor,
381
  ) -> Result<Tensor> {
@@ -395,7 +403,7 @@ fn preprocess_images(
395
  let tensor = Tensor::from_vec(batch, (images.len(), target_h, target_w, 3), &Device::Cpu)?
396
  .to_device(device)?
397
  .permute((0, 3, 1, 2))?
398
- .to_dtype(DType::F32)?;
399
  let tensor = if preprocessor.do_rescale {
400
  tensor.affine(preprocessor.rescale_factor as f64, 0.0)?
401
  } else {
@@ -416,9 +424,18 @@ fn post_process_outputs(
416
  threshold: f32,
417
  include_polygons: bool,
418
  ) -> Result<Vec<LayoutDetectionResult>> {
419
- let logits = outputs.logits.to_device(&Device::Cpu)?;
420
- let boxes = outputs.pred_boxes.to_device(&Device::Cpu)?;
421
- let order_logits = outputs.order_logits.to_device(&Device::Cpu)?;
 
 
 
 
 
 
 
 
 
422
 
423
  let (batch_size, num_queries, num_classes) = logits.dims3()?;
424
  if batch_size != images.len() {
@@ -429,7 +446,10 @@ fn post_process_outputs(
429
  let boxes = boxes.flatten_all()?.to_vec1::<f32>()?;
430
  let order_logits = order_logits.flatten_all()?.to_vec1::<f32>()?;
431
  let (masks, mask_h, mask_w) = if include_polygons {
432
- let masks = outputs.out_masks.to_device(&Device::Cpu)?;
 
 
 
433
  let (_, _, mask_h, mask_w) = masks.dims4()?;
434
  (Some(masks.flatten_all()?.to_vec1::<f32>()?), mask_h, mask_w)
435
  } else {
 
46
  config: PPDocLayoutV3Config,
47
  preprocessor: PPDocLayoutV3PreprocessorConfig,
48
  device: Device,
49
+ dtype: DType,
50
  mean: Tensor,
51
  std: Tensor,
52
  }
 
54
  impl PPDocLayoutV3 {
55
  pub async fn load(runtime: &RuntimeManager, cpu: bool) -> Result<Self> {
56
  let device = device(cpu)?;
57
+ let dtype = loading::model_dtype(&device);
58
  let downloads = runtime.downloads();
59
  let config_path = downloads.huggingface_model(HF_REPO, "config.json").await?;
60
  let preprocessor_path = downloads
 
65
  let preprocessor =
66
  loading::read_json::<PPDocLayoutV3PreprocessorConfig>(&preprocessor_path)
67
  .with_context(|| format!("failed to load {}", preprocessor_path.display()))?;
68
+ let mean =
69
+ Tensor::from_slice(&preprocessor.image_mean, (1, 3, 1, 1), &device)?.to_dtype(dtype)?;
70
+ let std =
71
+ Tensor::from_slice(&preprocessor.image_std, (1, 3, 1, 1), &device)?.to_dtype(dtype)?;
72
  let weights_path = downloads
73
  .huggingface_model(HF_REPO, "model.safetensors")
74
  .await?;
75
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
76
+ &weights_path,
77
+ &device,
78
+ dtype,
79
+ |vb| PPDocLayoutV3ForObjectDetection::load(vb, &config, &device),
80
+ )?;
81
 
82
  Ok(Self {
83
  model,
84
  config,
85
  preprocessor,
86
  device,
87
+ dtype,
88
  mean,
89
  std,
90
  })
 
122
  images,
123
  &self.preprocessor,
124
  &self.device,
125
+ self.dtype,
126
  &self.mean,
127
  &self.std,
128
  )?;
 
383
  images: &[DynamicImage],
384
  preprocessor: &PPDocLayoutV3PreprocessorConfig,
385
  device: &Device,
386
+ dtype: DType,
387
  mean: &Tensor,
388
  std: &Tensor,
389
  ) -> Result<Tensor> {
 
403
  let tensor = Tensor::from_vec(batch, (images.len(), target_h, target_w, 3), &Device::Cpu)?
404
  .to_device(device)?
405
  .permute((0, 3, 1, 2))?
406
+ .to_dtype(dtype)?;
407
  let tensor = if preprocessor.do_rescale {
408
  tensor.affine(preprocessor.rescale_factor as f64, 0.0)?
409
  } else {
 
424
  threshold: f32,
425
  include_polygons: bool,
426
  ) -> Result<Vec<LayoutDetectionResult>> {
427
+ let logits = outputs
428
+ .logits
429
+ .to_dtype(DType::F32)?
430
+ .to_device(&Device::Cpu)?;
431
+ let boxes = outputs
432
+ .pred_boxes
433
+ .to_dtype(DType::F32)?
434
+ .to_device(&Device::Cpu)?;
435
+ let order_logits = outputs
436
+ .order_logits
437
+ .to_dtype(DType::F32)?
438
+ .to_device(&Device::Cpu)?;
439
 
440
  let (batch_size, num_queries, num_classes) = logits.dims3()?;
441
  if batch_size != images.len() {
 
446
  let boxes = boxes.flatten_all()?.to_vec1::<f32>()?;
447
  let order_logits = order_logits.flatten_all()?.to_vec1::<f32>()?;
448
  let (masks, mask_h, mask_w) = if include_polygons {
449
+ let masks = outputs
450
+ .out_masks
451
+ .to_dtype(DType::F32)?
452
+ .to_device(&Device::Cpu)?;
453
  let (_, _, mask_h, mask_w) = masks.dims4()?;
454
  (Some(masks.flatten_all()?.to_vec1::<f32>()?), mask_h, mask_w)
455
  } else {
koharu-ml/src/pp_doclayout_v3/model.rs CHANGED
@@ -117,6 +117,15 @@ fn load_layer_norm(vb: VarBuilder, hidden_size: usize, eps: f64) -> Result<Layer
117
  Ok(layer_norm(hidden_size, eps, vb)?)
118
  }
119
 
 
 
 
 
 
 
 
 
 
120
  fn pad_bottom_right_one(xs: &Tensor) -> candle_core::Result<Tensor> {
121
  xs.pad_with_zeros(2, 0, 1)?.pad_with_zeros(3, 0, 1)
122
  }
@@ -693,7 +702,8 @@ impl PPDocLayoutV3SelfAttention {
693
  ) -> Result<Tensor> {
694
  let (batch_size, sequence_length, hidden_size) = hidden_states.dims3()?;
695
  let query_key_input = match position_embeddings {
696
- Some(position_embeddings) => hidden_states.broadcast_add(position_embeddings)?,
 
697
  None => hidden_states.clone(),
698
  };
699
 
@@ -726,9 +736,10 @@ impl PPDocLayoutV3SelfAttention {
726
  let mut attention_scores = query_states.matmul(&key_states_t)?;
727
  attention_scores = (attention_scores * self.scaling)?;
728
  if let Some(attention_mask) = attention_mask {
729
- attention_scores = attention_scores.broadcast_add(attention_mask)?;
 
730
  }
731
- let attention_probs = softmax(&attention_scores, D::Minus1)?;
732
  let context = attention_probs.matmul(&value_states)?;
733
  let context = context.transpose(1, 2)?.contiguous()?.reshape((
734
  batch_size,
@@ -1084,7 +1095,8 @@ impl PPDocLayoutV3AIFILayer {
1084
  let mut hidden = xs.flatten_from(2)?.transpose(1, 2)?;
1085
  let position_embedding = self
1086
  .position_embedding
1087
- .forward(width, height, xs.device())?;
 
1088
  for layer in &self.layers {
1089
  hidden = layer.forward(&hidden, Some(&position_embedding))?;
1090
  }
@@ -1486,7 +1498,8 @@ impl PPDocLayoutV3MultiscaleDeformableAttention {
1486
  spatial_shapes_list: &[(usize, usize)],
1487
  ) -> Result<Tensor> {
1488
  let hidden_states = match position_embeddings {
1489
- Some(position_embeddings) => hidden_states.broadcast_add(position_embeddings)?,
 
1490
  None => hidden_states.clone(),
1491
  };
1492
  let (batch_size, num_queries, _) = hidden_states.dims3()?;
@@ -1517,7 +1530,7 @@ impl PPDocLayoutV3MultiscaleDeformableAttention {
1517
  self.n_points,
1518
  2,
1519
  ))?;
1520
- let attention_weights = softmax(
1521
  &self.attention_weights.forward(&hidden_states)?.reshape((
1522
  batch_size,
1523
  num_queries,
@@ -1748,9 +1761,9 @@ impl PPDocLayoutV3Decoder {
1748
  )?;
1749
 
1750
  let predicted_corners = bbox_head.forward(&hidden_states)?;
1751
- reference_points = sigmoid(
1752
- &predicted_corners.broadcast_add(&inverse_sigmoid_tensor(&reference_points)?)?,
1753
- )?;
1754
 
1755
  let out_query = norm.forward(&hidden_states)?;
1756
  let mask_query_embed = mask_query_head.forward(&out_query)?;
@@ -1964,7 +1977,8 @@ impl PPDocLayoutV3ForObjectDetection {
1964
  let enc_out_masks =
1965
  batched_mask_projection(&mask_query_embed, &encoder_outputs.mask_feat)?;
1966
  reference_points_unact =
1967
- inverse_sigmoid_tensor(&mask_to_box_coordinate(&enc_out_masks, 0.0)?)?;
 
1968
  }
1969
 
1970
  let decoder_outputs = self.decoder.forward(
@@ -2097,8 +2111,12 @@ fn batched_mask_projection(mask_query_embed: &Tensor, mask_feat: &Tensor) -> Res
2097
  }
2098
 
2099
  fn inverse_sigmoid_tensor(tensor: &Tensor) -> Result<Tensor> {
 
2100
  let tensor = tensor.to_dtype(DType::F32)?.clamp(1e-5, 1.0 - 1e-5)?;
2101
- Ok(tensor.broadcast_div(&tensor.affine(-1.0, 1.0)?)?.log()?)
 
 
 
2102
  }
2103
 
2104
  fn mask_to_box_coordinate(mask_logits: &Tensor, threshold: f32) -> Result<Tensor> {
@@ -2199,6 +2217,9 @@ fn multi_scale_deformable_attention(
2199
  n_points: usize,
2200
  ) -> Result<Tensor> {
2201
  let (batch_size, sequence_length, num_heads, head_dim) = value.dims4()?;
 
 
 
2202
  let sampling_dims = sampling_offsets.dims().to_vec();
2203
  let (num_queries, num_levels, num_points) = match sampling_dims.as_slice() {
2204
  [_, queries, _, levels, points, _] => (*queries, *levels, *points),
 
117
  Ok(layer_norm(hidden_size, eps, vb)?)
118
  }
119
 
120
+ fn softmax_f32(xs: &Tensor, dim: D) -> Result<Tensor> {
121
+ let dtype = xs.dtype();
122
+ if dtype == DType::F32 {
123
+ Ok(softmax(xs, dim)?)
124
+ } else {
125
+ Ok(softmax(&xs.to_dtype(DType::F32)?, dim)?.to_dtype(dtype)?)
126
+ }
127
+ }
128
+
129
  fn pad_bottom_right_one(xs: &Tensor) -> candle_core::Result<Tensor> {
130
  xs.pad_with_zeros(2, 0, 1)?.pad_with_zeros(3, 0, 1)
131
  }
 
702
  ) -> Result<Tensor> {
703
  let (batch_size, sequence_length, hidden_size) = hidden_states.dims3()?;
704
  let query_key_input = match position_embeddings {
705
+ Some(position_embeddings) => hidden_states
706
+ .broadcast_add(&position_embeddings.to_dtype(hidden_states.dtype())?)?,
707
  None => hidden_states.clone(),
708
  };
709
 
 
736
  let mut attention_scores = query_states.matmul(&key_states_t)?;
737
  attention_scores = (attention_scores * self.scaling)?;
738
  if let Some(attention_mask) = attention_mask {
739
+ attention_scores = attention_scores
740
+ .broadcast_add(&attention_mask.to_dtype(attention_scores.dtype())?)?;
741
  }
742
+ let attention_probs = softmax_f32(&attention_scores, D::Minus1)?;
743
  let context = attention_probs.matmul(&value_states)?;
744
  let context = context.transpose(1, 2)?.contiguous()?.reshape((
745
  batch_size,
 
1095
  let mut hidden = xs.flatten_from(2)?.transpose(1, 2)?;
1096
  let position_embedding = self
1097
  .position_embedding
1098
+ .forward(width, height, xs.device())?
1099
+ .to_dtype(hidden.dtype())?;
1100
  for layer in &self.layers {
1101
  hidden = layer.forward(&hidden, Some(&position_embedding))?;
1102
  }
 
1498
  spatial_shapes_list: &[(usize, usize)],
1499
  ) -> Result<Tensor> {
1500
  let hidden_states = match position_embeddings {
1501
+ Some(position_embeddings) => hidden_states
1502
+ .broadcast_add(&position_embeddings.to_dtype(hidden_states.dtype())?)?,
1503
  None => hidden_states.clone(),
1504
  };
1505
  let (batch_size, num_queries, _) = hidden_states.dims3()?;
 
1530
  self.n_points,
1531
  2,
1532
  ))?;
1533
+ let attention_weights = softmax_f32(
1534
  &self.attention_weights.forward(&hidden_states)?.reshape((
1535
  batch_size,
1536
  num_queries,
 
1761
  )?;
1762
 
1763
  let predicted_corners = bbox_head.forward(&hidden_states)?;
1764
+ let reference_points_unact =
1765
+ inverse_sigmoid_tensor(&reference_points)?.to_dtype(predicted_corners.dtype())?;
1766
+ reference_points = sigmoid(&predicted_corners.broadcast_add(&reference_points_unact)?)?;
1767
 
1768
  let out_query = norm.forward(&hidden_states)?;
1769
  let mask_query_embed = mask_query_head.forward(&out_query)?;
 
1977
  let enc_out_masks =
1978
  batched_mask_projection(&mask_query_embed, &encoder_outputs.mask_feat)?;
1979
  reference_points_unact =
1980
+ inverse_sigmoid_tensor(&mask_to_box_coordinate(&enc_out_masks, 0.0)?)?
1981
+ .to_dtype(target.dtype())?;
1982
  }
1983
 
1984
  let decoder_outputs = self.decoder.forward(
 
2111
  }
2112
 
2113
  fn inverse_sigmoid_tensor(tensor: &Tensor) -> Result<Tensor> {
2114
+ let dtype = tensor.dtype();
2115
  let tensor = tensor.to_dtype(DType::F32)?.clamp(1e-5, 1.0 - 1e-5)?;
2116
+ Ok(tensor
2117
+ .broadcast_div(&tensor.affine(-1.0, 1.0)?)?
2118
+ .log()?
2119
+ .to_dtype(dtype)?)
2120
  }
2121
 
2122
  fn mask_to_box_coordinate(mask_logits: &Tensor, threshold: f32) -> Result<Tensor> {
 
2217
  n_points: usize,
2218
  ) -> Result<Tensor> {
2219
  let (batch_size, sequence_length, num_heads, head_dim) = value.dims4()?;
2220
+ let reference_points = reference_points.to_dtype(DType::F32)?;
2221
+ let sampling_offsets = sampling_offsets.to_dtype(DType::F32)?;
2222
+ let attention_weights = attention_weights.to_dtype(DType::F32)?;
2223
  let sampling_dims = sampling_offsets.dims().to_vec();
2224
  let (num_queries, num_levels, num_points) = match sampling_dims.as_slice() {
2225
  [_, queries, _, levels, points, _] => (*queries, *levels, *points),
koharu-ml/src/speech_bubble_segmentation/mod.rs CHANGED
@@ -45,6 +45,7 @@ pub struct SpeechBubbleSegmentation {
45
  model: YoloV8Seg,
46
  config: SpeechBubbleSegmentationConfig,
47
  device: Device,
 
48
  }
49
 
50
  #[derive(Debug, Clone)]
@@ -148,25 +149,32 @@ impl SpeechBubbleSegmentation {
148
  cpu: bool,
149
  ) -> Result<Self> {
150
  let device = device(cpu)?;
 
151
  let config = loading::read_json::<SpeechBubbleSegmentationConfig>(config_path.as_ref())
152
  .with_context(|| format!("failed to parse {}", config_path.as_ref().display()))?;
153
  config.validate()?;
154
  let multiples = variant_multiples(&config)?;
155
- let model = loading::load_mmaped_safetensors_path(weights_path.as_ref(), &device, |vb| {
156
- YoloV8Seg::load(
157
- vb,
158
- multiples,
159
- config.num_classes,
160
- config.num_masks,
161
- config.num_prototypes,
162
- config.reg_max,
163
- )
164
- })?;
 
 
 
 
 
165
 
166
  Ok(Self {
167
  model,
168
  config,
169
  device,
 
170
  })
171
  }
172
 
@@ -258,7 +266,7 @@ impl SpeechBubbleSegmentation {
258
  &self.device,
259
  )?
260
  .permute((0, 3, 1, 2))?
261
- .to_dtype(DType::F32)?;
262
  let pixel_values = (pixel_values * (1.0 / 255.0))?;
263
 
264
  Ok(PreparedInput {
@@ -306,8 +314,16 @@ fn postprocess(
306
  confidence_threshold: f32,
307
  nms_threshold: f32,
308
  ) -> Result<SpeechBubbleSegmentationResult> {
309
- let pred = outputs.pred.to_device(&Device::Cpu)?.i(0)?;
310
- let proto = outputs.proto.to_device(&Device::Cpu)?.i(0)?;
 
 
 
 
 
 
 
 
311
  let raw_regions =
312
  extract_regions(&pred, prepared, config, confidence_threshold, nms_threshold)?;
313
  let mut probability_map =
 
45
  model: YoloV8Seg,
46
  config: SpeechBubbleSegmentationConfig,
47
  device: Device,
48
+ dtype: DType,
49
  }
50
 
51
  #[derive(Debug, Clone)]
 
149
  cpu: bool,
150
  ) -> Result<Self> {
151
  let device = device(cpu)?;
152
+ let dtype = loading::model_dtype(&device);
153
  let config = loading::read_json::<SpeechBubbleSegmentationConfig>(config_path.as_ref())
154
  .with_context(|| format!("failed to parse {}", config_path.as_ref().display()))?;
155
  config.validate()?;
156
  let multiples = variant_multiples(&config)?;
157
+ let model = loading::load_mmaped_safetensors_path_with_dtype(
158
+ weights_path.as_ref(),
159
+ &device,
160
+ dtype,
161
+ |vb| {
162
+ YoloV8Seg::load(
163
+ vb,
164
+ multiples,
165
+ config.num_classes,
166
+ config.num_masks,
167
+ config.num_prototypes,
168
+ config.reg_max,
169
+ )
170
+ },
171
+ )?;
172
 
173
  Ok(Self {
174
  model,
175
  config,
176
  device,
177
+ dtype,
178
  })
179
  }
180
 
 
266
  &self.device,
267
  )?
268
  .permute((0, 3, 1, 2))?
269
+ .to_dtype(self.dtype)?;
270
  let pixel_values = (pixel_values * (1.0 / 255.0))?;
271
 
272
  Ok(PreparedInput {
 
314
  confidence_threshold: f32,
315
  nms_threshold: f32,
316
  ) -> Result<SpeechBubbleSegmentationResult> {
317
+ let pred = outputs
318
+ .pred
319
+ .to_dtype(DType::F32)?
320
+ .to_device(&Device::Cpu)?
321
+ .i(0)?;
322
+ let proto = outputs
323
+ .proto
324
+ .to_dtype(DType::F32)?
325
+ .to_device(&Device::Cpu)?
326
+ .i(0)?;
327
  let raw_regions =
328
  extract_regions(&pred, prepared, config, confidence_threshold, nms_threshold)?;
329
  let mut probability_map =
koharu-ml/src/speech_bubble_segmentation/model.rs CHANGED
@@ -1,4 +1,4 @@
1
- use candle_core::{D, DType, IndexOp, Result, Tensor};
2
  use candle_nn::{
3
  BatchNorm, Conv2d, Conv2dConfig, ConvTranspose2d, ConvTranspose2dConfig, Module, VarBuilder,
4
  batch_norm, conv_transpose2d, conv2d, conv2d_no_bias,
@@ -399,12 +399,13 @@ fn make_anchors(
399
  grid_cell_offset: f64,
400
  ) -> Result<(Tensor, Tensor)> {
401
  let device = xs0.device();
 
402
  let mut anchor_points = Vec::new();
403
  let mut stride_tensors = Vec::new();
404
  for (xs, stride) in [(xs0, strides.0), (xs1, strides.1), (xs2, strides.2)] {
405
  let (_, _, h, w) = xs.dims4()?;
406
- let sx = (Tensor::arange(0, w as u32, device)?.to_dtype(DType::F32)? + grid_cell_offset)?;
407
- let sy = (Tensor::arange(0, h as u32, device)?.to_dtype(DType::F32)? + grid_cell_offset)?;
408
  let sx = sx
409
  .reshape((1, sx.elem_count()))?
410
  .repeat((h, 1))?
@@ -414,7 +415,7 @@ fn make_anchors(
414
  .repeat((1, w))?
415
  .flatten_all()?;
416
  anchor_points.push(Tensor::stack(&[&sx, &sy], D::Minus1)?);
417
- stride_tensors.push((Tensor::ones(h * w, DType::F32, device)? * stride as f64)?);
418
  }
419
  let anchor_points = Tensor::cat(anchor_points.as_slice(), 0)?;
420
  let stride_tensor = Tensor::cat(stride_tensors.as_slice(), 0)?.unsqueeze(1)?;
 
1
+ use candle_core::{D, IndexOp, Result, Tensor};
2
  use candle_nn::{
3
  BatchNorm, Conv2d, Conv2dConfig, ConvTranspose2d, ConvTranspose2dConfig, Module, VarBuilder,
4
  batch_norm, conv_transpose2d, conv2d, conv2d_no_bias,
 
399
  grid_cell_offset: f64,
400
  ) -> Result<(Tensor, Tensor)> {
401
  let device = xs0.device();
402
+ let dtype = xs0.dtype();
403
  let mut anchor_points = Vec::new();
404
  let mut stride_tensors = Vec::new();
405
  for (xs, stride) in [(xs0, strides.0), (xs1, strides.1), (xs2, strides.2)] {
406
  let (_, _, h, w) = xs.dims4()?;
407
+ let sx = (Tensor::arange(0, w as u32, device)?.to_dtype(dtype)? + grid_cell_offset)?;
408
+ let sy = (Tensor::arange(0, h as u32, device)?.to_dtype(dtype)? + grid_cell_offset)?;
409
  let sx = sx
410
  .reshape((1, sx.elem_count()))?
411
  .repeat((h, 1))?
 
415
  .repeat((1, w))?
416
  .flatten_all()?;
417
  anchor_points.push(Tensor::stack(&[&sx, &sy], D::Minus1)?);
418
+ stride_tensors.push((Tensor::ones(h * w, dtype, device)? * stride as f64)?);
419
  }
420
  let anchor_points = Tensor::cat(anchor_points.as_slice(), 0)?;
421
  let stride_tensor = Tensor::cat(stride_tensors.as_slice(), 0)?.unsqueeze(1)?;