diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b766e6f2d3..0f7a0da210 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,6 +57,12 @@ repos: - repo: local hooks: + - id: normalize-yaml-ext + name: normalize .yml to .yaml in required places, right now only yaml files in modelopt_recipes + entry: python tools/precommit/normalize_yaml_ext.py + language: system + files: ^modelopt_recipes/.*\.yml$ + - id: check-modelopt-recipes name: validate modelopt recipes entry: python tools/precommit/check_modelopt_recipes.py diff --git a/modelopt_recipes/general/ptq/fp8_default-fp8_kv.yml b/modelopt_recipes/general/ptq/fp8_default-fp8_kv.yaml similarity index 100% rename from modelopt_recipes/general/ptq/fp8_default-fp8_kv.yml rename to modelopt_recipes/general/ptq/fp8_default-fp8_kv.yaml diff --git a/modelopt_recipes/general/ptq/nvfp4_default-fp8_kv.yml b/modelopt_recipes/general/ptq/nvfp4_default-fp8_kv.yaml similarity index 100% rename from modelopt_recipes/general/ptq/nvfp4_default-fp8_kv.yml rename to modelopt_recipes/general/ptq/nvfp4_default-fp8_kv.yaml diff --git a/modelopt_recipes/general/ptq/nvfp4_default-none_kv_gptq.yml b/modelopt_recipes/general/ptq/nvfp4_default-none_kv_gptq.yaml similarity index 100% rename from modelopt_recipes/general/ptq/nvfp4_default-none_kv_gptq.yml rename to modelopt_recipes/general/ptq/nvfp4_default-none_kv_gptq.yaml diff --git a/modelopt_recipes/general/ptq/nvfp4_experts_only-fp8_kv.yml b/modelopt_recipes/general/ptq/nvfp4_experts_only-fp8_kv.yaml similarity index 100% rename from modelopt_recipes/general/ptq/nvfp4_experts_only-fp8_kv.yml rename to modelopt_recipes/general/ptq/nvfp4_experts_only-fp8_kv.yaml diff --git a/modelopt_recipes/general/ptq/nvfp4_mlp_only-fp8_kv.yml b/modelopt_recipes/general/ptq/nvfp4_mlp_only-fp8_kv.yaml similarity index 100% rename from modelopt_recipes/general/ptq/nvfp4_mlp_only-fp8_kv.yml rename to modelopt_recipes/general/ptq/nvfp4_mlp_only-fp8_kv.yaml diff --git a/modelopt_recipes/general/ptq/nvfp4_omlp_only-fp8_kv.yml b/modelopt_recipes/general/ptq/nvfp4_omlp_only-fp8_kv.yaml similarity index 100% rename from modelopt_recipes/general/ptq/nvfp4_omlp_only-fp8_kv.yml rename to modelopt_recipes/general/ptq/nvfp4_omlp_only-fp8_kv.yaml diff --git a/tests/unit/recipe/test_loader.py b/tests/unit/recipe/test_loader.py index d1277d40c0..b8da2d140f 100644 --- a/tests/unit/recipe/test_loader.py +++ b/tests/unit/recipe/test_loader.py @@ -85,21 +85,21 @@ def test_load_config_missing_file_raises(tmp_path): def test_load_recipe_builtin_with_suffix(): """load_recipe loads a built-in PTQ recipe given the full YAML path.""" - recipe = load_recipe("general/ptq/fp8_default-fp8_kv.yml") + recipe = load_recipe("general/ptq/fp8_default-fp8_kv.yaml") assert recipe.recipe_type == RecipeType.PTQ assert isinstance(recipe, ModelOptPTQRecipe) assert recipe.quantize def test_load_recipe_builtin_without_suffix(): - """load_recipe resolves the .yml suffix automatically.""" + """load_recipe resolves the .yaml suffix automatically.""" recipe = load_recipe("general/ptq/fp8_default-fp8_kv") assert recipe.recipe_type == RecipeType.PTQ def test_load_recipe_builtin_description(): """The description field is loaded from the YAML metadata.""" - recipe = load_recipe("general/ptq/fp8_default-fp8_kv.yml") + recipe = load_recipe("general/ptq/fp8_default-fp8_kv.yaml") assert isinstance(recipe.description, str) assert len(recipe.description) > 0 @@ -196,10 +196,10 @@ def test_load_recipe_dir_missing_quantize_raises(tmp_path): @pytest.mark.parametrize( ("yaml_path", "model_cfg_name", "kv_cfg_name"), [ - ("general/ptq/fp8_default-fp8_kv.yml", "FP8_DEFAULT_CFG", "FP8_KV_CFG"), - ("general/ptq/nvfp4_default-fp8_kv.yml", "NVFP4_DEFAULT_CFG", "FP8_KV_CFG"), - ("general/ptq/nvfp4_mlp_only-fp8_kv.yml", "NVFP4_MLP_ONLY_CFG", "FP8_KV_CFG"), - ("general/ptq/nvfp4_omlp_only-fp8_kv.yml", "NVFP4_OMLP_ONLY_CFG", "FP8_KV_CFG"), + ("general/ptq/fp8_default-fp8_kv.yaml", "FP8_DEFAULT_CFG", "FP8_KV_CFG"), + ("general/ptq/nvfp4_default-fp8_kv.yaml", "NVFP4_DEFAULT_CFG", "FP8_KV_CFG"), + ("general/ptq/nvfp4_mlp_only-fp8_kv.yaml", "NVFP4_MLP_ONLY_CFG", "FP8_KV_CFG"), + ("general/ptq/nvfp4_omlp_only-fp8_kv.yaml", "NVFP4_OMLP_ONLY_CFG", "FP8_KV_CFG"), ], ) def test_general_ptq_yaml_matches_config_dicts(yaml_path, model_cfg_name, kv_cfg_name): diff --git a/tools/precommit/normalize_yaml_ext.py b/tools/precommit/normalize_yaml_ext.py new file mode 100644 index 0000000000..f4e8e19c4f --- /dev/null +++ b/tools/precommit/normalize_yaml_ext.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Pre-commit hook: normalize .yml to .yaml in modelopt_recipes/. + +Standardizes YAML file extensions to ``.yaml`` for consistency. When a +``.yml`` file is detected, it is renamed to ``.yaml`` and the hook exits +with code 1 so the user can re-stage and commit. +""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + + +def main() -> int: + """Rename .yml files to .yaml, exit 1 if any were renamed.""" + renamed: list[tuple[Path, Path]] = [] + collisions: list[tuple[Path, Path]] = [] + for f in sys.argv[1:]: + path = Path(f) + if path.suffix == ".yml" and path.is_file(): + new_path = path.with_suffix(".yaml") + if new_path.exists(): + collisions.append((path, new_path)) + continue + os.rename(path, new_path) + renamed.append((path, new_path)) + + if collisions: + for old, new in collisions: + print(f"ERROR: Cannot rename {old} -> {new} (destination already exists)") + return 1 + + if renamed: + for old, new in renamed: + print(f"Renamed: {old} -> {new}") + print( + f"\n{len(renamed)} file(s) renamed from .yml to .yaml. " + "Please re-stage the changes and commit again." + ) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())