huggingface / huggingface/transformers-mlinter
TRF034: the Layer/Block suffix heuristic flags conv and pooling stacks that are not checkpointing boundaries
- Dominant language
- Python
- Stars
- 6
- Forks
- 12
- Avg merge
- 10h 28m
- Merged PRs (30d)
- 19
Description
Reported by devs
> TRF034: Cannot handle the modular files -> false positives (also graciously saved by cutoff date)
## Measurement
Run over a `transformers` checkout of 2026-08-25 with cutoffs neutralised: **96 TRF034 findings, 41 of them in `modular_*.py`**. They split into two unrelated causes:
**a. Inherited from another model (13 findings)** — the base class lives in a different model directory, so the new model is flagged for structure it does not own and cannot change:
- `DFineRepVggBlock(RTDetrRepVggBlock)`, `DFineAIFILayer(RTDetrAIFILayer)`, `DFineDecoderLayer(RTDetrDecoderLayer)`
- `Deimv2ConvNormLayer(DFineConvNormLayer)`, `Deimv2RepVggBlock`, `Deimv2AIFILayer`
- `PPOCRV5ServerRecConvLayer`, `PPLCNetV3ConvLayer`, `SLANetConvLayer` (bases in `pp_lcnet`, `convnext`, `resnet`)
Parent models: `rt_detr` (4), `d_fine` (3), `pp_lcnet` (2), `resnet`, `convnext`, `pp_ocrv5_server_det`, `pp_ocrv5_server_rec`. Each parent is itself grandfathered by the cutoff, so the violation is only ever reported against the newer model. This half is tracked separately in #56 and is the more general fix.
**b. The `Layer` / `Block` suffix heuristic (the rest)** — it catches stacks that are not gradient-checkpointing boundaries:
`ConvNextLayer`, `ConvNextV2Layer`, `PPLCNetV3ConvLayer`, `SLANetConvLayer`, `Sapiens2ConvLayer`, `*RepVggBlock`, `*ConvNormLayer`, `BeitPyramidPoolingBlock`, `Data2VecVisionPyramidPoolingBlock`, `SpeechT5BatchNormConvLayer`, `FastSpeech2ConformerBatchNormConvLayer`, `Data2VecAudioPositionalConvLayer`, `UnivNetLvcResidualBlock`, `Sam3FPNLayer`, `VibeVoiceAcousticTokenizerConvNext1dLayer`.
## Discriminators I tested and would not ship
Two automatic ways to tell a transformer layer from a conv block, measured on the same 96:
- **`forward` takes `hidden_states`**: 58 yes / 28 no / 10 inherit their forward. It misfires on genuine layers that name the argument differently — `Sam3DetrEncoderLayer(vision_feats, prompt_feats)`, `LxmertXLayer(lang_feats, ...)`, `XLNetLayer(output_h, output_g)`, `GroundingDinoEncoderLayer(vision_features, ...)`, `PatchTSTEncoderLayer(hidden_state, ...)`.
- **`nn.ModuleList` built over a config-derived layer count**: 53 yes / 43 no. It misfires on depth-stacked vision models — `DinatLayer`, `HieraLayer`, `DonutSwinLayer`, `MaskFormerSwinLayer`, `ConvNextLayer` all range over a local `depth`.
Both would trade false positives for false negatives on real decoder stacks, so neither is a good deal.
## Proposal
Narrow by name instead, since that is where the imprecision comes from: exclude classes whose name matches conv/pooling idioms (`*ConvLayer`, `*Conv1dLayer`, `*ConvNormLayer`, `*ConvNext*Layer`, `*RepVggBlock`, `*PyramidPoolingBlock`, `*BatchNormConvLayer`, `*ResidualBlock`, `*FPNLayer`) from `LAYER_CLASS_SUFFIXES` matching. Same spirit as the existing "projections, heads or experts are out of scope" carve-out, just written down.
Alternative, if the denylist feels too open-ended: leave (b) as is and rely on `allowlist_models` — but note that `transformers`' repo-local `utils/rules.toml` already carries six model-wide TRF034 allowlist entries added to unblock PRs (`cohere_compass`, `granite_swa`, `granitemoe_swa`, `muse_glimmer`, `muse_glimmer_assistant`, `step3p7`), which mutes the rule for those models permanently.
Contributor guide
Assessment
This issue has not been assessed yet.