Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ rf-detr/

## Development Environment Setup

RF-DETR uses **`uv`** as the package manager for dependency management. Ensure you have Python >=3.10 installed (supports 3.10, 3.11, 3.12, 3.13).
RF-DETR uses **`uv`** as the package manager for dependency management. Ensure you have Python >=3.10 installed (supports 3.10, 3.11, 3.12, 3.13, 3.14).

### Installing uv

Expand Down Expand Up @@ -282,7 +282,7 @@ Tests marked with `@pytest.mark.gpu` are excluded from CPU CI workflows and run
Our continuous integration tests run on:

- **Operating Systems:** Ubuntu, Windows, macOS
- **Python Versions:** 3.10, 3.11, 3.12, 3.13
- **Python Versions:** 3.10, 3.11, 3.12, 3.13, 3.14
- **CPU Workflow:** `pytest -m "not gpu"` - Runs on all OS/Python combinations
Comment on lines +285 to 286
- **GPU Workflow:** `pytest -m gpu` - Runs separately on GPU infrastructure

Expand All @@ -292,7 +292,7 @@ This ensures your changes work across all supported platforms and Python version

**Key GitHub Actions workflow files** (in `.github/workflows/`):

- **ci-tests-cpu.yml** — CPU tests across Ubuntu/Windows/macOS × Python 3.10–3.13
- **ci-tests-cpu.yml** — CPU tests on Ubuntu across Python 3.10–3.14, plus Windows and macOS boundary versions
- **ci-tests-gpu.yml** — GPU-dependent tests
- **ci-legacy-checkpoints.yml** — Backward-compatibility checkpoint-loading tests across historical rfdetr releases (advisory only — not a required check; a compat break does not block merge)
- **build-package.yml** — Build and validate distributions (`uv build` + `twine check`)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-tests-cpu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
include:
- { os: "windows-latest", python-version: "3.10" }
- { os: "macos-latest", python-version: "3.10" }
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pip install uv
uv sync --all-groups
```

**Prerequisites:** Python >=3.10 (tested on 3.10-3.13)
**Prerequisites:** Python >=3.10 (tested on 3.10-3.14)

### Dependency Information

Expand Down Expand Up @@ -336,7 +336,7 @@ result = subprocess.run(

GitHub Actions workflows in `.github/workflows/`:

- **ci-tests-cpu.yml:** CPU tests across OS/Python versions
- **ci-tests-cpu.yml:** CPU tests on Linux across Python 3.10-3.14, plus Windows and macOS boundary versions
- **ci-tests-gpu.yml:** GPU-dependent tests
- **ci-legacy-checkpoints.yml:** Backward-compatibility checkpoint-loading tests across historical rfdetr releases (advisory only — not a required check; a compat break does not block merge)
- **ci-deps-resolution.yml:** Dependency resolution (`uv lock`) plus an install-plan check (`uv sync --dry-run`) for every extra on every Python interpreter allowed by requires-python (3.10-3.14). Resolution alone does not prove a pinned version ships a wheel for the interpreter in use. The `list-extras` job derives the checked set from every `[project.optional-dependencies]` extra, so a new extra is covered automatically
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Scientific/Engineering",
Expand Down
19 changes: 11 additions & 8 deletions src/rfdetr/models/criterion.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from __future__ import annotations

import sys
from collections.abc import Callable
from typing import Any, NamedTuple

Expand Down Expand Up @@ -312,7 +313,7 @@ def dice_loss(
return result


dice_loss_jit = torch.jit.script(dice_loss) # type: torch.jit.ScriptFunction[Any, Any]
dice_loss_jit = dice_loss if sys.version_info >= (3, 14) else torch.jit.script(dice_loss)


def sigmoid_ce_loss(
Expand All @@ -336,7 +337,7 @@ def sigmoid_ce_loss(
return loss.mean(1).sum() / num_masks


sigmoid_ce_loss_jit = torch.jit.script(sigmoid_ce_loss) # type: torch.jit.ScriptFunction[Any, Any]
sigmoid_ce_loss_jit = sigmoid_ce_loss if sys.version_info >= (3, 14) else torch.jit.script(sigmoid_ce_loss)


class SetCriterion(nn.Module):
Expand Down Expand Up @@ -760,14 +761,16 @@ def loss_masks(
# get gt labels
point_labels = _sample_target_masks_at_points(targets, indices, point_coords)

# ``sigmoid_ce_loss_jit`` and ``dice_loss_jit`` are TorchScripted with
# ``num_masks: float`` in their signatures, so they reject Tensor inputs at
# runtime with a "expected float, got Tensor" error. ``SetCriterion.forward``
# On supported Python versions, ``sigmoid_ce_loss_jit`` and ``dice_loss_jit``
# are TorchScripted with ``num_masks: float`` in their signatures, so they
# reject Tensor inputs at runtime with an "expected float, got Tensor" error.
# Python 3.14+ uses the eager aliases, but retaining the same scalar boundary
# keeps behavior consistent across supported interpreters. ``SetCriterion.forward``
# now hands the criterion a Tensor denominator (so it can be all-reduced across
# ranks and accumulated across grad-accum microbatches), so it must be unwrapped
# to a Python scalar exactly here before the JIT call boundary. Using
# ``float(...)`` instead of ``.item()`` keeps the conversion safe whether
# ``num_boxes`` arrives as a Tensor, a Python int/float, or a numpy scalar.
# to a Python scalar here. Using ``float(...)`` instead of ``.item()`` keeps the
# conversion safe whether ``num_boxes`` arrives as a Tensor, a Python int/float,
# or a numpy scalar.
num_boxes_scalar = float(num_boxes)
losses = {
"loss_mask_ce": sigmoid_ce_loss_jit(point_logits, point_labels, num_boxes_scalar),
Expand Down
8 changes: 6 additions & 2 deletions src/rfdetr/utilities/box_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from __future__ import annotations

import sys

import torch
import torch.nn.functional as F # noqa: N812
from torch import Tensor
Expand Down Expand Up @@ -207,7 +209,7 @@ def batch_dice_loss(inputs: Tensor, targets: Tensor) -> Tensor:
return loss


batch_dice_loss_jit = torch.jit.script(batch_dice_loss)
batch_dice_loss_jit = batch_dice_loss if sys.version_info >= (3, 14) else torch.jit.script(batch_dice_loss)


def batch_sigmoid_ce_loss(inputs: Tensor, targets: Tensor) -> Tensor:
Expand All @@ -231,4 +233,6 @@ def batch_sigmoid_ce_loss(inputs: Tensor, targets: Tensor) -> Tensor:
return loss / hw


batch_sigmoid_ce_loss_jit = torch.jit.script(batch_sigmoid_ce_loss)
batch_sigmoid_ce_loss_jit = (
batch_sigmoid_ce_loss if sys.version_info >= (3, 14) else torch.jit.script(batch_sigmoid_ce_loss)
)
27 changes: 27 additions & 0 deletions tests/utilities/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,33 @@ def test_top_level_import_sets_numpy_complex_alias(self) -> None:
f"stderr:\n{result.stderr}"
)

def test_top_level_import_avoids_unsupported_torchscript(self) -> None:
"""Top-level import must not invoke unsupported TorchScript compilation."""
result = subprocess.run(
[
sys.executable,
"-c",
(
"import warnings\n"
"warnings.filterwarnings(\n"
" 'error',\n"
" message=r'.*torch\\.jit\\.script.*',\n"
" category=DeprecationWarning,\n"
")\n"
"import rfdetr\n"
),
],
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, (
"Subprocess for top-level import failed:\n"
f"return code: {result.returncode}\n"
f"stdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)

def test_identity_across_import_paths(self) -> None:
"""The same class object must be returned regardless of import path.

Expand Down
Loading