Skip to content

resnet: keep a frozen backbone in eval mode; give backbones their own LR group - #652

Draft
RyanPCo wants to merge 1 commit into
ryanco/per-sample-augsfrom
ryanco/vision-backbone
Draft

RyanPCo wants to merge 1 commit into
ryanco/per-sample-augsfrom
ryanco/vision-backbone

Conversation

@RyanPCo

@RyanPCo RyanPCo commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes two defects that would confound any backbone comparison: a frozen ResNet backbone still ran BatchNorm in train mode, and the pretrained backbone had to share one LR with the randomly initialised trunk.

Behaviour changes

None for shipped HPT configs. No HPT config freezes the ResNet, so no run to date hit the BatchNorm bug, and backbone_lr_scale defaults to 1.0, which builds the optimizer exactly as before.

What changed

  • Frozen backbone stays in eval mode. requires_grad = False leaves BatchNorm in train mode, so a "frozen" backbone normalized with batch statistics and kept updating its running stats. ResNet.train() now puts a frozen backbone back in eval mode.
  • Backbone LR group. The new model.backbone_lr_scale key puts the encoders' backbone parameters (backbone_parameters()) in their own param group at lr * scale.
    • At 1.0 the optimizer is a single group over trainer.model.parameters(), as before.
    • Any other value needs an explicit model.optimizer.lr and raises without one.
    • Frozen parameters and encoders that don't declare backbone_parameters() stay in the main group.
    • hpt_bc_mecka_6d_300M.yaml declares backbone_lr_scale: 1.0.
  • Pretrained marker. ResNet declares _hpt_pretrained_attrs = ("net",), the same marker the text stems use, so a selective-init pass can find its pretrained weights. Nothing on this branch reads the marker yet; later branches in the stack do.

Usage

The RDT recipe (#680) is the first to use the two-group path: rdt_bc_mecka_6d_dinov3_vitb.yaml sets backbone_lr_scale: 0.1.

Tests

New tests/unit/test_vision_backbone.py: ResNet forward against a reference, BatchNorm mode and running stats under a frozen backbone, backbone_parameters(), the single-group default, the two-group split, frozen-parameter handling, and the base model config's backbone_lr_scale: 1.0.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HjCQBqQCr2igFEMR7yUovw

RyanPCo commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@RyanPCo
RyanPCo changed the base branch from ryanco/per-sample-augs to graphite-base/652 September 16, 2026 17:22
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch from 2aa2b08 to e7468e3 Compare September 16, 2026 17:30
@RyanPCo
RyanPCo changed the base branch from graphite-base/652 to ryanco/per-sample-augs September 16, 2026 17:30
@RyanPCo
RyanPCo force-pushed the ryanco/per-sample-augs branch from 1839466 to fcb519b Compare September 17, 2026 14:52
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch from e7468e3 to b086056 Compare September 17, 2026 14:52
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch 2 times, most recently from 4a79cc4 to d343a85 Compare October 12, 2026 21:50
@RyanPCo
RyanPCo force-pushed the ryanco/per-sample-augs branch from 7d1003f to 381c9cd Compare October 12, 2026 23:19
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch from d343a85 to 2302dcf Compare October 12, 2026 23:19
@RyanPCo RyanPCo mentioned this pull request Oct 19, 2026
@RyanPCo
RyanPCo force-pushed the ryanco/per-sample-augs branch from 381c9cd to d2cd90d Compare October 19, 2026 19:40
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch 2 times, most recently from e680eb5 to cb1db1d Compare October 19, 2026 20:22
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch from cb1db1d to f6d6d51 Compare October 30, 2026 18:27
@RyanPCo
RyanPCo force-pushed the ryanco/per-sample-augs branch from 84bb0db to 9a3eeac Compare October 30, 2026 18:27

@RyanPCo RyanPCo left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Automated adversarial review (read-only; findings were checked by a second skeptical pass before posting): 3 nit.

Comment thread egomimic/models/hpt_nets.py Outdated
"""
super().train(mode)
if self.freeze_backbone:
if isinstance(self.net, nn.ModuleList):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nit (simplification): nn.Module.eval() recurses, so self.net.eval() already handles the ModuleList case. The isinstance branch can go: if self.freeze_backbone: self.net.eval().

Comment thread egomimic/pl_utils/pl_model.py Outdated
if callable(optimizer):
optimizer = optimizer()
scale = float(cfg.model.get("backbone_lr_scale", 1.0))
groups = None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nit (simplification): groups is None exactly when scale == 1.0, and the two branches build the optimizer differently (instantiate with params plus a callable check, versus _partial_=True then a call). Use one path: factory = hydra.utils.instantiate(cfg.model.optimizer, _partial_=True), params = self._backbone_param_groups(lr, scale) if scale != 1.0 else self.trainer.model.parameters(), optimizer = factory(params=params).

Comment thread tests/unit/test_vision_backbone.py Outdated


def test_resnet_frozen_backbone_batchnorm_stays_in_eval():
"""0.15: a frozen ResNet must not update / use batch statistics."""

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nit (comments): Drop the "0.15:" prefix: it refers to an internal ideas list.

… LR group

Two defects that would confound any backbone comparison:

* a frozen `ResNet` backbone kept its BatchNorm layers in train mode, so it
  normalized with batch statistics and kept updating its running stats.
  `requires_grad = False` does not touch either. `train()` now puts a frozen
  backbone in eval mode. Latent today -- no shipped config freezes the ResNet
  -- so no run to date was affected.
* the pretrained backbone and the randomly initialised trunk shared one LR.
  The new `model.backbone_lr_scale` key gives the encoders' backbones
  (`backbone_parameters()`) their own param group at `lr * scale`; at the
  default 1.0 the optimizer is built exactly as before, as a single group over
  `trainer.model.parameters()`. Nothing shipped sets it to anything else, so
  the two-group path is exercised only by its tests for now.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01VBmhpVy7ZcMtsBJWh38cJp
@RyanPCo
RyanPCo force-pushed the ryanco/per-sample-augs branch from 9a3eeac to fd4c50b Compare October 30, 2026 23:05
@RyanPCo
RyanPCo force-pushed the ryanco/vision-backbone branch from f6d6d51 to 63e3374 Compare October 30, 2026 23:05
@RyanPCo

RyanPCo commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Review findings: status after the fixup pass. The fixes are squashed into this PR's commits, and the whole stack was force-pushed.

Fixed

  • ModuleList special-case in train() is unnecessary: Now just self.net.eval().
  • configure_optimizers has a groups = None sentinel and two instantiate paths: Single partial factory path; params are the backbone groups or all parameters.
  • Private plan number in a test docstring: Dropped '0.15:'.

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.

1 participant