项目文件夹

文件
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

291 行
12 KiB
Python
可执行文件

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/usr/bin/env python3
# ------------------------------------------------------------------------
# RF-DETR
# Copyright (c) 2025 Roboflow. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 [see LICENSE for details]
# ------------------------------------------------------------------------
"""Smoke-test model instantiation and basic inference with all available weights.
Tests detection, segmentation, and keypoint-preview model classes from rf-detr by importing and instantiating them.
Validates: imports, download, MD5 hash, model instantiation, from_checkpoint round-trip, and basic inference smoke
checks.
Usage:
python tests/run_smoke_all_models.py
"""
import argparse
import os
import sys
import tempfile
from functools import partial
import torch
from tqdm.auto import tqdm
import rfdetr as _rfdetr
from rfdetr import (
RFDETRKeypointPreview,
RFDETRLarge,
RFDETRMedium,
RFDETRNano,
RFDETRSeg2XLarge,
RFDETRSegLarge,
RFDETRSegMedium,
RFDETRSegNano,
RFDETRSegSmall,
RFDETRSegXLarge,
RFDETRSmall,
)
try:
from rfdetr import RFDETR2XLarge, RFDETRXLarge
except ImportError:
RFDETR2XLarge = None
RFDETRXLarge = None
# Explicitly list all models to validate
MODELS_TO_TEST = [
# Detection Models
RFDETRNano,
RFDETRSmall,
RFDETRMedium,
RFDETRLarge,
# Keypoint Models
RFDETRKeypointPreview,
# Segmentation Models
RFDETRSegNano,
RFDETRSegSmall,
RFDETRSegMedium,
RFDETRSegLarge,
RFDETRSegXLarge,
RFDETRSeg2XLarge,
]
if RFDETRXLarge is not None:
MODELS_TO_TEST.append(partial(RFDETRXLarge, accept_platform_model_license=True))
if RFDETR2XLarge is not None:
MODELS_TO_TEST.append(partial(RFDETR2XLarge, accept_platform_model_license=True))
# 1008 = LCM(12, 16) × 21: valid for all patch sizes (PE=63 for det ÷16,
# PE=84 for seg ÷12). Each model is tested at its default resolution and at
# 1008 (regression #1038).
#
# Note on Base: ``RFDETRBaseConfig.positional_encoding_size = 37`` is *not*
# formula-derived (see test_load_pretrain_weights.py:TestLoadPretrainWeightsPEInterpolation
# ::test_base_config_non_formula_pe_is_interpolated_from_smaller_checkpoint),
# so this `÷16` description applies only to Nano/Small/Medium/Large.
_CUSTOM_RESOLUTION = 1008
# Plus models (XLarge / 2XLarge) are heavy enough that running them at
# resolution=1008 risks the 15-min CI timeout on windows-latest / macos-latest
# runners. Smaller models still exercise the 1008 path for #1038 coverage.
_HEAVY_MODEL_NAMES = {
"xlarge",
"2xlarge",
"xxlarge",
"seg-xlarge",
"seg-2xlarge",
"seg-xxlarge",
"rfdetr-xlarge",
"rfdetr-2xlarge",
"rfdetr-xxlarge",
"rfdetr-keypoint-preview",
"rfdetr-seg-xlarge",
"rfdetr-seg-2xlarge",
"rfdetr-seg-xxlarge",
}
def _test_from_checkpoint(
model_instance: object, actual_cls: type, extra_kwargs: dict, *, test_starter: bool = True
) -> None:
"""Round-trip a model through from_checkpoint using a temp training checkpoint.
Saves the instantiated model's weights into a minimal training-style checkpoint (``{"args": ..., "model":
state_dict}``), calls ``rfdetr.from_checkpoint`` on it, and asserts the returned object is an instance of
*actual_cls*.
Args:
model_instance: An already-loaded RFDETR model instance.
actual_cls: The expected model class (e.g. ``RFDETRSmall``).
extra_kwargs: Extra kwargs to pass to ``from_checkpoint`` (e.g.
``{"accept_platform_model_license": True}`` for plus models).
test_starter: When ``True`` (default) also run the starter-like
checkpoint round-trip. Pass ``False`` for non-default resolutions
to avoid running the same resolution-independent test multiple times.
Raises:
AssertionError: If the recovered model is not an instance of *actual_cls*.
Exception: Propagates any error from ``from_checkpoint`` to the caller.
"""
# Build a minimal training-style checkpoint. The pretrain_weights value only
# needs to contain the model-size substring that from_checkpoint matches on
# (e.g. "small", "seg-large"). Using cls.size directly satisfies this.
fake_pretrain_name = f"{actual_cls.size}.pth"
num_classes = model_instance.model.args.num_classes
ckpt = {
"args": argparse.Namespace(
pretrain_weights=fake_pretrain_name,
num_classes=num_classes,
),
"model": model_instance.model.model.state_dict(),
}
starter_like_ckpt = {
"args": argparse.Namespace(
pretrain_weights="none",
num_classes=num_classes,
),
"model": model_instance.model.model.state_dict(),
}
tmp_fd, tmp_path = tempfile.mkstemp(suffix=".pth")
os.close(tmp_fd)
try:
torch.save(ckpt, tmp_path)
recovered = _rfdetr.from_checkpoint(tmp_path, **extra_kwargs)
assert recovered is not None, "from_checkpoint returned None"
assert hasattr(recovered, "model"), "from_checkpoint result missing 'model' attribute"
assert isinstance(recovered, actual_cls), (
f"from_checkpoint returned {type(recovered).__name__}, expected {actual_cls.__name__}"
)
finally:
os.unlink(tmp_path)
if test_starter:
starter_tmp_fd, starter_path = tempfile.mkstemp(prefix=f"{actual_cls.size}-starter-", suffix=".pth")
os.close(starter_tmp_fd)
try:
torch.save(starter_like_ckpt, starter_path)
starter_recovered = _rfdetr.from_checkpoint(starter_path, **extra_kwargs)
assert starter_recovered is not None, "from_checkpoint returned None for starter-like checkpoint"
assert hasattr(starter_recovered, "model"), "starter-like from_checkpoint result missing 'model' attribute"
got = type(starter_recovered).__name__
assert isinstance(starter_recovered, actual_cls), (
f"starter-like from_checkpoint returned {got}, expected {actual_cls.__name__}"
)
finally:
os.unlink(starter_path)
def _test_coco_class_name_mapping(model_instance: object) -> None:
"""Verify predict() uses sparse COCO category-ID → class-name mapping.
Issue #988: RFDETRSegSmall returned "sheep" for class_id=18 instead of "dog" because 0-indexed
``COCO_CLASS_NAMES[18]`` was used instead of the sparse-dict lookup ``COCO_CLASSES[18]``. threshold=0 forces all
top-k queries through so every class ID in the output is covered. Covers both detection and segmentation nano
variants (RFDETRNano, RFDETRSegNano).
Args:
model_instance: An already-loaded pretrained COCO model instance (det or seg).
Raises:
AssertionError: On any class-name mapping failure.
"""
import PIL.Image
from rfdetr.assets.coco_classes import COCO_CLASS_NAMES, COCO_CLASSES
# Sanity-check model properties required for the pretrained COCO branch.
class_names = model_instance.class_names
assert class_names is not None, "Pretrained COCO model must have class_names set"
assert len(class_names) == len(COCO_CLASS_NAMES), (
f"Expected {len(COCO_CLASS_NAMES)} COCO class names, got {len(class_names)}"
)
assert class_names == list(COCO_CLASS_NAMES), "model.class_names must equal COCO_CLASS_NAMES"
num_classes = model_instance.model.args.num_classes
assert num_classes == 90, f"Pretrained COCO model must have num_classes=90, got {num_classes}"
# Run at threshold=0 to exercise all top-k output slots.
img = PIL.Image.new("RGB", (640, 640), color=(128, 128, 128))
detections = model_instance.predict(img, threshold=0.0)
assert "class_name" in detections.data, "data['class_name'] must be present after predict()"
# For every detection whose class_id is a valid COCO category, class_name must
# use sparse-ID lookup (COCO_CLASSES[class_id]), not 0-indexed lookup.
# Canonical regression case: class_id=18 → "dog", NOT "sheep" (COCO_CLASS_NAMES[18]).
for class_id, class_name in zip(detections.class_id, detections.data["class_name"]):
cid = int(class_id)
if cid in COCO_CLASSES:
expected = COCO_CLASSES[cid]
assert class_name == expected, (
f"Sparse COCO ID mapping broken (issue #988): "
f"class_id={cid} must map to '{expected}', got '{class_name}'"
)
# Regression for PR #1051 HIGH-1: no COCO-pretrained detection may carry
# '__background__' — background is implicit (below threshold), never a sentinel label.
background_labeled = [
(int(cid), name)
for cid, name in zip(detections.class_id, detections.data["class_name"])
if name == "__background__"
]
assert not background_labeled, (
"COCO-pretrained predict() must never produce '__background__' class names "
f"(PR #1051 HIGH-1 regression); found: {background_labeled[:3]}"
)
def main() -> None:
"""Download, validate, instantiate all models, and test from_checkpoint round-trip."""
print("Model Instantiation & Download Validation\n")
succeeded = 0
pbar = tqdm(MODELS_TO_TEST, desc="Testing models", unit="model")
for model_class in pbar:
actual_cls = model_class.func if isinstance(model_class, partial) else model_class
extra_kwargs = model_class.keywords if isinstance(model_class, partial) else {}
base_name = actual_cls.size
for res in (None, _CUSTOM_RESOLUTION):
# Skip the 1008-resolution variant for heavyweight Plus models — they
# risk the 15-min CI timeout on windows-latest / macos-latest runners.
if res == _CUSTOM_RESOLUTION and base_name in _HEAVY_MODEL_NAMES:
continue
model_name = base_name if res is None else f"{base_name}@{res}"
# Build the kwargs once so `_test_from_checkpoint` and the
# instantiation call share the same parameter set (avoids the
# `functools.partial.size` AttributeError seen in the previous form).
instantiate_kwargs = dict(extra_kwargs)
if res is not None:
instantiate_kwargs["resolution"] = res
pbar.set_description(f"Testing {model_name}")
try:
# Instantiate model class - triggers download, MD5 validation, and loading
model_instance = actual_cls(**instantiate_kwargs)
# Verify model was created
assert model_instance is not None, "Model instance is None"
assert hasattr(model_instance, "model"), "Model missing 'model' attribute"
# from_checkpoint round-trip: save a training-style checkpoint and reload it.
# Pass the real class (not a partial) so `_test_from_checkpoint` can read
# `.size` and `.__name__` and run `isinstance(recovered, actual_cls)`.
_test_from_checkpoint(model_instance, actual_cls, instantiate_kwargs, test_starter=(res is None))
# Inference class-name regression for issue #988 — run on all
# nano-sized pretrained COCO models at default resolution only.
if "nano" in base_name.lower() and res is None:
_test_coco_class_name_mapping(model_instance)
succeeded += 1
except Exception as ex:
# Fail-fast: surface the first failing model directly so CI logs the
# root cause cleanly instead of burying it under later cascade failures.
pbar.close()
print(f"\n[FAIL] {model_name}: {ex}")
raise
pbar.close()
print("\nResults:")
print(f"\tSucceeded:\t{succeeded}")
print("\n[OK] All models validated successfully")
sys.exit(0)
if __name__ == "__main__":
main()