Skip to content

Post the model linter's first pass as a PR review - #47760

Open
ArthurZucker wants to merge 1 commit into
mainfrom
mlinter-pr-review
Open

Post the model linter's first pass as a PR review#47760
ArthurZucker wants to merge 1 commit into
mainfrom
mlinter-pr-review

Conversation

@ArthurZucker

@ArthurZucker ArthurZucker commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

CI

What this does

Runs transformers-mlinter on the model files a PR touches and posts the result as one review with inline comments, so the mechanical pass is already done when a human starts reading.

Each inline comment carries the rule id, what to change, a collapsed rationale with an example diff, and the # trf-ignore: TRFxxx escape hatch. The review body opens with a per-rule table and a plain statement that it is automated and advisory.

Here is the actual output, generated locally against a synthetic new model:

Model linter — first pass

transformers-mlinter 0.1.2 found 7 item(s) in the model files this PR touches. These are the structural conventions a maintainer would otherwise flag by hand, so getting them out of the way first makes the human review shorter.

This is automated and advisory. It does not block merging, and a maintainer may well tell you to ignore some of it.

rule count what it checks
TRF023 2 Layer dimensions must come from the config, not from an integer literal in the modeling file.
TRF025 1 A module whose forward only delegates to its single submodule adds nothing; inline it.
TRF026 1 Model files must raise explicit errors instead of using bare assert.
TRF027 1 Model files must carry the Apache 2.0 license header.
TRF028 1 A module taking config must not also take arguments that live on the config.
TRF031 1 Masked positions must be filled with torch.finfo(dtype).min, not a magic negative number.

…and one of the inline comments:

TRF026 — bare assert in a model file. Raise a ValueError with an actionable message instead; asserts vanish under python -O.

Why this matters

python -O strips assert statements, so a shape or config check written as an assert silently disappears in optimised runs. An assert also gives the user a bare AssertionError with no guidance, where a ValueError can name the offending value and what to do about it.

def forward(self, hidden_states):
-    assert hidden_states.dim() == 3
+    if hidden_states.dim() != 3:
+        raise ValueError(f"Expected a 3D tensor, got shape {tuple(hidden_states.shape)}.")

If this model genuinely needs to deviate, add # trf-ignore: TRF026 above the line.

Why

New model PRs get their structural conventions taught by hand, one line comment at a time, across several rounds. Across the 52 PRs in this repo that drew more than 100 review comments, reuse an existing model appears in 50 of them, config hygiene in 51, naming in 51, tests in 52 — and one reviewer carries 40% of that labour. The linter already knows most of it; nobody sees its output until a maintainer runs it.

Triggering

opened and reopened only, and the poster refuses to post twice on the same PR (it looks for its own marker in existing reviews). This is a first pass, not a bot that comments on every push.

Security

Split across two workflows, following pr-ci-post-dashboard-link.yml:

  • mlinter.ymlpull_request, contents: read. Runs the linter over untrusted PR code and only uploads an artifact. No token that can write anything.
  • mlinter-review.ymlworkflow_run, holds pull-requests: write, never checks out PR code. It downloads the artifact and fetches the review script by github.workflow_sha rather than from the PR branch, so a PR cannot alter the script that runs with write access.

No pull_request_target and no checkout of a fork's head in a privileged context.

Robustness

  • Comments are anchored only to lines that are actually in the diff (the patch is parsed to find them); anything else goes into a collapsed "points at lines outside this diff" list rather than 422-ing the whole review.
  • Capped at 40 inline comments and 10 per file, so one noisy file cannot bury the review.
  • If the API rejects the anchors anyway (force-push race, renamed file), it falls back to a summary-only review instead of losing the run.
  • A missing artifact, missing PR number or zero findings all exit cleanly without posting.

Testing

tests/utils/test_post_mlinter_review.py — 9 tests over the diff parser (hunk headers with and without counts, deletions excluded, malformed patches) and the rendering. I also ran the collector end-to-end against a synthetic new model in a real checkout to produce the output quoted above.

One note: my first attempt to test this planted a violation in modeling_llama.py and got zero findings — llama predates the rules' cutoff_date, so it is exempt. Worth knowing if you test this by hand: use a new model directory.

Depends on

The rules quoted above (TRF023, TRF025TRF031) are in flight in huggingface/transformers-mlinter#15, #16, #18 and #19. This PR works with whatever is released — it reads DEFAULT_ENABLED_TRF_RULES at runtime — but it pins transformers-mlinter>=0.1.2, which today ships TRF001TRF021. Bump that floor once the new rules are released if you want them included.

New model PRs currently get their structural conventions taught by hand,
one line comment at a time, across several review rounds: reuse the MLP
that exists, read the dimension off the config, drop the wrapper module,
name it hidden_size. The linter already knows all of it, but nobody sees
its output until a maintainer runs it.

This runs transformers-mlinter on the model files a PR touches and posts
the result as a single review with inline comments, so the mechanical pass
is done before a human starts reading. Each comment carries the rule id,
what to change, a collapsed rationale with an example diff, and the
`# trf-ignore: TRFxxx` escape hatch. It is advisory and does not block.

Fires on `opened`/`reopened` only, and refuses to post twice on the same
PR, so it is a first pass rather than a bot commenting on every push.

Split across two workflows for the usual reason: the job that runs
untrusted PR code has `contents: read` and only uploads an artifact, and a
separate `workflow_run` job holds `pull-requests: write` and never checks
out PR code. Follows the pattern already used by
pr-ci-post-dashboard-link.yml.

- utils/collect_mlinter_findings.py writes findings + the rule
  explanations that fired to JSON.
- utils/post_mlinter_review.py turns that into the review, anchoring each
  comment to a line that is actually in the diff and listing the rest in a
  collapsed summary. Falls back to a summary-only review if the API
  rejects the anchors.
- tests/utils/test_post_mlinter_review.py covers the diff parser and the
  rendering.
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 30892154792
Result: success | Grafana metrics are not available yet.

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@stevhliu stevhliu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PR: #47760
Decision: NO
Reasoning: This PR adds GitHub Actions workflows and helper/test code for an automated, advisory model-linter review on model PRs. It changes CI/contributor automation only; it introduces no public API, runtime behavior, common user workflow, dependency requirement, CLI, or migration.
Docs to update: None
Notes: Existing docs/source/en/modeling_rules.md and docs/source/en/pr_checks.md already cover the model-structure rules, local linter commands, suppressions, and pull-request checks.

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.

3 participants