Mayo commited on
perf: prefer bf16
Browse files- koharu-ml/src/aot_inpainting/mod.rs +15 -6
- koharu-ml/src/aot_inpainting/model.rs +19 -13
- koharu-ml/src/comic_text_bubble_detector/mod.rs +25 -9
- koharu-ml/src/comic_text_bubble_detector/model.rs +31 -13
- koharu-ml/src/comic_text_detector/mod.rs +32 -14
- koharu-ml/src/comic_text_detector/yolo_v5.rs +4 -3
- koharu-ml/src/flux2_klein/mod.rs +24 -12
- koharu-ml/src/flux2_klein/vae.rs +5 -1
- koharu-ml/src/font_detector/mod.rs +17 -5
- koharu-ml/src/font_detector/models.rs +3 -2
- koharu-ml/src/loading.rs +36 -2
- koharu-ml/src/manga_ocr/bert.rs +10 -1
- koharu-ml/src/manga_ocr/mod.rs +46 -47
- koharu-ml/src/manga_ocr/model.rs +1 -1
- koharu-ml/src/manga_text_segmentation_2025/mod.rs +9 -5
- koharu-ml/src/mit48px_ocr/mod.rs +15 -5
- koharu-ml/src/mit48px_ocr/model.rs +17 -5
- koharu-ml/src/pp_doclayout_v3/mod.rs +32 -12
- koharu-ml/src/pp_doclayout_v3/model.rs +32 -11
- koharu-ml/src/speech_bubble_segmentation/mod.rs +29 -13
- koharu-ml/src/speech_bubble_segmentation/model.rs +5 -4
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::
|
| 120 |
-
|
| 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(
|
| 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(
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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)?
|
|
|
|
|
|
|
| 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)?
|
| 360 |
-
|
| 361 |
-
in_channels,
|
| 362 |
-
|
| 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)?
|
| 386 |
-
|
| 387 |
-
out_channels,
|
| 388 |
-
|
| 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::
|
| 68 |
-
|
| 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(
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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 =
|
| 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
|
| 989 |
-
|
| 990 |
-
height,
|
| 991 |
-
|
| 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
|
|
|
|
| 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 =
|
| 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 |
-
|
| 1350 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 85 |
-
|
| 86 |
-
|
|
|
|
| 87 |
let unet_path = downloads
|
| 88 |
.huggingface_model(HF_REPO, "unet.safetensors")
|
| 89 |
.await?;
|
| 90 |
-
let unet = loading::
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
let dbnet = if load_dbnet {
|
| 92 |
let dbnet_path = downloads
|
| 93 |
.huggingface_model(HF_REPO, "dbnet.safetensors")
|
| 94 |
.await?;
|
| 95 |
-
Some(loading::
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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::{
|
| 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
|
| 342 |
-
let
|
|
|
|
| 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 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 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 =
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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::
|
| 58 |
-
|
| 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)?
|
|
|
|
|
|
|
| 88 |
let preprocess_elapsed = preprocess_started.elapsed();
|
| 89 |
|
| 90 |
let forward_started = Instant::now();
|
| 91 |
-
let logits = self
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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::{
|
| 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 =
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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 =
|
| 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::
|
| 53 |
-
|
| 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
|
| 71 |
-
|
| 72 |
-
self.preprocessor.
|
| 73 |
-
&self.preprocessor.
|
| 74 |
-
|
| 75 |
-
self.preprocessor.
|
| 76 |
-
self.
|
| 77 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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
|
|
|
|
| 48 |
path.as_ref(),
|
| 49 |
&device,
|
|
|
|
| 50 |
model::MangaTextSegmentationModel::load,
|
| 51 |
)?;
|
| 52 |
-
let mean =
|
| 53 |
-
|
| 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(
|
| 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,
|
| 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 |
-
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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),
|
| 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
|
|
|
|
|
|
|
| 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 =
|
| 67 |
-
.to_dtype(
|
| 68 |
-
let std =
|
| 69 |
-
.to_dtype(
|
| 70 |
let weights_path = downloads
|
| 71 |
.huggingface_model(HF_REPO, "model.safetensors")
|
| 72 |
.await?;
|
| 73 |
-
let model = loading::
|
| 74 |
-
|
| 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(
|
| 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
|
| 420 |
-
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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
|
|
|
|
| 730 |
}
|
| 731 |
-
let attention_probs =
|
| 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
|
|
|
|
| 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 =
|
| 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 |
-
|
| 1752 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
| 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::
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 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(
|
| 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
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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(
|
| 407 |
-
let sy = (Tensor::arange(0, h as u32, device)?.to_dtype(
|
| 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,
|
| 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)?;
|