Skip to content

Add pre-commit hook to validate modelopt recipes#1215

Open
shengliangxu wants to merge 2 commits intomainfrom
shengliangx/error-old-quant-cfg
Open

Add pre-commit hook to validate modelopt recipes#1215
shengliangxu wants to merge 2 commits intomainfrom
shengliangx/error-old-quant-cfg

Conversation

@shengliangxu
Copy link
Copy Markdown
Collaborator

@shengliangxu shengliangxu commented Apr 9, 2026

What does this PR do?

Reject modelopt recipes that are in someway invalid.

Right now check:

  1. PTQ YAML quant_cfg need to be in list format instead of the deprecated dict-format.

  2. Check general recipe validity. It checks correct top-level keys (e.g. ptq_cfg instead of quantize). And when modelopt is installed, each recipe is also loaded via load_recipe() to catch structural and validation errors.

The hook only works on changed or newly added recipes.

Testing

local test using invalid recipes

Summary by CodeRabbit

  • Chores
    • Added automated validation checks for ModelOpt recipe files to ensure proper structure and configuration compliance in the development workflow.

Reject modelopt recipes that are in some way invalid.

Right now check:

1. PTQ YAML quant_cfg need to be in list format instead of the deprecated dict-format.

2. Check general recipe validity. It checks correct top-level keys (e.g.
   ptq_cfg instead of quantize). And when modelopt is installed, each
   recipe is also loaded via load_recipe() to catch structural and
   validation errors.

The hook only works on changed or newly added recipes.

Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
@shengliangxu shengliangxu requested a review from a team as a code owner April 9, 2026 02:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

The PR introduces a new pre-commit hook for validating ModelOpt recipe files, consisting of a configuration entry and a new validation script that checks recipe structure, metadata, and quantizer configurations.

Changes

Cohort / File(s) Summary
Pre-commit Configuration
.pre-commit-config.yaml
Added new local pre-commit hook check-modelopt-recipes that runs validation script on files under modelopt_recipes/ directory.
Recipe Validation Script
tools/precommit/check_modelopt_recipes.py
New script that validates ModelOpt recipe files by resolving paths to single-file or directory-format recipes, parsing YAML, checking metadata and quantizer configuration structure, and attempting to load recipes via ModelOpt loader with error aggregation and reporting.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a pre-commit hook to validate modelopt recipes, which is exactly what the pull request does.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Security Anti-Patterns ✅ Passed The pull request contains no critical security issues: no eval()/exec() calls, safe yaml.safe_load() usage, no # nosec comments, no hardcoded dangerous parameters, and safe pathlib.Path handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch shengliangx/error-old-quant-cfg

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-1215/

Built to branch gh-pages at 2026-04-09 06:11 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/precommit/check_modelopt_recipes.py`:
- Around line 79-82: The current logic returns an empty list when metadata is
missing or not a dict (metadata variable) which causes malformed recipes to be
treated as non-recipes; change those early returns to append descriptive error
messages to the errors list (e.g., "missing or invalid metadata" / "missing
metadata.recipe_type") and continue validation instead of returning [];
similarly replace the other return at the similar check with an errors.append so
all malformed single-file recipes produce errors; finally, when importing
modelopt (the modelopt import block), ensure import failures do not
short-circuit validation — either fall back to a minimal in-file schema check
and append a clear import-failure error to errors or catch ImportError and
continue with basic validations so invalid recipes are still reported rather
than silently passing.
- Around line 50-53: The loop over quant_cfg currently skips non-dict entries
which allows malformed configs; change the behavior in the for i, entry in
enumerate(quant_cfg) loop to treat any non-dict entry as a failure: when not
isinstance(entry, dict) raise or return a clear error/exit (include the index i
and the offending value) instead of continue, and keep the existing
"quantizer_name" presence check for dict entries so the hook strictly enforces a
list-of-dicts for quant_cfg.
- Around line 106-116: The loop over ("quantize.yml","quantize.yaml") currently
always executes break unconditionally which prevents reporting a missing
quantize file; fix by moving the break so it only runs when a
quantize_file.is_file() branch is hit (i.e., break inside that if) and after the
loop check whether a quantize file was found—if not, append an error to errors
(use the same errors list) indicating the quantize.yml|yaml is missing; keep
using _load_yaml, quant_cfg, _check_quant_cfg and the quantize_file/dir_path
variables to locate and validate the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 46086238-1377-4e9d-8d5e-b2ee95b831b8

📥 Commits

Reviewing files that changed from the base of the PR and between cccfded and 33e5b57.

📒 Files selected for processing (2)
  • .pre-commit-config.yaml
  • tools/precommit/check_modelopt_recipes.py

Comment on lines +50 to +53
for i, entry in enumerate(quant_cfg):
if not isinstance(entry, dict):
continue
if "quantizer_name" not in entry:
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.

⚠️ Potential issue | 🟠 Major

Enforce list-of-dicts strictly in quant_cfg.

At Line 52, non-dict entries are silently ignored, so malformed quant_cfg can pass the hook.

Proposed fix
 elif isinstance(quant_cfg, list):
     for i, entry in enumerate(quant_cfg):
         if not isinstance(entry, dict):
-            continue
+            errors.append(
+                f"{label}: quant_cfg[{i}] must be a mapping with an explicit 'quantizer_name' key."
+            )
+            continue
         if "quantizer_name" not in entry:
             errors.append(
                 f"{label}: quant_cfg[{i}] is missing 'quantizer_name'. "
                 "Each entry must have an explicit 'quantizer_name' key. "
                 "See https://nvidia.github.io/Model-Optimizer/guides/_quant_cfg.html"
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i, entry in enumerate(quant_cfg):
if not isinstance(entry, dict):
continue
if "quantizer_name" not in entry:
for i, entry in enumerate(quant_cfg):
if not isinstance(entry, dict):
errors.append(
f"{label}: quant_cfg[{i}] must be a mapping with an explicit 'quantizer_name' key."
)
continue
if "quantizer_name" not in entry:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/precommit/check_modelopt_recipes.py` around lines 50 - 53, The loop
over quant_cfg currently skips non-dict entries which allows malformed configs;
change the behavior in the for i, entry in enumerate(quant_cfg) loop to treat
any non-dict entry as a failure: when not isinstance(entry, dict) raise or
return a clear error/exit (include the index i and the offending value) instead
of continue, and keep the existing "quantizer_name" presence check for dict
entries so the hook strictly enforces a list-of-dicts for quant_cfg.

Comment on lines +79 to +82
metadata = data.get("metadata")
if not isinstance(metadata, dict) or "recipe_type" not in metadata:
return [] # not a recipe file

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.

⚠️ Potential issue | 🟠 Major

Don’t treat missing recipe fields as “not a recipe.”

At Line 81 and Line 92, malformed single-file recipes can return no errors. If modelopt isn’t importable (Line 123), these invalid recipes pass pre-commit.

Proposed fix
 metadata = data.get("metadata")
-if not isinstance(metadata, dict) or "recipe_type" not in metadata:
-    return []  # not a recipe file
+if not isinstance(metadata, dict):
+    return [f"{label}: missing required top-level 'metadata' mapping."]
+if "recipe_type" not in metadata:
+    return [f"{label}: missing required 'metadata.recipe_type' field."]

 if "ptq_cfg" in data and "quantize" not in data:
     errors.append(
         f"{label}: uses 'ptq_cfg' as the top-level key. "
         "PTQ recipes must use 'quantize' instead."
     )
     quant_section = data["ptq_cfg"]
 elif "quantize" in data:
     quant_section = data["quantize"]
 else:
-    return errors
+    return [f"{label}: PTQ recipe must contain top-level 'quantize'."]

Also applies to: 91-92

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/precommit/check_modelopt_recipes.py` around lines 79 - 82, The current
logic returns an empty list when metadata is missing or not a dict (metadata
variable) which causes malformed recipes to be treated as non-recipes; change
those early returns to append descriptive error messages to the errors list
(e.g., "missing or invalid metadata" / "missing metadata.recipe_type") and
continue validation instead of returning []; similarly replace the other return
at the similar check with an errors.append so all malformed single-file recipes
produce errors; finally, when importing modelopt (the modelopt import block),
ensure import failures do not short-circuit validation — either fall back to a
minimal in-file schema check and append a clear import-failure error to errors
or catch ImportError and continue with basic validations so invalid recipes are
still reported rather than silently passing.

Comment on lines +106 to +116
for name in ("quantize.yml", "quantize.yaml"):
quantize_file = dir_path / name
if quantize_file.is_file():
data = _load_yaml(quantize_file)
if data is not None:
quant_cfg = data.get("quant_cfg")
if quant_cfg is not None:
errors.extend(_check_quant_cfg(quant_cfg, str(quantize_file)))
break

return errors
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.

⚠️ Potential issue | 🟠 Major

Directory recipes should fail when quantize.yml|yaml is missing.

Currently this function returns no error if neither quantize file exists, which creates another false-negative path when loader validation is skipped.

Proposed fix
 def _check_dir_recipe(dir_path: Path) -> list[str]:
     """Check a directory-format recipe (recipe.yml + quantize.yml)."""
     errors: list[str] = []
+    found_quantize = False

     for name in ("quantize.yml", "quantize.yaml"):
         quantize_file = dir_path / name
         if quantize_file.is_file():
+            found_quantize = True
             data = _load_yaml(quantize_file)
             if data is not None:
                 quant_cfg = data.get("quant_cfg")
                 if quant_cfg is not None:
                     errors.extend(_check_quant_cfg(quant_cfg, str(quantize_file)))
             break
+    if not found_quantize:
+        errors.append(
+            f"{dir_path}: missing quantize file. Looked for quantize.yml or quantize.yaml."
+        )

     return errors
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/precommit/check_modelopt_recipes.py` around lines 106 - 116, The loop
over ("quantize.yml","quantize.yaml") currently always executes break
unconditionally which prevents reporting a missing quantize file; fix by moving
the break so it only runs when a quantize_file.is_file() branch is hit (i.e.,
break inside that if) and after the loop check whether a quantize file was
found—if not, append an error to errors (use the same errors list) indicating
the quantize.yml|yaml is missing; keep using _load_yaml, quant_cfg,
_check_quant_cfg and the quantize_file/dir_path variables to locate and validate
the file.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.73%. Comparing base (04cd596) to head (af395e0).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1215      +/-   ##
==========================================
- Coverage   75.68%   71.73%   -3.95%     
==========================================
  Files         353      353              
  Lines       40491    40491              
==========================================
- Hits        30644    29048    -1596     
- Misses       9847    11443    +1596     
Flag Coverage Δ
unit 55.25% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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