Skip to content

Commit ab7086d

Browse files
Merge pull request #46 from DeepKnowledge1/develop
feat(inference): add backend-specific warm-up support - Implemented warmup() in each backend: • TorchBackend: runs dummy or provided tensor through model with AMP/cuDNN • TorchScriptBackend: runs dummy or provided tensor on-device a few times • OnnxBackend: generates dummy numpy input matching model metadata and executes • OpenVinoBackend: creates infer request and runs with dummy or provided input • TensorRTBackend: added stub (warns and skips, since not implemented) - Added ModelWrapper.warmup() to delegate to backend if supported - Updated detect.py to run warm-up with first dataloader batch before inference loop Warm-up stabilizes first-batch latency, preloads kernels, and reduces variance, especially on CUDA backends (Torch/TorchScript/ONNX).
2 parents f0d8b58 + e7040fd commit ab7086d

8 files changed

Lines changed: 134 additions & 19 deletions

File tree

anodet/inference/model/backends/onnx_backend.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,22 @@ def predict(self, batch: Batch) -> ScoresMaps:
7070
def close(self) -> None:
7171
"""Release the ONNX session."""
7272
self.session = None
73+
74+
75+
def warmup(self, batch, runs: int = 2) -> None:
76+
"""
77+
Warm up ONNX Runtime by running the session a few times.
78+
Args:
79+
batch: input batch to use for warm-up.
80+
runs: Number of warm-up runs to perform.
81+
"""
82+
if isinstance(batch, np.ndarray):
83+
input_arr = batch
84+
else:
85+
input_arr = batch.detach().cpu().numpy()
86+
87+
feeds = {self.input_names[0]: input_arr}
88+
for _ in range(max(1, runs)):
89+
_ = self.session.run(self.output_names, feeds)
90+
91+
logger.info("OnnxBackend warm-up completed (runs=%d, shape=%s).", runs, tuple(input_arr.shape))

anodet/inference/model/backends/openvino_backend.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,23 @@ def close(self) -> None:
9191
self.compiled_model = None
9292
self.model = None
9393
self.core = None
94+
95+
def warmup(self, batch=None, runs: int = 2) -> None:
96+
"""
97+
Warm up OpenVINO by creating an infer request and calling infer repeatedly.
98+
Args:
99+
batch: input batch to use for warm-up.
100+
runs: Number of warm-up runs to perform.
101+
"""
102+
103+
if isinstance(batch, np.ndarray):
104+
input_arr = batch
105+
else:
106+
input_arr = batch.detach().cpu().numpy()
107+
108+
infer_request = self.compiled_model.create_infer_request()
109+
110+
for _ in range(max(1, runs)):
111+
_ = infer_request.infer({self.input_layer.any_name: input_arr})
112+
113+
logger.info("OpenVinoBackend warm-up completed (runs=%d, shape=%s).", runs, tuple(input_arr.shape))

anodet/inference/model/backends/tensorrt_backend.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ def predict(self, batch: Batch) -> ScoresMaps:
2424

2525
def close(self) -> None:
2626
pass
27+
28+
def warmup(self, batch=None, runs: int = 2) -> None:
29+
logger.warning("TensorRT backend is not implemented; warm-up skipped.")
30+
raise NotImplementedError("TensorRT warm-up not implemented yet.")

anodet/inference/model/backends/torch_backend.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .base import Batch, ScoresMaps, InferenceBackend
1414
from anodet.utils import get_logger
1515

16+
1617
logger = get_logger(__name__)
1718

1819

@@ -77,3 +78,34 @@ def predict(self, batch: Batch) -> ScoresMaps:
7778
def close(self) -> None:
7879
"""Release PyTorch model."""
7980
self.model = None
81+
82+
def warmup(self, batch, runs: int = 2) -> None:
83+
"""
84+
Warm up PyTorch/TorchScript-on-PyTorch backend.
85+
Uses AMP on CUDA if enabled, and executes the model a few times.
86+
Args:
87+
batch: input batch to use for warm-up.
88+
runs: Number of warm-up runs to perform.
89+
90+
"""
91+
import torch
92+
from contextlib import nullcontext
93+
94+
if not isinstance(batch, torch.Tensor):
95+
batch = torch.as_tensor(batch, dtype=torch.float32, device=self.device)
96+
else:
97+
batch = batch.to(self.device, non_blocking=True)
98+
99+
# Avoid autograd + use AMP if configured
100+
autocast_ctx = (
101+
torch.autocast(device_type=self.device.type, dtype=torch.float16)
102+
if self.use_amp and self.device.type == "cuda"
103+
else nullcontext()
104+
)
105+
106+
with torch.inference_mode(), autocast_ctx:
107+
for _ in range(max(1, runs)):
108+
# Prefer model.predict to match your runtime path
109+
_ = self.model.predict(batch)
110+
111+
logger.info("TorchBackend warm-up completed (runs=%d, shape=%s).", runs, tuple(batch.shape))

anodet/inference/model/backends/torchscript_backend.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,23 @@ def close(self) -> None:
7171
self.model = None
7272
if self.device.type == "cuda":
7373
torch.cuda.empty_cache()
74+
75+
def warmup(self, batch=None, runs: int = 2) -> None:
76+
"""
77+
Warm up TorchScript backend by forwarding a few times on-device.
78+
Args:
79+
batch: input batch to use for warm-up.
80+
runs: Number of warm-up runs to perform.
81+
82+
"""
83+
84+
if isinstance(batch, torch.Tensor):
85+
batch = batch.to(self.device, non_blocking=True)
86+
else:
87+
batch = torch.as_tensor(batch, dtype=torch.float32, device=self.device)
88+
89+
with torch.no_grad():
90+
for _ in range(max(1, runs)):
91+
_ = self.model(batch)
92+
93+
logger.info("TorchScriptBackend warm-up completed (runs=%d, shape=%s).", runs, tuple(batch.shape))

anodet/inference/model/wrapper.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,9 @@ def close(self) -> None:
9595
"""Release resources associated with the backend."""
9696
logger.info("Closing ModelWrapper and releasing resources")
9797
self.backend.close()
98+
99+
def warmup(self, batch=None, runs: int = 2) -> None:
100+
if hasattr(self.backend, "warmup"):
101+
return self.backend.warmup(batch=batch, runs=runs)
102+
else:
103+
logger.info(f"{self.backend.__class__.__name__} does not support warm-up. Skipping.")

detect.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import numpy as np
1515
import torch
1616
from torch.utils.data import DataLoader
17+
import matplotlib
18+
matplotlib.use("Agg") # non-interactive, faster PNG writing
1719
import matplotlib.pyplot as plt
20+
1821
import argparse
1922
import time
2023
from datetime import datetime
@@ -46,7 +49,7 @@ def parse_args():
4649
parser.add_argument(
4750
"--model",
4851
type=str,
49-
default="padim_model.onnx",
52+
default="padim_model.pt",
5053
help="Model file (.pt for PyTorch, .onnx for ONNX, .engine for TensorRT)",
5154
)
5255
parser.add_argument(
@@ -85,7 +88,7 @@ def parse_args():
8588
)
8689
parser.add_argument(
8790
"--save_visualizations",
88-
action="store_false",
91+
action="store_true",
8992
help="Save visualization images to disk.",
9093
)
9194
parser.add_argument(
@@ -108,12 +111,6 @@ def parse_args():
108111
)
109112

110113

111-
parser.add_argument(
112-
"--show_first_batch_only",
113-
action="store_true",
114-
default=True,
115-
help="Show visualization only for the first batch.",
116-
)
117114
parser.add_argument(
118115
"--viz_alpha", type=float, default=0.5, help="Alpha value for heatmap overlay."
119116
)
@@ -231,6 +228,22 @@ def main(args):
231228

232229
logger.info(f"Processing {len(test_dataset)} images using AnomaVision {model_type.value.upper()}")
233230

231+
# ---- Warm-up
232+
try:
233+
first = next(iter(test_dataloader)) # (batch, images, _, _)
234+
first_batch = first[0].to(device_str)
235+
model.warmup(batch=first_batch, runs=2)
236+
logger.info("AnomaVision warm-up done with first batch %s.", tuple(first_batch.shape))
237+
except StopIteration:
238+
logger.warning("Dataset empty; skipping warm-up.")
239+
except Exception as e:
240+
logger.warning(f"Warm-up skipped due to error: {e}")
241+
242+
# ---- End warm-up
243+
244+
245+
246+
234247
# AnomaVision batch processing pipeline
235248
batch_count = 0
236249
try:
@@ -306,36 +319,37 @@ def main(args):
306319
if args.save_visualizations:
307320
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
308321

309-
# Display AnomaVision results for first batch only (if requested)
310-
if batch_idx == 0 and (not args.show_first_batch_only or batch_idx == 0):
322+
# Display AnomaVision results
323+
for img_id in range(len(images)):
311324
try:
312325
fig, axs = plt.subplots(1, 4, figsize=(16, 8))
313-
fig.suptitle(f"AnomaVision Detection Results - Batch {batch_idx + 1}", fontsize=14)
326+
fig.suptitle(f"AnomaVision Detection Results - Batch {img_id + 1}", fontsize=14)
314327

315-
axs[0].imshow(images[0])
328+
axs[0].imshow(images[img_id])
316329
axs[0].set_title("Original Image")
317330
axs[0].axis("off")
318331

319-
axs[1].imshow(boundary_images[0])
332+
axs[1].imshow(boundary_images[img_id])
320333
axs[1].set_title("AnomaVision Boundary Detection")
321334
axs[1].axis("off")
322335

323-
axs[2].imshow(heatmap_images[0])
336+
axs[2].imshow(heatmap_images[img_id])
324337
axs[2].set_title("AnomaVision Anomaly Heatmap")
325338
axs[2].axis("off")
326339

327-
axs[3].imshow(highlighted_images[0])
340+
axs[3].imshow(highlighted_images[img_id])
328341
axs[3].set_title("AnomaVision Highlighted Anomalies")
329342
axs[3].axis("off")
330343

331-
plt.tight_layout()
344+
# plt.tight_layout()
332345

333346
if args.save_visualizations:
334347
combined_filepath = os.path.join(RESULTS_PATH, f"anomavision_batch_{batch_idx}_{timestamp}.png")
335-
plt.savefig(combined_filepath, dpi=300, bbox_inches="tight")
348+
plt.savefig(combined_filepath, dpi=100, bbox_inches="tight")
336349
logger.info(f"AnomaVision visualization saved: {combined_filepath}")
337350

338-
plt.show()
351+
# plt.show()
352+
plt.close(fig)
339353

340354
except Exception as e:
341355
logger.warning(f"Failed to display AnomaVision visualization for batch {batch_idx}: {e}")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "AnomaVision"
3-
version = "2.0.30"
3+
version = "2.0.37"
44
description = "Deep learnIng Anomaly Detection EnvironMent [AnomaVision] is a deep learning library that aims to collect state-of-the-art anomaly detection algorithms for benchmarking on both public and private datasets. PaDimOpti provides several ready-to-use implementations of anomaly detection algorithms described in the recent literature, as well as a set of tools that facilitate the development and implementation of custom models. The library has a strong focus on image-based anomaly detection, where the goal of the algorithm is to identify anomalous images, or anomalous pixel regions within images in a dataset. PaDimOpti is constantly being updated with new algorithms and training/inference extensions, so stay tuned!!"
55
authors = ["Deep Knowledge <Deepp.Knowledge@gmail.com>"]
66
readme = "README.md"

0 commit comments

Comments
 (0)