Skip to content

feat(cli): adapt raw rf-detr PyTorch-Lightning checkpoints on upload#488

Merged
leeclemnet merged 1 commit into
mainfrom
feat/sdk-rfdetr-lightning-upload
Jul 7, 2026
Merged

feat(cli): adapt raw rf-detr PyTorch-Lightning checkpoints on upload#488
leeclemnet merged 1 commit into
mainfrom
feat/sdk-rfdetr-lightning-upload

Conversation

@leeclemnet

@leeclemnet leeclemnet commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Lets roboflow upload_model accept raw rf-detr PyTorch-Lightning checkpoints (e.g. checkpoint_best_ema.pth) and adapt them into the upload-ready bundle the backend conversion expects.

In _process_rfdetr, after loading the checkpoint:

  • Detect a PTL checkpoint via the pytorch-lightning_version key (_is_ptl_checkpoint).
  • Lazy-import rfdetr (capability-guarded on RFDETR.export_for_roboflow; actionable RuntimeError if missing/too old — mirrors the ultralytics/yolo pattern).
  • Load the model: RFDETR.from_checkpoint(pth), falling back to the SDK-local model_type→class map (_RFDETR_MODEL_TYPE_TO_CLASS) when the checkpoint lacks the metadata rf-detr needs to infer its class (no model_name/pretrain_weights).
  • model.export_for_roboflow(model_path) writes weights.pt (args with resolution) + class_names.txt.
  • Legacy path is unchanged and imports no rfdetr.

Related Issue(s): Requires rfdetr>=1.8.0, which ships export_for_roboflow (roboflow/rf-detr#1086, released in 1.8.0). RFDETR_MIN_VERSION is pinned to 1.8.0.

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

  • new unit tests
  • versionless IS PTL upload via CLI
roboflow upload_model \
  -t rfdetr-seg-medium \
  -p coco-dataset-vdnr1-5eed0 \
  -m /tmp/upload-test/model \
  -f checkpoint_best_ema.pth \
  -n rfdetr-seg-medium-ptl-test
  • versioned IS PTL upload via CLI
roboflow upload_model \
  -t rfdetr-seg-medium -p coco-dataset-vdnr1-5eed0 -v 1 \
  -m /tmp/upload-test/model -f checkpoint_best_ema.pth
image

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (CHANGELOG under unreleased)

leeclemnet added a commit to roboflow/rf-detr that referenced this pull request Jun 1, 2026
Add RFDETR.save_upload_bundle(output_dir) — the network-free core of
deploy_to_roboflow that writes weights.pt ({"model": state_dict, "args": args},
args carrying resolution + embedded class_names) and class_names.txt. Refactor
deploy_to_roboflow to call it; net behavior unchanged.

This gives the Roboflow SDK a single, drift-free way to turn raw PyTorch-Lightning
rf-detr checkpoints into a deploy-ready bundle (companion: roboflow/roboflow-python#488).

Also updates TestDeployToRoboflow, whose MagicMock(spec=RFDETR) fixture was
no-op'ing the now-delegated save_upload_bundle; the real method is bound so those
end-to-end regression tests still exercise it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch from 318dfbc to db28572 Compare June 1, 2026 17:07
leeclemnet added a commit to roboflow/rf-detr that referenced this pull request Jun 1, 2026
Add RFDETR.save_upload_bundle(output_dir) — the network-free core of
deploy_to_roboflow that writes weights.pt ({"model": state_dict, "args": args},
args carrying resolution + embedded class_names) and class_names.txt. Refactor
deploy_to_roboflow to call it; net behavior unchanged.

This gives the Roboflow SDK a single, drift-free way to turn raw PyTorch-Lightning
rf-detr checkpoints into a deploy-ready bundle (companion: roboflow/roboflow-python#488).

Also updates TestDeployToRoboflow, whose MagicMock(spec=RFDETR) fixture was
no-op'ing the now-delegated save_upload_bundle; the real method is bound so those
end-to-end regression tests still exercise it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
leeclemnet added a commit to roboflow/rf-detr that referenced this pull request Jun 1, 2026
Add RFDETR.export_for_roboflow(output_dir) — the network-free core of
deploy_to_roboflow that writes weights.pt ({"model": state_dict, "args": args},
args carrying resolution + embedded class_names) and class_names.txt. Creates
output_dir if missing. Refactor deploy_to_roboflow to call it; net behavior
unchanged.

Gives the Roboflow SDK a single, drift-free way to turn raw PyTorch-Lightning
rf-detr checkpoints into a deploy-ready bundle (companion: roboflow/roboflow-python#488).

Also updates TestDeployToRoboflow, whose MagicMock(spec=RFDETR) fixture was
no-op'ing the now-delegated method; the real method is bound so those end-to-end
regression tests still exercise it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch 2 times, most recently from 7150b7c to 8f5fc73 Compare June 1, 2026 18:05
@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch 3 times, most recently from 11f7758 to 6ebc5fa Compare June 16, 2026 19:49
@leeclemnet leeclemnet marked this pull request as ready for review June 23, 2026 22:16
@jarbas-roboflow

Copy link
Copy Markdown

Status: ❌ Request Changes — Gist: https://gist.github.com/jarbasrf/4ae989bfd95f2b470e183560724d5a53. Resumo: The raw rf-detr upload path ignores the caller-provided filename and picks the first .pt/.pth in the directory, so multi-checkpoint training directories can upload the wrong checkpoint.

@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch 2 times, most recently from d89c8d7 to f11b48b Compare July 7, 2026 17:01
@jarbas-roboflow

Copy link
Copy Markdown

PR Review: feat(cli): adapt raw rf-detr PyTorch-Lightning checkpoints on upload

Generated by Codex via roboflow-review-pr. Review is advisory; verify before merging.

Metadata:

📋 Summary

The prior filename-selection issue is resolved at current head: _process_rfdetr now prefers the caller-provided checkpoint filename when it exists, and the new regression test forces directory order that would have selected the wrong file before.

I found one current-head blocker. The PR enables raw PyTorch-Lightning rf-detr checkpoints via rfdetr>=1.8.0, but the task detector still classifies every non-segmentation model_name as detection, so keypoint rf-detr checkpoints can be accepted and uploaded as object-detection rfdetr-* models.

Previous review context:

🏗️ Architectural Review

The adapter boundary is mostly right: the SDK keeps rf-detr-specific bundle adaptation in model_processor, imports rfdetr only for raw PyTorch-Lightning checkpoints, and leaves the legacy .pt bundle path unchanged.

flowchart LR
    A[CLI or SDK deploy] --> B[process model_type]
    B --> C[_process_rfdetr]
    C --> D[select checkpoint filename]
    D --> E[torch.load checkpoint]
    E --> F[detect rf-detr task]
    F --> G{task matches model_type?}
    G -- no --> H[raise ValueError]
    G -- yes --> I{PTL checkpoint?}
    I -- no --> J[legacy class_names + weights.pt]
    I -- yes --> K[RFDETR.from_checkpoint]
    K --> L[export_for_roboflow]
    J --> M[roboflow_deploy.zip]
    L --> M
    M --> N[project type validation]
    N --> O[upload endpoint]
Loading

The risky boundary is the F -> G -> N contract. Version.deploy() and Workspace.deploy_model() validate project compatibility after processing by using the returned model_type, not by inspecting the exported checkpoint bundle. That makes _detect_rfdetr_task() the last meaningful guard against an unsupported raw rf-detr task being uploaded under a supported Roboflow model type.

🐛 Code Quality Issues

1. ⚠️ Keypoint rf-detr checkpoints are accepted as object-detection uploads

roboflow/util/model_processor.py#L352-L354 returns TASK_DET for every checkpoint model_name that does not contain "seg". With this PR, raw PTL checkpoints then enter the new RFDETR.from_checkpoint(...).export_for_roboflow(...) path at #L426-L438, and the upload path later validates only that model_type="rfdetr-base" implies object detection at version.py#L500-L507 or workspace.py#L803-L812.

That means a checkpoint with model_name="RFDETRKeypointPreview" is treated as detection, passes the rfdetr-base task check, exports a bundle, and can be uploaded to an object-detection project. rf-detr's current public surface includes keypoint detection preview and the RFDETRKeypointPreview class, so this is reachable with the same rfdetr>=1.8.0 dependency family this PR asks users to install.

Impact: users can accidentally upload an unsupported keypoint rf-detr checkpoint under a detection model type, producing an artifact whose task does not match the Roboflow project/model contract.

Suggested shape:

if isinstance(model_name, str):
    name = model_name.lower()
    if "keypoint" in name:
        return TASK_POSE
    return TASK_SEG if TASK_SEG in name else TASK_DET

Then let the existing det / pose mismatch raise until an explicit rf-detr keypoint upload type exists. Add tests for _detect_rfdetr_task({"model_name": "RFDETRKeypointPreview"}) and for _process_rfdetr("rfdetr-base", ...) rejecting a PTL keypoint checkpoint before export.

💡 Recommendations

  • Keep the current filename-first behavior. It matches rf-detr training directories that can contain multiple .pt / .pth files and fixes the earlier review concern.
  • Consider tightening the fallback error for Plus-only XLarge / 2XLarge classes later. If getattr(rfdetr, "RFDETRXLarge") fails because the Plus package is unavailable, users may see a less actionable error than the _require_rfdetr() install/upgrade message.
  • If this ships through the CLI, document that raw PTL checkpoint upload requires rfdetr>=1.8.0; the changelog covers it, but CLI users may hit the lazy import error first.

🧪 Tests

Strongest coverage is in tests/util/test_model_processor.py. It proves PTL checkpoint detection, missing/old rfdetr errors, successful export via a fake rfdetr module, fallback construction from model_type, legacy bundle behavior without importing rfdetr, and the fixed caller-filename selection.

Important remaining gap: there is no unsupported-task regression for keypoint rf-detr checkpoints. The existing detection-model test list stops at detection variants and does not cover RFDETRKeypointPreview.

Local verification:

  • python3 -m compileall -q roboflow/util/model_processor.py tests/util/test_model_processor.py passed.
  • python3 -m pytest tests/util/test_model_processor.py -q could not run because pytest is not installed.
  • python3 -m unittest tests.util.test_model_processor -v could not run because importing tests requires responses, which is not installed.
  • PYTHONPATH=. python3 tests/util/test_model_processor.py -v could not run because importing roboflow requires requests_toolbelt, which is not installed.

⚖️ Verdict — ❌ Request Changes

The previous checkpoint-selection regression is fixed, but the current raw rf-detr upload path can still accept a keypoint checkpoint as an object-detection model. Add an explicit unsupported-task guard and regression test before merging.

Timestamp: 2026-07-07 17:13:58 UTC

@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch 2 times, most recently from dcc6fed to 5fe1f34 Compare July 7, 2026 17:43
Detect a raw PTL rf-detr checkpoint (`pytorch-lightning_version` key) in
_process_rfdetr and rebuild it into an upload-ready bundle: lazy-import rfdetr
(capability-guarded on RFDETR.export_for_roboflow), load via
RFDETR.from_checkpoint, falling back to an SDK-local model_type→class map
(_RFDETR_MODEL_TYPE_TO_CLASS — also the single source for the supported-type
check) when the checkpoint lacks the metadata rf-detr needs to infer its class,
then call export_for_roboflow to write weights.pt (args with resolution) +
class_names.txt. The legacy path is unchanged and imports no rfdetr.

Depends on rf-detr's export_for_roboflow (roboflow/rf-detr#1086); capability-
guarded so it works once that release ships.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@leeclemnet leeclemnet force-pushed the feat/sdk-rfdetr-lightning-upload branch from 5fe1f34 to 5c381f2 Compare July 7, 2026 17:47
@jarbas-roboflow

Copy link
Copy Markdown

PR Review: feat(cli): adapt raw rf-detr PyTorch-Lightning checkpoints on upload

Generated by Codex via roboflow-review-pr. Review is advisory; verify before merging.

Metadata:

📋 Summary

I did a clean-slate review of the current PR head (5c381f21a2a2) and did not find any merge-blocking issues. The PR keeps legacy rf-detr bundle upload behavior intact while adding a narrowly-scoped raw PyTorch-Lightning checkpoint adaptation path through rfdetr>=1.8.0.

Previous review context:

🏗️ Architectural Review

The change is in the right boundary: _process_rfdetr() already owns rf-detr deploy-bundle creation, and the new raw Lightning branch stays inside that processor rather than leaking rfdetr requirements into CLI or workspace/version upload callers. rfdetr remains lazily imported only after a raw PyTorch-Lightning checkpoint is detected, so existing Roboflow-hosted .pt bundle uploads continue to run without the optional dependency.

flowchart LR
    A[upload_model or deploy] --> B[process model_type]
    B --> C[_process_rfdetr]
    C --> D[select checkpoint]
    D --> E[torch.load]
    E --> F[detect checkpoint task]
    F --> G{task matches model_type}
    G -- no --> H[raise ValueError]
    G -- yes --> I{Lightning checkpoint}
    I -- no --> J[legacy class_names + weights.pt]
    I -- yes --> K[require rfdetr >= 1.8.0]
    K --> L[from_checkpoint or model_type fallback]
    L --> M[export_for_roboflow]
    J --> N[roboflow_deploy.zip]
    M --> N
    N --> O[project type validation]
    O --> P[upload]
Loading

The key contracts line up:

🐛 Code Quality Issues

No correctness, architecture, or test-quality issues found at current PR head.

💡 Recommendations

  • Keep the caller-filename override. rf-detr training directories can contain multiple .pt / .pth files, and the current behavior avoids silently uploading a first-match distractor.
  • Consider a future polish pass for Plus-only rf-detr classes: if an installed rfdetr package lacks RFDETRXLarge or RFDETR2XLarge, the fallback currently depends on getattr() surfacing the missing class. That is not a blocker for this PR, but a tailored error would be more user-friendly.
  • The changelog correctly documents the new raw Lightning upload requirement under Unreleased at CHANGELOG.md#L5-L12.

🧪 Tests

Strongest coverage is in tests/util/test_model_processor.py. It proves the business claim that raw rf-detr PyTorch-Lightning checkpoints can be converted into an upload-ready bundle while legacy bundle uploads remain independent of rfdetr.

Important covered behaviors:

  • PTL checkpoint detection and legacy checkpoint separation.
  • Missing and too-old rfdetr dependency errors.
  • RFDETR.from_checkpoint() success path.
  • fallback construction from SDK model_type when rf-detr cannot infer the class.
  • keypoint checkpoints rejected before export.
  • caller-provided filename winning over directory first-match discovery.
  • legacy rf-detr path producing weights.pt and class_names.txt without importing rfdetr.

Local verification:

  • python3 -m compileall -q roboflow/util/model_processor.py tests/util/test_model_processor.py passed.
  • git diff --check origin/main...refs/remotes/origin/pr/488 passed.
  • python3 -m pytest tests/util/test_model_processor.py -q could not run because pytest is not installed.
  • python3 -m unittest tests.util.test_model_processor -v could not run because importing tests requires responses, which is not installed.
  • Environment dependency check: torch, rfdetr, pytest, responses, and requests_toolbelt are not installed here.

⚖️ Verdict — ✅ Approve

The current head fixes the prior rf-detr issues and adds focused regression coverage for both. I did not find a current blocker in the raw PyTorch-Lightning adaptation path.

Timestamp: 2026-07-07 18:07:40 UTC

@leeclemnet leeclemnet requested review from mkaic and probicheaux July 7, 2026 18:32
@leeclemnet leeclemnet merged commit 6aac94d into main Jul 7, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants