Skip to content

Fix SuperAnimal / pretrained load for RTMPose: implement convert_weights on RTMCCHead - #3270

Merged
MMathisLab merged 4 commits into
mainfrom
jaap/fix_superanimal_rtmcc_weightconversion
Apr 13, 2026
Merged

Fix SuperAnimal / pretrained load for RTMPose: implement convert_weights on RTMCCHead#3270
MMathisLab merged 4 commits into
mainfrom
jaap/fix_superanimal_rtmcc_weightconversion

Conversation

@deruyter92

Copy link
Copy Markdown
Collaborator

Description
Training or fine-tuning with SuperAnimal weight initialization and with_decoder=True loads a checkpoint and calls head.convert_weights(...) on each head so bodyparts can be subset and reordered. HeatmapHead already provides this via WeightConversionMixin, but RTMCCHead did not, which caused:

AttributeError: 'RTMCCHead' object has no attribute 'convert_weights'

This change makes RTMCCHead consistent with other heads that support SuperAnimal transfer.

What changed
RTMCCHead now subclasses WeightConversionMixin alongside BaseHead, matching the pattern used by HeatmapHead.

convert_weights implementation
final_layer: Reorders/subsets the Conv2d output channels using the same convention as DeconvModule.convert_weights for per–bodypart outputs (weight and bias indexed by the conversion tensor).
gau.w: The GatedAttentionUnit relative bias table changes size when the number of keypoints changes; it cannot be remapped by a simple channel permutation. The PR rebuilds w to shape 2 * K_new - 1 using a mapping from new token pairs to pretrained relative indices (with averaging when multiple pairs collapse to the same new relative offset), so the checkpoint can be loaded without shape errors. (If reviewers prefer a more conservative transfer, this part can be swapped for re-initialization of gau.w only.)

@JiwaniZakir JiwaniZakir left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convert_weights method in rtmcc_head.py checks if fl_w in state_dict before applying the conversion, but then unconditionally accesses state_dict[fl_b] without a corresponding guard. If a checkpoint uses a bias-free final_layer, this will raise a KeyError rather than silently skipping — worth adding a separate if fl_b in state_dict check or bundling both under a single check that verifies both keys exist.

The fallback torch.rand((), ...) when old_vals is empty introduces non-determinism into weight loading, which is particularly problematic for reproducibility of fine-tuning runs. Since this branch handles positions where no old keypoint pair contributes (an edge case whose practical frequency is unclear), torch.zeros or w_old.mean() would be safer and deterministic defaults.

It's also worth documenting the assumption baked into old_idx = int(conv[i] - conv[j]) + k_old - 1: this treats differences in original keypoint indices as relative positional distances in the GAU weight table. That coupling between semantic keypoint ordering and positional encoding is non-obvious and could silently produce incorrect weight remappings if the pretrained model was trained with a different keypoint ordering convention.

@deruyter92

deruyter92 commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator Author

@JiwaniZakir excellent feedback, thanks! Based on your suggestions, I incorporated the following changes:

  • added separate guard if fl_b in state_dict
  • default fallback for gau.w is now w_old.mean() to ensure determinism, keeping statistics close to the original.
  • added a flag (and explanation) omit_gau_w to not update gau.w at all. I agree that coupling between semantic keypoint ordering and positional encoding is non-obvious and a strong assumption. The flag can be set True for cases where this assumption is not met, keeping the weights transfer limited to only the final_layer weights and biases and ignoring the gau.w parameters.

Note: for convenience, I've set the default behavior omit_gau_w=False, which allows us to keep the DeepLabCut superanimal weight-init pipelines as is. The pipeline currently uses strict weight loading and therefore always assumes that all values are present in the state dict (i.e. convert_weights should always return new values for the gau.w parameter).

Thanks again for your review!

@deruyter92
deruyter92 force-pushed the jaap/fix_superanimal_rtmcc_weightconversion branch from 4a9d990 to 1d1b59c Compare April 7, 2026 08:48
@deruyter92
deruyter92 marked this pull request as ready for review April 7, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes SuperAnimal pretrained checkpoint loading for RTMPose models by adding weight-conversion support to RTMCCHead, aligning it with other heads that can subset/reorder keypoints during transfer learning.

Changes:

  • Make RTMCCHead subclass WeightConversionMixin to participate in the SuperAnimal conversion workflow.
  • Implement RTMCCHead.convert_weights(...) to remap final_layer output channels and adapt gau.w to new keypoint counts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread deeplabcut/pose_estimation_pytorch/models/heads/rtmcc_head.py
@MMathisLab
MMathisLab merged commit 7bde4cc into main Apr 13, 2026
14 of 15 checks passed
@MMathisLab
MMathisLab deleted the jaap/fix_superanimal_rtmcc_weightconversion branch April 13, 2026 13:26
@deruyter92 deruyter92 mentioned this pull request May 21, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants