Layer freezing

Freezing holds selected weights fixed while the rest of the model trains. Selectors address a family's own ordered freeze groups or its module names, not raw layer numbers from a YAML graph.

Freeze something

freeze is optional and defaults to no freezing.

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt") # The first 10 groups are the whole YOLOv9 backbone.model.train(data="my-dataset.yaml", epochs=50, freeze=10)
CLI
libreyolo train model=LibreYOLO9s.pt data=my-dataset.yaml \  epochs=50 freeze=10
By name
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs.pt")model.train(data="my-dataset.yaml", epochs=50, freeze="backbone")
Several selectors
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")model.train(data="my-dataset.yaml", freeze=["backbone", "neck"])

Freezing runs after the model is built and after any head rebuild for a new class count, and before the optimizer is created, so the optimizer only ever receives trainable parameters.

What a selector can be

ValueMeaning
None, False, "", "none"Train every parameter
10 or "10"Freeze the first ten family freeze groups
[0, 3, 7]Freeze those zero-based groups
"backbone"Freeze the matching group, module or parameter prefix
["backbone", "neck"]Freeze each listed selector
["backbone", 3]Mixed lists work

A string is parsed before it is interpreted, so the CLI and a YAML config accept the same shapes as Python. freeze="[0, 3, 'head']" is parsed as a literal list, freeze="backbone,neck" splits on the comma, and a bare decimal string becomes a count.

freeze=True is rejected as ambiguous.

Name selectors match a freeze group name, a module name, or a parameter-name prefix, and glob characters *, ? and [ work. A leading model. is treated flexibly, so backbone and model.backbone both hit whichever spelling the family uses internally.

Groups are family-defined

An integer addresses a family's own ordered list of freeze groups, not a position in a shared graph. LibreYOLO's families are not all one YAML-indexed sequential model, so a raw layer number would mean something different on each of them.

YOLOv9 orders its groups from the input side: ten backbone stages, then six neck stages, then the head. That is why freeze=10 is exactly the backbone. backbone, neck and head are stable name selectors on top of it.

RF-DETR's groups are backbone.encoder, backbone.projector, decoder, queries, transformer.encoder_output and head. Names are the better choice here, because transformer components do not map onto a layer count. backbone matches both backbone groups by prefix.

Families that do not define semantic groups fall back to a conservative default: each direct child of the model that owns at least one parameter, in declaration order. That is usually a short list, so a large integer will not find enough groups:

freeze index 10 is out of range for 3 available freeze groups.

To see the real list rather than guessing:

List a family's freeze groups in order
from libreyolo import LibreYOLO9from libreyolo.models.yolo9.trainer import YOLO9Trainer model = LibreYOLO9("LibreYOLO9s.pt", size="s")trainer = YOLO9Trainer(model=model.model, wrapper_model=model, size="s") for index, (name, _module) in enumerate(trainer.get_freeze_groups()):    print(index, name)

Failures are loud

Every way of getting this wrong raises rather than training something you did not ask for.

A selector that matches nothing raises, naming the selectors that missed:

freeze selector(s) matched no parameters: 'backbon'

A freeze that would leave nothing trainable raises, both at freeze time and again when the optimizer is built:

freeze would leave no trainable parameters. Use a smaller freeze value ortarget a narrower module.

Which is what freeze="all" does, since all matches every parameter.

When freezing succeeds, one line records what happened:

Layer freezing: selectors=[10], tensors=124, params=2103776, trainable=1863456/3967232

Frozen BatchNorm stops updating

A frozen parameter still sits inside a module whose running statistics would keep moving. Every BatchNorm-style module whose parameters land in the frozen set is switched to eval mode, and the trainer re-applies that after each epoch's model.train() call, so the statistics stay fixed for the whole run.

This is on by default and is what makes freezing a backbone actually freeze it.

Composing with LoRA

freeze and lora=True work together. On RF-DETR, DEIM and ConvNeXt the adapter parameters are preserved as trainable even when their parent group is frozen, which is the combination you want: a frozen backbone with adapters learning on top of it. See LoRA fine-tuning.

Scope

This is static freezing decided at startup. Scheduled unfreezing and progressive freezing are not part of the interface.

Verified against LibreYOLO v1.5.0.