Files
wehub-resource-sync 16031aae96
CPU tests Workflow / Testing (ubuntu-latest, 3.12) (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.13) (push) Failing after 0s
Mypy Type Check / Type Check (push) Failing after 0s
Docs/Test WorkFlow / Test docs build (push) Failing after 1s
PR Conflict Labeler / labeling (push) Failing after 1s
Dependency resolution / Resolve [tflite] extra — Python 3.12 (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.10) (push) Failing after 0s
Smoke Tests / try-all-models (ubuntu-latest, 3.13) (push) Failing after 1s
CPU tests Workflow / build-pkg (push) Failing after 1s
CPU tests Workflow / Testing (ubuntu-latest, 3.10) (push) Failing after 0s
CPU tests Workflow / Testing (ubuntu-latest, 3.11) (push) Failing after 0s
Smoke Tests / try-all-models (macos-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (macos-latest, 3.13) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.10) (push) Has been cancelled
Smoke Tests / try-all-models (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (macos-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.10) (push) Has been cancelled
CPU tests Workflow / Testing (windows-latest, 3.13) (push) Has been cancelled
CPU tests Workflow / testing-guardian (push) Has been cancelled
GPU tests Workflow / Testing (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:26:24 +08:00

137 lines
5.3 KiB
Python

# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Tests that unoptimized inference always runs the module in eval mode."""
from types import SimpleNamespace
import PIL.Image
import pytest
import torch
from rfdetr import detr as detr_module
from .helpers import _BaseFakeRFDETR
class _FakeModelWithDropout(torch.nn.Module):
"""Minimal module whose behavior differs between train and eval mode."""
def __init__(self) -> None:
super().__init__()
self.dropout = torch.nn.Dropout(p=0.5)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Pass input through dropout, active only in train mode."""
return self.dropout(x)
class _FakeModelContext:
"""Minimal model context supplying the attributes predict() and train() need."""
def __init__(self) -> None:
self.device = torch.device("cpu")
self.resolution = 28
self.model = _FakeModelWithDropout()
self.inference_model = None
class _FakeRFDETR(_BaseFakeRFDETR):
"""Concrete test double: provides a dropout-bearing model for eval-mode tests."""
def get_model(self, config: SimpleNamespace) -> _FakeModelContext:
"""Return a minimal model context with a dropout-bearing module."""
return _FakeModelContext()
class TestUnoptimizedInferenceEvalMode:
"""`_ensure_eval_mode_for_unoptimized_inference` must keep the module in eval mode."""
def test_eval_mode_reasserted_after_train_round_trip(self) -> None:
"""Eval mode must be applied to whatever self.model.model currently points to.
``train()`` rebinds ``self.model.model`` to a brand-new module left in training mode, so eval must be re-applied
to the *current* object on every call — not to a cached reference captured at init.
"""
rfdetr = _FakeRFDETR()
# First inference call: warns once and switches to eval mode.
rfdetr._ensure_eval_mode_for_unoptimized_inference()
assert rfdetr.model.model.training is False
# Simulate train() rebinding self.model.model to a fresh training-mode module.
rfdetr.model.model = _FakeModelWithDropout()
assert rfdetr.model.model.training is True # new object starts in train mode
# Every subsequent inference call must re-assert eval on the *new* object.
rfdetr._ensure_eval_mode_for_unoptimized_inference()
assert rfdetr.model.model.training is False
def test_optimized_model_skips_eval_assertion(self) -> None:
"""When _is_optimized_for_inference is True, the method must be a no-op.
The compiled inference_model snapshot is already in eval mode; calling eval() on the stale self.model.model
would target the wrong object.
"""
rfdetr = _FakeRFDETR()
rfdetr._is_optimized_for_inference = True
rfdetr.model.model.train()
assert rfdetr.model.model.training is True # confirm starting state
rfdetr._ensure_eval_mode_for_unoptimized_inference()
assert rfdetr.model.model.training is True # must remain unchanged
def test_not_optimized_warning_emitted_only_once(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""The not-optimized warning is logged at most once across repeated calls."""
warnings: list[str] = []
monkeypatch.setattr(detr_module.logger, "warning", lambda msg, *a, **k: warnings.append(msg))
rfdetr = _FakeRFDETR()
rfdetr._ensure_eval_mode_for_unoptimized_inference()
rfdetr.model.model.train()
rfdetr._ensure_eval_mode_for_unoptimized_inference()
rfdetr._ensure_eval_mode_for_unoptimized_inference()
assert len(warnings) == 1
def test_eval_mode_applied_on_every_call(self) -> None:
"""Eval() must run on every call, not just when the warning fires.
Simulate the code path where the warning has already been emitted
(``_has_warned_about_not_being_optimized_for_inference=True``) and verify
that ``eval()`` is still applied to the current module.
"""
rfdetr = _FakeRFDETR()
rfdetr._has_warned_about_not_being_optimized_for_inference = True
rfdetr.model.model.train()
rfdetr._ensure_eval_mode_for_unoptimized_inference()
assert rfdetr.model.model.training is False
def test_predict_puts_module_in_eval_mode(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Predict() must delegate to _ensure_eval_mode_for_unoptimized_inference, leaving module in eval mode."""
rfdetr = _FakeRFDETR()
img = PIL.Image.new("RGB", (640, 640), color=(128, 128, 128))
monkeypatch.setattr(
rfdetr.model.model,
"forward",
lambda batch: {"pred_logits": torch.zeros(1, 10, 81), "pred_boxes": torch.zeros(1, 10, 4)},
)
monkeypatch.setattr(
rfdetr.model,
"postprocess",
lambda preds, target_sizes: [
{"scores": torch.zeros(0), "labels": torch.zeros(0, dtype=torch.long), "boxes": torch.zeros(0, 4)}
],
raising=False,
)
rfdetr.predict(img)
assert rfdetr.model.model.training is False