chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# ------------------------------------------------------------------------
|
||||
# RF-DETR
|
||||
# Copyright (c) 2025 Roboflow. All Rights Reserved.
|
||||
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
|
||||
# ------------------------------------------------------------------------
|
||||
"""Tests for dual-projector backbone joiner routing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
|
||||
from rfdetr.models.backbone import Joiner
|
||||
from rfdetr.utilities.tensors import NestedTensor
|
||||
|
||||
|
||||
class _FakeBackbone(nn.Module):
|
||||
"""Backbone shim used to validate Joiner contract changes."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
features: list[NestedTensor],
|
||||
cross_attention_features: list[object] | None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._features = features
|
||||
self._cross_attention_features = cross_attention_features
|
||||
|
||||
def forward(self, tensor: torch.Tensor | NestedTensor):
|
||||
if isinstance(tensor, torch.Tensor):
|
||||
feats = [f.tensors for f in self._features]
|
||||
masks = [f.mask for f in self._features]
|
||||
return feats, masks, self._cross_attention_features
|
||||
return self._features, self._cross_attention_features
|
||||
|
||||
|
||||
class _FakePositionEncoding(nn.Module):
|
||||
"""Tiny callable that behaves like a position encoder."""
|
||||
|
||||
def forward(self, nested_tensor: NestedTensor | torch.Tensor, align_dim_orders: bool = False) -> torch.Tensor:
|
||||
if isinstance(nested_tensor, NestedTensor):
|
||||
base = nested_tensor.tensors
|
||||
else:
|
||||
base = nested_tensor
|
||||
if base.dim() == 3:
|
||||
base = base[:, None]
|
||||
return torch.zeros((base.shape[0], 1, base.shape[-2], base.shape[-1]), dtype=base.dtype, device=base.device)
|
||||
|
||||
|
||||
def _feature(shape: tuple[int, ...], batch_size: int = 2) -> NestedTensor:
|
||||
channels, height, width = shape
|
||||
return NestedTensor(
|
||||
tensors=torch.ones((batch_size, channels, height, width), dtype=torch.float32),
|
||||
mask=torch.zeros((batch_size, height, width), dtype=torch.bool),
|
||||
)
|
||||
|
||||
|
||||
def _input_tensor(batch_size: int = 2) -> tuple[NestedTensor, torch.Tensor]:
|
||||
return (
|
||||
NestedTensor(
|
||||
tensors=torch.ones((batch_size, 3, 16, 16), dtype=torch.float32),
|
||||
mask=torch.zeros((batch_size, 16, 16), dtype=torch.bool),
|
||||
),
|
||||
torch.ones((batch_size, 3, 16, 16), dtype=torch.float32),
|
||||
)
|
||||
|
||||
|
||||
def test_joiner_dual_projector_disabled_contract() -> None:
|
||||
"""Joiner should forward one feature stream and a ``None`` cross-attention stream when disabled."""
|
||||
features = [_feature((256, 16, 16))]
|
||||
joiner = Joiner(_FakeBackbone(features, None), _FakePositionEncoding())
|
||||
|
||||
input_tensor, image = _input_tensor()
|
||||
|
||||
_, _, cross_attention = joiner(input_tensor)
|
||||
assert cross_attention is None
|
||||
assert len(joiner(input_tensor)[0]) == 1
|
||||
|
||||
exported = joiner.forward_export(image)
|
||||
assert exported[3] is None
|
||||
assert len(exported[0]) == 1
|
||||
assert exported[2][0].shape == (2, 16, 16)
|
||||
|
||||
|
||||
def test_joiner_dual_projector_enabled_contract() -> None:
|
||||
"""Joiner should forward cross-attention features in parallel with feature features when enabled."""
|
||||
features = [_feature((256, 16, 16)), _feature((256, 8, 8))]
|
||||
cross_attention_features = [_feature((256, 16, 16)), _feature((256, 8, 8))]
|
||||
joiner = Joiner(_FakeBackbone(features, cross_attention_features), _FakePositionEncoding())
|
||||
|
||||
input_tensor, _ = _input_tensor()
|
||||
|
||||
feature_tensors, _, cross_attention = joiner(input_tensor)
|
||||
assert len(feature_tensors) == len(cross_attention)
|
||||
assert all(f.tensors.shape == c.tensors.shape for f, c in zip(feature_tensors, cross_attention))
|
||||
assert all(f.mask is not None for f in cross_attention)
|
||||
|
||||
|
||||
def test_joiner_forward_export_contract() -> None:
|
||||
"""Exported joiner contracts should remain 4-tuples and preserve cross-attention stream arity."""
|
||||
exported_features = [torch.ones(2, 256, 16, 16), torch.ones(2, 256, 8, 8)]
|
||||
exported_masks = [torch.zeros(2, 16, 16, dtype=torch.bool), torch.zeros(2, 8, 8, dtype=torch.bool)]
|
||||
export_backbone = _FakeBackbone(
|
||||
[NestedTensor(t, mask) for t, mask in zip(exported_features, exported_masks)],
|
||||
[torch.ones(2, 256, 16, 16), torch.ones(2, 256, 8, 8)],
|
||||
)
|
||||
joiner = Joiner(export_backbone, _FakePositionEncoding())
|
||||
|
||||
outputs = joiner.forward_export(torch.ones(2, 3, 16, 16))
|
||||
feats_out, masks_out, poss, cross_attention = outputs
|
||||
|
||||
assert len(feats_out) == len(exported_features)
|
||||
assert len(masks_out) == len(exported_masks)
|
||||
assert feats_out[0].shape == exported_features[0].shape
|
||||
assert masks_out[0].shape == exported_masks[0].shape
|
||||
assert len(outputs) == 4
|
||||
assert poss[0].shape == exported_features[0][:, :1, :, :].shape
|
||||
assert isinstance(cross_attention, list)
|
||||
assert all(isinstance(feature, torch.Tensor) for feature in cross_attention)
|
||||
Reference in New Issue
Block a user