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

229 lines
7.9 KiB
Python

# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
import pytest
import torch
from rfdetr.detr import RFDETR
from rfdetr.training import auto_batch
from rfdetr.training.auto_batch import AutoBatchResult
class _TinyModule(torch.nn.Module):
def __init__(self):
super().__init__()
self.w = torch.nn.Parameter(torch.ones(1))
def test_recommend_grad_accum_steps_rounds_up():
assert auto_batch.recommend_grad_accum_steps(3, 16) == 6
def test_probe_max_micro_batch_uses_exponential_then_binary_search():
model = _TinyModule()
criterion = _TinyModule()
threshold = 7
def _fake_probe(*args, **kwargs):
micro_batch_size = args[2]
return micro_batch_size <= threshold
with (
patch("rfdetr.training.auto_batch._probe_step", side_effect=_fake_probe),
patch("rfdetr.training.auto_batch.torch.cuda.empty_cache"),
):
safe = auto_batch.probe_max_micro_batch(
model=model,
criterion=criterion,
resolution=64,
device=torch.device("cuda"),
num_classes=5,
amp=False,
safety_margin=1.0,
max_micro_batch=32,
)
assert safe == threshold
def test_probe_max_micro_batch_raises_if_one_is_not_safe():
model = _TinyModule()
criterion = _TinyModule()
with (
patch("rfdetr.training.auto_batch._probe_step", return_value=False),
patch("rfdetr.training.auto_batch.torch.cuda.empty_cache"),
pytest.raises(RuntimeError, match="micro_batch_size=1"),
):
auto_batch.probe_max_micro_batch(
model=model,
criterion=criterion,
resolution=64,
device=torch.device("cuda"),
num_classes=5,
amp=False,
)
def test_probe_step_raises_when_loss_keys_do_not_overlap_weight_keys():
"""_probe_step must fail fast when weighted loss would be empty."""
class _DummyCriterion(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.weight_dict = {"loss_bbox": 1.0}
def forward(self, outputs, targets):
return {"loss_ce": torch.tensor(1.0)}
class _DummyModel(torch.nn.Module):
def forward(self, samples, targets):
return {}
model = _DummyModel()
criterion = _DummyCriterion()
with (
patch(
"rfdetr.training.auto_batch._make_synthetic_batch",
return_value=(MagicMock(), []),
),
pytest.raises(RuntimeError, match="no overlap between criterion loss_dict and weight_dict keys"),
):
auto_batch._probe_step(
model=model,
criterion=criterion,
micro_batch_size=1,
resolution=64,
device=torch.device("cpu"),
num_classes=5,
amp=False,
)
def test_resolve_auto_batch_config_requires_cuda():
model_context = SimpleNamespace(device=torch.device("cpu"), model=MagicMock())
model_config = SimpleNamespace(resolution=64, num_classes=5, amp=False, segmentation_head=False)
train_config = SimpleNamespace(batch_size="auto", auto_batch_target_effective=16)
with (
patch("rfdetr.training.auto_batch.torch.cuda.is_available", return_value=False),
pytest.raises(RuntimeError, match="requires a CUDA device"),
):
auto_batch.resolve_auto_batch_config(model_context, model_config, train_config)
def test_resolve_auto_batch_config_returns_expected_values():
model_context = SimpleNamespace(device=torch.device("cuda"), model=MagicMock())
model_config = SimpleNamespace(resolution=64, num_classes=5, amp=False, segmentation_head=True)
train_config = SimpleNamespace(batch_size="auto", auto_batch_target_effective=16)
criterion = MagicMock()
criterion.to.return_value = criterion
with (
patch("rfdetr.training.auto_batch.torch.cuda.is_available", return_value=True),
patch("rfdetr.training.auto_batch.build_criterion_from_config", return_value=(criterion, None)),
patch("rfdetr.training.auto_batch.probe_max_micro_batch", return_value=5),
patch("rfdetr.training.auto_batch.torch.cuda.get_device_name", return_value="Fake GPU"),
):
result = auto_batch.resolve_auto_batch_config(model_context, model_config, train_config)
assert isinstance(result, AutoBatchResult)
assert result.safe_micro_batch == 5
assert result.recommended_grad_accum_steps == 4
assert result.effective_batch_size == 20
assert result.device_name == "Fake GPU"
@patch("rfdetr.detr.is_main_process", return_value=False)
@patch("rfdetr.training.auto_batch.resolve_auto_batch_config")
@patch("rfdetr.training.build_trainer")
@patch("rfdetr.training.RFDETRDataModule")
@patch("rfdetr.training.RFDETRModelModule")
@patch("rfdetr.detr._move_model_context_to_device")
def test_train_auto_batch_ensures_model_on_device_before_resolve(
mock_move: MagicMock,
_mock_module: MagicMock,
_mock_data_module: MagicMock,
_mock_build_trainer: MagicMock,
mock_resolve: MagicMock,
_mock_is_main: MagicMock,
) -> None:
"""Model weights must be moved before resolve_auto_batch_config when batch_size='auto'."""
auto_result = SimpleNamespace(safe_micro_batch=4, recommended_grad_accum_steps=1, effective_batch_size=4)
call_order: list[str] = []
def _move_side_effect(model: object) -> None:
call_order.append("ensure")
def _resolve_side_effect(**_kwargs: object) -> object:
call_order.append("resolve")
return auto_result
mock_move.side_effect = _move_side_effect
mock_resolve.side_effect = _resolve_side_effect
train_config = SimpleNamespace(
batch_size="auto",
grad_accum_steps=99,
dataset_dir=None,
resume=None,
class_names=None,
save_dataset_grids=False,
)
mock_self = MagicMock()
mock_self.model_config = SimpleNamespace(model_name=None)
mock_self.get_train_config.return_value = train_config
RFDETR.train(mock_self)
assert train_config.batch_size == 4
assert train_config.grad_accum_steps == 1
mock_move.assert_called_once_with(mock_self.model)
mock_resolve.assert_called_once_with(
model_context=mock_self.model,
model_config=mock_self.model_config,
train_config=train_config,
)
assert call_order == ["ensure", "resolve"]
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required for segmentation probe")
def test_probe_step_with_real_segmentation_criterion(tmp_path):
"""Run one probe step with real segmentation model and criterion so loss_masks and t['masks'] are exercised."""
from rfdetr._namespace import _namespace_from_configs
from rfdetr.config import RFDETRSegNanoConfig, SegmentationTrainConfig
from rfdetr.models.lwdetr import build_criterion_and_postprocessors, build_model
mc = RFDETRSegNanoConfig(pretrain_weights=None, device="cuda", num_classes=2)
tc = SegmentationTrainConfig(
dataset_dir=str(tmp_path / "ds"),
output_dir=str(tmp_path / "out"),
batch_size=2,
grad_accum_steps=1,
tensorboard=False,
)
args = _namespace_from_configs(mc, tc)
model = build_model(args)
criterion, _ = build_criterion_and_postprocessors(args)
device = torch.device("cuda")
model = model.to(device)
criterion = criterion.to(device)
ok = auto_batch._probe_step(
model=model,
criterion=criterion,
micro_batch_size=1,
resolution=mc.resolution,
device=device,
num_classes=mc.num_classes,
amp=False,
segmentation_head=True,
)
assert ok is True