From 8bbe24c903c88b544917e7bacd385dce62bb0692 Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Thu, 17 Sep 2026 11:50:24 -0700 Subject: [PATCH 01/20] Build the ONNX round-and-pack extension outside the per-test timeout The windows unit job fails often, and always the same way: a test dies on pytest-timeout while MSVC is still running, with INFO - Loading extension modelopt_round_and_pack_ext... .rendered.modelopt_round_and_pack_ext.cpp ... msvc.compile -> subprocess.wait +++ Timeout +++ modelopt/onnx/quantization/extensions.py runs cppimport.imp at module import, and that module is imported lazily from inside quant_utils.round_and_pack. So the first test that needs it pays a full C++ compile inside its own per-test timeout. Which test pays depends on collection order, which is why the failure appears to move around. pyproject sets timeout_func_only, so the per-test clock covers the call only. Importing the module from a session-scoped autouse fixture puts the build outside it. tests/gpu_megatron/conftest.py already does this for the quant CUDA extensions, for the same reason. It cannot reuse that helper: load_cpp_extension skips every quant extension when CUDA is unavailable, which is the case on the CPU-only windows runner, so precompile() would warm nothing there. modelopt_round_and_pack_ext is a different loader (cppimport) and is not CUDA-gated, which is exactly why it is the one that builds on that runner. Best-effort: extensions.py already falls back to a Python implementation when the build fails, so a failed prebuild must not fail the session. Verified: the fixture is collected at session scope (pytest --setup-plan shows SETUP S _prebuild_onnx_round_and_pack_ext) and is a clean no-op where cppimport is absent. NOT verified locally that it fixes the timeout -- this environment has neither onnxruntime nor cppimport, so tests/unit/onnx cannot run here and the extension never builds. The windows job on this PR is the real test. Signed-off-by: Shengliang Xu --- tests/unit/conftest.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index f397205f022..aa2a675b665 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -16,6 +16,8 @@ import contextlib import os +import pytest + # Enforce no HuggingFace Hub network access for unit tests os.environ["HF_HUB_OFFLINE"] = "1" os.environ["HF_DATASETS_OFFLINE"] = "1" @@ -25,3 +27,28 @@ import huggingface_hub.constants as _hf_constants _hf_constants.HF_HUB_OFFLINE = True + + +@pytest.fixture(scope="session", autouse=True) +def _prebuild_onnx_round_and_pack_ext(): + """Build the ONNX round-and-pack extension before per-test timeouts start. + + ``modelopt/onnx/quantization/extensions.py`` runs ``cppimport.imp`` at module import, and + that module is imported lazily from inside ``quant_utils.round_and_pack``. So the first test + to need it pays a full C++ compile INSIDE its own per-test timeout -- on the Windows runner + that is an MSVC build measured in minutes, and the test dies with pytest-timeout while + ``compiler.compile`` is still running. Which test pays is down to collection order, so the + failure appears to wander between runs. + + ``pyproject`` sets ``timeout_func_only``, so the per-test clock covers the call only; doing + the import here in session setup puts the build outside it. This mirrors + ``tests/gpu_megatron/conftest.py``, which prebuilds the quant CUDA extensions for the same + reason -- but it cannot reuse that helper: ``load_cpp_extension`` skips every quant extension + when CUDA is unavailable, which is exactly the case on the CPU-only Windows runner, so + ``precompile()`` would warm nothing here. + + Best-effort. The extension is an optimisation with a Python fallback -- ``extensions.py`` + already swallows its own build failures -- so a failure to prebuild must not fail the session. + """ + with contextlib.suppress(Exception): + import modelopt.onnx.quantization.extensions # noqa: F401 From 83c6bfff3bdf38dd4ef08aa155d470657a29fccb Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Thu, 17 Sep 2026 12:35:26 -0700 Subject: [PATCH 02/20] Run the windows unit job in UTF-8 mode Windows defaults text I/O to the locale codepage (cp1252 on these runners), so any read of a UTF-8 file without an explicit encoding= dies with UnicodeDecodeError on the first non-Latin-1 byte -- a failure no other platform sees. PEP 540 UTF-8 mode makes the whole test process read UTF-8 regardless of locale, which covers the test tree without touching call sites. It does not replace explicit encodings in library code: a user process will not have PYTHONUTF8 set, so modelopt must still say what it means. Python 3.15 makes UTF-8 mode the default (PEP 686), at which point this line can go. Signed-off-by: Shengliang Xu --- .github/workflows/unit_tests.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 2eea7332252..c561048f73a 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -92,6 +92,13 @@ jobs: with: python-version: "3.12" - name: Run unit tests (without coverage) + # PEP 540 UTF-8 mode. Windows defaults text I/O to the locale codepage (cp1252 on these + # runners), so any read of a UTF-8 file without an explicit encoding= dies with + # UnicodeDecodeError on the first non-Latin-1 byte -- a failure no other platform sees. + # This makes the whole test process read UTF-8 regardless of locale. It does NOT excuse + # library code from passing encoding= explicitly: user processes will not have this set. + env: + PYTHONUTF8: "1" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" multi-version: if: needs.check-file-changes.outputs.any_changed == 'true' From c581ab96af19656c0028378ff2b8878ac43ebd9b Mon Sep 17 00:00:00 2001 From: Shengliang Xu Date: Thu, 17 Sep 2026 12:35:39 -0700 Subject: [PATCH 03/20] Make text I/O encoding explicit, and keep it that way Text I/O without an explicit encoding uses the locale codepage. On Windows that is cp1252, so reading a UTF-8 file raises UnicodeDecodeError on the first non-Latin-1 byte and writing non-ASCII raises UnicodeEncodeError -- failures no other platform sees, and ones a library cannot dismiss as a CI problem because a user process will not have PYTHONUTF8 set. Two classes, because ruff only implements one of them: - 259 calls, fixed by PLW1514 --unsafe-fixes. Unsafe is the right label: the fix deliberately changes behaviour from locale-dependent to UTF-8, which is the point. - 345 Path.read_text/write_text calls, fixed by script. PLW1514 does not cover these -- modelopt/recipe/loader.py passes the rule clean while reading recipe YAML through the locale codec, which is exactly the shape that would bite a Windows user. PLW1514 is enabled so this cannot come back, but it is still a preview rule and plain also switches on preview BEHAVIOUR for the stable rules already selected -- 3755 findings on this tree. explicit-preview-rules contains it to the one rule named. Preview did surface 20 real findings in stable rules (18 C419, 2 F401); those are fixed here too. A pygrep pre-commit hook covers read_text/write_text, since enabling PLW1514 alone would look like the class was policed while 88 known sites stayed invisible to it. 43 files needed reformatting afterwards: the added kwarg pushed lines over the limit. Signed-off-by: Shengliang Xu --- .pre-commit-config.yaml | 11 + examples/alpamayo/quantize.py | 4 +- examples/deepseek/deepseek_v3/ptq.py | 4 +- .../deepseek/deepseek_v3/quantize_to_nvfp4.py | 12 +- examples/deepseek/deepseek_v4/ptq.py | 6 +- .../deepseek/deepseek_v4/quantize_to_nvfp4.py | 12 +- .../distillation/distillation_trainer.py | 8 +- examples/diffusers/fastgen/dmd2_recipe.py | 4 +- .../fastgen/export_diffusers_qwen_image.py | 4 +- .../fastgen/inference_dmd2_qwen_image.py | 2 +- .../preprocess/preprocessing_multiprocess.py | 4 +- examples/diffusers/quantization/utils.py | 2 +- .../gpt-oss/convert_oai_mxfp4_weight_only.py | 4 +- examples/hf_ptq/example_utils.py | 8 +- examples/kimi/kimi_k3/quantize_to_nvfp4.py | 24 +- examples/llm_eval/lm_eval_hf.py | 2 +- examples/llm_eval/modeling.py | 3 +- examples/llm_eval/simple_evals.py | 6 +- examples/llm_qat/dataset_utils.py | 2 +- examples/llm_qat/export.py | 4 +- .../llm_sparsity/weight_sparsity/data_prep.py | 4 +- examples/llm_sparsity/weight_sparsity/eval.py | 2 +- .../minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py | 13 +- examples/onnx_ptq/evaluate.py | 2 +- examples/onnx_ptq/evaluation.py | 2 +- .../scenario2_puzzletron.ipynb | 4 +- examples/specdec_bench/run.py | 2 +- .../specdec_bench/datasets/mtbench.py | 2 +- .../specdec_bench/datasets/specbench.py | 2 +- .../specdec_bench/metrics/aa_timing.py | 4 +- .../specdec_bench/metrics/acceptance_rate.py | 4 +- .../specdec_bench/metrics/base.py | 4 +- .../specdec_bench/metrics/mtbench.py | 4 +- .../specdec_bench/metrics/specbench.py | 8 +- .../specdec_bench/metrics/timing.py | 8 +- examples/specdec_bench/specdec_bench/utils.py | 8 +- examples/specdec_bench/upload_to_s3.py | 2 +- .../collect_hidden_states/common.py | 2 +- .../send_conversations_for_hiddens.py | 2 +- examples/speculative_decoding/eagle_utils.py | 4 +- examples/speculative_decoding/example.ipynb | 8 +- examples/speculative_decoding/medusa_utils.py | 4 +- .../scripts/calibrate_draft_vocab.py | 2 +- .../scripts/quantize_drafter.py | 8 +- .../scripts/send_conversation_vllm.py | 2 +- .../scripts/server_generate.py | 12 +- examples/torch_trt/torch_tensorrt_accuracy.py | 2 +- examples/torch_trt/torch_tensorrt_ptq.py | 2 +- .../fvd_metrics/compute_fvd.py | 2 +- .../compute_kl_divergence.py | 2 +- .../accuracy_benchmark/mmlu_benchmark.py | 4 +- .../perplexity_metrics/perplexity_metrics.py | 2 +- .../accuracy_benchmark/trtllm_utils.py | 6 +- .../sample_example_qad_diffusers.py | 8 +- experimental/dms/models/qwen3/train.py | 8 +- modelopt/deploy/llm/generate.py | 2 +- .../onnx/graph_surgery/utils/whisper_utils.py | 12 +- .../onnx/llm_export_utils/export_utils.py | 2 +- .../quantization/autotune/autotuner_base.py | 4 +- .../onnx/quantization/autotune/benchmark.py | 2 +- modelopt/onnx/quantization/autotune/common.py | 4 +- .../quantization/autotune/region_search.py | 4 +- modelopt/onnx/quantization/autotune/utils.py | 2 +- modelopt/onnx/quantization/calib_utils.py | 2 +- modelopt/onnx/trt_utils.py | 4 +- modelopt/recipe/loader.py | 4 +- modelopt/torch/_deploy/_runtime/common.py | 4 +- modelopt/torch/_deploy/_runtime/ort_client.py | 2 +- modelopt/torch/distill/plugins/megatron.py | 2 +- modelopt/torch/export/layerwise_export.py | 2 +- .../export/plugins/hf_checkpoint_utils.py | 4 +- .../torch/export/plugins/hf_spec_export.py | 10 +- modelopt/torch/export/plugins/mcore_custom.py | 14 +- .../export/trtllm/model_config_export.py | 4 +- modelopt/torch/export/unified_export_hf.py | 22 +- .../export/unified_export_hf_streaming.py | 2 +- .../torch/export/unified_export_megatron.py | 12 +- modelopt/torch/nas/hparams/concat.py | 2 +- modelopt/torch/opt/plugins/transformers.py | 6 +- modelopt/torch/opt/searcher.py | 4 +- modelopt/torch/prune/fastnas.py | 10 +- .../prune/importance_hooks/base_hooks.py | 2 +- .../compare_module_outputs.py | 2 +- .../puzzletron/anymodel/converter/base.py | 2 +- .../models/gpt_oss/gpt_oss_pruned_to_mxfp4.py | 10 +- modelopt/torch/puzzletron/mip/run_puzzle.py | 20 +- modelopt/torch/puzzletron/mip/sweep.py | 8 +- .../torch/puzzletron/pruning/pruning_utils.py | 2 +- .../build_replacement_library.py | 2 +- .../puzzletron/replacement_library/library.py | 2 +- .../calc_subblock_params_and_memory.py | 2 +- .../subblock_stats/calc_subblock_stats.py | 6 +- .../subblock_stats/runtime_utils.py | 8 +- .../puzzletron/subblock_stats/runtime_vllm.py | 6 +- .../tools/bypassed_training/child_init.py | 6 +- .../puzzletron/tools/checkpoint_utils.py | 6 +- .../tools/sharded_checkpoint_utils.py | 2 +- .../torch/puzzletron/tools/validate_model.py | 4 +- ...validate_puzzle_with_multi_replacements.py | 5 +- .../puzzletron/utils/checkpoint_manager.py | 4 +- modelopt/torch/puzzletron/utils/misc.py | 2 +- .../torch/quantization/plugins/attention.py | 2 +- .../quantization/utils/layerwise_calib.py | 4 +- .../calibration/ruler_dataset.py | 6 +- .../speculative/plugins/modeling_fakebase.py | 2 +- modelopt/torch/utils/logging.py | 6 +- modelopt/torch/utils/mlflow.py | 10 +- .../torch/utils/plugins/model_load_utils.py | 2 +- modelopt/torch/utils/robust_json.py | 4 +- .../scripts/benchmark_via_builtin.py | 6 +- .../tests/test_benchmark_via_builtin.py | 2 +- .../day0-release/scripts/gate_compare.py | 4 +- .../skills/day0-release/scripts/gate_ptq.py | 2 +- .../skills/day0-release/scripts/gate_run.py | 2 +- .../day0-release/scripts/gate_verbosity.py | 4 +- .../tests/test_agent_definitions.py | 2 +- pyproject.toml | 11 + tests/_test_utils/deploy_utils.py | 4 +- .../examples/megatron_example_runner.py | 1 - .../examples/onnx_ptq/aggregate_results.py | 4 +- tests/_test_utils/torch/diffusers_models.py | 4 +- .../torch/export/unified_checkpoint.py | 2 +- .../torch/quantization/quant_utils.py | 2 +- .../fastgen/test_vendored_migration.py | 8 +- .../diffusers/sparsity/test_sparsity.py | 4 +- .../test_export_diffusers_hf_ckpt.py | 2 +- tests/examples/gpt-oss/test_gpt_oss_qat.py | 2 +- .../hf_ptq/test_cast_mxfp4_to_nvfp4.py | 8 +- tests/examples/hf_ptq/test_example_utils.py | 41 ++- tests/examples/hf_ptq/test_hf_ptq_args.py | 30 +- tests/examples/llm_qat/test_llm_qat.py | 4 +- .../examples/megatron_bridge/test_distill.py | 2 +- tests/examples/megatron_bridge/test_qad.py | 4 +- .../specdec_bench/test_upload_to_s3.py | 18 +- .../examples/speculative_decoding/conftest.py | 2 +- .../torch_onnx/test_torch_quant_to_onnx.py | 6 +- .../vllm_serve/test_vllm_mlflow_utils.py | 9 +- tests/gpu/onnx/quantization/test_plugin.py | 10 +- tests/gpu/onnx/test_ort_patching.py | 2 +- tests/gpu/onnx/test_simplify.py | 2 +- tests/gpu/torch/export/test_export.py | 2 +- .../gpu/torch/export/test_export_diffusers.py | 2 +- tests/gpu/torch/export/test_fsdp2_export.py | 4 +- .../gpu/torch/export/test_layerwise_export.py | 22 +- tests/gpu/torch/export/test_offload_export.py | 4 +- tests/gpu/torch/puzzletron/test_puzzletron.py | 2 +- .../tools/test_save_ckpt_from_shards.py | 8 +- .../plugins/test_accelerate_gpu.py | 8 +- .../test_gpt_oss_mxfp4_nvfp4_cast_cuda.py | 3 +- .../gpu/torch/utils/test_model_load_utils.py | 2 +- .../export/test_unified_export_megatron.py | 12 +- .../test_vllm_fakequant_megatron_export.py | 2 +- .../quantization/plugins/test_megatron.py | 6 +- .../torch/speculative/test_dflash.py | 4 +- .../torch/speculative/test_dflash_offline.py | 2 +- .../test_kimi_k3_quantize_to_nvfp4.py | 19 +- .../onnx/autocast/test_referencerunner.py | 8 +- .../quantization/autotune/test_autotuner.py | 4 +- .../autotune/test_pattern_cache.py | 4 +- tests/unit/recipe/test_loader.py | 258 +++++++++++------- tests/unit/test_example_run_command.py | 2 +- tests/unit/tools/test_resource_monitor.py | 6 +- .../_runtime/tensorrt/test_engine_builder.py | 4 +- .../torch/export/test_export_diffusers.py | 6 +- .../export/test_fsdp2_parallel_export.py | 4 +- .../torch/export/test_hf_checkpoint_utils.py | 40 +-- .../export/test_mcore_save_safetensors.py | 4 +- tests/unit/torch/export/test_nvfp4_utils.py | 2 +- .../unit/torch/export/test_offload_export.py | 6 +- .../torch/export/test_shard_cast_utils.py | 18 +- .../unit/torch/opt/plugins/test_lr_config.py | 6 +- .../opt/plugins/test_modelopt_arg_parser.py | 12 +- .../puzzletron/test_checkpoint_utils_hf.py | 4 +- .../quantization/test_layerwise_calibrate.py | 8 +- .../test_sequential_checkpoint.py | 4 +- .../speculative/plugins/test_fakebase.py | 2 +- .../speculative/plugins/test_hf_dflash.py | 4 +- .../speculative/plugins/test_hf_domino.py | 2 +- .../speculative/plugins/test_hf_dspark.py | 10 +- .../speculative/plugins/test_hf_lilicorr.py | 4 +- tests/unit/torch/utils/test_mlflow.py | 30 +- .../unit/torch/utils/test_model_load_utils.py | 3 +- tools/launcher/common/check_regression.py | 2 +- tools/launcher/common/query.py | 2 +- tools/launcher/core.py | 6 +- tools/launcher/tests/conftest.py | 2 +- tools/launcher/tests/test_docker_execution.py | 2 +- tools/launcher/tests/test_docker_launch.py | 8 +- tools/launcher/tests/test_examples_resolve.py | 2 +- tools/launcher/tests/test_yaml_formats.py | 6 +- tools/mcp/modelopt_mcp/bridge.py | 4 +- tools/mcp/tests/test_bridge.py | 93 ++++--- tools/resource_monitor.py | 4 +- 193 files changed, 824 insertions(+), 628 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2f4fdd595e3..90e12a67674 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,16 @@ # NOTE: Make sure to update version in dev requirements (pyproject.toml) as well! repos: + - repo: local + hooks: + - id: explicit-text-encoding + name: read_text/write_text must pass encoding + description: > + ruff PLW1514 only covers `open`, so Path.read_text/write_text can silently use the + locale codepage -- cp1252 on Windows -- and fail on UTF-8 content. Keep them explicit. + language: pygrep + types: [python] + entry: '\.(read|write)_text\((?![^)]*encoding=)' + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: diff --git a/examples/alpamayo/quantize.py b/examples/alpamayo/quantize.py index 6a7b3d56f61..316af8b53a6 100644 --- a/examples/alpamayo/quantize.py +++ b/examples/alpamayo/quantize.py @@ -641,7 +641,9 @@ def main(): model.config.save_pretrained(args.output_dir) quant_cfg = get_quant_config(model) - with open(os.path.join(args.output_dir, "hf_quant_config.json"), "w") as f: + with open( + os.path.join(args.output_dir, "hf_quant_config.json"), "w", encoding="utf-8" + ) as f: json.dump(quant_cfg, f) print(f"Quantized checkpoint saved to {args.output_dir}") diff --git a/examples/deepseek/deepseek_v3/ptq.py b/examples/deepseek/deepseek_v3/ptq.py index 96e99624da9..1ee77e4cfca 100644 --- a/examples/deepseek/deepseek_v3/ptq.py +++ b/examples/deepseek/deepseek_v3/ptq.py @@ -326,7 +326,7 @@ def load_deepseek_model( torch.set_default_dtype(torch.bfloat16) # get config and build model - with open(model_config) as f: + with open(model_config, encoding="utf-8") as f: model_args = deekseep_model.ModelArgs(**json.load(f)) model_args.max_batch_size = max(batch_size, model_args.max_batch_size) with torch.device("cuda"): @@ -536,7 +536,7 @@ def state_dict_filter(state_dict): if quantized_layers: quant_config["quantization"]["quantized_layers"] = quantized_layers - with open(os.path.join(output_path, "hf_quant_config.json"), "w") as f: + with open(os.path.join(output_path, "hf_quant_config.json"), "w", encoding="utf-8") as f: json.dump(quant_config, f, indent=4) diff --git a/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py b/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py index e54fdbebf46..a21ace015e9 100644 --- a/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py +++ b/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py @@ -88,10 +88,10 @@ def remove_quantization_config_from_original_config(export_dir: str) -> None: Assumes the exported checkpoint directory has a `config.json` containing `quantization_config`. """ config_path = os.path.join(export_dir, "config.json") - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: cfg = json.load(f) del cfg["quantization_config"] - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(cfg, f, indent=2, sort_keys=True) f.write("\n") @@ -129,7 +129,7 @@ def load_and_preprocess_state_dict(modelopt_state_root, world_size=8): def process_quant_config(quant_config_path: str, save_path: str) -> dict[str, Any]: - with open(quant_config_path) as f: + with open(quant_config_path, encoding="utf-8") as f: quant_config = json.load(f) if "exclude_modules" in quant_config["quantization"]: @@ -142,7 +142,7 @@ def process_quant_config(quant_config_path: str, save_path: str) -> dict[str, An _remap_key(quant_config["quantization"]["quantized_layers"]) per_layer_quant_config = quant_config["quantization"]["quantized_layers"] - with open(save_path, "w") as f: + with open(save_path, "w", encoding="utf-8") as f: json.dump(quant_config, f, indent=4) return per_layer_quant_config @@ -173,7 +173,7 @@ def amax_to_fp8_scaling_factor(amax): torch.set_default_dtype(torch.bfloat16) model_index_file = os.path.join(fp8_root, "model.safetensors.index.json") os.makedirs(save_root, exist_ok=True) - with open(model_index_file) as f: + with open(model_index_file, encoding="utf-8") as f: model_index = json.load(f) weight_map = model_index["weight_map"] @@ -286,7 +286,7 @@ def get_tensor(tensor_name): scale_inv_name = f"{weight_name}_scale_inv" if scale_inv_name in weight_map: weight_map.pop(scale_inv_name) - with open(new_model_index_file, "w") as f: + with open(new_model_index_file, "w", encoding="utf-8") as f: json.dump({"metadata": {}, "weight_map": weight_map}, f, indent=2) diff --git a/examples/deepseek/deepseek_v4/ptq.py b/examples/deepseek/deepseek_v4/ptq.py index 89debc5d8a6..da16e96efed 100644 --- a/examples/deepseek/deepseek_v4/ptq.py +++ b/examples/deepseek/deepseek_v4/ptq.py @@ -247,7 +247,7 @@ def load_deepseek_v4( torch.cuda.set_device(local_rank) torch.set_default_dtype(torch.bfloat16) - with open(model_config) as f: + with open(model_config, encoding="utf-8") as f: margs = deekseep_v4_model.ModelArgs(**json.load(f)) margs.max_batch_size = max(batch_size, margs.max_batch_size) with torch.device("cuda"): @@ -534,7 +534,9 @@ def _trace(msg): assert m is not None merged.update(m["quantized_layers"]) manifest["quantized_layers"] = sorted(merged) - with open(os.path.join(output_path, "quantized_layers_manifest.json"), "w") as f: + with open( + os.path.join(output_path, "quantized_layers_manifest.json"), "w", encoding="utf-8" + ) as f: json.dump(manifest, f, indent=2) diff --git a/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py b/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py index ac164d7c7c0..8380e9f9158 100644 --- a/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py +++ b/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py @@ -433,7 +433,7 @@ def _rewrite_config_json( sibling ``hf_quant_config.json``. """ dst = dst_dir / "config.json" - cfg = json.loads(src.read_text()) + cfg = json.loads(src.read_text(encoding="utf-8")) quant_cfg = cfg.get("quantization_config") if not isinstance(quant_cfg, dict): quant_cfg = {} @@ -453,7 +453,7 @@ def _rewrite_config_json( quant_cfg.pop("exclude_modules", None) quant_cfg["ignore"] = moe_quantization["exclude_modules"] cfg["quantization_config"] = quant_cfg - dst.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n") + dst.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding="utf-8") def _write_index_and_manifest( @@ -471,11 +471,13 @@ def _write_index_and_manifest( for k in added: weight_map[k] = shard_name new_index = {"metadata": src_index.get("metadata", {}), "weight_map": weight_map} - (output_ckpt / "model.safetensors.index.json").write_text(json.dumps(new_index, indent=2)) + (output_ckpt / "model.safetensors.index.json").write_text( + json.dumps(new_index, indent=2), encoding="utf-8" + ) _log(f"[index] wrote model.safetensors.index.json ({len(weight_map)} keys)") cfg = _build_hf_quant_config(quantized_layer_names) - (output_ckpt / "hf_quant_config.json").write_text(json.dumps(cfg, indent=2)) + (output_ckpt / "hf_quant_config.json").write_text(json.dumps(cfg, indent=2), encoding="utf-8") def _routed_experts_prefix(expert_proj: str) -> str: @@ -538,7 +540,7 @@ def main(): "model.safetensors.index.json", ) src_config_path = resolve_checkpoint_file(args.source_ckpt, "config.json") - src_index = json.loads(src_index_path.read_text()) + src_index = json.loads(src_index_path.read_text(encoding="utf-8")) amax, input_fallback = _load_merged_amax(args.amax_path, world_size=args.world_size) diff --git a/examples/diffusers/distillation/distillation_trainer.py b/examples/diffusers/distillation/distillation_trainer.py index 38908f5c8f3..4f7e835e4cc 100644 --- a/examples/diffusers/distillation/distillation_trainer.py +++ b/examples/diffusers/distillation/distillation_trainer.py @@ -711,7 +711,7 @@ def _load_calibration_prompts(self) -> list[str]: if not prompts_path.exists(): raise FileNotFoundError(f"Calibration prompts file not found: {prompts_path}") logger.info(f"Loading calibration prompts from {prompts_path}") - with open(prompts_path) as f: + with open(prompts_path, encoding="utf-8") as f: prompts = [line.strip() for line in f if line.strip()] else: logger.info( @@ -1153,7 +1153,7 @@ def _save_config(self) -> None: import yaml config_path = Path(self._config.output_dir) / "training_config.yaml" - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: yaml.dump(self._config.model_dump(), f, default_flow_style=False, indent=2) logger.info( f"Training configuration saved to: {config_path.relative_to(self._config.output_dir)}" @@ -1230,7 +1230,7 @@ def _save_training_state(self) -> Path | None: "quant_cfg": self._distillation_config.quant_cfg, } metadata_path = tmp_dir / "distillation_metadata.json" - with open(metadata_path, "w") as f: + with open(metadata_path, "w", encoding="utf-8") as f: json.dump(metadata, f, indent=2) # Barrier: ensure all ranks finished writing before rename @@ -1376,7 +1376,7 @@ def _load_training_state(self, checkpoint_dir: Path) -> int: # Load custom metadata to get global_step metadata_path = checkpoint_dir / "distillation_metadata.json" if metadata_path.exists(): - with open(metadata_path) as f: + with open(metadata_path, encoding="utf-8") as f: metadata = json.load(f) resumed_step = metadata.get("global_step", 0) logger.info(f"Restored global_step={resumed_step} from metadata") diff --git a/examples/diffusers/fastgen/dmd2_recipe.py b/examples/diffusers/fastgen/dmd2_recipe.py index 7934a07cf13..b8ebb394350 100644 --- a/examples/diffusers/fastgen/dmd2_recipe.py +++ b/examples/diffusers/fastgen/dmd2_recipe.py @@ -669,7 +669,7 @@ def _write_dmd_complete_marker(self, path: str) -> None: "checkpoint": os.path.basename(os.path.realpath(path)), "dmd_iteration": int(self._dmd_pipeline._iteration), } - with open(marker_path, "w") as f: + with open(marker_path, "w", encoding="utf-8") as f: json.dump(payload, f) f.write("\n") logging.info("[DMD2] marked checkpoint complete -> %s", marker_path) @@ -833,7 +833,7 @@ def _resolve_checkpoint_pointer(self, pointer: str) -> str | None: return None elif os.path.isfile(pointer + ".txt"): try: - with open(pointer + ".txt") as f: + with open(pointer + ".txt", encoding="utf-8") as f: resolved = f.read().strip() except OSError: return None diff --git a/examples/diffusers/fastgen/export_diffusers_qwen_image.py b/examples/diffusers/fastgen/export_diffusers_qwen_image.py index 65a17c9c3c0..930801b1d56 100644 --- a/examples/diffusers/fastgen/export_diffusers_qwen_image.py +++ b/examples/diffusers/fastgen/export_diffusers_qwen_image.py @@ -117,9 +117,9 @@ def export_diffusers( # 1. model_index.json — copy verbatim (the class registry is the same # whether the transformer weights are live or DMD-distilled). dst_index = os.path.join(output_dir, "model_index.json") - with open(base_index) as f: + with open(base_index, encoding="utf-8") as f: index = json.load(f) - with open(dst_index, "w") as f: + with open(dst_index, "w", encoding="utf-8") as f: json.dump(index, f, indent=2) logger.info("[Diffusers-Export] Wrote %s", dst_index) diff --git a/examples/diffusers/fastgen/inference_dmd2_qwen_image.py b/examples/diffusers/fastgen/inference_dmd2_qwen_image.py index 5907d0f1b86..df1a5b72009 100644 --- a/examples/diffusers/fastgen/inference_dmd2_qwen_image.py +++ b/examples/diffusers/fastgen/inference_dmd2_qwen_image.py @@ -483,7 +483,7 @@ def _smoke_test( os.makedirs(os.path.dirname(output_png), exist_ok=True) image.save(output_png) sidecar = output_png.replace(".png", "_stats.json") - with open(sidecar, "w") as f: + with open(sidecar, "w", encoding="utf-8") as f: json.dump(stats, f, indent=2) print(json.dumps(stats, indent=2)) print(f"\nImage saved to: {output_png}") diff --git a/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py b/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py index d11efe30f9e..cd8b46baf15 100644 --- a/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py +++ b/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py @@ -122,7 +122,7 @@ def _save_metadata_shards( chunk_data = all_metadata[chunk_start : chunk_start + shard_size] chunk_idx = chunk_start // shard_size shard_file = output_dir / f"metadata_shard_{shard_prefix}s{chunk_idx:04d}.json" - with open(shard_file, "w") as f: + with open(shard_file, "w", encoding="utf-8") as f: json.dump(chunk_data, f, indent=2) shard_files.append(shard_file.name) @@ -140,7 +140,7 @@ def _save_metadata_shards( metadata["shard_rank"] = shard_rank metadata["shard_world"] = shard_world - with open(output_dir / index_filename, "w") as f: + with open(output_dir / index_filename, "w", encoding="utf-8") as f: json.dump(metadata, f, indent=2) diff --git a/examples/diffusers/quantization/utils.py b/examples/diffusers/quantization/utils.py index c3cfdcd5cdd..e7e016ca456 100644 --- a/examples/diffusers/quantization/utils.py +++ b/examples/diffusers/quantization/utils.py @@ -143,7 +143,7 @@ def load_calib_prompts( ) -> list[list[str]]: prompt_list: list[str] = [] if isinstance(calib_data_path, Path): - with open(calib_data_path) as f: + with open(calib_data_path, encoding="utf-8") as f: prompt_list = f.readlines() else: dataset = load_dataset(calib_data_path) diff --git a/examples/gpt-oss/convert_oai_mxfp4_weight_only.py b/examples/gpt-oss/convert_oai_mxfp4_weight_only.py index cb4f03ae553..41a42b1150d 100644 --- a/examples/gpt-oss/convert_oai_mxfp4_weight_only.py +++ b/examples/gpt-oss/convert_oai_mxfp4_weight_only.py @@ -71,7 +71,7 @@ def convert_and_save(model, tokenizer, output_path: str): config_path = os.path.join(output_path, "config.json") config_data = {} - with open(config_path) as file: + with open(config_path, encoding="utf-8") as file: config_data = json.load(file) config_data["quantization_config"] = { @@ -86,7 +86,7 @@ def convert_and_save(model, tokenizer, output_path: str): config_data.pop("torch_dtype", None) - with open(config_path, "w") as file: + with open(config_path, "w", encoding="utf-8") as file: json.dump(config_data, file, indent=4) # Save tokenizer diff --git a/examples/hf_ptq/example_utils.py b/examples/hf_ptq/example_utils.py index be8300d4d67..7b1f25d11fa 100755 --- a/examples/hf_ptq/example_utils.py +++ b/examples/hf_ptq/example_utils.py @@ -462,7 +462,7 @@ def _load_tensors_matching( index_file = model_dir / "model.safetensors.index.json" if index_file.exists(): - with open(index_file) as f: + with open(index_file, encoding="utf-8") as f: weight_map = json.load(f)["weight_map"] per_shard: dict[str, list[str]] = {} for key, shard_name in weight_map.items(): @@ -506,7 +506,7 @@ def mtp_layer_prefixes_from_checkpoint(model_path: str) -> list[str]: index_file = Path(model_path) / "model.safetensors.index.json" if not index_file.exists(): return [] - weight_map = json.load(open(index_file))["weight_map"] + weight_map = json.load(open(index_file, encoding="utf-8"))["weight_map"] mtp_keys = [k for k, v in weight_map.items() if "mtp" in k or "mtp" in v] return list(_keys_to_prefixes(mtp_keys)) @@ -607,7 +607,7 @@ def _resolve_file(filename): checkpoint_weights = {} index_file = _resolve_file("model.safetensors.index.json") if index_file: - with open(index_file) as f: + with open(index_file, encoding="utf-8") as f: index = json.load(f) st_filenames = list(set(index.get("weight_map", {}).values())) else: @@ -1449,7 +1449,7 @@ def _log_experiment_json( if not args.checkpoint_exported: return try: - (export_path / _EXPERIMENT_JSON).write_text(text) + (export_path / _EXPERIMENT_JSON).write_text(text, encoding="utf-8") except OSError as e: print(f"[mlflow] WARNING: could not write {export_path / _EXPERIMENT_JSON}: {e}") diff --git a/examples/kimi/kimi_k3/quantize_to_nvfp4.py b/examples/kimi/kimi_k3/quantize_to_nvfp4.py index 5eb612c029b..6ad617fd1a3 100644 --- a/examples/kimi/kimi_k3/quantize_to_nvfp4.py +++ b/examples/kimi/kimi_k3/quantize_to_nvfp4.py @@ -663,7 +663,7 @@ def _rewrite_config_json(src: Path, dst_dir: Path, hf_quant_config: dict[str, An would make a loader dequantize the NVFP4 experts as MXFP4. It is replaced wholesale by the ModelOpt mixed-precision manifest. """ - cfg = json.loads(src.read_text()) + cfg = json.loads(src.read_text(encoding="utf-8")) quant_cfg = convert_hf_quant_config_format(hf_quant_config) # ``convert_hf_quant_config_format`` targets the llm-compressor layout and # stamps ``quant_method="modelopt"``. Loaders gate their mixed-precision @@ -679,7 +679,9 @@ def _rewrite_config_json(src: Path, dst_dir: Path, hf_quant_config: dict[str, An if isinstance(text_cfg, dict): text_cfg.pop("quantization_config", None) cfg["quantization_config"] = quant_cfg - (dst_dir / "config.json").write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n") + (dst_dir / "config.json").write_text( + json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) def _write_index_and_manifest( @@ -724,15 +726,19 @@ def _write_index_and_manifest( metadata = dict(src_index.get("metadata", {})) metadata["total_size"] = sum(r["tensor_bytes"] for r in results) new_index = {"metadata": metadata, "weight_map": weight_map} - (output_ckpt / "model.safetensors.index.json").write_text(json.dumps(new_index, indent=2)) + (output_ckpt / "model.safetensors.index.json").write_text( + json.dumps(new_index, indent=2), encoding="utf-8" + ) _log(f"[index] wrote model.safetensors.index.json ({len(weight_map)} keys)") - (output_ckpt / "hf_quant_config.json").write_text(json.dumps(hf_quant_config, indent=2)) + (output_ckpt / "hf_quant_config.json").write_text( + json.dumps(hf_quant_config, indent=2), encoding="utf-8" + ) def _write_json_atomic(path: Path, value: Any) -> None: tmp = path.with_suffix(path.suffix + ".tmp") - tmp.write_text(json.dumps(value, indent=2, sort_keys=True)) + tmp.write_text(json.dumps(value, indent=2, sort_keys=True), encoding="utf-8") os.replace(tmp, path) @@ -786,7 +792,7 @@ def _rank0_ready( """Check that rank 0 published matching rendezvous settings.""" if not ready_path.exists(): return False - ready = json.loads(ready_path.read_text()) + ready = json.loads(ready_path.read_text(encoding="utf-8")) if ready.get("run_id") != run_id: return False published_world_size = ready.get("world_size") @@ -809,7 +815,7 @@ def _rank_report_ready( if not report_path.exists(): return False try: - report = json.loads(report_path.read_text()) + report = json.loads(report_path.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): return False if report.get("run_id") != run_id: @@ -958,7 +964,7 @@ def main(): "model.safetensors.index.json", ) src_config_path = resolve_checkpoint_file(args.source_ckpt, "config.json") - src_index = json.loads(src_index_path.read_text()) + src_index = json.loads(src_index_path.read_text(encoding="utf-8")) shards = sorted(args.source_ckpt.glob("model-*-of-*.safetensors")) assert shards, f"no HF-style shards in {args.source_ckpt}" @@ -1074,7 +1080,7 @@ def all_ranks_done() -> bool: ) _wait_for(all_ranks_done, f"{args.world_size} rank reports", args.sync_timeout) - rank_reports = [json.loads(path.read_text()) for path in rank_paths] + rank_reports = [json.loads(path.read_text(encoding="utf-8")) for path in rank_paths] for rank, report in enumerate(rank_reports): if report.get("run_id") != args.run_id or report.get("rank") != rank: raise RuntimeError(f"rank {rank} report changed after rendezvous validation") diff --git a/examples/llm_eval/lm_eval_hf.py b/examples/llm_eval/lm_eval_hf.py index a96a730c16a..6439272c36a 100755 --- a/examples/llm_eval/lm_eval_hf.py +++ b/examples/llm_eval/lm_eval_hf.py @@ -301,7 +301,7 @@ def _enforce_accuracy_gate(output_path, task, lower_bound): raise FileNotFoundError(f"No results*.json under {output_path}") # Sort by mtime, not path: a reused output_path nests results under a # / dir, and lexical order would pick the wrong run's file. - with open(max(files, key=os.path.getmtime)) as f: + with open(max(files, key=os.path.getmtime), encoding="utf-8") as f: scores = json.load(f)["results"].get(task, {}) # lm-eval keys metrics by filter, e.g. "acc,none"; take acc (never acc_stderr). acc = next((float(v) for k, v in scores.items() if k == "acc" or k.startswith("acc,")), None) diff --git a/examples/llm_eval/modeling.py b/examples/llm_eval/modeling.py index 341e21d956e..3a03c8a9f5a 100644 --- a/examples/llm_eval/modeling.py +++ b/examples/llm_eval/modeling.py @@ -48,7 +48,6 @@ from pathlib import Path import openai -import rwkv import rwkv.utils import tiktoken import torch @@ -105,7 +104,7 @@ def load(self): if self.tokenizer is None: self.tokenizer = tiktoken.get_encoding("cl100k_base") # chatgpt/gpt-4 - with open(self.model_path) as f: + with open(self.model_path, encoding="utf-8") as f: info = json.load(f) openai.api_key = info["key"] self.engine = info["engine"] diff --git a/examples/llm_eval/simple_evals.py b/examples/llm_eval/simple_evals.py index 2ef12bd51d8..bcde367969b 100644 --- a/examples/llm_eval/simple_evals.py +++ b/examples/llm_eval/simple_evals.py @@ -121,19 +121,19 @@ def get_evals(eval_name, debug_mode): file_stem = f"{eval_name}_{model_name}" report_filename = f"/tmp/{file_stem}{debug_suffix}.html" print(f"Writing report to {report_filename}") - with open(report_filename, "w") as fh: + with open(report_filename, "w", encoding="utf-8") as fh: fh.write(common.make_report(result)) metrics = result.metrics | {"score": result.score} print(metrics) result_filename = f"/tmp/{file_stem}{debug_suffix}.json" - with open(result_filename, "w") as f: + with open(result_filename, "w", encoding="utf-8") as f: f.write(json.dumps(metrics, indent=2)) print(f"Writing results to {result_filename}") mergekey2resultpath[f"{file_stem}"] = result_filename merge_metrics = [] for eval_model_name, result_filename in mergekey2resultpath.items(): try: - result = json.load(open(result_filename, "r+")) + result = json.load(open(result_filename, "r+", encoding="utf-8")) except Exception as e: print(e, result_filename) continue diff --git a/examples/llm_qat/dataset_utils.py b/examples/llm_qat/dataset_utils.py index eaf067026df..eef5a797092 100644 --- a/examples/llm_qat/dataset_utils.py +++ b/examples/llm_qat/dataset_utils.py @@ -151,7 +151,7 @@ def is_distributed(self) -> bool: def load_blend_config(config_path: str) -> BlendConfig: """Parse a dataset blend YAML file into a :class:`BlendConfig`.""" - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: raw = yaml.safe_load(f) sources = [DatasetSourceConfig(**s) for s in raw.get("sources", [])] diff --git a/examples/llm_qat/export.py b/examples/llm_qat/export.py index afe2bd4d1cf..24a26bcce28 100644 --- a/examples/llm_qat/export.py +++ b/examples/llm_qat/export.py @@ -86,7 +86,7 @@ def main(args): model, is_modelopt_qlora=is_qlora ) - with open(f"{base_model_dir}/hf_quant_config.json", "w") as file: + with open(f"{base_model_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: json.dump(hf_quant_config, file, indent=4) hf_quant_config = convert_hf_quant_config_format(hf_quant_config) @@ -104,7 +104,7 @@ def main(args): config_data["quantization_config"] = hf_quant_config - with open(config_path, "w") as file: + with open(config_path, "w", encoding="utf-8") as file: json.dump(config_data, file, indent=4) # Save tokenizer diff --git a/examples/llm_sparsity/weight_sparsity/data_prep.py b/examples/llm_sparsity/weight_sparsity/data_prep.py index 62be755eeca..2aa00d90c2b 100644 --- a/examples/llm_sparsity/weight_sparsity/data_prep.py +++ b/examples/llm_sparsity/weight_sparsity/data_prep.py @@ -71,9 +71,9 @@ def main(): # save dataset to disk os.makedirs(args.save_path, exist_ok=True) - with open(os.path.join(args.save_path, "cnn_train.json"), "w") as write_f: + with open(os.path.join(args.save_path, "cnn_train.json"), "w", encoding="utf-8") as write_f: json.dump(list(tokenized_dataset["train"]["text"]), write_f, indent=4, ensure_ascii=False) - with open(os.path.join(args.save_path, "cnn_eval.json"), "w") as write_f: + with open(os.path.join(args.save_path, "cnn_eval.json"), "w", encoding="utf-8") as write_f: json.dump(list(tokenized_dataset["test"]["text"]), write_f, indent=4, ensure_ascii=False) diff --git a/examples/llm_sparsity/weight_sparsity/eval.py b/examples/llm_sparsity/weight_sparsity/eval.py index a5f2fb91b2d..5c1e1f160f1 100644 --- a/examples/llm_sparsity/weight_sparsity/eval.py +++ b/examples/llm_sparsity/weight_sparsity/eval.py @@ -81,7 +81,7 @@ def prepare_tokenizer(accelerator, checkpoint_path, model_max_length, padding_si def preprocess_cnndailymail(accelerator, data_path, calib=False): # Load from CNN dailymail - with open(data_path) as fh: + with open(data_path, encoding="utf-8") as fh: list_data_dict = json.load(fh) sources = [G_PROMPT_INPUT.format_map(example) for example in list_data_dict] diff --git a/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py b/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py index 80cb473cc63..97afbd21d4f 100644 --- a/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py +++ b/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py @@ -65,7 +65,7 @@ def _log(message: str) -> None: def _load_index(checkpoint: Path) -> dict[str, str]: - index = json.loads((checkpoint / "model.safetensors.index.json").read_text()) + index = json.loads((checkpoint / "model.safetensors.index.json").read_text(encoding="utf-8")) return index["weight_map"] @@ -332,7 +332,7 @@ def main() -> None: _copy_mxfp8_base(mxfp8, destination, mxfp8_map, new_index) new_index = _rename_checkpoint_shards(destination, new_index) - mxfp8_config = json.loads((mxfp8 / "config.json").read_text()) + mxfp8_config = json.loads((mxfp8 / "config.json").read_text(encoding="utf-8")) vendor_quantization = mxfp8_config.get("quantization_config", {}) mixed_quant_config = _build_quant_config( mxfp8_map, @@ -341,10 +341,13 @@ def main() -> None: ) mxfp8_config["quantization_config"] = mixed_quant_config["quantization"] - (destination / "config.json").write_text(json.dumps(mxfp8_config, indent=2)) - (destination / "hf_quant_config.json").write_text(json.dumps(mixed_quant_config, indent=2)) + (destination / "config.json").write_text(json.dumps(mxfp8_config, indent=2), encoding="utf-8") + (destination / "hf_quant_config.json").write_text( + json.dumps(mixed_quant_config, indent=2), encoding="utf-8" + ) (destination / "model.safetensors.index.json").write_text( - json.dumps({"metadata": {"format": "pt"}, "weight_map": new_index}, indent=2) + json.dumps({"metadata": {"format": "pt"}, "weight_map": new_index}, indent=2), + encoding="utf-8", ) _copy_ancillary_files(mxfp8, destination) _log(f"[mixed] done -> {destination}") diff --git a/examples/onnx_ptq/evaluate.py b/examples/onnx_ptq/evaluate.py index 89d6daca070..c6803821b35 100644 --- a/examples/onnx_ptq/evaluate.py +++ b/examples/onnx_ptq/evaluate.py @@ -125,7 +125,7 @@ def main(): ["Top 5", top5_accuracy], ["Latency", latency], ] - with open(args.results_path, "w", newline="") as csvfile: + with open(args.results_path, "w", encoding="utf-8", newline="") as csvfile: writer = csv.writer(csvfile) writer.writerows(results) diff --git a/examples/onnx_ptq/evaluation.py b/examples/onnx_ptq/evaluation.py index 0fdcfd18b9a..5d8d1287524 100644 --- a/examples/onnx_ptq/evaluation.py +++ b/examples/onnx_ptq/evaluation.py @@ -77,7 +77,7 @@ def __init__(self, root, transform=None): transform: Optional transform to apply to images. """ img_dir = Path(root) / "validation" - with open(Path(root) / "val.txt") as f: + with open(Path(root) / "val.txt", encoding="utf-8") as f: entries = [line.strip().split() for line in f] self.samples = [(img_dir / name, int(label)) for name, label in entries] self.transform = transform diff --git a/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb b/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb index 85a79aa8836..b9d9b8e57d2 100644 --- a/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb +++ b/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb @@ -384,7 +384,7 @@ "\n", "config_path = \"/opt/Model-Optimizer/examples/puzzletron/configs/qwen3-8b_pruneffn_memory/qwen3_8b_pruneffn_memory.yaml\"\n", "\n", - "with open(config_path) as f:\n", + "with open(config_path, encoding=\"utf-8\") as f:\n", " config = yaml.safe_load(f)\n", "\n", "# Add sweep configuration\n", @@ -394,7 +394,7 @@ " \"output_csv\": \"/workspace/puzzle_dir/mip_sweep_results.csv\",\n", "}\n", "\n", - "with open(config_path, \"w\") as f:\n", + "with open(config_path, \"w\", encoding=\"utf-8\") as f:\n", " yaml.dump(config, f, default_flow_style=False)\n", "\n", "print(\"Sweep config added. Compression rates: [0.5, 0.6, 0.7, 0.8, 0.9, 1.0]\")" diff --git a/examples/specdec_bench/run.py b/examples/specdec_bench/run.py index 9ed7ec44272..2343690f2ba 100644 --- a/examples/specdec_bench/run.py +++ b/examples/specdec_bench/run.py @@ -405,7 +405,7 @@ def run_simple(args): args = parser.parse_args() if args.runtime_params is not None: - with open(args.runtime_params) as f: + with open(args.runtime_params, encoding="utf-8") as f: args.runtime_params = yaml.safe_load(f) else: args.runtime_params = {} diff --git a/examples/specdec_bench/specdec_bench/datasets/mtbench.py b/examples/specdec_bench/specdec_bench/datasets/mtbench.py index cb58dd21038..7485b2182d6 100644 --- a/examples/specdec_bench/specdec_bench/datasets/mtbench.py +++ b/examples/specdec_bench/specdec_bench/datasets/mtbench.py @@ -36,7 +36,7 @@ def __init__(self, path, num_samples=80, **kwargs): self._preprocess(path) def _preprocess(self, path): - with open(path) as f: + with open(path, encoding="utf-8") as f: for json_line in f: line = json.loads(json_line) key = "turns" if "turns" in line else "prompt" diff --git a/examples/specdec_bench/specdec_bench/datasets/specbench.py b/examples/specdec_bench/specdec_bench/datasets/specbench.py index a14d3403903..cc5b3a3ab01 100644 --- a/examples/specdec_bench/specdec_bench/datasets/specbench.py +++ b/examples/specdec_bench/specdec_bench/datasets/specbench.py @@ -25,7 +25,7 @@ def __init__(self, path, num_samples=480, **kwargs): self._preprocess(path) def _preprocess(self, path): - with open(path) as f: + with open(path, encoding="utf-8") as f: for json_line in f: line = json.loads(json_line) self.data.append( diff --git a/examples/specdec_bench/specdec_bench/metrics/aa_timing.py b/examples/specdec_bench/specdec_bench/metrics/aa_timing.py index cce735d5f1c..40c780e6b96 100644 --- a/examples/specdec_bench/specdec_bench/metrics/aa_timing.py +++ b/examples/specdec_bench/specdec_bench/metrics/aa_timing.py @@ -45,8 +45,8 @@ def process_step(self, step_outputs, request_id, turn_id): def process_final(self, text_outputs): gen_tp_time = [] - start_time = min([t[0] for t in self.timing]) - end_time = max([t[-1] for t in self.timing]) + start_time = min(t[0] for t in self.timing) + end_time = max(t[-1] for t in self.timing) self.out["AA Output TPS"] = sum(self.total_tokens) / (end_time - start_time) for tokens, times in zip(self.total_tokens, self.timing): if len(times) > 2: diff --git a/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py b/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py index 819f251a3d8..4eb8ef35024 100644 --- a/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py +++ b/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py @@ -88,7 +88,9 @@ def clear(self): self.prompt_ar = [] def _format_write_output(self, outputs): - with open(os.path.join(self.directory, "responses.jsonl"), "w") as outfile: + with open( + os.path.join(self.directory, "responses.jsonl"), "w", encoding="utf-8" + ) as outfile: for i, messages in enumerate(outputs): q_id = i out_line = {} diff --git a/examples/specdec_bench/specdec_bench/metrics/base.py b/examples/specdec_bench/specdec_bench/metrics/base.py index 62a2fbf2cf3..cc45b690c30 100644 --- a/examples/specdec_bench/specdec_bench/metrics/base.py +++ b/examples/specdec_bench/specdec_bench/metrics/base.py @@ -38,13 +38,13 @@ def write(self): if self.out: filename = os.path.join(self.directory, f"{self.name}.json") if os.path.exists(filename): - with open(filename) as json_file: + with open(filename, encoding="utf-8") as json_file: existing_data = json.load(json_file) existing_data.append(self.out) else: existing_data = [self.out] - with open(filename, "w") as json_file: + with open(filename, "w", encoding="utf-8") as json_file: json.dump(existing_data, json_file, indent=4) @classmethod diff --git a/examples/specdec_bench/specdec_bench/metrics/mtbench.py b/examples/specdec_bench/specdec_bench/metrics/mtbench.py index 48655de1fba..dfa97c5a08c 100644 --- a/examples/specdec_bench/specdec_bench/metrics/mtbench.py +++ b/examples/specdec_bench/specdec_bench/metrics/mtbench.py @@ -62,7 +62,9 @@ def process_final(self, text_outputs): self._format_write_output(text_outputs) def _format_write_output(self, outputs): - with open(os.path.join(self.directory, "mtbench_responses.jsonl"), "w") as outfile: + with open( + os.path.join(self.directory, "mtbench_responses.jsonl"), "w", encoding="utf-8" + ) as outfile: for i, messages in enumerate(outputs): q_id = i + 81 out_line = {} diff --git a/examples/specdec_bench/specdec_bench/metrics/specbench.py b/examples/specdec_bench/specdec_bench/metrics/specbench.py index 5364e719d24..0f39a45e602 100644 --- a/examples/specdec_bench/specdec_bench/metrics/specbench.py +++ b/examples/specdec_bench/specdec_bench/metrics/specbench.py @@ -72,7 +72,9 @@ def process_final(self, text_outputs): self._create_visualizations(text_outputs) def _format_write_output(self, outputs): - with open(os.path.join(self.directory, "specbench_responses.jsonl"), "w") as outfile: + with open( + os.path.join(self.directory, "specbench_responses.jsonl"), "w", encoding="utf-8" + ) as outfile: for i, messages in enumerate(outputs): out_line = {} out_line["question_id"] = self.requests[i].question_id @@ -106,7 +108,9 @@ def _pretty_print_results(self): console.print(table) def _dump_results(self): - with open(os.path.join(self.directory, "specbench_results.json"), "w") as outfile: + with open( + os.path.join(self.directory, "specbench_results.json"), "w", encoding="utf-8" + ) as outfile: json.dump(self.out, outfile, indent=4) def _create_visualizations( diff --git a/examples/specdec_bench/specdec_bench/metrics/timing.py b/examples/specdec_bench/specdec_bench/metrics/timing.py index 5bf33c604e0..49c5b002648 100644 --- a/examples/specdec_bench/specdec_bench/metrics/timing.py +++ b/examples/specdec_bench/specdec_bench/metrics/timing.py @@ -28,17 +28,15 @@ def __init__(self, tp_size): def process_step(self, step_outputs, request_id, turn_id): self.timing.append(step_outputs["token_times"]) - self.total_tokens.append( - sum([sum([len(j) for j in i]) for i in step_outputs["output_ids"]]) - ) + self.total_tokens.append(sum(sum(len(j) for j in i) for i in step_outputs["output_ids"])) def process_final(self, text_outputs): e2e_time = [] ttft_time = [] tpot_time = [] gen_tp_time = [] - start_time = min([t[0] for t in self.timing]) - end_time = max([t[-1] for t in self.timing]) + start_time = min(t[0] for t in self.timing) + end_time = max(t[-1] for t in self.timing) self.out["Output TPS"] = sum(self.total_tokens) / (end_time - start_time) self.out["Output TPS/gpu"] = self.out["Output TPS"] / self.tp_size for tokens, times in zip(self.total_tokens, self.timing): diff --git a/examples/specdec_bench/specdec_bench/utils.py b/examples/specdec_bench/specdec_bench/utils.py index 1f7a7b9bef9..e3c294c6cec 100644 --- a/examples/specdec_bench/specdec_bench/utils.py +++ b/examples/specdec_bench/specdec_bench/utils.py @@ -38,7 +38,7 @@ def get_tokenizer(path, trust_remote_code=False): extra_special_tokens = None tokenizer_config_path = os.path.join(path, "tokenizer_config.json") if os.path.exists(tokenizer_config_path): - with open(tokenizer_config_path) as f: + with open(tokenizer_config_path, encoding="utf-8") as f: tokenizer_config = json.load(f) extra_special_tokens = tokenizer_config.get("extra_special_tokens") @@ -68,7 +68,7 @@ def decode_chat(tokenizer, out_tokens): def read_json(path): if path is not None: - with open(path) as f: + with open(path, encoding="utf-8") as f: data = json.load(f) return data return {} @@ -149,7 +149,7 @@ def _git_sha(path): def _shard_files_from_index(index_path): """Return the set of shard filenames referenced by a safetensors index JSON.""" try: - with open(index_path) as f: + with open(index_path, encoding="utf-8") as f: wm = json.load(f).get("weight_map", {}) or {} return set(wm.values()) except Exception: @@ -320,5 +320,5 @@ def dump_env(args, save_dir, overrides=None): config["huggingface_model_id"] = os.environ.get("HUGGINGFACE_MODEL_ID") or None os.makedirs(save_dir, exist_ok=True) - with open(os.path.join(save_dir, "configuration.json"), "w") as f: + with open(os.path.join(save_dir, "configuration.json"), "w", encoding="utf-8") as f: json.dump(config, f, indent=4, default=str) diff --git a/examples/specdec_bench/upload_to_s3.py b/examples/specdec_bench/upload_to_s3.py index 067ea25bce3..4a6910f68db 100644 --- a/examples/specdec_bench/upload_to_s3.py +++ b/examples/specdec_bench/upload_to_s3.py @@ -63,7 +63,7 @@ def _check_provenance(run_dir: Path) -> list[str]: if not cfg_path.is_file(): return list(_REQUIRED_PROVENANCE_FIELDS) try: - with open(cfg_path) as f: + with open(cfg_path, encoding="utf-8") as f: cfg = json.load(f) except (OSError, json.JSONDecodeError): return list(_REQUIRED_PROVENANCE_FIELDS) diff --git a/examples/speculative_decoding/collect_hidden_states/common.py b/examples/speculative_decoding/collect_hidden_states/common.py index 78b317853b6..e113169e8b5 100644 --- a/examples/speculative_decoding/collect_hidden_states/common.py +++ b/examples/speculative_decoding/collect_hidden_states/common.py @@ -106,7 +106,7 @@ def load_chat_template(path: Path | None) -> str | None: """Read a Jinja chat template from ``path``, or return ``None`` if not provided.""" if path is None: return None - with open(path) as f: + with open(path, encoding="utf-8") as f: return f.read() diff --git a/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py b/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py index f0bbe4f951e..528681eaa62 100644 --- a/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py +++ b/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py @@ -142,7 +142,7 @@ async def main(args: argparse.Namespace) -> None: # Use /tmp/meta.json to communicate with the local serving engine. # See usage guide for more details - with temp_meta_file.open("w") as f: + with temp_meta_file.open("w", encoding="utf-8") as f: json.dump( { "conversation_id": conversation_id, diff --git a/examples/speculative_decoding/eagle_utils.py b/examples/speculative_decoding/eagle_utils.py index 0691ca06f52..6c29f07dccb 100644 --- a/examples/speculative_decoding/eagle_utils.py +++ b/examples/speculative_decoding/eagle_utils.py @@ -74,7 +74,7 @@ def make_speculative_data_module( chat_template = None if getattr(data_args, "chat_template", None): template_path = data_args.chat_template - with open(template_path) as f: + with open(template_path, encoding="utf-8") as f: chat_template = f.read() print_rank_0(f"Loaded chat template from {template_path}") @@ -347,7 +347,7 @@ def on_save(self, args, state, control, **kwargs): save_file(drafter_sd, os.path.join(export_dir, "model.safetensors")) config = exporter._export_config() - with open(os.path.join(export_dir, "config.json"), "w") as f: + with open(os.path.join(export_dir, "config.json"), "w", encoding="utf-8") as f: json.dump(config, f, indent=2) total_mb = sum(v.nbytes for v in drafter_sd.values()) / 1024 / 1024 diff --git a/examples/speculative_decoding/example.ipynb b/examples/speculative_decoding/example.ipynb index d9d9be3a668..0f91387358e 100644 --- a/examples/speculative_decoding/example.ipynb +++ b/examples/speculative_decoding/example.ipynb @@ -92,7 +92,7 @@ "from eagle_utils import DataCollatorWithPadding, LazySupervisedDataset\n", "from transformers import Trainer\n", "\n", - "with open(\"/tmp/Daring-Anteater/train.jsonl\") as f:\n", + "with open(\"/tmp/Daring-Anteater/train.jsonl\", encoding=\"utf-8\") as f:\n", " data_json = [json.loads(line) for line in f]\n", "train_dataset = LazySupervisedDataset(data_json[: int(len(data_json) * 0.95)], tokenizer=tokenizer)\n", "eval_dataset = LazySupervisedDataset(data_json[int(len(data_json) * 0.95) :], tokenizer=tokenizer)\n", @@ -196,10 +196,10 @@ "\"\"\"\n", "\n", "# Dump the two scripts into /tmp\n", - "with open(\"/tmp/trtllm_serve.sh\", \"w\") as f:\n", + "with open(\"/tmp/trtllm_serve.sh\", \"w\", encoding=\"utf-8\") as f:\n", " f.write(trtllm_serve_script)\n", "\n", - "with open(\"/tmp/extra-llm-api-config.yml\", \"w\") as f:\n", + "with open(\"/tmp/extra-llm-api-config.yml\", \"w\", encoding=\"utf-8\") as f:\n", " f.write(extra_llm_api_config)" ] }, @@ -349,7 +349,7 @@ " --dtype float16\n", "\"\"\"\n", "\n", - "with open(\"/tmp/sglang_serve.sh\", \"w\") as f:\n", + "with open(\"/tmp/sglang_serve.sh\", \"w\", encoding=\"utf-8\") as f:\n", " f.write(sglang_serve_script)" ] }, diff --git a/examples/speculative_decoding/medusa_utils.py b/examples/speculative_decoding/medusa_utils.py index 30dc238c35a..a73fb39650e 100644 --- a/examples/speculative_decoding/medusa_utils.py +++ b/examples/speculative_decoding/medusa_utils.py @@ -209,10 +209,10 @@ def make_medusa_supervised_data_module( print_rank_0("Loading data...") if data_args.data_path.endswith("jsonl"): - with open(data_args.data_path) as f: + with open(data_args.data_path, encoding="utf-8") as f: data_json = [json.loads(line) for line in f] else: - data_json = json.load(open(data_args.data_path)) + data_json = json.load(open(data_args.data_path, encoding="utf-8")) train_dataset = dataset_cls(data_json[: int(len(data_json) * 0.95)], tokenizer=tokenizer) eval_dataset = dataset_cls(data_json[int(len(data_json) * 0.95) :], tokenizer=tokenizer) diff --git a/examples/speculative_decoding/scripts/calibrate_draft_vocab.py b/examples/speculative_decoding/scripts/calibrate_draft_vocab.py index 19f387a6546..90eb9c9a0a7 100644 --- a/examples/speculative_decoding/scripts/calibrate_draft_vocab.py +++ b/examples/speculative_decoding/scripts/calibrate_draft_vocab.py @@ -47,7 +47,7 @@ def main(): print("Calibrating vocab...") tokenizer = AutoTokenizer.from_pretrained(args.model) - with open(args.data) as f: + with open(args.data, encoding="utf-8") as f: lines = islice(f, args.calibrate_size) if args.calibrate_size else f conversations = [ (d := json.loads(line)).get("messages") or d["conversations"] for line in lines diff --git a/examples/speculative_decoding/scripts/quantize_drafter.py b/examples/speculative_decoding/scripts/quantize_drafter.py index 0f4237d22ab..26a23d5aae5 100644 --- a/examples/speculative_decoding/scripts/quantize_drafter.py +++ b/examples/speculative_decoding/scripts/quantize_drafter.py @@ -304,7 +304,7 @@ def main(): export_dir.mkdir(parents=True, exist_ok=True) save_file(export_sd, export_dir / "model.safetensors", metadata={"format": "pt"}) - config = json.loads((source_dir / "config.json").read_text()) + config = json.loads((source_dir / "config.json").read_text(encoding="utf-8")) hf_quant_config = get_quant_config(root) # ``get_quant_config`` only knows the linear view, so tensors it never saw (norms, 1-D # weights) are missing and a loader walking the checkpoint expects a scale for them. @@ -351,8 +351,10 @@ def main(): # ``ignore``, not ``exclude_modules``. config["quantization_config"]["ignore"] = list(exclude_modules) config["torch_dtype"] = args.dtype - (export_dir / "config.json").write_text(json.dumps(config, indent=2)) - (export_dir / "hf_quant_config.json").write_text(json.dumps(hf_quant_config, indent=2)) + (export_dir / "config.json").write_text(json.dumps(config, indent=2), encoding="utf-8") + (export_dir / "hf_quant_config.json").write_text( + json.dumps(hf_quant_config, indent=2), encoding="utf-8" + ) for extra in SIDECAR_FILES: if (source_dir / extra).is_file(): diff --git a/examples/speculative_decoding/scripts/send_conversation_vllm.py b/examples/speculative_decoding/scripts/send_conversation_vllm.py index 9271af121bc..33f60533e41 100644 --- a/examples/speculative_decoding/scripts/send_conversation_vllm.py +++ b/examples/speculative_decoding/scripts/send_conversation_vllm.py @@ -192,7 +192,7 @@ async def main(args: argparse.Namespace) -> None: # Use /tmp/meta.json to communicate with the local serving engine. # See usage guide for more details - with temp_meta_file.open("w") as f: + with temp_meta_file.open("w", encoding="utf-8") as f: json.dump( { "conversation_id": conversation_id, diff --git a/examples/speculative_decoding/scripts/server_generate.py b/examples/speculative_decoding/scripts/server_generate.py index a0516bc3922..6a16f464e8d 100644 --- a/examples/speculative_decoding/scripts/server_generate.py +++ b/examples/speculative_decoding/scripts/server_generate.py @@ -58,10 +58,10 @@ if args.data_path.endswith("jsonl"): - with open(args.data_path) as f: + with open(args.data_path, encoding="utf-8") as f: data = [json.loads(line) for line in f] else: - data = json.load(open(args.data_path)) + data = json.load(open(args.data_path, encoding="utf-8")) client = OpenAI( base_url=args.url, @@ -129,7 +129,7 @@ def generate_data(messages, idx, system_prompt): to_write = {"conversation_id": idx, "conversations": output_messages} if truncated: to_write["truncated"] = True - with open(args.output_path, "a") as f: + with open(args.output_path, "a", encoding="utf-8") as f: # write in share gpt format f.write(json.dumps(to_write) + "\n") else: @@ -150,7 +150,7 @@ def generate_data(messages, idx, system_prompt): spaces_between_special_tokens=False, ) response = response.choices[0].text.strip() - with open(args.output_path, "a") as f: + with open(args.output_path, "a", encoding="utf-8") as f: # write in share gpt format if args.log_empty_conversations: to_write = {"conversation_id": idx, "text": prompt + response} @@ -167,7 +167,7 @@ def generate_data(messages, idx, system_prompt): finished_ids = [] done = False if os.path.exists(args.output_path): - with open(args.output_path) as f: + with open(args.output_path, encoding="utf-8") as f: for line in f: outdata = json.loads(line) finished_ids.append(outdata.get("conversation_id", -1)) @@ -199,5 +199,5 @@ def generate_data(messages, idx, system_prompt): future.result() if args.log_empty_conversations: - with open(args.output_path, "a") as f: + with open(args.output_path, "a", encoding="utf-8") as f: f.write(json.dumps({"finished": True}) + "\n") diff --git a/examples/torch_trt/torch_tensorrt_accuracy.py b/examples/torch_trt/torch_tensorrt_accuracy.py index c450b458993..40453faba36 100644 --- a/examples/torch_trt/torch_tensorrt_accuracy.py +++ b/examples/torch_trt/torch_tensorrt_accuracy.py @@ -219,7 +219,7 @@ def to_eval_model(m: torch.nn.Module, what: str) -> torch.nn.Module: results.append([tag, top1, top5]) if args.results_path: - with open(args.results_path, "w", newline="") as f: + with open(args.results_path, "w", encoding="utf-8", newline="") as f: csv.writer(f).writerows(results) print(f"\nWrote results to {args.results_path}") diff --git a/examples/torch_trt/torch_tensorrt_ptq.py b/examples/torch_trt/torch_tensorrt_ptq.py index 60f8bd6ec23..8b66e01f10a 100644 --- a/examples/torch_trt/torch_tensorrt_ptq.py +++ b/examples/torch_trt/torch_tensorrt_ptq.py @@ -175,7 +175,7 @@ def dump_trt_layer_info(trt_model: torch.nn.Module, path: Path) -> None: print("No TorchTensorRTModule found; nothing to dump (whole graph fell back to PyTorch?).") return path.parent.mkdir(parents=True, exist_ok=True) - path.write_text("\n".join(infos)) + path.write_text("\n".join(infos), encoding="utf-8") print(f"Wrote TRT layer info ({len(infos)} engine(s)) to {path}") diff --git a/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py b/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py index 4513ae87955..0c909eecf20 100644 --- a/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py +++ b/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py @@ -405,7 +405,7 @@ def main(): out_dir = os.path.dirname(args.output) if out_dir: os.makedirs(out_dir, exist_ok=True) - with open(args.output, "w") as f: + with open(args.output, "w", encoding="utf-8") as f: json.dump(result, f, indent=2) log.info(f"Results saved to {args.output}") diff --git a/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py b/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py index 3a3fbbdd2b9..aa2d4e886f4 100644 --- a/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py +++ b/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py @@ -854,7 +854,7 @@ def main(): # Save results if output file specified if args.output: print(f"\n[INFO] Saving results to: {args.output}") - with open(args.output, "w") as f: + with open(args.output, "w", encoding="utf-8") as f: json.dump(final_results, f, indent=2) print("[INFO] Results saved successfully") diff --git a/examples/windows/accuracy_benchmark/mmlu_benchmark.py b/examples/windows/accuracy_benchmark/mmlu_benchmark.py index 54573e6425e..9732608baf5 100644 --- a/examples/windows/accuracy_benchmark/mmlu_benchmark.py +++ b/examples/windows/accuracy_benchmark/mmlu_benchmark.py @@ -409,7 +409,7 @@ def evaluate_ort_native(args, subject, sess, tokenizer, dev_df, test_df, config) def save_results_to_json(results, output_file="results.json"): os.makedirs(os.path.dirname(output_file), exist_ok=True) - with open(output_file, "w") as f: + with open(output_file, "w", encoding="utf-8") as f: json.dump(results, f, indent=4) @@ -487,7 +487,7 @@ def evaluate_func(args, subject, dev_df, test_df): # Create the InferenceSession with the selected provider sess = rt.InferenceSession(os.path.join(onnx_model_path, "model.onnx"), providers=providers) - with open(os.path.join(onnx_model_path, "config.json")) as config_file: + with open(os.path.join(onnx_model_path, "config.json"), encoding="utf-8") as config_file: config = json.load(config_file) tokenizer = AutoTokenizer.from_pretrained(onnx_model_path, local_files_only=True) diff --git a/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py b/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py index 899cc810bf5..0f900e50a4c 100644 --- a/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py +++ b/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py @@ -336,7 +336,7 @@ def perplexity_eval(model_dir, input_len=1024, chunk_size=None): # Load model configuration from JSON file (optional) model_cfg_json = None try: - with open(f"{model_dir}/genai_config.json") as file: + with open(f"{model_dir}/genai_config.json", encoding="utf-8") as file: model_cfg_json = json.load(file) if DEBUG: print( diff --git a/examples/windows/accuracy_benchmark/trtllm_utils.py b/examples/windows/accuracy_benchmark/trtllm_utils.py index 6977b12935c..71981ad35cd 100644 --- a/examples/windows/accuracy_benchmark/trtllm_utils.py +++ b/examples/windows/accuracy_benchmark/trtllm_utils.py @@ -86,7 +86,7 @@ def supports_inflight_batching(engine_dir): def read_decoder_start_token_id(engine_dir): - with open(Path(engine_dir) / "config.json") as f: + with open(Path(engine_dir) / "config.json", encoding="utf-8") as f: config = json.load(f) return config["pretrained_config"]["decoder_start_token_id"] @@ -94,7 +94,7 @@ def read_decoder_start_token_id(engine_dir): def read_model_name(engine_dir: str): engine_version = get_engine_version(engine_dir) - with open(Path(engine_dir) / "config.json") as f: + with open(Path(engine_dir) / "config.json", encoding="utf-8") as f: config = json.load(f) if engine_version is None: @@ -163,7 +163,7 @@ def load_tokenizer( if "qwen" in model_name.lower() and model_version == "qwen": if tokenizer_dir is None: raise ValueError("tokenizer_dir must be provided for QWEN models") - with open(Path(tokenizer_dir) / "generation_config.json") as f: + with open(Path(tokenizer_dir) / "generation_config.json", encoding="utf-8") as f: gen_config = json.load(f) pad_id = gen_config["pad_token_id"] end_id = gen_config["eos_token_id"] diff --git a/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py b/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py index 855136bbd32..c085a0c3fae 100644 --- a/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py +++ b/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py @@ -548,7 +548,7 @@ def _save_checkpoint(self) -> Path: amax_dict = extract_amax_values(state_dict) if amax_dict: amax_path = save_dir / f"amax_step_{self._global_step:05d}.json" - with open(amax_path, "w") as f: + with open(amax_path, "w", encoding="utf-8") as f: json.dump( {"total_amax_keys": len(amax_dict), "amax_values": amax_dict}, f, @@ -678,7 +678,7 @@ def create_inference_checkpoint( if amax_dict: amax_path = output_path.parent / (output_path.stem + "_amax.json") output_path.parent.mkdir(parents=True, exist_ok=True) - with open(amax_path, "w") as f: + with open(amax_path, "w", encoding="utf-8") as f: json.dump( {"total_amax_keys": len(amax_dict), "amax_values": amax_dict}, f, @@ -750,7 +750,7 @@ def create_inference_checkpoint( print(f" {conv}: {cnt} tensors") dtype_log_path = output_path.parent / (output_path.stem + "_dtype_fixes.json") - with open(dtype_log_path, "w") as f: + with open(dtype_log_path, "w", encoding="utf-8") as f: json.dump({"total": dtype_fixed, "fixes": dtype_mismatches}, f, indent=2) print(f" Dtype fix log saved to: {dtype_log_path}") @@ -948,7 +948,7 @@ def main(): # ── Train ── import yaml - with open(args.config) as f: + with open(args.config, encoding="utf-8") as f: config_dict = yaml.safe_load(f) # Extract QAD-specific config (not part of LtxTrainerConfig) diff --git a/experimental/dms/models/qwen3/train.py b/experimental/dms/models/qwen3/train.py index 78fbb19a2fb..ffa960a058d 100644 --- a/experimental/dms/models/qwen3/train.py +++ b/experimental/dms/models/qwen3/train.py @@ -66,7 +66,7 @@ def load_config(path: str) -> dict: """Load a YAML configuration file.""" - with open(path) as f: + with open(path, encoding="utf-8") as f: return yaml.safe_load(f) @@ -74,7 +74,7 @@ def save_config(cfg: dict, output_dir: str) -> None: """Save the configuration to the output directory for reproducibility.""" os.makedirs(output_dir, exist_ok=True) config_path = os.path.join(output_dir, "config.yaml") - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: yaml.dump(cfg, f, default_flow_style=False, sort_keys=False) logger.info(f"Saved config to {config_path}") @@ -238,11 +238,11 @@ def extract_student_model( # Update config.json with auto_map config_path = Path(save_path) / "config.json" - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config = json.load(f) config.pop("architectures", None) config["auto_map"] = AUTO_MAP_CONFIG - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) # Copy model implementation files for trust_remote_code diff --git a/modelopt/deploy/llm/generate.py b/modelopt/deploy/llm/generate.py index 39306504137..8d8062973eb 100644 --- a/modelopt/deploy/llm/generate.py +++ b/modelopt/deploy/llm/generate.py @@ -79,7 +79,7 @@ def __init__( reuse, shared-prefix requests only return logits for the recomputed suffix, which breaks per-token logprob computation. """ - with open(Path(checkpoint_dir) / "config.json") as config_file: + with open(Path(checkpoint_dir) / "config.json", encoding="utf-8") as config_file: config = json.load(config_file) assert medusa_choices is None, "medusa_choices is not supported with the torch llmapi" diff --git a/modelopt/onnx/graph_surgery/utils/whisper_utils.py b/modelopt/onnx/graph_surgery/utils/whisper_utils.py index 012355af3a1..3987c74d1e9 100644 --- a/modelopt/onnx/graph_surgery/utils/whisper_utils.py +++ b/modelopt/onnx/graph_surgery/utils/whisper_utils.py @@ -129,7 +129,7 @@ def save_audio_processor_config( # Save to file os.makedirs(output_dir, exist_ok=True) - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: json.dump(audio_processor_cfg, f, indent=4) logger.info(f"Saved audio_processor_config.json to {output_dir}") @@ -379,7 +379,7 @@ def save_genai_config( # Save to file os.makedirs(output_dir, exist_ok=True) - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: json.dump(genai_cfg, f, indent=4) logger.info(f"Saved genai_config.json to {output_dir}") @@ -406,7 +406,7 @@ def update_genai_config_encoder( Returns: Updated configuration dictionary. """ - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config = json.load(f) # Update encoder section @@ -420,7 +420,7 @@ def update_genai_config_encoder( ) # Save updated config - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=4) logger.info(f"Updated encoder section in {config_path}") @@ -451,7 +451,7 @@ def update_genai_config_decoder( Returns: Updated configuration dictionary. """ - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config = json.load(f) # Update decoder section @@ -463,7 +463,7 @@ def update_genai_config_decoder( config["model"]["decoder"]["outputs"]["present_value_names"] = decoder_present_value_pattern # Save updated config - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=4) logger.info(f"Updated decoder section in {config_path}") diff --git a/modelopt/onnx/llm_export_utils/export_utils.py b/modelopt/onnx/llm_export_utils/export_utils.py index 2016e872e28..592d5637010 100644 --- a/modelopt/onnx/llm_export_utils/export_utils.py +++ b/modelopt/onnx/llm_export_utils/export_utils.py @@ -46,7 +46,7 @@ def __init__(self, hf_model_path: str, config_path: str): def get_model_type(self): """Get model type from config file.""" - with open(self.config_path) as f: + with open(self.config_path, encoding="utf-8") as f: return json.load(f).get("model_type") def load_model(self, trust_remote_code: bool = False) -> AutoModelForCausalLM: diff --git a/modelopt/onnx/quantization/autotune/autotuner_base.py b/modelopt/onnx/quantization/autotune/autotuner_base.py index 22ddf0ea098..fd3d41d0e76 100644 --- a/modelopt/onnx/quantization/autotune/autotuner_base.py +++ b/modelopt/onnx/quantization/autotune/autotuner_base.py @@ -737,7 +737,7 @@ def save_state(self, output_path: str) -> None: "patterns": [pattern_schemes.to_dict() for pattern_schemes in self.profiled_patterns], } - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) num_patterns = len(self.profiled_patterns) @@ -775,7 +775,7 @@ def load_state(self, input_path: str) -> None: AutotunerNotInitializedError: If initialize() hasn't been called FileNotFoundError: If the input_path doesn't exist """ - with open(input_path) as f: + with open(input_path, encoding="utf-8") as f: state = yaml.safe_load(f) if state.get("baseline_latency_ms") is not None: diff --git a/modelopt/onnx/quantization/autotune/benchmark.py b/modelopt/onnx/quantization/autotune/benchmark.py index ba5cf1142bf..b25f7f82156 100644 --- a/modelopt/onnx/quantization/autotune/benchmark.py +++ b/modelopt/onnx/quantization/autotune/benchmark.py @@ -138,7 +138,7 @@ def _write_log_file(self, file: Path | str | None, content: str) -> None: file = Path(file) try: file.parent.mkdir(parents=True, exist_ok=True) - file.write_text(content) + file.write_text(content, encoding="utf-8") self.logger.debug(f"Saved logs to: {file}") except Exception as e: self.logger.warning(f"Failed to save logs to {file}: {e}") diff --git a/modelopt/onnx/quantization/autotune/common.py b/modelopt/onnx/quantization/autotune/common.py index 31983423cd9..a0aa6744cca 100644 --- a/modelopt/onnx/quantization/autotune/common.py +++ b/modelopt/onnx/quantization/autotune/common.py @@ -739,7 +739,7 @@ def save(self, output_path: str) -> None: """ state = self.to_dict() - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) logger.info( @@ -768,7 +768,7 @@ def load(cls, input_path: str) -> "PatternCache": Raises: FileNotFoundError: If the input_path doesn't exist """ - with open(input_path) as f: + with open(input_path, encoding="utf-8") as f: state = yaml.safe_load(f) cache = cls.from_dict(state) diff --git a/modelopt/onnx/quantization/autotune/region_search.py b/modelopt/onnx/quantization/autotune/region_search.py index 02f8282a014..a6f73a0247e 100644 --- a/modelopt/onnx/quantization/autotune/region_search.py +++ b/modelopt/onnx/quantization/autotune/region_search.py @@ -884,9 +884,7 @@ def _split_sequence_regions(self, root: Region) -> list[Region]: nodes_after_merge.update(consumer.get_nodes()) nodes_after_merge.update(common_use_region.get_nodes()) node_ops = [self.graph.nodes[idx].op for idx in nodes_after_merge] - boundary_op_count = sum( - [1 if op in self.boundary_op_types else 0 for op in node_ops] - ) + boundary_op_count = sum(1 if op in self.boundary_op_types else 0 for op in node_ops) if boundary_op_count > 3: can_merge = False continue diff --git a/modelopt/onnx/quantization/autotune/utils.py b/modelopt/onnx/quantization/autotune/utils.py index 8782f004da7..d451afb6c4b 100644 --- a/modelopt/onnx/quantization/autotune/utils.py +++ b/modelopt/onnx/quantization/autotune/utils.py @@ -87,7 +87,7 @@ def get_node_filter_list(node_filter_list_path: str) -> list | None: if node_filter_list_path: filter_file = validate_file_path(node_filter_list_path, "Node filter list file") if filter_file: - with open(filter_file) as f: + with open(filter_file, encoding="utf-8") as f: node_filter_list = [ line.strip() for line in f if line.strip() and not line.strip().startswith("#") ] diff --git a/modelopt/onnx/quantization/calib_utils.py b/modelopt/onnx/quantization/calib_utils.py index 82f3af16d2c..cb0f9fe2970 100644 --- a/modelopt/onnx/quantization/calib_utils.py +++ b/modelopt/onnx/quantization/calib_utils.py @@ -161,7 +161,7 @@ def import_scales_from_calib_cache(cache_path: str) -> dict[str, float]: Dictionary with scales in the format {tensor_name: float_scale}. """ logger.info(f"Importing scales from calibration cache: {cache_path}") - with open(cache_path) as f: + with open(cache_path, encoding="utf-8") as f: scales_dict = {} lines = f.readlines() for i, line in enumerate(lines): diff --git a/modelopt/onnx/trt_utils.py b/modelopt/onnx/trt_utils.py index b407fdc5411..d1e12e3e606 100644 --- a/modelopt/onnx/trt_utils.py +++ b/modelopt/onnx/trt_utils.py @@ -503,8 +503,8 @@ def interpret_trt_plugins_precision_flag( if not custom_op_nodes: logger.warning(f"No nodes of type {op_type} were found. Skipping.") continue - num_inps = max([len(node.inputs) for node in custom_op_nodes]) - num_outs = max([len(node.outputs) for node in custom_op_nodes]) + num_inps = max(len(node.inputs) for node in custom_op_nodes) + num_outs = max(len(node.outputs) for node in custom_op_nodes) # Now split the remainder of the string to get the I/O precisions if trt_plugin_precision.count(":") == 1: diff --git a/modelopt/recipe/loader.py b/modelopt/recipe/loader.py index 91e2bac75de..7f8591b0123 100644 --- a/modelopt/recipe/loader.py +++ b/modelopt/recipe/loader.py @@ -171,7 +171,7 @@ def _peek_recipe_type(recipe_file: Path | Traversable) -> RecipeType | None: import yaml try: - raw = yaml.safe_load(recipe_file.read_text()) + raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) return RecipeType(raw["metadata"]["recipe_type"]) except (TypeError, KeyError, ValueError): return None @@ -201,7 +201,7 @@ def _load_recipe_from_file( if required_section is not None: import yaml - raw = yaml.safe_load(recipe_file.read_text()) or {} + raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict) or required_section not in raw: # Strip only the ``speculative_`` prefix so multi-word non-speculative types # (e.g. ``auto_quantize``) keep their full name: AUTO_QUANTIZE, not QUANTIZE. diff --git a/modelopt/torch/_deploy/_runtime/common.py b/modelopt/torch/_deploy/_runtime/common.py index 6e82c7dd294..3d1f2136492 100644 --- a/modelopt/torch/_deploy/_runtime/common.py +++ b/modelopt/torch/_deploy/_runtime/common.py @@ -59,7 +59,7 @@ def read_bytes(file_path: str | Path) -> bytes: def read_string(file_path: str | Path) -> str: path = Path(file_path) - return path.read_text() + return path.read_text(encoding="utf-8") def write_bytes(data: bytes, file_path: str | Path) -> None: @@ -69,4 +69,4 @@ def write_bytes(data: bytes, file_path: str | Path) -> None: def write_string(data: str, file_path: str | Path) -> None: path = Path(file_path) - path.write_text(data) + path.write_text(data, encoding="utf-8") diff --git a/modelopt/torch/_deploy/_runtime/ort_client.py b/modelopt/torch/_deploy/_runtime/ort_client.py index be025cef9d9..41dfef454a6 100644 --- a/modelopt/torch/_deploy/_runtime/ort_client.py +++ b/modelopt/torch/_deploy/_runtime/ort_client.py @@ -114,7 +114,7 @@ def _profile( # end profiling and load results prof_file = ort_session.end_profiling() - with open(prof_file) as p_file: + with open(prof_file, encoding="utf-8") as p_file: results = json.load(p_file) # get latency from profiling results (latencies are in nano-seconds) diff --git a/modelopt/torch/distill/plugins/megatron.py b/modelopt/torch/distill/plugins/megatron.py index c93f0961d1f..581f670759c 100644 --- a/modelopt/torch/distill/plugins/megatron.py +++ b/modelopt/torch/distill/plugins/megatron.py @@ -120,7 +120,7 @@ def setup_distillation_config( elif isinstance(config_or_path, DistillationConfig): cfg = config_or_path else: - with open(config_or_path) as f: + with open(config_or_path, encoding="utf-8") as f: cfg = yaml.safe_load(f) cfg = DistillationConfig(**cfg) diff --git a/modelopt/torch/export/layerwise_export.py b/modelopt/torch/export/layerwise_export.py index 9767d5635a4..04178e2e761 100644 --- a/modelopt/torch/export/layerwise_export.py +++ b/modelopt/torch/export/layerwise_export.py @@ -512,7 +512,7 @@ def _write_index(self) -> None: weight_map[key] = shard.name total_size += _shard_data_bytes(shard) index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} - (self._export_dir / _INDEX_FILE).write_text(json.dumps(index, indent=2)) + (self._export_dir / _INDEX_FILE).write_text(json.dumps(index, indent=2), encoding="utf-8") def _holds_meta_tensor(module: nn.Module) -> bool: diff --git a/modelopt/torch/export/plugins/hf_checkpoint_utils.py b/modelopt/torch/export/plugins/hf_checkpoint_utils.py index 9d508e58904..1c4f65d570a 100644 --- a/modelopt/torch/export/plugins/hf_checkpoint_utils.py +++ b/modelopt/torch/export/plugins/hf_checkpoint_utils.py @@ -237,7 +237,7 @@ def load_multimodal_components( index_file = index_dir / "model.safetensors.index.json" if index_file.is_file(): try: - weight_map = json.loads(index_file.read_text())["weight_map"] + weight_map = json.loads(index_file.read_text(encoding="utf-8"))["weight_map"] except (json.JSONDecodeError, KeyError) as exc: raise ValueError(f"Malformed safetensors index in {repo_id}.") from exc wanted = sorted( @@ -273,7 +273,7 @@ def load_multimodal_components( elif safetensors_index_file.is_file(): print(f"Loading multimodal components from sharded model: {hf_checkpoint_path}") - with open(safetensors_index_file) as f: + with open(safetensors_index_file, encoding="utf-8") as f: safetensors_index = json.load(f) all_shard_files = sorted( diff --git a/modelopt/torch/export/plugins/hf_spec_export.py b/modelopt/torch/export/plugins/hf_spec_export.py index 06be11b8a57..2a95c950203 100644 --- a/modelopt/torch/export/plugins/hf_spec_export.py +++ b/modelopt/torch/export/plugins/hf_spec_export.py @@ -260,7 +260,7 @@ def _export_lora(self, export_dir: Path, full_sd: dict): target_modules=target_modules, bias="none", ) - with open(export_dir / "adapter_config.json", "w") as f: + with open(export_dir / "adapter_config.json", "w", encoding="utf-8") as f: json.dump( lora_config.to_dict(), f, @@ -294,12 +294,12 @@ def export( drafter_config = self._export_config() if hf_quant_config is not None: drafter_config["quantization_config"] = hf_quant_config - with open(f"{export_dir}/config.json", "w") as file: + with open(f"{export_dir}/config.json", "w", encoding="utf-8") as file: json.dump(drafter_config, file, indent=4) # Export hf_quant_config for backward compatibility if hf_quant_config is not None: - with open(f"{export_dir}/hf_quant_config.json", "w") as file: + with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: json.dump(hf_quant_config, file, indent=4) # Export LoRA adapter weights separately @@ -483,11 +483,11 @@ def export(self, export_dir: Path | str, dtype: torch.dtype | None = None): drafter_config["torch_dtype"] = str(dtype).replace("torch.", "") if hf_quant_config is not None: drafter_config["quantization_config"] = hf_quant_config - with open(f"{export_dir}/config.json", "w") as f: + with open(f"{export_dir}/config.json", "w", encoding="utf-8") as f: json.dump(drafter_config, f, indent=2) if hf_quant_config is not None: - with open(f"{export_dir}/hf_quant_config.json", "w") as f: + with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as f: json.dump(hf_quant_config, f, indent=2) print( diff --git a/modelopt/torch/export/plugins/mcore_custom.py b/modelopt/torch/export/plugins/mcore_custom.py index ed3e00fd962..d3a5cf623aa 100644 --- a/modelopt/torch/export/plugins/mcore_custom.py +++ b/modelopt/torch/export/plugins/mcore_custom.py @@ -310,7 +310,7 @@ def save_safetensors(state_dict, save_directory: str | os.PathLike): local_total_size += val.numel() * val.element_size() weight_map[key] = ckpt_filename - with open(save_directory + "/" + meta_filename, "w") as f: + with open(save_directory + "/" + meta_filename, "w", encoding="utf-8") as f: json.dump( {"metadata": {"total_size": local_total_size}, "weight_map": weight_map}, f, @@ -328,12 +328,12 @@ def save_safetensors(state_dict, save_directory: str | os.PathLike): } for global_idx in range(global_count): meta_filename = f"model-{global_idx + 1:05d}-of-{global_count:05d}.json" - with open(save_directory + "/" + meta_filename) as f: + with open(save_directory + "/" + meta_filename, encoding="utf-8") as f: shard = json.load(f) safetensor_index["metadata"]["total_size"] += shard["metadata"]["total_size"] safetensor_index["weight_map"].update(shard["weight_map"]) - with open(save_directory + "/model.safetensors.index.json", "w") as f: + with open(save_directory + "/model.safetensors.index.json", "w", encoding="utf-8") as f: json.dump(safetensor_index, f, indent=4) @@ -371,7 +371,7 @@ def save_safetensors_by_layer_index( layer_total_size += tensor_size weight_map[key] = ckpt_filename - with open(save_directory + "/" + meta_filename, "w") as f: + with open(save_directory + "/" + meta_filename, "w", encoding="utf-8") as f: json.dump( {"metadata": {"total_size": layer_total_size}, "weight_map": weight_map}, f, @@ -388,12 +388,12 @@ def save_safetensors_by_layer_index( } for layer_index in range(total_layers): meta_filename = name_template.format(layer_index + 1, total_layers) + ".json" - with open(save_directory + "/" + meta_filename) as f: + with open(save_directory + "/" + meta_filename, encoding="utf-8") as f: shard = json.load(f) safetensor_index["metadata"]["total_size"] += shard["metadata"]["total_size"] safetensor_index["weight_map"].update(shard["weight_map"]) - with open(save_directory + "/model.safetensors.index.json", "w") as f: + with open(save_directory + "/model.safetensors.index.json", "w", encoding="utf-8") as f: json.dump(safetensor_index, f, indent=4) @@ -409,7 +409,7 @@ def _get_safetensors_file(pretrained_model_path: str | Path, key: str) -> Path | if safetensors_file.is_file(): pass elif safetensors_index_file.is_file(): - with open(safetensors_index_file) as f: + with open(safetensors_index_file, encoding="utf-8") as f: safetensors_index = json.load(f) safetensors_file = ( (Path(pretrained_model_path) / safetensors_index["weight_map"][key]) diff --git a/modelopt/torch/export/trtllm/model_config_export.py b/modelopt/torch/export/trtllm/model_config_export.py index 8eae2aa94e4..89f03a8f35d 100644 --- a/modelopt/torch/export/trtllm/model_config_export.py +++ b/modelopt/torch/export/trtllm/model_config_export.py @@ -570,13 +570,13 @@ def export_tensorrt_llm_checkpoint( tensorrt_llm_config["quantization"] = { k: quant_config[k] for k in ("quant_algo", "kv_cache_quant_algo") } - with open(export_dir / "quant_cfg.json", "w") as f: + with open(export_dir / "quant_cfg.json", "w", encoding="utf-8") as f: json.dump(quant_config, f, indent=4) else: # Excluded modules information is only included in non auto_quant case tensorrt_llm_config["quantization"]["exclude_modules"] = list(exclude_modules) - with open(export_dir / "config.json", "w") as f: + with open(export_dir / "config.json", "w", encoding="utf-8") as f: json.dump(tensorrt_llm_config, f, indent=4) # Hacky implementation for Encoder-Decoder for now diff --git a/modelopt/torch/export/unified_export_hf.py b/modelopt/torch/export/unified_export_hf.py index 8670af77403..7210f9e4ff5 100644 --- a/modelopt/torch/export/unified_export_hf.py +++ b/modelopt/torch/export/unified_export_hf.py @@ -160,7 +160,7 @@ def _save_component_state_dict_safetensors( metadata=metadata, ) - with open(component_export_dir / "config.json", "w") as f: + with open(component_export_dir / "config.json", "w", encoding="utf-8") as f: json.dump(metadata, f, indent=4) @@ -1354,10 +1354,10 @@ def _export_diffusers_checkpoint( if hf_quant_config is not None: config_path = component_export_dir / "config.json" if config_path.exists(): - with open(config_path) as file: + with open(config_path, encoding="utf-8") as file: config_data = json.load(file) config_data["quantization_config"] = hf_quant_config - with open(config_path, "w") as file: + with open(config_path, "w", encoding="utf-8") as file: json.dump(config_data, file, indent=4) finally: # Drop the temporary promoted export buffers so the live module is @@ -1375,10 +1375,10 @@ def _export_diffusers_checkpoint( if sparse_attn_config is not None: config_path = component_export_dir / "config.json" if config_path.exists(): - with open(config_path) as file: + with open(config_path, encoding="utf-8") as file: config_data = json.load(file) config_data["sparse_attention_config"] = sparse_attn_config - with open(config_path, "w") as file: + with open(config_path, "w", encoding="utf-8") as file: json.dump(config_data, file, indent=4) print(f" Added sparse_attention_config to {config_path.name}") @@ -1426,9 +1426,9 @@ def _export_diffusers_checkpoint( if source_path: candidate_model_index = Path(source_path) / "model_index.json" if candidate_model_index.exists(): - with open(candidate_model_index) as file: + with open(candidate_model_index, encoding="utf-8") as file: model_index = json.load(file) - with open(model_index_path, "w") as file: + with open(model_index_path, "w", encoding="utf-8") as file: json.dump(model_index, file, indent=4) # Full-export fallback to Diffusers-native config serialization. @@ -1447,7 +1447,7 @@ def _export_diffusers_checkpoint( library = module.split(".")[0] model_index[name] = [library, type(comp).__name__] - with open(model_index_path, "w") as file: + with open(model_index_path, "w", encoding="utf-8") as file: json.dump(model_index, file, indent=4) print(f"Export complete. Saved to: {export_dir}") @@ -1561,12 +1561,12 @@ def _write_hf_export_config( ) quantization_config = None if hf_quant_config is not None and is_quantized_export: - with open(f"{export_dir}/hf_quant_config.json", "w") as file: + with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: json.dump(hf_quant_config, file, indent=4) quantization_config = convert_hf_quant_config_format(hf_quant_config) original_config = f"{export_dir}/config.json" - with open(original_config) as file: + with open(original_config, encoding="utf-8") as file: config_data = json.load(file) sanitize_hf_config_for_deployment(config_data, model) if quantization_config is not None: @@ -1575,7 +1575,7 @@ def _write_hf_export_config( sparse_attn_config = export_sparse_attention_config(model) if sparse_attn_config is not None: config_data["sparse_attention_config"] = sparse_attn_config - with open(original_config, "w") as file: + with open(original_config, "w", encoding="utf-8") as file: json.dump(config_data, file, indent=4) diff --git a/modelopt/torch/export/unified_export_hf_streaming.py b/modelopt/torch/export/unified_export_hf_streaming.py index 32477173e51..1e8ba0f0153 100644 --- a/modelopt/torch/export/unified_export_hf_streaming.py +++ b/modelopt/torch/export/unified_export_hf_streaming.py @@ -178,7 +178,7 @@ def name_shards_and_write_index( weight_map = {key: shard_names[part_idx] for key, part_idx in key_to_part.items()} index_path = export_dir / "model.safetensors.index.json" - with open(index_path, "w") as f: + with open(index_path, "w", encoding="utf-8") as f: json.dump({"metadata": {"total_size": total_size}, "weight_map": weight_map}, f) return weight_map diff --git a/modelopt/torch/export/unified_export_megatron.py b/modelopt/torch/export/unified_export_megatron.py index 7ac06e73e17..38a9d316a34 100644 --- a/modelopt/torch/export/unified_export_megatron.py +++ b/modelopt/torch/export/unified_export_megatron.py @@ -402,7 +402,7 @@ def save_pretrained( }, "quantization": quantization_config, } - with open(save_directory + "/hf_quant_config.json", "w") as f: + with open(save_directory + "/hf_quant_config.json", "w", encoding="utf-8") as f: json.dump(self._hf_quant_config, f, indent=4) # Add multimodal components to state_dict. Since only support decoder model quantization, @@ -427,12 +427,12 @@ def save_pretrained( torch.distributed.barrier() config_json_file = save_directory + "/config.json" if is_writer_rank and self._hf_quant_config and os.path.exists(config_json_file): - with open(config_json_file) as f: + with open(config_json_file, encoding="utf-8") as f: config_dict = json.load(f) config_dict["quantization_config"] = convert_hf_quant_config_format( self._hf_quant_config ) - with open(config_json_file, "w") as f: + with open(config_json_file, "w", encoding="utf-8") as f: json.dump(config_dict, f, indent=4) torch.distributed.barrier() @@ -491,7 +491,7 @@ def _verify_exported_keys(self, save_directory, pretrained_model_name_or_path) - with safe_open(str(single), framework="pt", device="cpu") as f: exported = set(f.keys()) else: - with open(index_file) as f: + with open(index_file, encoding="utf-8") as f: exported = set(json.load(f)["weight_map"]) if not source: warn_rank_0(f"Export self-check skipped: no tensor index found in {source_dir}.") @@ -838,7 +838,7 @@ def _copy_mtp_state_dict_from_pretrained(self) -> dict[str, torch.Tensor]: return mtp_state_dict if safetensors_index_file is not None and safetensors_index_file.exists(): - with open(safetensors_index_file) as f: + with open(safetensors_index_file, encoding="utf-8") as f: safetensors_index = json.load(f) model_dir = safetensors_index_file.parent for key in safetensors_index["weight_map"]: @@ -2023,7 +2023,7 @@ def _read_checkpoint_keys(checkpoint_dir) -> set[str]: directory = Path(checkpoint_dir) index_file = directory / "model.safetensors.index.json" if index_file.exists(): - with open(index_file) as f: + with open(index_file, encoding="utf-8") as f: return set(json.load(f)["weight_map"]) single_file = directory / "model.safetensors" if single_file.exists(): diff --git a/modelopt/torch/nas/hparams/concat.py b/modelopt/torch/nas/hparams/concat.py index 31274ab052e..2e3e00e33ae 100644 --- a/modelopt/torch/nas/hparams/concat.py +++ b/modelopt/torch/nas/hparams/concat.py @@ -178,7 +178,7 @@ def _get_importance(self) -> TracedHp.Importance: # We need to aggregate between split importances when the come from the same hparam! imps = [ - sum([imp_ for imp_, hp in zip(imps, self._inputs) if hp is self._inputs[i]]) + sum(imp_ for imp_, hp in zip(imps, self._inputs) if hp is self._inputs[i]) for i, imp in enumerate(imps) ] diff --git a/modelopt/torch/opt/plugins/transformers.py b/modelopt/torch/opt/plugins/transformers.py index a291b5abf36..517bb97ed22 100644 --- a/modelopt/torch/opt/plugins/transformers.py +++ b/modelopt/torch/opt/plugins/transformers.py @@ -344,7 +344,7 @@ def parse_args_into_dataclasses(self, args=None, **kwargs): args = args[:idx] + args[idx + 2 :] # strip --config from argv import yaml - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config = yaml.safe_load(f) if config: known_by_parser = {a.dest for a in self._actions} @@ -446,7 +446,7 @@ def _sort_key(dc): # Remove trailing blank lines so markdownlint won't modify the file while lines and lines[-1] == "": lines.pop() - Path(output_path).write_text("\n".join(lines) + "\n") + Path(output_path).write_text("\n".join(lines) + "\n", encoding="utf-8") print(f"Generated {output_path}") @staticmethod @@ -676,7 +676,7 @@ def load_lr_config(path: str) -> dict[str, dict[str, Any]]: """ import yaml - with open(path) as f: + with open(path, encoding="utf-8") as f: cfg = yaml.safe_load(f) if not isinstance(cfg, dict): raise ValueError(f"lr_config must be a YAML mapping, got {type(cfg).__name__}") diff --git a/modelopt/torch/opt/searcher.py b/modelopt/torch/opt/searcher.py index 386948cb4a6..9a3cd2fd5b3 100644 --- a/modelopt/torch/opt/searcher.py +++ b/modelopt/torch/opt/searcher.py @@ -359,7 +359,7 @@ def _build_objective_problem( objective_value = 0 for layer_id, layer_vars in enumerate(selection_vars): objective_value += sum( - [z * a for z, a in zip(layer_vars, self.candidate_scores[layer_id])] + z * a for z, a in zip(layer_vars, self.candidate_scores[layer_id]) ) problem += (objective_value, "L") return problem @@ -375,7 +375,7 @@ def _build_budget_constraints(self, selection_vars: list[list[pulp.LpVariable]]) ) in self.constraints_to_candidate_costs.items(): cost = 0 for layer_vars, candidate_costs in zip(selection_vars, candidate_costs_list): - cost += sum([z * b for z, b in zip(layer_vars, candidate_costs)]) + cost += sum(z * b for z, b in zip(layer_vars, candidate_costs)) if isinstance(self.constraints[constraint_name], tuple): lower_bound, upper_bound = self.constraints[constraint_name] # type: ignore[misc] else: diff --git a/modelopt/torch/prune/fastnas.py b/modelopt/torch/prune/fastnas.py index 4852efdad2b..b8511bfa1a9 100644 --- a/modelopt/torch/prune/fastnas.py +++ b/modelopt/torch/prune/fastnas.py @@ -114,8 +114,8 @@ def before_search(self) -> None: # compute and register the construction of sensitivity map self._build_sensitivity_map(self.config["verbose"]) - self.max_degrade = max([max(v.values()) for v in self.sensitivity_map.values()]) - self.min_degrade = min([min(v.values()) for v in self.sensitivity_map.values()]) + self.max_degrade = max(max(v.values()) for v in self.sensitivity_map.values()) + self.min_degrade = min(min(v.values()) for v in self.sensitivity_map.values()) # overwrite the score function to be a fake function, returning the -max degrade def max_degrade(_model): @@ -133,7 +133,7 @@ def before_step(self) -> None: def _apply_fastnas_according_to_threshold(self, threshold): cfg = { - name: min([k for k, v in sensitivity.items() if v <= threshold]) + name: min(k for k, v in sensitivity.items() if v <= threshold) for name, sensitivity in self.sensitivity_map.items() } select(self.model, cfg, strict=False) @@ -208,7 +208,7 @@ def _build_sensitivity_map(self, verbose=False) -> None: # Getting the number of choices needed to validate total_choices_to_validate = sum( - [len(hparam.choices) for hparam in binary_search_hps.values()] + len(hparam.choices) for hparam in binary_search_hps.values() ) assert total_choices_to_validate != 0, f"{type(self).__name__}: no searchable hparams found" @@ -223,7 +223,7 @@ def _build_sensitivity_map(self, verbose=False) -> None: } remaining_choices_to_validate = sum( - [len(hparam.choices) for hparam in binary_search_hps.values()] + len(hparam.choices) for hparam in binary_search_hps.values() ) if remaining_choices_to_validate == 0: diff --git a/modelopt/torch/prune/importance_hooks/base_hooks.py b/modelopt/torch/prune/importance_hooks/base_hooks.py index 5eccd033d65..e74fbc9e6ed 100644 --- a/modelopt/torch/prune/importance_hooks/base_hooks.py +++ b/modelopt/torch/prune/importance_hooks/base_hooks.py @@ -778,7 +778,7 @@ def _save_channel_importance_results( # Save the output output_path = activations_log_dir / "channel_importance_results.json" print(f"Saving channel importance data to {output_path}") - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: json.dump(output_data, f, indent=2) # Print summary statistics diff --git a/modelopt/torch/prune/importance_hooks/compare_module_outputs.py b/modelopt/torch/prune/importance_hooks/compare_module_outputs.py index 37e7ef69340..d86f66ffcc4 100644 --- a/modelopt/torch/prune/importance_hooks/compare_module_outputs.py +++ b/modelopt/torch/prune/importance_hooks/compare_module_outputs.py @@ -297,7 +297,7 @@ def compare_multi_layer(ref_data: dict, comp_data: dict, output_json: str | None results["aggregated"].pop("rmse", None) results["aggregated"].pop("cosine_sim_mean", None) - with open(output_json, "w") as f: + with open(output_json, "w", encoding="utf-8") as f: json.dump(results, f, indent=2) print(f"Saved comparison results to {output_json}") diff --git a/modelopt/torch/puzzletron/anymodel/converter/base.py b/modelopt/torch/puzzletron/anymodel/converter/base.py index c8e01ffe289..c8762876fa4 100644 --- a/modelopt/torch/puzzletron/anymodel/converter/base.py +++ b/modelopt/torch/puzzletron/anymodel/converter/base.py @@ -50,7 +50,7 @@ def _get_weight_map(input_dir: Path) -> Dict[str, str]: if index_path.exists(): # Sharded model - with open(index_path, "r") as f: + with open(index_path, "r", encoding="utf-8") as f: index = json.load(f) return index["weight_map"] elif single_file_path.exists(): diff --git a/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py b/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py index 85355146aba..760a119735d 100644 --- a/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py +++ b/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py @@ -186,7 +186,7 @@ def deduce_experts_for_layer( def load_original_index(path: str) -> Dict[str, Any]: """Load the original model's safetensors index.""" - with open(path, "r") as f: + with open(path, "r", encoding="utf-8") as f: return json.load(f) @@ -381,7 +381,7 @@ def copy_config_files(student_path: str, output_path: str): if not os.path.exists(src_config): raise FileNotFoundError(f"config.json not found at {src_config}") - with open(src_config, "r") as f: + with open(src_config, "r", encoding="utf-8") as f: config = json.load(f) # type: ignore[arg-type] # Set architecture to DeciGptOssForCausalLM for MXFP4 support @@ -399,7 +399,7 @@ def copy_config_files(student_path: str, output_path: str): } dst_config = os.path.join(output_path, "config.json") - with open(dst_config, "w") as f: + with open(dst_config, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) # type: ignore[arg-type] @@ -475,7 +475,7 @@ def main(): # Save experts_to_keep.json experts_to_keep_output = os.path.join(args.output_path, "experts_to_keep.json") - with open(experts_to_keep_output, "w") as f: + with open(experts_to_keep_output, "w", encoding="utf-8") as f: json.dump(experts_to_keep, f, indent=2) print(f" Saved experts_to_keep mapping to {experts_to_keep_output}") @@ -515,7 +515,7 @@ def main(): index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} index_path = os.path.join(args.output_path, "model.safetensors.index.json") - with open(index_path, "w") as f: + with open(index_path, "w", encoding="utf-8") as f: json.dump(index, f, indent=2) print(f"\nCheckpoint created successfully at: {args.output_path}") diff --git a/modelopt/torch/puzzletron/mip/run_puzzle.py b/modelopt/torch/puzzletron/mip/run_puzzle.py index 22c8b471546..6c9bf668b2e 100644 --- a/modelopt/torch/puzzletron/mip/run_puzzle.py +++ b/modelopt/torch/puzzletron/mip/run_puzzle.py @@ -328,7 +328,7 @@ def run_single_puzzle_config( solution_repr_0 = solutions[0]["solution_repr"] mprint(f"\n{solution_repr_0}") mprint(f"Total costs: {solutions[0]['total_costs']}") - (output_folder / "solution_repr_0.txt").write_text(solution_repr_0) + (output_folder / "solution_repr_0.txt").write_text(solution_repr_0, encoding="utf-8") solutions_file = output_folder / "solutions.json" json_dump(solutions, solutions_file) @@ -439,7 +439,7 @@ def _get_minimal_unique_names(dicts: list[dict]) -> list[str]: def run_puzzle(args: DictConfig) -> list[str]: # Loads config from args/puzzle_profile if args.puzzle_profile is not None: - with open(args.puzzle_profile) as f: + with open(args.puzzle_profile, encoding="utf-8") as f: puzzle_profile = yaml.safe_load(f) _override_args_from_profile(args, puzzle_profile) mprint(f"Loaded Puzzle profile from {args.puzzle_profile}") @@ -449,7 +449,7 @@ def run_puzzle(args: DictConfig) -> list[str]: # Read Metrics and Stats if args.gathered_metrics_path is not None: - gathered_metrics = json.loads(args.gathered_metrics_path.read_text()) + gathered_metrics = json.loads(args.gathered_metrics_path.read_text(encoding="utf-8")) else: gathered_metrics = gather_multi_layer_puzzle_metrics( args.single_block_replacement_validation_dir @@ -458,7 +458,7 @@ def run_puzzle(args: DictConfig) -> list[str]: if args.metric_overrides is not None: gathered_metrics = {**gathered_metrics, **args.metric_overrides} - subblock_stats = json.loads(args.subblock_stats_path.read_text()) + subblock_stats = json.loads(args.subblock_stats_path.read_text(encoding="utf-8")) all_subblock_args = _load_all_subblock_stats_args(args, puzzle_profile) all_subblock_output_folders = [ @@ -533,7 +533,7 @@ def gather_multi_layer_puzzle_metrics( def _parse_single_block_replacement_metrics(metrics_path: Path) -> dict: - raw_metrics = json.loads(metrics_path.read_text()) + raw_metrics = json.loads(metrics_path.read_text(encoding="utf-8")) single_block_replacement = raw_metrics["puzzle_solution"]["single_block_replacement"] variant_metrics = { "block_config": BlockConfig(**single_block_replacement["block_config"]), @@ -544,7 +544,7 @@ def _parse_single_block_replacement_metrics(metrics_path: Path) -> dict: def _parse_single_sequence_replacement_metrics(metrics_path: Path) -> dict: - raw_metrics = json.loads(metrics_path.read_text()) + raw_metrics = json.loads(metrics_path.read_text(encoding="utf-8")) single_sequence_replacement = raw_metrics["puzzle_solution"]["single_sequence_replacement"] if len(single_sequence_replacement["child_block_configs"]) > 1: raise NotImplementedError( @@ -565,7 +565,9 @@ def _parse_teacher_block_metrics( single_block_replacement_validation_dir: Path, all_metric_names: Iterable[str] = ("kl_div_loss",), ) -> list[dict]: - raw_metrics = json.loads((single_block_replacement_validation_dir / "teacher.json").read_text()) + raw_metrics = json.loads( + (single_block_replacement_validation_dir / "teacher.json").read_text(encoding="utf-8") + ) teacher_checkpoint_dir = Path(raw_metrics["args"]["teacher_dir"]).resolve() descriptor_name = raw_metrics["args"]["descriptor"] descriptor = ModelDescriptorFactory.get(descriptor_name) @@ -578,7 +580,9 @@ def _parse_teacher_block_metrics( replacement_library_path = raw_metrics["args"].get("replacement_library_path") if replacement_library_path is not None: teacher_replacements = dict() - all_layer_replacements = json.loads(Path(replacement_library_path).read_text()) + all_layer_replacements = json.loads( + Path(replacement_library_path).read_text(encoding="utf-8") + ) for layer_replacement in all_layer_replacements: layer_replacement = parse_layer_replacement(layer_replacement) if replacement_is_teacher( diff --git a/modelopt/torch/puzzletron/mip/sweep.py b/modelopt/torch/puzzletron/mip/sweep.py index ea4e95dc3ed..dcc89efac28 100644 --- a/modelopt/torch/puzzletron/mip/sweep.py +++ b/modelopt/torch/puzzletron/mip/sweep.py @@ -69,7 +69,7 @@ def _load_teacher_subblock_stats(hydra_cfg: DictConfig) -> tuple[dict[str, Any], "Please run the full pipeline first without --mip-only flag." ) - with open(subblock_stats_path) as f: + with open(subblock_stats_path, encoding="utf-8") as f: subblock_stats_list = json.load(f) try: @@ -158,7 +158,7 @@ def extract_solution_results( # Load solutions.json for actual memory and parameters solutions_file = solution_dir / "solutions.json" - with open(solutions_file) as f: + with open(solutions_file, encoding="utf-8") as f: solutions_data = json.load(f) solution = solutions_data[0] # First solution total_costs = solution.get("total_costs", {}) @@ -170,7 +170,7 @@ def extract_solution_results( # TODO: There could be multiple solutions, but we only need the first one. Is it the best solution? solution_0_file = validation_dir / "solution_0.json" - with open(solution_0_file) as f: + with open(solution_0_file, encoding="utf-8") as f: validation_data = json.load(f) result["lm_loss"] = validation_data.get("lm_loss", {}).get("avg", None) result["token_accuracy_top_1"] = validation_data.get("token_accuracy_top_1", {}).get( @@ -212,7 +212,7 @@ def write_results_to_csv(results: list, output_csv: str): output_path = Path(output_csv) output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w", newline="") as f: + with open(output_path, "w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=columns) writer.writeheader() writer.writerows(results) diff --git a/modelopt/torch/puzzletron/pruning/pruning_utils.py b/modelopt/torch/puzzletron/pruning/pruning_utils.py index 38ab7a2e0be..5ed1d317aaf 100644 --- a/modelopt/torch/puzzletron/pruning/pruning_utils.py +++ b/modelopt/torch/puzzletron/pruning/pruning_utils.py @@ -652,7 +652,7 @@ def _load_expert_scores( assert mlp_init_config is not None if "expert_scores_file" in mlp_init_config: expert_scores_file = mlp_init_config["expert_scores_file"] - with open(expert_scores_file, "r") as f: + with open(expert_scores_file, "r", encoding="utf-8") as f: expert_scores = json.load(f) elif "activations_log_dir" in mlp_init_config: _cache_activations_log(mlp_init_config) diff --git a/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py b/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py index ae156ad8e19..fb1cd347e67 100644 --- a/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py +++ b/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py @@ -521,7 +521,7 @@ def _gather_layer_replacements_from_checkpoints( ) for checkpoint_dir in checkpoint_dirs: if (layer_replacements_path := checkpoint_dir / "replacement_library.json").exists(): - layer_replacements = json.loads(layer_replacements_path.read_text()) + layer_replacements = json.loads(layer_replacements_path.read_text(encoding="utf-8")) for layer_replacement in layer_replacements: layer_replacement["child_block_configs"] = [ BlockConfig(**block_config_dict) diff --git a/modelopt/torch/puzzletron/replacement_library/library.py b/modelopt/torch/puzzletron/replacement_library/library.py index d6012f596a2..fb3b27331e6 100644 --- a/modelopt/torch/puzzletron/replacement_library/library.py +++ b/modelopt/torch/puzzletron/replacement_library/library.py @@ -61,7 +61,7 @@ def __init__( @staticmethod def _load_replacement_library(replacement_library_path: str | Path) -> list[dict]: - replacement_library = json.loads(Path(replacement_library_path).read_text()) + replacement_library = json.loads(Path(replacement_library_path).read_text(encoding="utf-8")) replacement_library = [ parse_layer_replacement(layer_replacement) for layer_replacement in replacement_library ] diff --git a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py index 531f7a3f0a1..0142740738a 100644 --- a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py +++ b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py @@ -252,7 +252,7 @@ def load_moe_stats(stats_file: str) -> dict: distribution over experts for the corresponding block. If a block's expert list is empty, its entry is 0. """ - with open(stats_file) as f: + with open(stats_file, encoding="utf-8") as f: stats = json.load(f) return [ np.array(expert_probs) / np.sum(expert_probs) if len(expert_probs) > 0 else 0 diff --git a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py index 1d04cc01add..fd7d380a15a 100644 --- a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py +++ b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py @@ -295,7 +295,7 @@ def calculate_subblock_stats_for_puzzle_dir( ) if subblock_stats_file.exists(): - with open(subblock_stats_file) as f: + with open(subblock_stats_file, encoding="utf-8") as f: subblock_stats = json.load(f) else: subblock_stats = [] @@ -416,7 +416,9 @@ def _load_subblock_configs_from_replacement_library( Args: master_puzzle_dir: Directory with "replacement_library.json" file """ - replacement_library = json.loads((master_puzzle_dir / "replacement_library.json").read_text()) + replacement_library = json.loads( + (master_puzzle_dir / "replacement_library.json").read_text(encoding="utf-8") + ) subblock_configs = set() for layer_replacement in replacement_library: layer_replacement = parse_layer_replacement(layer_replacement) diff --git a/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py b/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py index 204c2a74305..a8d5c491f91 100644 --- a/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py +++ b/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py @@ -84,10 +84,10 @@ def save_model_as_anymodel(model, output_dir: Path, descriptor): config_path = output_dir / "config.json" if config_path.exists(): - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config_data = json.load(f) config_data["architectures"] = ["AnyModel"] - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(config_data, f, indent=2) @@ -105,7 +105,7 @@ def convert_config_to_vllm_anymodel(config_dir: Path): shutil.copy(config_path, backup_config_path) try: - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config_data = json.load(f) except json.JSONDecodeError as e: raise ValueError(f"Error loading config file: {e}") from e @@ -118,7 +118,7 @@ def convert_config_to_vllm_anymodel(config_dir: Path): mprint("Converted block configs to per-layer config") else: mprint("No block configs to convert") - with open(config_path, "w") as f: + with open(config_path, "w", encoding="utf-8") as f: json.dump(vars(config), f, indent=2) diff --git a/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py b/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py index 386f3615d49..0935b182332 100644 --- a/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py +++ b/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py @@ -46,13 +46,13 @@ def run_vllm_latency_benchmark(model_path: Path, runtime_config: RuntimeConfig) output_json_path = model_path / "vllm_latency_benchmark.json" max_model_len = runtime_config.prefill_seq_len + runtime_config.generation_seq_len - with open(model_path / "config.json") as f: + with open(model_path / "config.json", encoding="utf-8") as f: config = json.load(f) config = SimpleNamespace(**config) if convert_block_configs_to_per_layer_config(config): mprint("Converted block configs to per-layer config") - with open(model_path / "config.json", "w") as f: + with open(model_path / "config.json", "w", encoding="utf-8") as f: json.dump(vars(config), f, indent=2) else: mprint("No block configs to convert") @@ -109,7 +109,7 @@ def run_vllm_latency_benchmark(model_path: Path, runtime_config: RuntimeConfig) raise RuntimeError(exc.stderr or exc.stdout or "vLLM latency benchmark failed") from exc if output_json_path.exists(): - with open(output_json_path) as f: + with open(output_json_path, encoding="utf-8") as f: vllm_results = json.load(f) if "avg_latency" in vllm_results: return vllm_results["avg_latency"] * 1000 # seconds -> milliseconds diff --git a/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py b/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py index 3979f305261..cd131dc3b2a 100644 --- a/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py +++ b/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py @@ -918,7 +918,9 @@ def _parse_model_config_overrides( if os.path.exists( model_config_overrides_json ): # using os.path.exists, because Path.exists throws an exception on long strings - model_config_overrides_json = Path(model_config_overrides_json).read_text() + model_config_overrides_json = Path(model_config_overrides_json).read_text( + encoding="utf-8" + ) print(f"I'm json loadsing over here. {model_config_overrides_json=}") model_config_overrides_dict = json.loads(model_config_overrides_json) @@ -975,7 +977,7 @@ def _apply_hidden_size_pruning( channel_ranking = None if hidden_size_init_mode == HiddenSizeInitMode.PruneByChannelRanking: if channel_importance_path is not None: - with open(channel_importance_path, "r") as f: + with open(channel_importance_path, "r", encoding="utf-8") as f: channel_ranking = json.load(f)["channel_importance_ranking"] else: raise ValueError( diff --git a/modelopt/torch/puzzletron/tools/checkpoint_utils.py b/modelopt/torch/puzzletron/tools/checkpoint_utils.py index becabf04314..162733d7679 100644 --- a/modelopt/torch/puzzletron/tools/checkpoint_utils.py +++ b/modelopt/torch/puzzletron/tools/checkpoint_utils.py @@ -182,7 +182,9 @@ def copy_tokenizer( """ source_tokenizer_name_path = Path(source_dir_or_tokenizer_name) / "tokenizer_name.txt" if source_tokenizer_name_path.exists(): - source_dir_or_tokenizer_name = source_tokenizer_name_path.read_text().strip() + source_dir_or_tokenizer_name = source_tokenizer_name_path.read_text( + encoding="utf-8" + ).strip() tokenizer = None try: @@ -204,4 +206,4 @@ def copy_tokenizer( target_tokenizer_name_path = target_dir / "tokenizer_name.txt" is_given_tokenizer_name_as_argument = not Path(source_dir_or_tokenizer_name).exists() if is_given_tokenizer_name_as_argument: - target_tokenizer_name_path.write_text(source_dir_or_tokenizer_name) + target_tokenizer_name_path.write_text(source_dir_or_tokenizer_name, encoding="utf-8") diff --git a/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py b/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py index 9a9ebbaade1..047b26e093a 100644 --- a/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py +++ b/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py @@ -367,7 +367,7 @@ def save_sharded_model( index = {"metadata": metadata, "weight_map": weight_map} index_path = Path(str(out_path) + ".index.json") - index_path.write_text(json.dumps(index, indent=2)) + index_path.write_text(json.dumps(index, indent=2), encoding="utf-8") else: torch.distributed.gather_object(shard_metadata, dst=0) diff --git a/modelopt/torch/puzzletron/tools/validate_model.py b/modelopt/torch/puzzletron/tools/validate_model.py index b5d997286f9..a13cbe76cc9 100644 --- a/modelopt/torch/puzzletron/tools/validate_model.py +++ b/modelopt/torch/puzzletron/tools/validate_model.py @@ -198,7 +198,9 @@ def validate_model( results_str = textwrap.dedent(results_str) aprint(results_str) if args.write_results: - Path(f"{args.model_name_or_path}/validate_model_results.txt").write_text(results_str) + Path(f"{args.model_name_or_path}/validate_model_results.txt").write_text( + results_str, encoding="utf-8" + ) if activation_hooks is not None: hook_class.dump_activations_logs(activation_hooks, args.activations_log_dir, args) diff --git a/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py b/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py index 3ed4b517b3e..60183377d4b 100644 --- a/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py +++ b/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py @@ -277,12 +277,13 @@ def load_puzzle_solutions( assert solutions_path.exists(), f"{solutions_path=} does not exist" if solutions_path.is_file(): - puzzle_solutions = json.loads(solutions_path.read_text()) + puzzle_solutions = json.loads(solutions_path.read_text(encoding="utf-8")) if isinstance(puzzle_solutions, dict): puzzle_solutions = [puzzle_solutions] else: puzzle_solutions = [ - json.loads(p.read_text()) for p in solutions_path.glob("*solution*.json") + json.loads(p.read_text(encoding="utf-8")) + for p in solutions_path.glob("*solution*.json") ] if len(puzzle_solutions) == 0: diff --git a/modelopt/torch/puzzletron/utils/checkpoint_manager.py b/modelopt/torch/puzzletron/utils/checkpoint_manager.py index e0b90deaeac..342b5ac0ddc 100644 --- a/modelopt/torch/puzzletron/utils/checkpoint_manager.py +++ b/modelopt/torch/puzzletron/utils/checkpoint_manager.py @@ -75,7 +75,7 @@ def load_checkpoint(self) -> dict[str, Any] | None: return None try: - with open(self.progress_file) as f: + with open(self.progress_file, encoding="utf-8") as f: checkpoint_data = json.load(f) # Validate checkpoint @@ -222,7 +222,7 @@ def save_checkpoint(self): # Write progress atomically temp_file = self.progress_file.with_suffix(".tmp") - with open(temp_file, "w") as f: + with open(temp_file, "w", encoding="utf-8") as f: json.dump(progress_data, f, indent=2) temp_file.replace(self.progress_file) diff --git a/modelopt/torch/puzzletron/utils/misc.py b/modelopt/torch/puzzletron/utils/misc.py index 68751d1e07e..36ef1d336b4 100644 --- a/modelopt/torch/puzzletron/utils/misc.py +++ b/modelopt/torch/puzzletron/utils/misc.py @@ -98,7 +98,7 @@ def load_json(file_path: str): print("file does not exist {file_path}") return None - with open(file=file_path) as f: + with open(encoding="utf-8", file=file_path) as f: return json.load(f) diff --git a/modelopt/torch/quantization/plugins/attention.py b/modelopt/torch/quantization/plugins/attention.py index 2113edea8a7..ec5d635e248 100644 --- a/modelopt/torch/quantization/plugins/attention.py +++ b/modelopt/torch/quantization/plugins/attention.py @@ -255,7 +255,7 @@ def _create_quantized_class_from_ast( temp_file_name = temp_file.name print(f"Definition of {new_class_name} saved to {temp_file_name}") else: - with open(temp_file_name, "w") as f: + with open(temp_file_name, "w", encoding="utf-8") as f: f.write(module_code_str) # Exec with python runtime and extract the new class diff --git a/modelopt/torch/quantization/utils/layerwise_calib.py b/modelopt/torch/quantization/utils/layerwise_calib.py index 56e7554f522..56070b2d11e 100644 --- a/modelopt/torch/quantization/utils/layerwise_calib.py +++ b/modelopt/torch/quantization/utils/layerwise_calib.py @@ -568,7 +568,7 @@ def _read_manifest(checkpoint_dir: str) -> dict | None: if not os.path.isfile(path): return None try: - with open(path) as f: + with open(path, encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, OSError): return None @@ -585,7 +585,7 @@ def _write_manifest( """Atomically write manifest.json. Config keys are persisted so resume can detect drift.""" path = os.path.join(checkpoint_dir, "manifest.json") tmp = path + ".tmp" - with open(tmp, "w") as f: + with open(tmp, "w", encoding="utf-8") as f: json.dump( { "last_completed_layer": last_completed_layer, diff --git a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py index abbbc399d6b..74a2ba5fc7e 100644 --- a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py +++ b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py @@ -142,7 +142,7 @@ def _load_paul_graham_essays_from_files(data_dir: Path) -> str: all_essays = [] for filepath in essay_files: - text = filepath.read_text() + text = filepath.read_text(encoding="utf-8") all_essays.append(text) combined_text = " ".join(all_essays) @@ -740,7 +740,7 @@ def _load_cached_data(self, cache_path: Path) -> list[dict[str, Any]] | None: """Load calibration data from cache if it exists.""" if cache_path.exists(): try: - with open(cache_path) as f: + with open(cache_path, encoding="utf-8") as f: data = json.load(f) print(f"Loaded {len(data)} cached calibration samples from {cache_path}") return data @@ -752,7 +752,7 @@ def _save_cached_data(self, cache_path: Path, data: list[dict[str, Any]]) -> Non """Save calibration data to cache.""" try: cache_path.parent.mkdir(parents=True, exist_ok=True) - with open(cache_path, "w") as f: + with open(cache_path, "w", encoding="utf-8") as f: json.dump(data, f) print(f"Saved calibration samples to cache: {cache_path}") except Exception as e: diff --git a/modelopt/torch/speculative/plugins/modeling_fakebase.py b/modelopt/torch/speculative/plugins/modeling_fakebase.py index 2b5fe989c03..6731f9721b2 100644 --- a/modelopt/torch/speculative/plugins/modeling_fakebase.py +++ b/modelopt/torch/speculative/plugins/modeling_fakebase.py @@ -242,7 +242,7 @@ def _try_fetch(name: str) -> str | None: return None if (index_path := _try_fetch(_SAFETENSORS_INDEX_FILENAME)) is not None: - with open(index_path) as f: + with open(index_path, encoding="utf-8") as f: return json.load(f).get("weight_map", {}) for single_name in _SAFETENSORS_SINGLE_FILENAMES: if (single_path := _try_fetch(single_name)) is not None: diff --git a/modelopt/torch/utils/logging.py b/modelopt/torch/utils/logging.py index 85d3b9df18f..1137f737b50 100644 --- a/modelopt/torch/utils/logging.py +++ b/modelopt/torch/utils/logging.py @@ -102,7 +102,11 @@ def _new_init(self, *args, **kwargs): def no_stdout(): """Silences stdout within the invoked context.""" # Special disable for tqdm - with open(os.devnull, "w") as f, contextlib.redirect_stdout(f), _disable_tqdm(): + with ( + open(os.devnull, "w", encoding="utf-8") as f, + contextlib.redirect_stdout(f), + _disable_tqdm(), + ): yield diff --git a/modelopt/torch/utils/mlflow.py b/modelopt/torch/utils/mlflow.py index 13740aeb858..5322640f095 100644 --- a/modelopt/torch/utils/mlflow.py +++ b/modelopt/torch/utils/mlflow.py @@ -168,10 +168,10 @@ def _git_sha() -> str: try: git_path = Path(__file__).resolve().parents[3] / ".git" if git_path.is_file(): - git_dir = Path(git_path.read_text().split("gitdir:", 1)[1].strip()) + git_dir = Path(git_path.read_text(encoding="utf-8").split("gitdir:", 1)[1].strip()) else: git_dir = git_path - head = (git_dir / "HEAD").read_text().strip() + head = (git_dir / "HEAD").read_text(encoding="utf-8").strip() if not head.startswith("ref: "): return head[:9] # detached HEAD ref = head.removeprefix("ref: ") @@ -179,13 +179,13 @@ def _git_sha() -> str: bases = [git_dir] commondir = git_dir / "commondir" if commondir.is_file(): - bases.append((git_dir / commondir.read_text().strip()).resolve()) + bases.append((git_dir / commondir.read_text(encoding="utf-8").strip()).resolve()) for base in bases: if (base / ref).is_file(): - return (base / ref).read_text().strip()[:9] + return (base / ref).read_text(encoding="utf-8").strip()[:9] packed = base / "packed-refs" if packed.is_file(): - for line in packed.read_text().splitlines(): + for line in packed.read_text(encoding="utf-8").splitlines(): sha, _, name = line.partition(" ") if name.strip() == ref: return sha[:9] diff --git a/modelopt/torch/utils/plugins/model_load_utils.py b/modelopt/torch/utils/plugins/model_load_utils.py index cd66567fa9a..0eaa8dff241 100644 --- a/modelopt/torch/utils/plugins/model_load_utils.py +++ b/modelopt/torch/utils/plugins/model_load_utils.py @@ -83,7 +83,7 @@ def weight_map_for(ckpt_path: str) -> dict[str, str]: index_path = os.path.join(ckpt_path, "model.safetensors.index.json") single_file = os.path.join(ckpt_path, "model.safetensors") if os.path.exists(index_path): - with open(index_path) as f: + with open(index_path, encoding="utf-8") as f: return json.load(f)["weight_map"] if os.path.exists(single_file): with safe_open(single_file, framework="pt", device="cpu") as f: diff --git a/modelopt/torch/utils/robust_json.py b/modelopt/torch/utils/robust_json.py index 23a3091637f..c3cd7116660 100644 --- a/modelopt/torch/utils/robust_json.py +++ b/modelopt/torch/utils/robust_json.py @@ -75,11 +75,11 @@ def json_dump(obj: Any, path: Path | str) -> None: path = Path(path) path.parent.mkdir(exist_ok=True, parents=True) json_text = json_dumps(obj) - path.write_text(json_text) + path.write_text(json_text, encoding="utf-8") def json_load(path: Path | str) -> dict: """Load JSON from file and return as dictionary.""" path = Path(path) - text = path.read_text() + text = path.read_text(encoding="utf-8") return json.loads(text) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py index 71563d2534b..633e1ba4fe4 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py @@ -315,7 +315,7 @@ def _write_builtin(path: Path, rows: list[dict[str, str]]) -> None: for row in rows: for key in row: fieldnames.setdefault(key, None) - with path.open("w", newline="") as stream: + with path.open("w", encoding="utf-8", newline="") as stream: writer = csv.DictWriter( stream, fieldnames=list(fieldnames), restval="", lineterminator="\n" ) @@ -645,7 +645,7 @@ def _write_results( columns = ["module_name", "M", "N", "K", "backend", "with_quant", "runtime"] gemm = [case for case in cases if case.section == "gemm" and case.result is not None] moe = [case for case in cases if case.section == "moe" and case.result is not None] - with path.open("w", newline="") as stream: + with path.open("w", encoding="utf-8", newline="") as stream: writer = csv.writer(stream, lineterminator="\n") if header: writer.writerow([header]) @@ -752,7 +752,7 @@ def _execute_cases( """ case_csv = workdir / "case_result.csv" rows: list[dict[str, str]] = [] - with driver_log.open("w") as log: + with driver_log.open("w", encoding="utf-8") as log: print(header, flush=True) log.write(header + "\n") for case in cases: diff --git a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py index d40d97bb5c5..2088765899b 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py @@ -395,7 +395,7 @@ def test_case_rows_with_foreign_tags_are_treated_as_failures(monkeypatch, tmp_pa def fake_run_case(benchmarks_dir, argv, log): output = Path(argv[argv.index("--output_path") + 1]) - output.write_text("case_tag,median_time\nsomeone_else,0.001\n") + output.write_text("case_tag,median_time\nsomeone_else,0.001\n", encoding="utf-8") return 0, [] monkeypatch.setattr(benchmark, "_run_case", fake_run_case) diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_compare.py b/plugins/modelopt/skills/day0-release/scripts/gate_compare.py index 16767630f5e..767ca50c133 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_compare.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_compare.py @@ -201,9 +201,9 @@ def main(argv=None): args = p.parse_args(argv) try: - with open(args.baseline) as f: + with open(args.baseline, encoding="utf-8") as f: baseline = json.load(f) - with open(args.candidate) as f: + with open(args.candidate, encoding="utf-8") as f: candidate = json.load(f) scales = json.loads(args.scales) if args.scales else None except (OSError, json.JSONDecodeError) as e: diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py b/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py index 5fa61862bc8..9005e273039 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py @@ -309,7 +309,7 @@ def main(argv=None): return 2 try: - with open(args.summary) as f: + with open(args.summary, encoding="utf-8") as f: summary = json.load(f) except (OSError, json.JSONDecodeError) as e: print( diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_run.py b/plugins/modelopt/skills/day0-release/scripts/gate_run.py index d5dcbe94a70..ce333417cc0 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_run.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_run.py @@ -144,7 +144,7 @@ def main(argv=None): args = p.parse_args(argv) try: - with open(args.run) as f: + with open(args.run, encoding="utf-8") as f: summary = json.load(f) except (OSError, json.JSONDecodeError) as e: print(json.dumps({"pass": False, "failure_class": "USER_CONFIG_ERROR", "detail": str(e)})) diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py b/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py index fbfb86649f7..2742fbcda74 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py @@ -274,7 +274,7 @@ def _task_from_metadata(artifacts_dir): """ for fname in ("metadata.yaml", "config.yml"): try: - with open(os.path.join(artifacts_dir, fname)) as f: + with open(os.path.join(artifacts_dir, fname), encoding="utf-8") as f: text = f.read() except OSError: continue @@ -342,7 +342,7 @@ def harvest(side, glob="eval_*", exclude="", diagnostics=None): head, _, tail = name.rpartition(".") task = head if head and re.fullmatch(r"\d+", tail) else name try: - with open(path) as f: + with open(path, encoding="utf-8") as f: stats = json.load(f).get("response_stats", {}) except (OSError, json.JSONDecodeError) as e: unreadable.append(f"{path}: {e}") diff --git a/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py b/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py index 883195bf491..3f03016ac00 100644 --- a/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py +++ b/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py @@ -30,7 +30,7 @@ def _load_claude_agent(path: Path) -> tuple[str, str]: - text = path.read_text() + text = path.read_text(encoding="utf-8") assert text.startswith("---\n"), f"{path} has no YAML frontmatter" frontmatter, body = text.removeprefix("---\n").split("\n---\n", 1) names = [ diff --git a/pyproject.toml b/pyproject.toml index 0886e0380b3..410ec6ee1ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,6 +197,12 @@ docstring-code-line-length = "dynamic" [tool.ruff.lint] # See available rules at https://docs.astral.sh/ruff/rules/ # Flake8 is equivalent to pycodestyle + pyflakes + mccabe. +# PLW1514 (unspecified-encoding) is still a preview rule, and plain `preview = true` would also +# switch on preview BEHAVIOUR for the stable rules above -- 3755 findings on this tree. +# explicit-preview-rules keeps that contained: only preview rules named exactly here are enabled. +preview = true +explicit-preview-rules = true + select = [ "C4", # Flake8 comprehensions "D", # pydocstyle @@ -210,6 +216,11 @@ select = [ "PGH", # pygrep-hooks "PIE", # flake8-pie "PLE", # pylint errors + # Text I/O without an explicit encoding uses the locale codepage, which is cp1252 on the + # Windows runners -- a UTF-8 file then dies with UnicodeDecodeError on the first non-Latin-1 + # byte, a failure no other platform sees. Covers `open` only; see the read_text/write_text + # pre-commit hook for the half ruff does not implement. + "PLW1514", # pylint: unspecified encoding "PLR", # pylint refactor "PT", # flake8-pytest-style "RUF", # ruff diff --git a/tests/_test_utils/deploy_utils.py b/tests/_test_utils/deploy_utils.py index 00abcdf0b74..d8979c3865a 100644 --- a/tests/_test_utils/deploy_utils.py +++ b/tests/_test_utils/deploy_utils.py @@ -178,7 +178,9 @@ def _run_deploy_via_subprocess( cmd = [sys.executable, "-c", code] if backend == "trtllm": - with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + with tempfile.NamedTemporaryFile( + encoding="utf-8", mode="w", suffix=".py", delete=False + ) as f: f.write(code) tmp_path = f.name try: diff --git a/tests/_test_utils/examples/megatron_example_runner.py b/tests/_test_utils/examples/megatron_example_runner.py index ac974a5a07e..d01d9fc11e8 100644 --- a/tests/_test_utils/examples/megatron_example_runner.py +++ b/tests/_test_utils/examples/megatron_example_runner.py @@ -31,7 +31,6 @@ import contextlib import gc -import importlib import importlib.util import logging import os diff --git a/tests/_test_utils/examples/onnx_ptq/aggregate_results.py b/tests/_test_utils/examples/onnx_ptq/aggregate_results.py index 4a25de9e8fe..4181e515d6e 100644 --- a/tests/_test_utils/examples/onnx_ptq/aggregate_results.py +++ b/tests/_test_utils/examples/onnx_ptq/aggregate_results.py @@ -19,7 +19,7 @@ def get_metrics_from_csv(file_path): - with open(file_path) as csv_file: + with open(file_path, encoding="utf-8") as csv_file: csv_reader = csv.reader(csv_file) next(csv_reader) top1_accuracy, top5_accuracy, latency = None, None, None @@ -171,7 +171,7 @@ def main(): output_file_path = os.path.join(build_folder_path, "aggregated_results.csv") # Write aggregated data to a new CSV file - with open(output_file_path, mode="w", newline="") as output_file: + with open(output_file_path, encoding="utf-8", mode="w", newline="") as output_file: csv_writer = csv.writer(output_file) # Write header csv_writer.writerow( diff --git a/tests/_test_utils/torch/diffusers_models.py b/tests/_test_utils/torch/diffusers_models.py index c680c64bc31..d938c85265d 100644 --- a/tests/_test_utils/torch/diffusers_models.py +++ b/tests/_test_utils/torch/diffusers_models.py @@ -355,8 +355,8 @@ def _build_local_qwen2_tokenizer(out_dir: Path): vocab = {token: idx for idx, token in enumerate(_byte_level_unicode_chars())} for special in ("<|endoftext|>", "<|im_start|>", "<|im_end|>"): vocab.setdefault(special, len(vocab)) - (out_dir / "vocab.json").write_text(json.dumps(vocab)) - (out_dir / "merges.txt").write_text("#version: 0.2\n") + (out_dir / "vocab.json").write_text(json.dumps(vocab), encoding="utf-8") + (out_dir / "merges.txt").write_text("#version: 0.2\n", encoding="utf-8") special_kwargs = { "unk_token": "<|endoftext|>", diff --git a/tests/_test_utils/torch/export/unified_checkpoint.py b/tests/_test_utils/torch/export/unified_checkpoint.py index bfa0647c69b..50f3d946624 100644 --- a/tests/_test_utils/torch/export/unified_checkpoint.py +++ b/tests/_test_utils/torch/export/unified_checkpoint.py @@ -61,7 +61,7 @@ def assert_safetensors_index_consistent(export_dir: Path | str) -> None: index_file = export_dir / "model.safetensors.index.json" if not index_file.exists(): # single unsharded file: nothing to cross-check return - weight_map = json.loads(index_file.read_text())["weight_map"] + weight_map = json.loads(index_file.read_text(encoding="utf-8"))["weight_map"] missing_files = {f for f in set(weight_map.values()) if not (export_dir / f).exists()} assert not missing_files, f"index.json references missing shards: {sorted(missing_files)}" exported = set(load_safetensors_dir(export_dir)) diff --git a/tests/_test_utils/torch/quantization/quant_utils.py b/tests/_test_utils/torch/quantization/quant_utils.py index 5c997b86c97..38abeaf5a59 100644 --- a/tests/_test_utils/torch/quantization/quant_utils.py +++ b/tests/_test_utils/torch/quantization/quant_utils.py @@ -33,7 +33,7 @@ def quant(x, amax, num_bits=8, fake=False, narrow_range=True): def get_model_size(model): - return sum([p.element_size() * p.nelement() for p in model.parameters()]) + return sum(p.element_size() * p.nelement() for p in model.parameters()) def nvfp4_static_amax_dtypes(model): diff --git a/tests/examples/diffusers/fastgen/test_vendored_migration.py b/tests/examples/diffusers/fastgen/test_vendored_migration.py index d881a6e49a6..e5c3cdfcb01 100644 --- a/tests/examples/diffusers/fastgen/test_vendored_migration.py +++ b/tests/examples/diffusers/fastgen/test_vendored_migration.py @@ -77,7 +77,7 @@ def test_all_configs_target_vendored_builders(): configs = sorted((_FASTGEN_DIR / "configs").glob("*.yaml")) assert configs, "no configs found under configs/" for cfg in configs: - text = cfg.read_text() + text = cfg.read_text(encoding="utf-8") assert "nemo_automodel.components.datasets.diffusion.build_" not in text, ( f"{cfg.name} still targets the upstream dataloader builder (breaks on stock upstream)" ) @@ -93,7 +93,7 @@ def test_no_tools_star_imports_in_vendored_code(): str(py.relative_to(_FASTGEN_DIR)) for sub in ("fastgen_data", "preprocess") for py in (_FASTGEN_DIR / sub).rglob("*.py") - if pat.search(py.read_text()) + if pat.search(py.read_text(encoding="utf-8")) ] assert not offenders, f"tools.* imports found in vendored code: {offenders}" @@ -133,7 +133,7 @@ def test_all_staged_automodel_files_are_removable(): def test_formerly_vendored_files_use_standard_nvidia_header(): """They carry only the standard NVIDIA SPDX header — no provenance note, no duplicate license.""" for target in FORMERLY_VENDORED: - text = (_FASTGEN_DIR / target).read_text() + text = (_FASTGEN_DIR / target).read_text(encoding="utf-8") assert text.startswith( "# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES" ), f"{target}: must start with the standard NVIDIA SPDX header" @@ -220,7 +220,7 @@ def test_partial_load_checkpointer_overrides_only_load_optimizer(): def test_recipe_injects_partial_load_checkpointer_in_load_checkpoint(): """The recipe upgrades self.checkpointer in load_checkpoint (before the parent restore).""" - src = (_FASTGEN_DIR / "dmd2_recipe.py").read_text() + src = (_FASTGEN_DIR / "dmd2_recipe.py").read_text(encoding="utf-8") assert "from fastgen_checkpoint import make_optimizer_partial_load_tolerant" in src assert "make_optimizer_partial_load_tolerant(self.checkpointer)" in src diff --git a/tests/examples/diffusers/sparsity/test_sparsity.py b/tests/examples/diffusers/sparsity/test_sparsity.py index bca94e3dafb..86689bc0fbc 100644 --- a/tests/examples/diffusers/sparsity/test_sparsity.py +++ b/tests/examples/diffusers/sparsity/test_sparsity.py @@ -137,7 +137,7 @@ def test_wan22_export_sparse_checkpoint(tiny_wan22_path, tmp_path): assert component_dir.exists(), f"Missing component dir: {component}" config_path = component_dir / "config.json" assert config_path.exists(), f"Missing config.json for {component}" - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config_data = json.load(f) # Fixed (uncalibrated) threshold has nothing to export. assert "sparse_attention_config" not in config_data, ( @@ -208,7 +208,7 @@ def test_wan22_calibrated_export(tiny_wan22_path, tmp_path): for component in ["transformer", "transformer_2"]: config_path = export_dir / component / "config.json" assert config_path.exists(), f"Missing config.json for {component}" - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config_data = json.load(f) assert "sparse_attention_config" in config_data, ( f"No sparse_attention_config in {component}/config.json" diff --git a/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py b/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py index c0385c088c1..9f75957ed4f 100644 --- a/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py +++ b/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py @@ -234,7 +234,7 @@ def test_qwen_image_hf_ckpt_export( transformer_dir = hf_ckpt_dir / "transformer" config_path = transformer_dir / "config.json" assert config_path.exists(), f"no transformer/config.json in {hf_ckpt_dir}" - quant_config = json.loads(config_path.read_text()).get("quantization_config") + quant_config = json.loads(config_path.read_text(encoding="utf-8")).get("quantization_config") assert quant_config is not None, "missing quantization_config" assert quant_config.get("quant_method") == "modelopt" diff --git a/tests/examples/gpt-oss/test_gpt_oss_qat.py b/tests/examples/gpt-oss/test_gpt_oss_qat.py index 34b9ab4a2ca..ac7cd332432 100644 --- a/tests/examples/gpt-oss/test_gpt_oss_qat.py +++ b/tests/examples/gpt-oss/test_gpt_oss_qat.py @@ -235,7 +235,7 @@ def deploy_gpt_oss_trtllm(self, tmp_path, model_path_override=None): if not os.path.exists(benchmark_file) or os.path.getsize(benchmark_file) == 0: print(f"Creating dataset file '{benchmark_file}'...") - with open(benchmark_file, "w") as fp: + with open(benchmark_file, "w", encoding="utf-8") as fp: subprocess.run( f"python {script} --stdout --tokenizer={self.model_path} token-norm-dist --input-mean 128 \ --output-mean 128 --input-stdev 0 --output-stdev 0 --num-requests 1400", diff --git a/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py b/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py index 6ab49eabf0a..973f95f533e 100644 --- a/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py +++ b/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py @@ -77,7 +77,7 @@ def _write_synthetic_mxfp4_checkpoint( "metadata": {"total_size": sum(t.numel() * t.element_size() for t in state.values())}, "weight_map": dict.fromkeys(state, shard_name), } - (ckpt_dir / "model.safetensors.index.json").write_text(json.dumps(index)) + (ckpt_dir / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") return ckpt_dir @@ -135,7 +135,8 @@ def test_build_amax_map_no_scales_raises(tmp_path): "metadata": {}, "weight_map": {"model.layers.0.weight": "model-00001-of-00001.safetensors"}, } - ) + ), + encoding="utf-8", ) with pytest.raises(SystemExit, match="No '\\*_scales'"): cast.build_amax_map(empty) @@ -229,7 +230,8 @@ def test_apply_to_model_raises_on_missing_blocks_pair(tmp_path): "metadata": {}, "weight_map": {"experts.gate_up_proj_scales": "model-00001-of-00001.safetensors"}, } - ) + ), + encoding="utf-8", ) model = _FakeModel(num_blocks=4) with pytest.raises(AssertionError, match=r"no paired '.*_blocks' tensor"): diff --git a/tests/examples/hf_ptq/test_example_utils.py b/tests/examples/hf_ptq/test_example_utils.py index e532af09fed..a6b778a5610 100644 --- a/tests/examples/hf_ptq/test_example_utils.py +++ b/tests/examples/hf_ptq/test_example_utils.py @@ -73,13 +73,17 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): "model.gguf": "source weights\n", } for file_name, contents in source_files.items(): - (source_dir / file_name).write_text(contents) + (source_dir / file_name).write_text(contents, encoding="utf-8") - (export_dir / "config.json").write_text('{"export": "config"}\n') - (export_dir / "generation_config.json").write_text('{"export": "generation"}\n') - (export_dir / "hf_quant_config.json").write_text('{"export": "quant"}\n') - (export_dir / "chat_template.jinja").write_text("{{ exported_messages }}\n") - (export_dir / "tokenizer_config.json").write_text('{"chat_template": "export"}\n') + (export_dir / "config.json").write_text('{"export": "config"}\n', encoding="utf-8") + (export_dir / "generation_config.json").write_text( + '{"export": "generation"}\n', encoding="utf-8" + ) + (export_dir / "hf_quant_config.json").write_text('{"export": "quant"}\n', encoding="utf-8") + (export_dir / "chat_template.jinja").write_text("{{ exported_messages }}\n", encoding="utf-8") + (export_dir / "tokenizer_config.json").write_text( + '{"chat_template": "export"}\n', encoding="utf-8" + ) example_utils.copy_custom_model_files(str(source_dir), str(export_dir), trust_remote_code=False) @@ -91,11 +95,15 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): "chat_template.jinja", "generation_config.json", ]: - assert (export_dir / file_name).read_text() == source_files[file_name] - - assert (export_dir / "config.json").read_text() == '{"export": "config"}\n' - assert (export_dir / "hf_quant_config.json").read_text() == '{"export": "quant"}\n' - assert (export_dir / "tokenizer_config.json").read_text() == '{"chat_template": "export"}\n' + assert (export_dir / file_name).read_text(encoding="utf-8") == source_files[file_name] + + assert (export_dir / "config.json").read_text(encoding="utf-8") == '{"export": "config"}\n' + assert (export_dir / "hf_quant_config.json").read_text( + encoding="utf-8" + ) == '{"export": "quant"}\n' + assert (export_dir / "tokenizer_config.json").read_text( + encoding="utf-8" + ) == '{"chat_template": "export"}\n' assert not (export_dir / "quant_config.json").exists() assert not (export_dir / "quantize_config.json").exists() assert not (export_dir / "recipe.yaml").exists() @@ -103,13 +111,17 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): assert not (export_dir / "model-00001-of-00001.safetensors").exists() assert not (export_dir / "model.gguf").exists() - (export_dir / "generation_config.json").write_text('{"export": "generation"}\n') + (export_dir / "generation_config.json").write_text( + '{"export": "generation"}\n', encoding="utf-8" + ) example_utils.copy_custom_model_files( str(source_dir), str(export_dir), exclude_files={"generation_config.json"}, ) - assert (export_dir / "generation_config.json").read_text() == '{"export": "generation"}\n' + assert (export_dir / "generation_config.json").read_text( + encoding="utf-8" + ) == '{"export": "generation"}\n' def test_resolve_model_path_snapshot_download_stays_allowlisted(monkeypatch, tmp_path): @@ -215,7 +227,8 @@ def test_load_mtp_weights_separate_indexed_shard(tmp_path): **dict.fromkeys(mtp_tensors, mtp_shard), } } - ) + ), + encoding="utf-8", ) cfg = SimpleNamespace(num_hidden_layers=4, num_nextn_predict_layers=0) diff --git a/tests/examples/hf_ptq/test_hf_ptq_args.py b/tests/examples/hf_ptq/test_hf_ptq_args.py index 2f5c860437e..7103cef4416 100644 --- a/tests/examples/hf_ptq/test_hf_ptq_args.py +++ b/tests/examples/hf_ptq/test_hf_ptq_args.py @@ -682,7 +682,7 @@ def test_experiment_json_lands_in_the_checkpoint_and_on_the_server( with example_utils.mlflow_run(args): _exported(args) - written = json.loads((tmp_path / ".experiment.json").read_text()) + written = json.loads((tmp_path / ".experiment.json").read_text(encoding="utf-8")) assert written["experiment_name"] == "tester/hf_ptq/Qwen3-0.6B-fp8" assert written["run_id"] == "deadbeef" assert written["run_url"] == "https://mlflow.example.com/#/experiments/7/runs/deadbeef" @@ -702,7 +702,10 @@ def test_experiment_json_is_written_when_a_run_fails_after_exporting( _exported(args) raise RuntimeError("crashed while cleaning up") - assert json.loads((tmp_path / ".experiment.json").read_text())["run_id"] == "deadbeef" + assert ( + json.loads((tmp_path / ".experiment.json").read_text(encoding="utf-8"))["run_id"] + == "deadbeef" + ) assert fake_mlflow.status == "FAILED" @@ -713,14 +716,19 @@ def test_no_local_pointer_when_the_export_never_completed( already hold a valid checkpoint from an earlier attempt. Neither is evidence that this run wrote the weights, so a run that fails before export must not claim them.""" args = _tracked_run(monkeypatch, tmp_path) - (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers found in model\n") + (tmp_path / ".quant_summary.txt").write_text( + "706 TensorQuantizers found in model\n", encoding="utf-8" + ) previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "the-run-that-really-wrote-this"}\n') + previous.write_text('{"run_id": "the-run-that-really-wrote-this"}\n', encoding="utf-8") with pytest.raises(RuntimeError), example_utils.mlflow_run(args): raise RuntimeError("OOM during calibration") - assert json.loads(previous.read_text())["run_id"] == "the-run-that-really-wrote-this" + assert ( + json.loads(previous.read_text(encoding="utf-8"))["run_id"] + == "the-run-that-really-wrote-this" + ) # Still traceable from the server side: the run opened, it just produced no checkpoint. assert json.loads(fake_mlflow.texts["experiment.json"])["run_id"] == "deadbeef" assert fake_mlflow.status == "FAILED" @@ -744,13 +752,13 @@ def explode(name): ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "from-an-earlier-run"}\n') + previous.write_text('{"run_id": "from-an-earlier-run"}\n', encoding="utf-8") with example_utils.mlflow_run(args): _exported(args) assert args.mlflow_required is False - assert json.loads(previous.read_text())["run_id"] == "from-an-earlier-run" + assert json.loads(previous.read_text(encoding="utf-8"))["run_id"] == "from-an-earlier-run" def test_untracked_export_drops_a_pointer_it_would_otherwise_inherit( @@ -763,7 +771,7 @@ def test_untracked_export_drops_a_pointer_it_would_otherwise_inherit( ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) inherited = tmp_path / ".experiment.json" - inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n') + inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n', encoding="utf-8") with example_utils.mlflow_run(args): _exported(args) @@ -778,12 +786,12 @@ def test_untracked_failure_leaves_an_existing_pointer_alone(monkeypatch, example ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "still-valid"}\n') + previous.write_text('{"run_id": "still-valid"}\n', encoding="utf-8") with pytest.raises(RuntimeError), example_utils.mlflow_run(args): raise RuntimeError("died before export") - assert json.loads(previous.read_text())["run_id"] == "still-valid" + assert json.loads(previous.read_text(encoding="utf-8"))["run_id"] == "still-valid" def test_only_the_main_rank_clears_an_inherited_pointer(monkeypatch, example_utils, tmp_path): @@ -794,7 +802,7 @@ def test_only_the_main_rank_clears_an_inherited_pointer(monkeypatch, example_uti ) args.dist_state = SimpleNamespace(is_main=False, world_size=8) inherited = tmp_path / ".experiment.json" - inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n') + inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n', encoding="utf-8") with example_utils.mlflow_run(args): _exported(args) diff --git a/tests/examples/llm_qat/test_llm_qat.py b/tests/examples/llm_qat/test_llm_qat.py index a7b610a807c..7736b70aeac 100644 --- a/tests/examples/llm_qat/test_llm_qat.py +++ b/tests/examples/llm_qat/test_llm_qat.py @@ -188,7 +188,7 @@ def test_qwen3_lora_qat_nvfp4(tiny_qwen3_path, tmp_path): _run_export(str(lora_qat_output_dir), str(export_dir)) base_model_dir = export_dir / "base_model" - with open(base_model_dir / "hf_quant_config.json") as f: + with open(base_model_dir / "hf_quant_config.json", encoding="utf-8") as f: assert json.load(f)["quantization"]["quant_algo"] == "NVFP4" base_weights = load_file(base_model_dir / "model.safetensors") @@ -283,7 +283,7 @@ def test_qwen3_qlora_nvfp4(tiny_qwen3_path, tmp_path): assert (export_dir / "adapter_model.safetensors").is_file() assert (base_model_dir / "hf_quant_config.json").is_file() - with open(base_model_dir / "hf_quant_config.json") as f: + with open(base_model_dir / "hf_quant_config.json", encoding="utf-8") as f: assert json.load(f)["quantization"]["quant_algo"] == "NVFP4" # NVFP4 needs the packed weight and *both* scales to be dequantizable downstream. diff --git a/tests/examples/megatron_bridge/test_distill.py b/tests/examples/megatron_bridge/test_distill.py index 75c3e430958..a4522f6bfe2 100644 --- a/tests/examples/megatron_bridge/test_distill.py +++ b/tests/examples/megatron_bridge/test_distill.py @@ -94,7 +94,7 @@ def test_distill_llm_sft(tmp_path, num_gpus): records = [{"input": f"Q: what follows {i}?\nA:", "output": f" {i + 1}"} for i in range(64)] for split in ("training", "validation"): (dataset_root / f"{split}.jsonl").write_text( - "\n".join(json.dumps(r) for r in records) + "\n" + "\n".join(json.dumps(r) for r in records) + "\n", encoding="utf-8" ) distill_output_dir = tmp_path / "distill_output" diff --git a/tests/examples/megatron_bridge/test_qad.py b/tests/examples/megatron_bridge/test_qad.py index ddaa6538ef2..8e7bb8dd75e 100644 --- a/tests/examples/megatron_bridge/test_qad.py +++ b/tests/examples/megatron_bridge/test_qad.py @@ -57,7 +57,7 @@ def test_qad(tmp_path: Path, num_gpus, create_student): export is covered more cheaply by test_quantize_export.py, so keep this to one LLM and one VLM. """ hf_model_path = create_student(tmp_path) - is_vlm = "vision_config" in (hf_model_path / "config.json").read_text() + is_vlm = "vision_config" in (hf_model_path / "config.json").read_text(encoding="utf-8") quantized_megatron_path = tmp_path / "quantized_megatron" distill_output_dir = tmp_path / "qad_output" train_iters = 3 @@ -131,7 +131,7 @@ def test_qad(tmp_path: Path, num_gpus, create_student): assert (hf_export_path / "hf_quant_config.json").exists() # A quantized export writes routed experts one per expert while the BF16 reference packs # them, so both sides of that expansion differ from the reference. - text_config = json.loads((hf_model_path / "config.json").read_text()) + text_config = json.loads((hf_model_path / "config.json").read_text(encoding="utf-8")) is_moe = bool(text_config.get("text_config", text_config).get("num_experts")) # QAD trains the student, so language-model weights drift from the reference; the vision # tower is never trained and must still come through byte for byte. diff --git a/tests/examples/specdec_bench/test_upload_to_s3.py b/tests/examples/specdec_bench/test_upload_to_s3.py index bfc663e90e6..9e9d7505cc4 100644 --- a/tests/examples/specdec_bench/test_upload_to_s3.py +++ b/tests/examples/specdec_bench/test_upload_to_s3.py @@ -51,8 +51,8 @@ def test_parsing(self, path, expected): def _make_run_dir(path: Path) -> Path: """Create a directory shaped like a specdec_bench run output.""" path.mkdir(parents=True, exist_ok=True) - (path / "configuration.json").write_text("{}") - (path / "timing.json").write_text("{}") + (path / "configuration.json").write_text("{}", encoding="utf-8") + (path / "timing.json").write_text("{}", encoding="utf-8") return path @@ -65,7 +65,7 @@ def test_empty_dir(self, tmp_path): assert upload_to_s3._is_run_dir(tmp_path) is False def test_non_sentinel_files(self, tmp_path): - (tmp_path / "results.txt").write_text("") + (tmp_path / "results.txt").write_text("", encoding="utf-8") assert upload_to_s3._is_run_dir(tmp_path) is False @@ -116,7 +116,7 @@ def test_empty_prefix_flat_layout(self, tmp_path): def test_ignores_non_run_files(self, tmp_path): root = tmp_path / "mixed" _make_run_dir(root / "a") - (root / "notes.txt").write_text("ignore me") + (root / "notes.txt").write_text("ignore me", encoding="utf-8") queue = upload_to_s3._discover_runs(root, "results") assert len(queue) == 1 assert queue[0][0].name == "a" @@ -126,13 +126,15 @@ class TestCheckProvenance: def test_complete(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text('{"container_image": "vllm/vllm-openai:nightly"}') + (run / "configuration.json").write_text( + '{"container_image": "vllm/vllm-openai:nightly"}', encoding="utf-8" + ) assert upload_to_s3._check_provenance(run) == [] def test_missing_container_image(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text('{"container_image": null}') + (run / "configuration.json").write_text('{"container_image": null}', encoding="utf-8") assert upload_to_s3._check_provenance(run) == ["container_image"] def test_no_configuration_json(self, tmp_path): @@ -143,11 +145,11 @@ def test_no_configuration_json(self, tmp_path): def test_malformed_configuration_json(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text("{ not valid json") + (run / "configuration.json").write_text("{ not valid json", encoding="utf-8") assert upload_to_s3._check_provenance(run) == list(upload_to_s3._REQUIRED_PROVENANCE_FIELDS) def test_empty_string_is_missing(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text('{"container_image": ""}') + (run / "configuration.json").write_text('{"container_image": ""}', encoding="utf-8") assert upload_to_s3._check_provenance(run) == ["container_image"] diff --git a/tests/examples/speculative_decoding/conftest.py b/tests/examples/speculative_decoding/conftest.py index 3b487805be9..a8fc774d58a 100644 --- a/tests/examples/speculative_decoding/conftest.py +++ b/tests/examples/speculative_decoding/conftest.py @@ -39,7 +39,7 @@ def tiny_conversations_path(tmp_path_factory): } for i in range(5) ] - with open(output_file, "w") as f: + with open(output_file, "w", encoding="utf-8") as f: f.writelines(json.dumps(conv) + "\n" for conv in conversations) return output_file diff --git a/tests/examples/torch_onnx/test_torch_quant_to_onnx.py b/tests/examples/torch_onnx/test_torch_quant_to_onnx.py index d2748942972..f5c4a207095 100644 --- a/tests/examples/torch_onnx/test_torch_quant_to_onnx.py +++ b/tests/examples/torch_onnx/test_torch_quant_to_onnx.py @@ -107,7 +107,8 @@ def test_torch_onnx_recipe_flag(tmp_path): " algorithm: max\n" " quant_cfg:\n" " - quantizer_name: '*'\n" - " enable: false\n" + " enable: false\n", + encoding="utf-8", ) cmd_parts = extend_cmd_parts( @@ -150,7 +151,8 @@ def test_torch_onnx_auto_quantize_recipe(tmp_path): " - $import: fp8\n" " - $import: int8\n" " auto_quantize_method: gradient\n" - " score_size: 1\n" + " score_size: 1\n", + encoding="utf-8", ) cmd_parts = extend_cmd_parts( diff --git a/tests/examples/vllm_serve/test_vllm_mlflow_utils.py b/tests/examples/vllm_serve/test_vllm_mlflow_utils.py index 04b97c31580..65222874b56 100644 --- a/tests/examples/vllm_serve/test_vllm_mlflow_utils.py +++ b/tests/examples/vllm_serve/test_vllm_mlflow_utils.py @@ -99,7 +99,10 @@ def log_text(self, text, artifact_file): self.texts[artifact_file] = text def log_artifact(self, local_path, artifact_path=None): - self.artifacts[Path(local_path).name] = (artifact_path, Path(local_path).read_text()) + self.artifacts[Path(local_path).name] = ( + artifact_path, + Path(local_path).read_text(encoding="utf-8"), + ) def log_metrics(self, metrics): self.metrics.update(metrics) @@ -462,7 +465,9 @@ def test_quant_summary_is_uploaded_from_the_staging_directory( # Stand in for mtq.print_quant_summary(model, output_dir=...), which is what writes it. def write_summary(model, output_dir): - Path(output_dir, ".quant_summary.txt").write_text("2 TensorQuantizers found in model\n") + Path(output_dir, ".quant_summary.txt").write_text( + "2 TensorQuantizers found in model\n", encoding="utf-8" + ) monkeypatch.setattr( importlib.import_module("modelopt.torch.quantization"), diff --git a/tests/gpu/onnx/quantization/test_plugin.py b/tests/gpu/onnx/quantization/test_plugin.py index f15f4ecf4cd..7c938570da2 100755 --- a/tests/gpu/onnx/quantization/test_plugin.py +++ b/tests/gpu/onnx/quantization/test_plugin.py @@ -104,7 +104,7 @@ def _create_test_model_trt(): def test_trt_plugin_quantization(tmp_path): model = _create_test_model_trt() - with open(os.path.join(tmp_path, "model_with_trt_plugin.onnx"), "w") as f: + with open(os.path.join(tmp_path, "model_with_trt_plugin.onnx"), "w", encoding="utf-8") as f: onnx.save_model(model, f.name) # Check that the model contains TRT custom op @@ -130,7 +130,9 @@ def test_trt_plugin_quantization(tmp_path): def test_trt_plugin_quantization_int4_awq(tmp_path): model = _create_test_model_trt() - with open(os.path.join(tmp_path, "model_with_trt_plugin_int4.onnx"), "w") as f: + with open( + os.path.join(tmp_path, "model_with_trt_plugin_int4.onnx"), "w", encoding="utf-8" + ) as f: onnx.save_model(model, f.name) # Quantize at int4 with awq_clip (the path that forces opset >= 21). @@ -186,7 +188,9 @@ def test_get_custom_layers_file_backed_matches_in_memory(tmp_path, monkeypatch): def test_trt_plugin_autocast(tmp_path): model = _create_test_model_trt() - with open(os.path.join(tmp_path, "model_with_trt_plugin_autocast.onnx"), "w") as f: + with open( + os.path.join(tmp_path, "model_with_trt_plugin_autocast.onnx"), "w", encoding="utf-8" + ) as f: onnx.save_model(model, f.name) # Check that the model contains TRT custom op diff --git a/tests/gpu/onnx/test_ort_patching.py b/tests/gpu/onnx/test_ort_patching.py index 84224dcffa0..9f936db9b3d 100644 --- a/tests/gpu/onnx/test_ort_patching.py +++ b/tests/gpu/onnx/test_ort_patching.py @@ -334,7 +334,7 @@ class TestInferenceSession: def test_create_inference_session_with_ep_config(self, mock_calibrator, tmp_path): """Test inference session creation with EP configuration.""" model_path = tmp_path / "test_model.onnx" - model_path.write_text("dummy") + model_path.write_text("dummy", encoding="utf-8") with patch("onnxruntime.InferenceSession") as mock_session: mock_inference_session = Mock() diff --git a/tests/gpu/onnx/test_simplify.py b/tests/gpu/onnx/test_simplify.py index 5ca8449b391..5f59666461d 100644 --- a/tests/gpu/onnx/test_simplify.py +++ b/tests/gpu/onnx/test_simplify.py @@ -38,7 +38,7 @@ def test_onnx_simplification(tmp_path): onnx_filename = os.path.join(tmp_path, "model_non_simplified.onnx") _create_test_model(onnx_filename) - with open(onnx_filename) as f: + with open(onnx_filename, encoding="utf-8") as f: graph = gs.import_onnx(onnx.load(f.name)) # Check that the model contains Identity nodes, indicating that constant folding did not happen. diff --git a/tests/gpu/torch/export/test_export.py b/tests/gpu/torch/export/test_export.py index 6f90c9104c7..136f4388f1e 100644 --- a/tests/gpu/torch/export/test_export.py +++ b/tests/gpu/torch/export/test_export.py @@ -533,7 +533,7 @@ def test_qwen3_moe_nvfp4_experts_only_export_exclude_modules(tmp_path): # Load the generated hf_quant_config.json hf_quant_config_path = export_dir / "hf_quant_config.json" assert hf_quant_config_path.exists(), "hf_quant_config.json should be generated" - with open(hf_quant_config_path) as f: + with open(hf_quant_config_path, encoding="utf-8") as f: hf_quant_config = json.load(f) quant_section = hf_quant_config["quantization"] diff --git a/tests/gpu/torch/export/test_export_diffusers.py b/tests/gpu/torch/export/test_export_diffusers.py index 392eaf92b6c..a0fa9e798a6 100644 --- a/tests/gpu/torch/export/test_export_diffusers.py +++ b/tests/gpu/torch/export/test_export_diffusers.py @@ -25,7 +25,7 @@ def _load_config(config_path): - with open(config_path) as file: + with open(config_path, encoding="utf-8") as file: return json.load(file) diff --git a/tests/gpu/torch/export/test_fsdp2_export.py b/tests/gpu/torch/export/test_fsdp2_export.py index 939fe3e581f..e42d6b4735f 100644 --- a/tests/gpu/torch/export/test_fsdp2_export.py +++ b/tests/gpu/torch/export/test_fsdp2_export.py @@ -428,7 +428,9 @@ def calib_fn(m): export_dir = Path(export_dir) assert not list(export_dir.glob("__shard_part*")), "part files left behind after the merge" - index = json.loads((export_dir / "model.safetensors.index.json").read_text()) + index = json.loads( + (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") + ) weight_map = index["weight_map"] assert len(set(weight_map.values())) >= 2, ( "every key landed in one shard file, so the ranks did not each write their own share" diff --git a/tests/gpu/torch/export/test_layerwise_export.py b/tests/gpu/torch/export/test_layerwise_export.py index 84244a8fec1..a0c7f9b0e8c 100644 --- a/tests/gpu/torch/export/test_layerwise_export.py +++ b/tests/gpu/torch/export/test_layerwise_export.py @@ -90,7 +90,7 @@ def _layerwise_cfg(export_dir, checkpoint_dir, base=None): def _load_checkpoint(export_dir): index = export_dir / "model.safetensors.index.json" shards = ( - set(json.loads(index.read_text())["weight_map"].values()) + set(json.loads(index.read_text(encoding="utf-8"))["weight_map"].values()) if index.exists() else ["model.safetensors"] ) @@ -128,8 +128,8 @@ def _assert_same_quant_config(baseline_dir, export_dir): ) if not want.is_file(): continue - expected = json.loads(want.read_text()).get(key) - actual = json.loads(got.read_text()).get(key) + expected = json.loads(want.read_text(encoding="utf-8")).get(key) + actual = json.loads(got.read_text(encoding="utf-8")).get(key) assert actual == expected, ( f"{name}[{key}] differs:\n baseline={expected}\n fused={actual}" ) @@ -397,7 +397,9 @@ def test_orphaned_tensors_reach_the_tail_shard(tmp_path): for key, value in orphans.items(): assert key in exported, f"{key} missing from the exported checkpoint" assert torch.equal(exported[key].cpu(), value) - weight_map = json.loads((export_dir / "model.safetensors.index.json").read_text())["weight_map"] + weight_map = json.loads( + (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") + )["weight_map"] assert set(orphans) <= set(weight_map), "orphans written but left out of the index" @@ -456,7 +458,9 @@ def test_index_resolves_every_key_to_the_shard_holding_it(tmp_path): export_dir = tmp_path / "fused" _layerwise_quantize(_build_model(), _layerwise_cfg(export_dir, tmp_path / "ckpt")) - weight_map = json.loads((export_dir / "model.safetensors.index.json").read_text())["weight_map"] + weight_map = json.loads( + (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") + )["weight_map"] on_disk = {} for shard in sorted(set(weight_map.values())): assert (export_dir / shard).is_file(), f"index names a missing shard {shard}" @@ -506,9 +510,9 @@ def test_resume_without_matching_shards_fails_fast(tmp_path): _layerwise_quantize(_build_model(), _layerwise_cfg(tmp_path / "fused", checkpoint_dir)) manifest_path = checkpoint_dir / "manifest.json" - manifest = json.loads(manifest_path.read_text()) + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) manifest["last_completed_layer"] = 1 - manifest_path.write_text(json.dumps(manifest)) + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") with pytest.raises(RuntimeError, match="shards are missing"): _layerwise_quantize( @@ -555,9 +559,9 @@ def test_shards_without_resume_record_refuse(tmp_path, damage): if damage == "deleted": manifest.unlink() else: - record = json.loads(manifest.read_text()) + record = json.loads(manifest.read_text(encoding="utf-8")) record.pop("last_completed_layer") - manifest.write_text(json.dumps(record)) + manifest.write_text(json.dumps(record), encoding="utf-8") with pytest.raises(RuntimeError, match="no usable resume record"): _layerwise_quantize(_build_model(), _layerwise_cfg(export_dir, checkpoint_dir)) diff --git a/tests/gpu/torch/export/test_offload_export.py b/tests/gpu/torch/export/test_offload_export.py index 9ee6139077e..406737842ac 100644 --- a/tests/gpu/torch/export/test_offload_export.py +++ b/tests/gpu/torch/export/test_offload_export.py @@ -95,7 +95,7 @@ def forward_loop(m): # 1. hf_quant_config.json must exist and declare fp8 quant_config_path = export_dir / "hf_quant_config.json" assert quant_config_path.exists(), "hf_quant_config.json not written" - with open(quant_config_path) as f: + with open(quant_config_path, encoding="utf-8") as f: quant_config = json.load(f) assert quant_config["quantization"]["quant_algo"] == "FP8", ( f"Expected FP8, got {quant_config['quantization'].get('quant_algo')}" @@ -132,7 +132,7 @@ def _read_shards(export_dir): index_path = export_dir / "model.safetensors.index.json" if index_path.exists(): - with open(index_path) as f: + with open(index_path, encoding="utf-8") as f: weight_map = json.load(f)["weight_map"] assert set(weight_map) == set(tensors), "index weight_map disagrees with shard contents" for shard_name in set(weight_map.values()): diff --git a/tests/gpu/torch/puzzletron/test_puzzletron.py b/tests/gpu/torch/puzzletron/test_puzzletron.py index 4953db2c737..6924fbb5f40 100644 --- a/tests/gpu/torch/puzzletron/test_puzzletron.py +++ b/tests/gpu/torch/puzzletron/test_puzzletron.py @@ -236,7 +236,7 @@ def _check_lm_loss(puzzle_dir: Path, hf_model_name: str, tolerance: float = 0.15 if not solution_0_path.exists(): errors.append(f"Expected {solution_0_path} to exist for lm_loss check") return errors - with open(solution_0_path) as f: + with open(solution_0_path, encoding="utf-8") as f: validation = json.load(f) actual_lm_loss = validation["lm_loss"]["avg"] diff --git a/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py b/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py index a31c687cc1e..e75c8a00511 100644 --- a/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py +++ b/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py @@ -45,7 +45,7 @@ def test_creates_config_index_and_subblocks(self, tmp_path): # test safetensors index file exists and contains weight map index_path = tmp_path / SAFE_WEIGHTS_INDEX_NAME assert index_path.exists(), "safetensors index file was not written" - index = json.loads(index_path.read_text()) + index = json.loads(index_path.read_text(encoding="utf-8")) assert "weight_map" in index assert set(index["weight_map"].keys()) == expected_keys @@ -59,7 +59,7 @@ def test_creates_config_index_and_subblocks(self, tmp_path): # test config.json saved config_path = tmp_path / "config.json" assert config_path.exists(), "config.json was not saved" - cfg = json.loads(config_path.read_text()) + cfg = json.loads(config_path.read_text(encoding="utf-8")) assert cfg["num_hidden_layers"] == get_tiny_llama().config.num_hidden_layers # test subblock filenames follow descriptor groups @@ -72,7 +72,7 @@ def test_tie_word_embeddings_excluded(self, tmp_path): model = get_tiny_llama(tie_word_embeddings=True) save_checkpoint_from_shards(model, tmp_path, LlamaModelDescriptor) - index = json.loads((tmp_path / SAFE_WEIGHTS_INDEX_NAME).read_text()) + index = json.loads((tmp_path / SAFE_WEIGHTS_INDEX_NAME).read_text(encoding="utf-8")) assert "lm_head.weight" not in index["weight_map"] reloaded_sd = {} @@ -122,7 +122,7 @@ def test_distributed_save_creates_valid_checkpoint(self, tmp_path): index_path = tmp_path / SAFE_WEIGHTS_INDEX_NAME assert index_path.exists() - index = json.loads(index_path.read_text()) + index = json.loads(index_path.read_text(encoding="utf-8")) model = get_tiny_llama() expected_keys = set(model.state_dict().keys()) diff --git a/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py b/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py index 6aab8fe48c8..5a9e86a53ed 100644 --- a/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py +++ b/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py @@ -187,7 +187,7 @@ def test_layerwise_calibrate_cpu_offloaded(tmp_path, use_checkpoint): if use_checkpoint: manifest_path = os.path.join(ckpt_dir, "manifest.json") assert os.path.isfile(manifest_path) - with open(manifest_path) as f: + with open(manifest_path, encoding="utf-8") as f: manifest = json.load(f) assert manifest["last_completed_layer"] == num_layers - 1 assert manifest["num_layers"] == num_layers @@ -233,7 +233,7 @@ def test_sequential_checkpoint_resume_cpu_offloaded(tmp_path): # Simulate crash after layer 0 by truncating the manifest and removing later layers last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w") as f: + with open(manifest_path, "w", encoding="utf-8") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) @@ -288,7 +288,7 @@ def _make_multi_offload_model(): # Simulate crash after layer 0 last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w") as f: + with open(manifest_path, "w", encoding="utf-8") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) @@ -378,7 +378,7 @@ def test_sequential_gptq_checkpoint_resume_cpu_offloaded(tmp_path): # Simulate crash after layer 0 last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w") as f: + with open(manifest_path, "w", encoding="utf-8") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) diff --git a/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py b/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py index 14cc68dbec2..4e704997f4d 100644 --- a/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py +++ b/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py @@ -74,7 +74,8 @@ def _write_lossless_mxfp4_source(model, ckpt_dir: Path) -> None: (ckpt_dir / "model.safetensors.index.json").write_text( json.dumps( {"metadata": {}, "weight_map": dict.fromkeys(state, "model-00001-of-00001.safetensors")} - ) + ), + encoding="utf-8", ) diff --git a/tests/gpu/torch/utils/test_model_load_utils.py b/tests/gpu/torch/utils/test_model_load_utils.py index 7c972319d97..ed402181067 100644 --- a/tests/gpu/torch/utils/test_model_load_utils.py +++ b/tests/gpu/torch/utils/test_model_load_utils.py @@ -82,7 +82,7 @@ def _test_parallel_load_and_export(rank, size, ckpt_dir, export_dir, cpu_offload export_hf_checkpoint(model, export_dir=export_dir, dtype=torch.bfloat16) if rank == 0: - with open(os.path.join(export_dir, "config.json")) as f: + with open(os.path.join(export_dir, "config.json"), encoding="utf-8") as f: cfg = json.load(f) assert cfg["architectures"] == ["LlamaForCausalLM"] diff --git a/tests/gpu_megatron/torch/export/test_unified_export_megatron.py b/tests/gpu_megatron/torch/export/test_unified_export_megatron.py index 614b5d96e2a..8cc59abd0a1 100644 --- a/tests/gpu_megatron/torch/export/test_unified_export_megatron.py +++ b/tests/gpu_megatron/torch/export/test_unified_export_megatron.py @@ -55,8 +55,8 @@ def _verify_model_quant_config( export_dir: Path, quant_config: str | None = None, kv_cache_quant_cfg: str | None = None ): """Verify config.json and hf_quant_config.json""" - config_dict = json.load(open(export_dir / "config.json")) - hf_quant_config_dict = json.load(open(export_dir / "hf_quant_config.json")) + config_dict = json.load(open(export_dir / "config.json", encoding="utf-8")) + hf_quant_config_dict = json.load(open(export_dir / "hf_quant_config.json", encoding="utf-8")) # Make sure config.json and hf_quant_config.json are consistent assert ( config_dict["quantization_config"]["quant_algo"] @@ -430,7 +430,7 @@ def _test_qkv_slicing_gqa_tp2(tmp_path, rank, size): "num_key_value_heads": num_query_groups, "torch_dtype": "bfloat16", } - with open(tmp_path / "config.json", "w") as f: + with open(tmp_path / "config.json", "w", encoding="utf-8") as f: json.dump(pretrained_config, f) export_dir = tmp_path / "export" @@ -507,7 +507,7 @@ def _fake_get_mtp_state_dict(self): shard_keys_cache = {} all_weight_map_keys = set() for shard_json_file in shard_json_files: - with open(shard_json_file) as f: + with open(shard_json_file, encoding="utf-8") as f: shard_meta = json.load(f) for key, shard_file in shard_meta["weight_map"].items(): all_weight_map_keys.add(key) @@ -679,7 +679,7 @@ def test_mtp_state_dict_index_file(tmp_path): "mtp.0.hnorm.weight": "model-00002-of-00002.safetensors", } } - with open(model_dir / "model.safetensors.index.json", "w") as f: + with open(model_dir / "model.safetensors.index.json", "w", encoding="utf-8") as f: json.dump(index, f) exporter = _make_exporter_for_mtp(model_dir) @@ -908,7 +908,7 @@ def _make_exporter_for_key_check(num_layers: int) -> GPTModelExporter: def _write_index(dir_path: Path, keys) -> None: dir_path.mkdir(parents=True, exist_ok=True) (dir_path / "model.safetensors.index.json").write_text( - json.dumps({"weight_map": dict.fromkeys(keys, "model-00001.safetensors")}) + json.dumps({"weight_map": dict.fromkeys(keys, "model-00001.safetensors")}), encoding="utf-8" ) diff --git a/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py b/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py index 61fdabed8b3..e8b376bb04f 100644 --- a/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py +++ b/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py @@ -79,7 +79,7 @@ def forward_loop(model): "torch_dtype": "bfloat16", } - with open(tmp_path / "config.json", "w") as f: + with open(tmp_path / "config.json", "w", encoding="utf-8") as f: json.dump(pretrained_config, f) # Export directory diff --git a/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py b/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py index 041a0bd3fca..8a5f497cadb 100644 --- a/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py +++ b/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py @@ -1518,7 +1518,7 @@ def forward_backward_step(model, batch): assert summed_cost == pytest.approx(local_total, rel=1e-6) if rank == 0: - Path(result_path).write_text(repr(summed_cost)) + Path(result_path).write_text(repr(summed_cost), encoding="utf-8") @pytest.mark.skipif(not HAS_MAMBA, reason="Mamba not installed") @@ -1543,8 +1543,8 @@ def test_auto_quantize_mamba_hybrid_ep_cost(dist_workers, tmp_path): result_path=str(ep2_path), ) ) - cost_ep1 = float(ep1_path.read_text()) - cost_ep2 = float(ep2_path.read_text()) + cost_ep1 = float(ep1_path.read_text(encoding="utf-8")) + cost_ep2 = float(ep2_path.read_text(encoding="utf-8")) assert cost_ep1 == pytest.approx(cost_ep2, rel=1e-6) diff --git a/tests/regression/torch/speculative/test_dflash.py b/tests/regression/torch/speculative/test_dflash.py index 18e95148fcd..4ee5a69d114 100644 --- a/tests/regression/torch/speculative/test_dflash.py +++ b/tests/regression/torch/speculative/test_dflash.py @@ -100,7 +100,7 @@ def test_dflash_training(qwen3_model_name, dflash_output_dir): # Regression: verify loss decreased trainer_state = os.path.join(output_dir, "trainer_state.json") assert os.path.exists(trainer_state), "trainer_state.json not found" - with open(trainer_state) as f: + with open(trainer_state, encoding="utf-8") as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] assert len(logs) >= 2, f"Expected at least 2 log entries, got {len(logs)}" @@ -149,7 +149,7 @@ def test_dflash_export(dflash_output_dir): assert os.path.exists(os.path.join(export_dir, "model.safetensors")) assert os.path.exists(os.path.join(export_dir, "config.json")) - with open(os.path.join(export_dir, "config.json")) as f: + with open(os.path.join(export_dir, "config.json"), encoding="utf-8") as f: config = json.load(f) assert config["architectures"] == ["DFlashDraftModel"] assert config["model_type"] == "qwen3" diff --git a/tests/regression/torch/speculative/test_dflash_offline.py b/tests/regression/torch/speculative/test_dflash_offline.py index 678742686cb..837142b448e 100644 --- a/tests/regression/torch/speculative/test_dflash_offline.py +++ b/tests/regression/torch/speculative/test_dflash_offline.py @@ -132,7 +132,7 @@ def test_dflash_offline_training( trainer_state = os.path.join(output_dir, "trainer_state.json") assert os.path.exists(trainer_state), "trainer_state.json not found" - with open(trainer_state) as f: + with open(trainer_state, encoding="utf-8") as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] assert len(logs) >= 2, f"Expected at least 2 log entries, got {len(logs)}" diff --git a/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py b/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py index ce63b1a96c2..901c4f6a44f 100644 --- a/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py +++ b/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py @@ -112,7 +112,8 @@ def test_rank0_rendezvous_rejects_mismatched_configuration(tmp_path): ready_path = tmp_path / "ready.json" fingerprint = {"source_ckpt": "/models/Kimi-K3", "shards": ["model-1.safetensors"]} ready_path.write_text( - json.dumps({"run_id": "run-1", "world_size": 4, "fingerprint": fingerprint}) + json.dumps({"run_id": "run-1", "world_size": 4, "fingerprint": fingerprint}), + encoding="utf-8", ) assert not k3_cast._rank0_ready( @@ -141,7 +142,8 @@ def test_rank_report_rejects_mismatched_fingerprint(tmp_path): "rank": 1, "fingerprint": {"cast_mxfp4_to_nvfp4": False}, } - ) + ), + encoding="utf-8", ) with pytest.raises(ValueError, match="rank 1 report conversion fingerprint"): @@ -195,7 +197,7 @@ def _write_source_checkpoint(tmp_path: Path) -> tuple[Path, str, dict[str, torch "metadata": {"total_size": sum(t.numel() * t.element_size() for t in state.values())}, "weight_map": dict.fromkeys(state, shard_name), } - (source / "model.safetensors.index.json").write_text(json.dumps(index)) + (source / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") (source / "config.json").write_text( json.dumps( { @@ -207,9 +209,10 @@ def _write_source_checkpoint(tmp_path: Path) -> tuple[Path, str, dict[str, torch } }, } - ) + ), + encoding="utf-8", ) - (source / "tokenizer_config.json").write_text("{}") + (source / "tokenizer_config.json").write_text("{}", encoding="utf-8") return source, shard_name, state @@ -411,7 +414,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): hf_quant_config = k3_cast._build_hf_quant_config( report["banks"], report["attn_modules"], attn_fp8=True ) - source_index = json.loads((source / "model.safetensors.index.json").read_text()) + source_index = json.loads((source / "model.safetensors.index.json").read_text(encoding="utf-8")) k3_cast._write_index_and_manifest( output, source_index, @@ -421,7 +424,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): ) k3_cast._rewrite_config_json(source / "config.json", output, hf_quant_config) - index = json.loads((output / "model.safetensors.index.json").read_text()) + index = json.loads((output / "model.safetensors.index.json").read_text(encoding="utf-8")) weight_map = index["weight_map"] expert = "language_model.model.layers.1.block_sparse_moe.experts.0.w1" assert expert + ".weight_packed" not in weight_map @@ -431,7 +434,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): assert weight_map[expert + ".input_scale"] == shard_name assert index["metadata"]["total_size"] == report["tensor_bytes"] - config = json.loads((output / "config.json").read_text()) + config = json.loads((output / "config.json").read_text(encoding="utf-8")) assert "quantization_config" not in config["text_config"] quant = config["quantization_config"] assert quant["quant_method"] == "modelopt_mixed" diff --git a/tests/unit/onnx/autocast/test_referencerunner.py b/tests/unit/onnx/autocast/test_referencerunner.py index 5c5c3c00ab2..cfeb90e2a72 100644 --- a/tests/unit/onnx/autocast/test_referencerunner.py +++ b/tests/unit/onnx/autocast/test_referencerunner.py @@ -204,7 +204,7 @@ def test_mismatched_input_names(reference_runner): "wrong_name2": np.array([[4.0, 5.0, 6.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name @@ -221,7 +221,7 @@ def test_invalid_json(reference_runner): """Test error handling for non-Polygraphy JSON format.""" inputs = {"X1": [[1.0, 2.0, 3.0]], "X2": [[4.0, 5.0, 6.0]]} - with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: json.dump(inputs, f) input_path = f.name try: @@ -253,7 +253,7 @@ def test_compare_outputs(reference_runner): "X2": np.array([[4.0, 5.0, 6.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name @@ -275,7 +275,7 @@ def test_compare_outputs(reference_runner): "X2": np.array([[1.0, 2.0, 3.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name diff --git a/tests/unit/onnx/quantization/autotune/test_autotuner.py b/tests/unit/onnx/quantization/autotune/test_autotuner.py index 26e390a2354..22e83fc2d2b 100644 --- a/tests/unit/onnx/quantization/autotune/test_autotuner.py +++ b/tests/unit/onnx/quantization/autotune/test_autotuner.py @@ -254,7 +254,9 @@ def test_save_and_load_state(self, simple_conv_model): # Submit some results autotuner.submit(10.5) # baseline - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + with tempfile.NamedTemporaryFile( + encoding="utf-8", mode="w", suffix=".yaml", delete=False + ) as f: state_path = f.name try: diff --git a/tests/unit/onnx/quantization/autotune/test_pattern_cache.py b/tests/unit/onnx/quantization/autotune/test_pattern_cache.py index a2d61c507b9..b5f6a8d6779 100644 --- a/tests/unit/onnx/quantization/autotune/test_pattern_cache.py +++ b/tests/unit/onnx/quantization/autotune/test_pattern_cache.py @@ -121,7 +121,9 @@ def test_yaml_round_trip(self): scheme.latency_ms = 15.0 ps.schemes.append(scheme) cache.add_pattern_schemes(ps) - with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: + with tempfile.NamedTemporaryFile( + encoding="utf-8", mode="w", suffix=".yaml", delete=False + ) as f: yaml_path = f.name try: cache.save(yaml_path) diff --git a/tests/unit/recipe/test_loader.py b/tests/unit/recipe/test_loader.py index 006e7d57927..7398fd67a3c 100644 --- a/tests/unit/recipe/test_loader.py +++ b/tests/unit/recipe/test_loader.py @@ -94,15 +94,15 @@ def _write_quantizer_attribute(path, body: str): - path.write_text(QUANTIZER_ATTRIBUTE_SCHEMA + body) + path.write_text(QUANTIZER_ATTRIBUTE_SCHEMA + body, encoding="utf-8") def _write_quantizer_cfg_entry(path, body: str): - path.write_text(QUANTIZER_CFG_ENTRY_SCHEMA + body) + path.write_text(QUANTIZER_CFG_ENTRY_SCHEMA + body, encoding="utf-8") def _write_quantizer_cfg_list(path, body: str): - path.write_text(QUANTIZER_CFG_LIST_SCHEMA + body) + path.write_text(QUANTIZER_CFG_LIST_SCHEMA + body, encoding="utf-8") def _cfg_to_dict(cfg): @@ -123,13 +123,13 @@ def _cfg_to_dict(cfg): def test_load_config_plain(tmp_path): """A plain config is returned as-is.""" - (tmp_path / "cfg.yml").write_text(CFG_AB) + (tmp_path / "cfg.yml").write_text(CFG_AB, encoding="utf-8") assert load_config(tmp_path / "cfg.yml") == {"a": 1, "b": 2} def test_load_config_suffix_probe(tmp_path): """load_config finds a .yml file when suffix is omitted from a string path.""" - (tmp_path / "mycfg.yml").write_text(CFG_KEY_VAL) + (tmp_path / "mycfg.yml").write_text(CFG_KEY_VAL, encoding="utf-8") assert load_config(str(tmp_path / "mycfg")) == {"key": "val"} @@ -268,7 +268,8 @@ def test_load_recipe_local_tree_overrides_builtin_even_on_name_collision(tmp_pat local = tmp_path / old_rel local.parent.mkdir(parents=True) local.write_text( - "metadata:\n recipe_type: ptq\nquantize:\n quant_cfg: {}\n algorithm: max\n" + "metadata:\n recipe_type: ptq\nquantize:\n quant_cfg: {}\n algorithm: max\n", + encoding="utf-8", ) monkeypatch.chdir(tmp_path) @@ -446,7 +447,7 @@ def test_load_recipe_missing_raises(tmp_path): def test_load_recipe_missing_recipe_type_raises(tmp_path): """load_recipe raises ValueError when metadata.recipe_type is absent.""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_TYPE) + bad.write_text(CFG_RECIPE_MISSING_TYPE, encoding="utf-8") with pytest.raises(ValueError, match="recipe_type"): load_recipe(bad) @@ -454,7 +455,7 @@ def test_load_recipe_missing_recipe_type_raises(tmp_path): def test_load_recipe_missing_quantize_raises(tmp_path): """A PTQ recipe missing the ``quantize`` section is rejected (no silent default).""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_quantize) + bad.write_text(CFG_RECIPE_MISSING_quantize, encoding="utf-8") with pytest.raises(ValueError, match="quantize"): load_recipe(bad) @@ -462,7 +463,7 @@ def test_load_recipe_missing_quantize_raises(tmp_path): def test_load_recipe_missing_metadata_raises(tmp_path): """A recipe missing the ``metadata`` section is rejected (no silent default).""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_METADATA) + bad.write_text(CFG_RECIPE_MISSING_METADATA, encoding="utf-8") with pytest.raises(ValueError, match="metadata"): load_recipe(bad) @@ -470,7 +471,7 @@ def test_load_recipe_missing_metadata_raises(tmp_path): def test_load_recipe_unsupported_type_raises(tmp_path): """load_recipe raises ValueError for an unknown recipe_type.""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_UNSUPPORTED_TYPE) + bad.write_text(CFG_RECIPE_UNSUPPORTED_TYPE, encoding="utf-8") # Schema-driven validation reports the failure via the metadata schema's enum check. with pytest.raises(ValueError, match="recipe_type"): load_recipe(bad) @@ -483,8 +484,10 @@ def test_load_recipe_unsupported_type_raises(tmp_path): def test_load_recipe_dir(tmp_path): """load_recipe loads a recipe from a directory with metadata.yml + quantize.yml.""" - (tmp_path / "metadata.yml").write_text("recipe_type: ptq\ndescription: Dir test.\n") - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n") + (tmp_path / "metadata.yml").write_text( + "recipe_type: ptq\ndescription: Dir test.\n", encoding="utf-8" + ) + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n", encoding="utf-8") recipe = load_recipe(tmp_path) assert recipe.recipe_type == RecipeType.PTQ assert recipe.description == "Dir test." @@ -494,14 +497,14 @@ def test_load_recipe_dir(tmp_path): def test_load_recipe_dir_missing_metadata_raises(tmp_path): """load_recipe raises ValueError when metadata.yml is absent from the directory.""" - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: {}\n") + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: {}\n", encoding="utf-8") with pytest.raises(ValueError, match="metadata"): load_recipe(tmp_path) def test_load_recipe_dir_missing_quantize_raises(tmp_path): """load_recipe raises ValueError when quantize.yml is absent from the directory.""" - (tmp_path / "metadata.yml").write_text("recipe_type: ptq\n") + (tmp_path / "metadata.yml").write_text("recipe_type: ptq\n", encoding="utf-8") with pytest.raises(ValueError, match="quantize"): load_recipe(tmp_path) @@ -525,7 +528,7 @@ def test_load_recipe_eagle_builtin(): def test_load_recipe_eagle_missing_section_raises(tmp_path): """load_recipe raises ValueError when 'eagle' is absent for a SPECULATIVE_EAGLE recipe.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: speculative_eagle\n") + bad.write_text("metadata:\n recipe_type: speculative_eagle\n", encoding="utf-8") with pytest.raises(ValueError, match="eagle"): load_recipe(bad) @@ -534,7 +537,8 @@ def test_load_recipe_eagle_field_validation_raises(tmp_path): """Invalid EAGLE field values must fail Pydantic validation at load time.""" bad = tmp_path / "bad.yml" bad.write_text( - "metadata:\n recipe_type: speculative_eagle\neagle:\n eagle_ttt_steps: not_an_int\n" + "metadata:\n recipe_type: speculative_eagle\neagle:\n eagle_ttt_steps: not_an_int\n", + encoding="utf-8", ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(bad) @@ -559,7 +563,7 @@ def test_load_recipe_dflash_builtin(): def test_load_recipe_dflash_missing_section_raises(tmp_path): """load_recipe raises ValueError when 'dflash' is absent for a SPECULATIVE_DFLASH recipe.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: speculative_dflash\n") + bad.write_text("metadata:\n recipe_type: speculative_dflash\n", encoding="utf-8") with pytest.raises(ValueError, match="dflash"): load_recipe(bad) @@ -572,7 +576,8 @@ def test_load_recipe_eagle_with_training_sections(tmp_path): "model:\n model_name_or_path: TinyLlama/TinyLlama-1.1B-Chat-v1.0\n" "data:\n data_path: train.jsonl\n" "training:\n output_dir: ckpts/test\n" - "eagle:\n eagle_decoder_type: llama\n eagle_ttt_steps: 2\n" + "eagle:\n eagle_decoder_type: llama\n eagle_ttt_steps: 2\n", + encoding="utf-8", ) recipe = load_recipe(recipe_path) assert isinstance(recipe, ModelOptEagleRecipe) @@ -589,7 +594,8 @@ def test_typed_model_section_rejects_unknown_field(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "model:\n typo_name: oops\n" - "eagle:\n eagle_decoder_type: llama\n" + "eagle:\n eagle_decoder_type: llama\n", + encoding="utf-8", ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(recipe_path) @@ -604,7 +610,8 @@ def test_typed_training_section_accepts_hf_extras(tmp_path): " num_train_epochs: 3\n" # HF field — accepted as extra " learning_rate: 1.0e-4\n" # HF field — accepted as extra " training_seq_len: 4096\n" # our extension field — validated - "eagle:\n eagle_decoder_type: llama\n" + "eagle:\n eagle_decoder_type: llama\n", + encoding="utf-8", ) recipe = load_recipe(recipe_path) assert isinstance(recipe, ModelOptEagleRecipe) @@ -682,7 +689,8 @@ def test_load_recipe_with_overrides(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "model:\n trust_remote_code: false\n" - "eagle:\n eagle_ttt_steps: 3\n" + "eagle:\n eagle_ttt_steps: 3\n", + encoding="utf-8", ) recipe = load_recipe( recipe_path, @@ -695,8 +703,8 @@ def test_load_recipe_with_overrides(tmp_path): def test_load_recipe_overrides_rejected_for_dir(tmp_path): """Overrides are not allowed for directory-format recipes.""" - (tmp_path / "recipe.yml").write_text("metadata:\n recipe_type: ptq\n") - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n") + (tmp_path / "recipe.yml").write_text("metadata:\n recipe_type: ptq\n", encoding="utf-8") + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n", encoding="utf-8") with pytest.raises(ValueError, match="directory-format"): load_recipe(tmp_path, overrides=["quantize.algorithm=gptq"]) @@ -707,7 +715,8 @@ def test_typed_data_sample_size_validator(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "data:\n sample_size: 0\n" - "eagle:\n eagle_decoder_type: llama\n" + "eagle:\n eagle_decoder_type: llama\n", + encoding="utf-8", ) with pytest.raises(Exception, match="sample_size"): # pydantic.ValidationError load_recipe(recipe_path) @@ -717,7 +726,8 @@ def test_load_recipe_dflash_field_validation_raises(tmp_path): """Invalid DFlash field values must fail Pydantic validation at load time.""" bad = tmp_path / "bad.yml" bad.write_text( - "metadata:\n recipe_type: speculative_dflash\ndflash:\n dflash_block_size: not_an_int\n" + "metadata:\n recipe_type: speculative_dflash\ndflash:\n dflash_block_size: not_an_int\n", + encoding="utf-8", ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(bad) @@ -805,7 +815,8 @@ def test_import_resolves_cfg_reference(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) entry = recipe.quantize["quant_cfg"][0] @@ -829,7 +840,8 @@ def test_import_same_name_used_twice(tmp_path): f" $import: fp8\n" f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["cfg"] == recipe.quantize["quant_cfg"][1]["cfg"] @@ -854,7 +866,8 @@ def test_import_multiple_snippets(tmp_path): f" $import: nvfp4\n" f" - quantizer_name: '*[kv]_bmm_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["cfg"]["num_bits"] == (2, 1) @@ -879,7 +892,8 @@ def test_import_inline_cfg_not_affected(tmp_path): f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" f" num_bits: 8\n" - f" axis: 0\n" + f" axis: 0\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][1]["cfg"].model_dump(exclude_unset=True) == { @@ -901,7 +915,8 @@ def test_import_unknown_reference_raises(tmp_path): " quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: nonexistent\n" + " $import: nonexistent\n", + encoding="utf-8", ) with pytest.raises(ValueError, match=r"Unknown \$import reference"): load_recipe(recipe_file) @@ -917,7 +932,8 @@ def test_import_empty_path_raises(tmp_path): " recipe_type: ptq\n" "quantize:\n" " algorithm: max\n" - " quant_cfg: []\n" + " quant_cfg: []\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="empty config path"): load_recipe(recipe_file) @@ -925,7 +941,7 @@ def test_import_empty_path_raises(tmp_path): def test_import_snippet_without_schema_raises(tmp_path): """Every imported snippet must declare modelopt-schema, including dict snippets.""" - (tmp_path / "fp8.yml").write_text("num_bits: e4m3\n") + (tmp_path / "fp8.yml").write_text("num_bits: e4m3\n", encoding="utf-8") recipe_file = tmp_path / "ptq.yml" recipe_file.write_text( f"imports:\n" @@ -937,7 +953,8 @@ def test_import_snippet_without_schema_raises(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="modelopt-schema"): load_recipe(recipe_file) @@ -953,7 +970,8 @@ def test_import_not_a_dict_raises(tmp_path): " recipe_type: ptq\n" "quantize:\n" " algorithm: max\n" - " quant_cfg: []\n" + " quant_cfg: []\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="must be a dict"): load_recipe(recipe_file) @@ -969,7 +987,8 @@ def test_import_no_imports_section(tmp_path): " algorithm: max\n" " quant_cfg:\n" " - quantizer_name: '*'\n" - " enable: false\n" + " enable: false\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["enable"] is False @@ -997,7 +1016,8 @@ def test_import_entry_single_element_list(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: disable_all\n" + f" - $import: disable_all\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 1 @@ -1018,7 +1038,8 @@ def test_import_entry_element_schema_appends(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: disable_all\n" + f" - $import: disable_all\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) # Entry was loaded against the QuantizerCfgEntry pydantic schema, so it is now a @@ -1044,7 +1065,8 @@ def test_import_entry_wrong_schema_raises(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: fp8\n" + f" - $import: fp8\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="expected either"): load_recipe(recipe_file) @@ -1068,7 +1090,8 @@ def test_import_entry_list_splice(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*'\n" f" enable: false\n" - f" - $import: disables\n" + f" - $import: disables\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 3 @@ -1089,7 +1112,8 @@ def test_import_entry_sibling_keys_with_list_snippet_raises(tmp_path): f" algorithm: max\n" f" quant_cfg:\n" f" - $import: disable_all\n" - f" quantizer_name: '*extra*'\n" + f" quantizer_name: '*extra*'\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="must resolve to a dict"): load_recipe(recipe_file) @@ -1110,7 +1134,8 @@ def test_import_cfg_extend(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" axis: 0\n" + f" axis: 0\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1132,7 +1157,8 @@ def test_import_cfg_inline_overrides_import(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" num_bits: 8\n" + f" num_bits: 8\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1152,7 +1178,8 @@ def test_import_in_non_cfg_dict_value(tmp_path): f"quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" my_field:\n" - f" $import: extra\n" + f" $import: extra\n", + encoding="utf-8", ) data = load_config(config_file) entry = data["quant_cfg"][0] @@ -1173,7 +1200,8 @@ def test_import_in_multiple_dict_values(tmp_path): f" cfg:\n" f" $import: fp8\n" f" my_field:\n" - f" $import: extra\n" + f" $import: extra\n", + encoding="utf-8", ) data = load_config(config_file) entry = data["quant_cfg"][0] @@ -1198,7 +1226,8 @@ def test_import_cfg_multi_import(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: [bits, axis]\n" + f" $import: [bits, axis]\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1221,7 +1250,8 @@ def test_import_cfg_multi_import_later_overrides_earlier(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: [a, b]\n" + f" $import: [a, b]\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1247,7 +1277,8 @@ def test_import_cfg_multi_import_with_extend(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: [bits, extra]\n" - f" axis: 0\n" + f" axis: 0\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1261,7 +1292,9 @@ def test_import_cfg_multi_import_with_extend(tmp_path): def test_import_dir_format(tmp_path): """Imports in quantize.yml work with the directory recipe format.""" _write_quantizer_attribute(tmp_path / "fp8.yml", "num_bits: e4m3\naxis:\n") - (tmp_path / "metadata.yml").write_text("recipe_type: ptq\ndescription: Dir with imports.\n") + (tmp_path / "metadata.yml").write_text( + "recipe_type: ptq\ndescription: Dir with imports.\n", encoding="utf-8" + ) (tmp_path / "quantize.yml").write_text( f"imports:\n" f" fp8: {tmp_path / 'fp8.yml'}\n" @@ -1269,7 +1302,8 @@ def test_import_dir_format(tmp_path): "quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: fp8\n" + " $import: fp8\n", + encoding="utf-8", ) recipe = load_recipe(tmp_path) assert recipe.quantize["quant_cfg"][0]["cfg"].model_dump(exclude_unset=True) == { @@ -1282,14 +1316,15 @@ def test_import_dir_format_metadata_imports_do_not_apply_to_quantize(tmp_path): """metadata.yml imports are scoped to metadata.yml, not quantize.yml.""" _write_quantizer_attribute(tmp_path / "fp8.yml", "num_bits: e4m3\n") (tmp_path / "metadata.yml").write_text( - f"imports:\n fmt: {tmp_path / 'fp8.yml'}\nrecipe_type: ptq\n" + f"imports:\n fmt: {tmp_path / 'fp8.yml'}\nrecipe_type: ptq\n", encoding="utf-8" ) (tmp_path / "quantize.yml").write_text( "algorithm: max\n" "quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: fmt\n" + " $import: fmt\n", + encoding="utf-8", ) with pytest.raises(ValueError, match=r"Unknown \$import reference"): load_recipe(tmp_path) @@ -1304,7 +1339,8 @@ def test_import_multi_document_list_snippet(tmp_path): """List snippet using multi-document YAML (imports --- content) resolves $import.""" (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n" + "num_bits: e4m3\n", + encoding="utf-8", ) (tmp_path / "kv.yaml").write_text( f"# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig\n" @@ -1313,7 +1349,8 @@ def test_import_multi_document_list_snippet(tmp_path): f"---\n" f"- quantizer_name: '*[kv]_bmm_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) recipe_file = tmp_path / "ptq.yml" recipe_file.write_text( @@ -1324,7 +1361,8 @@ def test_import_multi_document_list_snippet(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: kv\n" + f" - $import: kv\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 1 @@ -1354,7 +1392,8 @@ def test_import_in_top_level_dict_value(tmp_path): _write_quantizer_attribute(tmp_path / "algo.yml", "num_bits: 8\naxis: 0\n") config_file = tmp_path / "config.yml" config_file.write_text( - f"imports:\n algo: {tmp_path / 'algo.yml'}\nalgorithm:\n $import: algo\nquant_cfg: []\n" + f"imports:\n algo: {tmp_path / 'algo.yml'}\nalgorithm:\n $import: algo\nquant_cfg: []\n", + encoding="utf-8", ) data = load_config(config_file) assert data["algorithm"] == {"num_bits": 8, "axis": 0} @@ -1370,7 +1409,8 @@ def test_import_in_nested_dict(tmp_path): f"training:\n" f" optimizer:\n" f" params:\n" - f" $import: settings\n" + f" $import: settings\n", + encoding="utf-8", ) data = load_config(config_file) assert data["training"]["optimizer"]["params"] == {"num_bits": (4, 3)} @@ -1390,7 +1430,8 @@ def test_import_list_splice_outside_typed_list_raises(tmp_path): f"tasks:\n" f" - name: task_a\n" f" - $import: extra\n" - f" - name: task_d\n" + f" - name: task_d\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="requires a typed list schema"): load_config(config_file) @@ -1410,7 +1451,8 @@ def test_import_in_nested_list_of_dicts(tmp_path): f" verbose: true\n" f" - name: test\n" f" config:\n" - f" $import: defaults\n" + f" $import: defaults\n", + encoding="utf-8", ) data = load_config(config_file) assert data["stages"][0]["config"] == {"num_bits": 8, "verbose": True} @@ -1421,12 +1463,14 @@ def test_import_mixed_tree(tmp_path): """$import resolves at multiple levels in the same config.""" (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n" + "num_bits: e4m3\n", + encoding="utf-8", ) (tmp_path / "disables.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig\n" "- quantizer_name: '*lm_head*'\n" - " enable: false\n" + " enable: false\n", + encoding="utf-8", ) config_file = tmp_path / "config.yml" config_file.write_text( @@ -1439,7 +1483,8 @@ def test_import_mixed_tree(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" - $import: disables\n" + f" - $import: disables\n", + encoding="utf-8", ) data = load_config(config_file) # Dict import inside list entry @@ -1465,7 +1510,8 @@ def test_import_recursive(tmp_path): # base: dict snippet with FP8 attributes (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n" + "num_bits: e4m3\n", + encoding="utf-8", ) # mid: list snippet that imports base and uses $import in cfg (tmp_path / "mid.yaml").write_text( @@ -1475,7 +1521,8 @@ def test_import_recursive(tmp_path): f"---\n" f"- quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) # recipe imports mid recipe_file = tmp_path / "ptq.yml" @@ -1487,7 +1534,8 @@ def test_import_recursive(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: mid\n" + f" - $import: mid\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1510,7 +1558,8 @@ def test_import_circular_raises(tmp_path): f" recipe_type: ptq\n" f"quantize:\n" f" algorithm: max\n" - f" quant_cfg: []\n" + f" quant_cfg: []\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="Circular import"): load_recipe(recipe_file) @@ -1537,7 +1586,8 @@ def test_import_circular_via_path_aliases_raises(tmp_path): f" recipe_type: ptq\n" f"quantize:\n" f" algorithm: max\n" - f" quant_cfg: []\n" + f" quant_cfg: []\n", + encoding="utf-8", ) cwd = os.getcwd() os.chdir(tmp_path) @@ -1585,7 +1635,8 @@ def test_import_cross_file_same_name_no_conflict(tmp_path): f" $import: fmt\n" f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" - f" $import: child\n" + f" $import: child\n", + encoding="utf-8", ) recipe = load_recipe(recipe_file) # Parent's "fmt" resolves to fp8 (e4m3), not child's nvfp4. @@ -1633,7 +1684,8 @@ def test_modelopt_schema_comment_returns_instance(tmp_path): config_file.write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" "num_bits: e4m3\n" - "axis:\n" + "axis:\n", + encoding="utf-8", ) data = load_config(config_file) assert isinstance(data, QuantizerAttributeConfig) @@ -1646,7 +1698,8 @@ def test_modelopt_schema_comment_validation_error(tmp_path): config_file = tmp_path / "bad.yaml" config_file.write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "unknown_field: true\n" + "unknown_field: true\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="does not match modelopt-schema"): load_config(config_file) @@ -1667,7 +1720,8 @@ def test_modelopt_schema_comment_validates_after_import_resolution(tmp_path): """Schema validation runs after nested imports have been resolved.""" (tmp_path / "fp8.yaml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n" + "num_bits: e4m3\n", + encoding="utf-8", ) config_file = tmp_path / "entry.yaml" config_file.write_text( @@ -1677,7 +1731,8 @@ def test_modelopt_schema_comment_validates_after_import_resolution(tmp_path): f"---\n" f"- quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n" + f" $import: fp8\n", + encoding="utf-8", ) data = load_config(config_file) # data is a list of QuantizerCfgEntry pydantic instances, not raw dicts. Dump with @@ -1696,11 +1751,13 @@ def test_import_dict_snippet_imports_in_union_typed_list_field(tmp_path): "num_bits: 4\n" "block_sizes:\n" " -1: 128\n" - " type: static\n" + " type: static\n", + encoding="utf-8", ) (tmp_path / "fp8.yaml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n" + "num_bits: e4m3\n", + encoding="utf-8", ) config_file = tmp_path / "config.yaml" config_file.write_text( @@ -1713,7 +1770,8 @@ def test_import_dict_snippet_imports_in_union_typed_list_field(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" - $import: int4\n" - f" - $import: fp8\n" + f" - $import: fp8\n", + encoding="utf-8", ) data = load_config(config_file) @@ -1740,7 +1798,8 @@ def test_import_dict_snippet_in_union_typed_list_field_with_inline_item(tmp_path f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" - $import: int4\n" - f" - num_bits: e4m3\n" + f" - num_bits: e4m3\n", + encoding="utf-8", ) data = load_config(config_file) assert _cfg_to_dict(data["quant_cfg"][0]["cfg"]) == [ @@ -1757,7 +1816,7 @@ def test_import_dict_snippet_in_union_typed_list_field_with_inline_item(tmp_path def test_load_config_path_object(tmp_path): """load_config accepts a Path object.""" cfg_file = tmp_path / "test.yaml" - cfg_file.write_text("key: value\n") + cfg_file.write_text("key: value\n", encoding="utf-8") data = load_config(cfg_file) assert data == {"key": "value"} @@ -1765,7 +1824,7 @@ def test_load_config_path_object(tmp_path): def test_load_config_path_without_suffix(tmp_path): """load_config probes .yml/.yaml suffixes for a Path without suffix.""" cfg_file = tmp_path / "test.yaml" - cfg_file.write_text("key: value\n") + cfg_file.write_text("key: value\n", encoding="utf-8") data = load_config(tmp_path / "test") # no suffix assert data == {"key": "value"} @@ -1773,7 +1832,7 @@ def test_load_config_path_without_suffix(tmp_path): def test_load_config_empty_yaml(tmp_path): """load_config returns empty dict for empty YAML file.""" cfg_file = tmp_path / "empty.yaml" - cfg_file.write_text("") + cfg_file.write_text("", encoding="utf-8") data = load_config(cfg_file) assert data == {} @@ -1781,7 +1840,7 @@ def test_load_config_empty_yaml(tmp_path): def test_load_config_null_yaml(tmp_path): """load_config returns empty dict for YAML file containing only null.""" cfg_file = tmp_path / "null.yaml" - cfg_file.write_text("---\n") + cfg_file.write_text("---\n", encoding="utf-8") data = load_config(cfg_file) assert data == {} @@ -1789,7 +1848,7 @@ def test_load_config_null_yaml(tmp_path): def test_load_config_multi_doc_dict_dict(tmp_path): """Multi-document YAML with two dicts merges them.""" cfg_file = tmp_path / "multi.yaml" - cfg_file.write_text("imports:\n fp8: some/path\n---\nalgorithm: max\n") + cfg_file.write_text("imports:\n fp8: some/path\n---\nalgorithm: max\n", encoding="utf-8") data = _load_raw_config(cfg_file) assert data["imports"] == {"fp8": "some/path"} assert data["algorithm"] == "max" @@ -1798,7 +1857,7 @@ def test_load_config_multi_doc_dict_dict(tmp_path): def test_load_config_multi_doc_null_content(tmp_path): """Multi-document YAML where second doc is null treats content as empty dict.""" cfg_file = tmp_path / "multi_null.yaml" - cfg_file.write_text("key: value\n---\n") + cfg_file.write_text("key: value\n---\n", encoding="utf-8") data = _load_raw_config(cfg_file) assert data == {"key": "value"} @@ -1806,7 +1865,7 @@ def test_load_config_multi_doc_null_content(tmp_path): def test_load_config_multi_doc_first_not_dict_raises(tmp_path): """Multi-document YAML with non-dict first document raises ValueError.""" cfg_file = tmp_path / "bad_multi.yaml" - cfg_file.write_text("- item1\n---\nkey: value\n") + cfg_file.write_text("- item1\n---\nkey: value\n", encoding="utf-8") with pytest.raises(ValueError, match="first YAML document must be a mapping"): load_config(cfg_file) @@ -1814,7 +1873,7 @@ def test_load_config_multi_doc_first_not_dict_raises(tmp_path): def test_load_config_multi_doc_second_not_dict_or_list_raises(tmp_path): """Multi-document YAML with scalar second document raises ValueError.""" cfg_file = tmp_path / "bad_multi2.yaml" - cfg_file.write_text("key: value\n---\njust a string\n") + cfg_file.write_text("key: value\n---\njust a string\n", encoding="utf-8") with pytest.raises(ValueError, match="second YAML document must be a mapping or list"): load_config(cfg_file) @@ -1822,7 +1881,7 @@ def test_load_config_multi_doc_second_not_dict_or_list_raises(tmp_path): def test_load_config_three_docs_raises(tmp_path): """YAML with 3+ documents raises ValueError.""" cfg_file = tmp_path / "three_docs.yaml" - cfg_file.write_text("a: 1\n---\nb: 2\n---\nc: 3\n") + cfg_file.write_text("a: 1\n---\nb: 2\n---\nc: 3\n", encoding="utf-8") with pytest.raises(ValueError, match="expected 1 or 2 YAML documents"): load_config(cfg_file) @@ -1842,7 +1901,8 @@ def test_load_config_list_valued_yaml(tmp_path): " cfg:\n" " num_bits: 8\n" "- quantizer_name: '*input_quantizer'\n" - " enable: false\n" + " enable: false\n", + encoding="utf-8", ) data = load_config(cfg_file) assert isinstance(data, list) @@ -1870,7 +1930,8 @@ def test_import_dict_value_resolves_to_list_raises(tmp_path): ) config_file = tmp_path / "config.yml" config_file.write_text( - f"imports:\n entries: {tmp_path / 'entries.yml'}\nmy_field:\n $import: entries\n" + f"imports:\n entries: {tmp_path / 'entries.yml'}\nmy_field:\n $import: entries\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="must resolve to a dict"): load_config(config_file) @@ -1879,7 +1940,7 @@ def test_import_dict_value_resolves_to_list_raises(tmp_path): def test_import_imports_not_a_dict_raises(tmp_path): """imports section that is a list raises ValueError.""" config_file = tmp_path / "config.yml" - config_file.write_text("imports:\n - some/path\nkey: value\n") + config_file.write_text("imports:\n - some/path\nkey: value\n", encoding="utf-8") with pytest.raises(ValueError, match="must be a dict"): load_config(config_file) @@ -1905,7 +1966,7 @@ def test_import_imports_not_a_dict_raises(tmp_path): def test_load_recipe_autoquantize_minimal(tmp_path): """Minimal AutoQuantize recipe loads with the right type and field defaults.""" recipe_file = tmp_path / "aq.yml" - recipe_file.write_text(_AQ_MINIMAL_BODY) + recipe_file.write_text(_AQ_MINIMAL_BODY, encoding="utf-8") recipe = load_recipe(recipe_file) assert recipe.recipe_type == RecipeType.AUTO_QUANTIZE @@ -1953,7 +2014,8 @@ def test_load_recipe_autoquantize_active_moe_cost_roundtrip(tmp_path): " - algorithm: max\n" " quant_cfg: []\n" " - algorithm: max\n" - " quant_cfg: []\n" + " quant_cfg: []\n", + encoding="utf-8", ) constraints = load_recipe(recipe_file).auto_quantize.constraints assert constraints.cost_model == "active_moe" @@ -1968,7 +2030,7 @@ def test_load_recipe_autoquantize_active_moe_cost_roundtrip(tmp_path): def test_load_recipe_autoquantize_missing_section_raises(tmp_path): """Missing auto_quantize section gives the clean loader-level error.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: auto_quantize\n") + bad.write_text("metadata:\n recipe_type: auto_quantize\n", encoding="utf-8") with pytest.raises( ValueError, match=r"AUTO_QUANTIZE recipe file .* must contain 'auto_quantize'" ): @@ -1981,7 +2043,8 @@ def test_load_recipe_autoquantize_empty_candidates_raises(tmp_path): bad.write_text( "metadata:\n recipe_type: auto_quantize\n" "auto_quantize:\n constraints:\n effective_bits: 4.8\n" - " candidate_formats: []\n" + " candidate_formats: []\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="candidate_formats or at least one"): load_recipe(bad) @@ -1993,7 +2056,8 @@ def test_load_recipe_autoquantize_single_candidate_ok(tmp_path): recipe_file.write_text( "metadata:\n recipe_type: auto_quantize\n" "auto_quantize:\n constraints:\n effective_bits: 6.0\n" - " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" + " candidate_formats:\n - algorithm: max\n quant_cfg: []\n", + encoding="utf-8", ) aq = load_recipe(recipe_file).auto_quantize assert len(aq.candidate_formats) == 1 @@ -2002,7 +2066,9 @@ def test_load_recipe_autoquantize_single_candidate_ok(tmp_path): def test_load_recipe_autoquantize_effective_bits_out_of_range_raises(tmp_path): """effective_bits outside (0, 16] is rejected.""" bad = tmp_path / "bad.yml" - bad.write_text(_AQ_MINIMAL_BODY.replace("effective_bits: 4.8", "effective_bits: 20")) + bad.write_text( + _AQ_MINIMAL_BODY.replace("effective_bits: 4.8", "effective_bits: 20"), encoding="utf-8" + ) with pytest.raises(ValueError, match="effective_bits"): load_recipe(bad) @@ -2052,7 +2118,8 @@ def test_load_recipe_autoquantize_fixed_baseline_rejects_global_fallback(tmp_pat " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" " module_search_spaces:\n" " - module_name_patterns: ['*mlp*']\n" - " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" + " candidate_formats:\n - algorithm: max\n quant_cfg: []\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="must omit top-level"): @@ -2064,7 +2131,8 @@ def test_load_recipe_autoquantize_fixed_baseline_requires_explicit_search(tmp_pa recipe_file.write_text( "metadata:\n recipe_type: auto_quantize\n" "quantize:\n algorithm: max\n quant_cfg: []\n" - "auto_quantize:\n constraints:\n effective_bits: 6.0\n" + "auto_quantize:\n constraints:\n effective_bits: 6.0\n", + encoding="utf-8", ) with pytest.raises(ValueError, match="candidate_formats or at least one"): diff --git a/tests/unit/test_example_run_command.py b/tests/unit/test_example_run_command.py index 09a1f681876..d8496fb55d0 100644 --- a/tests/unit/test_example_run_command.py +++ b/tests/unit/test_example_run_command.py @@ -38,7 +38,7 @@ def test_run_capturing_does_not_block_on_a_survivor_holding_the_pipe( os.environ.copy(), ) - survivor = int(pid_file.read_text()) + survivor = int(pid_file.read_text(encoding="utf-8")) try: assert returncode == -9 assert "out" in output # captured despite the survivor diff --git a/tests/unit/tools/test_resource_monitor.py b/tests/unit/tools/test_resource_monitor.py index 79d9e7848ca..b9e8d806e58 100644 --- a/tests/unit/tools/test_resource_monitor.py +++ b/tests/unit/tools/test_resource_monitor.py @@ -155,7 +155,7 @@ def test_standalone_writes_csv_and_summary(tmp_path): check=True, ) - with open(csv_path, newline="") as f: + with open(csv_path, encoding="utf-8", newline="") as f: rows = list(csv.DictReader(f)) assert rows, "expected at least one sample row" for col in ( @@ -169,7 +169,7 @@ def test_standalone_writes_csv_and_summary(tmp_path): ): assert col in rows[0] - summary = summary_path.read_text() + summary = summary_path.read_text(encoding="utf-8") assert "sys_cpu_total_mb:" in summary assert "peak_sys_cpu_used_mb:" in summary assert "min_sys_cpu_free_mb:" in summary @@ -210,6 +210,6 @@ def test_wrap_mode_propagates_exit_code(tmp_path): assert fail.returncode == 3 # Wrap mode tracks the child tree, so proc_rss is populated. - with open(tmp_path / "a.csv", newline="") as f: + with open(tmp_path / "a.csv", encoding="utf-8", newline="") as f: rows = list(csv.DictReader(f)) assert rows and rows[0]["proc_rss_mb"] != "" diff --git a/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py b/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py index ff7f77cf617..656c145374a 100755 --- a/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py +++ b/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py @@ -44,10 +44,10 @@ def setup_mocks(): (tmp_path / "model.engine").write_bytes(engine_bytes) (tmp_path / f"{dummy_hash}-profile.json").write_text( - json.dumps([{"count": 1}, {"name": "dummy_layer", "averageMs": 0.001}]) + json.dumps([{"count": 1}, {"name": "dummy_layer", "averageMs": 0.001}]), encoding="utf-8" ) (tmp_path / f"{dummy_hash}-layerInfo.json").write_text( - json.dumps({"Layers": [{"Name": "dummy_layer"}]}) + json.dumps({"Layers": [{"Name": "dummy_layer"}]}), encoding="utf-8" ) mock_onnx = mock.Mock() diff --git a/tests/unit/torch/export/test_export_diffusers.py b/tests/unit/torch/export/test_export_diffusers.py index 753c81a4b0e..f553bc9311a 100644 --- a/tests/unit/torch/export/test_export_diffusers.py +++ b/tests/unit/torch/export/test_export_diffusers.py @@ -42,7 +42,7 @@ def _load_config(config_path): - with open(config_path) as file: + with open(config_path, encoding="utf-8") as file: return json.load(file) @@ -63,7 +63,9 @@ def _write_sharded_checkpoint(export_dir, shards): weight_map[key] = filename total_size += tensor.numel() * tensor.element_size() index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} - with open(export_dir / "diffusion_pytorch_model.safetensors.index.json", "w") as file: + with open( + export_dir / "diffusion_pytorch_model.safetensors.index.json", "w", encoding="utf-8" + ) as file: json.dump(index, file) diff --git a/tests/unit/torch/export/test_fsdp2_parallel_export.py b/tests/unit/torch/export/test_fsdp2_parallel_export.py index 58baa7defae..b2406b6a34d 100644 --- a/tests/unit/torch/export/test_fsdp2_parallel_export.py +++ b/tests/unit/torch/export/test_fsdp2_parallel_export.py @@ -59,7 +59,7 @@ def _tiny_quantized_llama(quant_cfg=None, tie=True): def _load_all(export_dir: Path) -> dict: index = export_dir / "model.safetensors.index.json" if index.exists(): - weight_map = json.loads(index.read_text())["weight_map"] + weight_map = json.loads(index.read_text(encoding="utf-8"))["weight_map"] out: dict = {} for fname in set(weight_map.values()): out.update(load_file(str(export_dir / fname))) @@ -127,7 +127,7 @@ def test_streaming_export_subsplits_by_max_shard_size(tmp_path): d = tmp_path _export_fsdp2_checkpoint_streaming(model, torch.bfloat16, export_dir=d, max_shard_size=2048) - index = json.loads((d / "model.safetensors.index.json").read_text()) + index = json.loads((d / "model.safetensors.index.json").read_text(encoding="utf-8")) assert len(set(index["weight_map"].values())) > 1 loaded = _load_all(d) assert set(loaded) == set(index["weight_map"]) diff --git a/tests/unit/torch/export/test_hf_checkpoint_utils.py b/tests/unit/torch/export/test_hf_checkpoint_utils.py index 08292c65f9f..a387a721cd0 100644 --- a/tests/unit/torch/export/test_hf_checkpoint_utils.py +++ b/tests/unit/torch/export/test_hf_checkpoint_utils.py @@ -35,11 +35,11 @@ def test_copy_non_safetensor_files_from_ckpt_supports_additional_exclusions(tmp_path): src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "model.safetensors").write_text("weights") - (src_dir / "model.safetensors.index.json").write_text('{"weight_map": {}}') - (src_dir / "pytorch_model.bin").write_text("weights") - (src_dir / "stats.npy").write_text("stats") - (src_dir / "reasoning_parser.py").write_text("parser") + (src_dir / "model.safetensors").write_text("weights", encoding="utf-8") + (src_dir / "model.safetensors.index.json").write_text('{"weight_map": {}}', encoding="utf-8") + (src_dir / "pytorch_model.bin").write_text("weights", encoding="utf-8") + (src_dir / "stats.npy").write_text("stats", encoding="utf-8") + (src_dir / "reasoning_parser.py").write_text("parser", encoding="utf-8") default_dst = tmp_path / "default" copy_non_safetensor_files_from_ckpt(src_dir, default_dst) @@ -62,8 +62,8 @@ def test_copy_non_safetensor_files_from_ckpt_supports_additional_exclusions(tmp_ def test_copy_non_safetensor_files_from_ckpt_continues_after_copy_failure(tmp_path, monkeypatch): src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "bad.py").write_text("bad") - (src_dir / "good.py").write_text("good") + (src_dir / "bad.py").write_text("bad", encoding="utf-8") + (src_dir / "good.py").write_text("good", encoding="utf-8") original_copy2 = hf_checkpoint_utils.shutil.copy2 @@ -83,19 +83,21 @@ def test_copy_hf_ckpt_remote_code_local_dir(tmp_path): """copy_hf_ckpt_remote_code copies top-level .py files from a local directory.""" src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "modeling_custom.py").write_text("# custom model") - (src_dir / "configuration_custom.py").write_text("# custom config") - (src_dir / "not_python.txt").write_text("not python") + (src_dir / "modeling_custom.py").write_text("# custom model", encoding="utf-8") + (src_dir / "configuration_custom.py").write_text("# custom config", encoding="utf-8") + (src_dir / "not_python.txt").write_text("not python", encoding="utf-8") (src_dir / "subdir").mkdir() - (src_dir / "subdir" / "nested.py").write_text("# nested — should not be copied") + (src_dir / "subdir" / "nested.py").write_text( + "# nested — should not be copied", encoding="utf-8" + ) dst_dir = tmp_path / "dst" dst_dir.mkdir() copy_hf_ckpt_remote_code(src_dir, dst_dir) - assert (dst_dir / "modeling_custom.py").read_text() == "# custom model" - assert (dst_dir / "configuration_custom.py").read_text() == "# custom config" + assert (dst_dir / "modeling_custom.py").read_text(encoding="utf-8") == "# custom model" + assert (dst_dir / "configuration_custom.py").read_text(encoding="utf-8") == "# custom config" assert not (dst_dir / "not_python.txt").exists(), "non-.py files should not be copied" assert not (dst_dir / "nested.py").exists(), "nested .py files should not be copied" @@ -104,7 +106,7 @@ def test_copy_hf_ckpt_remote_code_local_dir_no_py_files(tmp_path): """copy_hf_ckpt_remote_code is a no-op when the local directory has no .py files.""" src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "config.json").write_text("{}") + (src_dir / "config.json").write_text("{}", encoding="utf-8") dst_dir = tmp_path / "dst" dst_dir.mkdir() @@ -119,8 +121,8 @@ def test_copy_hf_ckpt_remote_code_hub_id(tmp_path, monkeypatch): dst_dir = tmp_path / "dst" snapshot_dir = tmp_path / "snapshot" snapshot_dir.mkdir() - (snapshot_dir / "modeling_custom.py").write_text("# custom model") - (snapshot_dir / "not_python.txt").write_text("not python") + (snapshot_dir / "modeling_custom.py").write_text("# custom model", encoding="utf-8") + (snapshot_dir / "not_python.txt").write_text("not python", encoding="utf-8") monkeypatch.delenv("HF_HUB_OFFLINE", raising=False) with patch( @@ -134,7 +136,7 @@ def test_copy_hf_ckpt_remote_code_hub_id(tmp_path, monkeypatch): allow_patterns=["*.py"], local_files_only=False, ) - assert (dst_dir / "modeling_custom.py").read_text() == "# custom model" + assert (dst_dir / "modeling_custom.py").read_text(encoding="utf-8") == "# custom model" assert not (dst_dir / "not_python.txt").exists(), "non-.py files should not be copied" @@ -143,7 +145,7 @@ def test_copy_hf_ckpt_remote_code_hub_id_offline_uses_cache(tmp_path, monkeypatc dst_dir = tmp_path / "dst" snapshot_dir = tmp_path / "snapshot" snapshot_dir.mkdir() - (snapshot_dir / "nemotron_reasoning_parser.py").write_text("# parser") + (snapshot_dir / "nemotron_reasoning_parser.py").write_text("# parser", encoding="utf-8") monkeypatch.setenv("HF_HUB_OFFLINE", "1") with patch( @@ -157,7 +159,7 @@ def test_copy_hf_ckpt_remote_code_hub_id_offline_uses_cache(tmp_path, monkeypatc allow_patterns=["*.py"], local_files_only=True, ) - assert (dst_dir / "nemotron_reasoning_parser.py").read_text() == "# parser" + assert (dst_dir / "nemotron_reasoning_parser.py").read_text(encoding="utf-8") == "# parser" def test_copy_hf_ckpt_remote_code_hub_id_offline_missing_cache_raises(tmp_path, monkeypatch): diff --git a/tests/unit/torch/export/test_mcore_save_safetensors.py b/tests/unit/torch/export/test_mcore_save_safetensors.py index 9f2050c405f..182c18c3efa 100644 --- a/tests/unit/torch/export/test_mcore_save_safetensors.py +++ b/tests/unit/torch/export/test_mcore_save_safetensors.py @@ -43,9 +43,9 @@ def _fake_save_file(tensors, path, metadata=None): ) shard_name = "model-00001-of-00001.safetensors" - with open(tmp_path / "model-00001-of-00001.json") as f: + with open(tmp_path / "model-00001-of-00001.json", encoding="utf-8") as f: shard_meta = json.load(f) - with open(tmp_path / "model.safetensors.index.json") as f: + with open(tmp_path / "model.safetensors.index.json", encoding="utf-8") as f: index_meta = json.load(f) json_keys = set(shard_meta["weight_map"].keys()) diff --git a/tests/unit/torch/export/test_nvfp4_utils.py b/tests/unit/torch/export/test_nvfp4_utils.py index 7aed23f0b7a..ea46e8d7737 100644 --- a/tests/unit/torch/export/test_nvfp4_utils.py +++ b/tests/unit/torch/export/test_nvfp4_utils.py @@ -185,7 +185,7 @@ def test_padding_and_swizzle(self, tmp_path): def test_sharded_guard(self, tmp_path): save_file({"w": torch.randn(2, 2)}, str(tmp_path / "model.safetensors")) - (tmp_path / "model.safetensors.index.json").write_text("{}") + (tmp_path / "model.safetensors.index.json").write_text("{}", encoding="utf-8") with pytest.raises(NotImplementedError, match="sharded"): _postprocess_safetensors( diff --git a/tests/unit/torch/export/test_offload_export.py b/tests/unit/torch/export/test_offload_export.py index 09a4d353784..9b3ef3c697a 100644 --- a/tests/unit/torch/export/test_offload_export.py +++ b/tests/unit/torch/export/test_offload_export.py @@ -212,7 +212,7 @@ def test_streaming_shard_writer_multi_shard(): assert index_path.exists(), "model.safetensors.index.json not written" assert weight_map["x"] != weight_map["y"], "keys must be in different shards" - with open(index_path) as f: + with open(index_path, encoding="utf-8") as f: index = json.load(f) assert index["metadata"]["total_size"] > 0 @@ -322,7 +322,9 @@ def test_name_shards_and_write_index_merges_disjoint_writers(): weight_map = name_shards_and_write_index(tmpdir, closed) - index = json.loads((Path(tmpdir) / "model.safetensors.index.json").read_text()) + index = json.loads( + (Path(tmpdir) / "model.safetensors.index.json").read_text(encoding="utf-8") + ) assert set(index["weight_map"]) == set(ref) assert weight_map == index["weight_map"] assert not list(Path(tmpdir).glob("__shard_part*")), "every part should be renamed" diff --git a/tests/unit/torch/export/test_shard_cast_utils.py b/tests/unit/torch/export/test_shard_cast_utils.py index b93da516052..a0fd0e4b591 100644 --- a/tests/unit/torch/export/test_shard_cast_utils.py +++ b/tests/unit/torch/export/test_shard_cast_utils.py @@ -89,10 +89,10 @@ def test_link_aux_files_preserves_sidecars_and_applies_skips(tmp_path): output = tmp_path / "output" (source / "assets").mkdir(parents=True) (source / ".cache").mkdir() - (source / "tokenizer_config.json").write_text("{}") + (source / "tokenizer_config.json").write_text("{}", encoding="utf-8") (source / "model.safetensors").write_bytes(b"shard") - (source / "assets" / "config.txt").write_text("keep") - (source / ".cache" / "stale.json").write_text("{}") + (source / "assets" / "config.txt").write_text("keep", encoding="utf-8") + (source / ".cache" / "stale.json").write_text("{}", encoding="utf-8") link_aux_files( source, @@ -102,8 +102,8 @@ def test_link_aux_files_preserves_sidecars_and_applies_skips(tmp_path): skip_file=lambda path: path.suffix == ".safetensors", ) - assert (output / "tokenizer_config.json").read_text() == "{}" - assert (output / "assets" / "config.txt").read_text() == "keep" + assert (output / "tokenizer_config.json").read_text(encoding="utf-8") == "{}" + assert (output / "assets" / "config.txt").read_text(encoding="utf-8") == "keep" assert not (output / "model.safetensors").exists() assert not (output / ".cache").exists() @@ -115,7 +115,7 @@ def test_link_aux_files_accepts_huggingface_snapshot_blobs(tmp_path): blob = repository / "blobs" / "tokenizer-blob" snapshot.mkdir(parents=True) blob.parent.mkdir() - blob.write_text("tokenizer") + blob.write_text("tokenizer", encoding="utf-8") (snapshot / "tokenizer.json").symlink_to(os.path.relpath(blob, snapshot)) assert resolve_checkpoint_file(snapshot, "tokenizer.json") == blob.resolve() @@ -123,7 +123,7 @@ def test_link_aux_files_accepts_huggingface_snapshot_blobs(tmp_path): output = tmp_path / "output" link_aux_files(snapshot, output) - assert (output / "tokenizer.json").read_text() == "tokenizer" + assert (output / "tokenizer.json").read_text(encoding="utf-8") == "tokenizer" assert not (output / "tokenizer.json").is_symlink() @@ -138,7 +138,7 @@ def test_link_aux_files_rejects_unsafe_sources(tmp_path, source_kind, message): unsafe = source / "unsafe" if source_kind == "symlink": outside = tmp_path / "outside" - outside.write_text("secret") + outside.write_text("secret", encoding="utf-8") unsafe.symlink_to(outside) else: os.mkfifo(unsafe) @@ -154,7 +154,7 @@ def test_link_aux_files_rejects_unsafe_sources(tmp_path, source_kind, message): def test_resolve_checkpoint_file_rejects_oversized_metadata(tmp_path): source = tmp_path / "source" source.mkdir() - (source / "config.json").write_text("12345") + (source / "config.json").write_text("12345", encoding="utf-8") with pytest.raises(ValueError, match="4-byte size limit"): resolve_checkpoint_file(source, "config.json", max_bytes=4) diff --git a/tests/unit/torch/opt/plugins/test_lr_config.py b/tests/unit/torch/opt/plugins/test_lr_config.py index a3506e2f034..09d3ce097f5 100644 --- a/tests/unit/torch/opt/plugins/test_lr_config.py +++ b/tests/unit/torch/opt/plugins/test_lr_config.py @@ -58,7 +58,7 @@ def dummy_dataset(): def _write_lr_config(tmp_path, cfg: dict) -> str: path = tmp_path / "lr_config.yaml" - path.write_text(yaml.dump(cfg)) + path.write_text(yaml.dump(cfg), encoding="utf-8") return str(path) @@ -114,13 +114,13 @@ def test_load_with_weight_decay_and_betas(self, tmp_path): def test_load_invalid_not_dict(self, tmp_path): path = tmp_path / "lr_config.yaml" - path.write_text("- item1\n- item2\n") + path.write_text("- item1\n- item2\n", encoding="utf-8") with pytest.raises(ValueError, match="YAML mapping"): ModelOptHFTrainer.load_lr_config(str(path)) def test_load_invalid_entry(self, tmp_path): path = tmp_path / "lr_config.yaml" - path.write_text('"*lm_head*": 0.001\n') + path.write_text('"*lm_head*": 0.001\n', encoding="utf-8") with pytest.raises(ValueError, match="str -> dict"): ModelOptHFTrainer.load_lr_config(str(path)) diff --git a/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py b/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py index 78a89a27ff9..a1189b0da47 100644 --- a/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py +++ b/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py @@ -49,7 +49,7 @@ def test_cli_args_only(self): def test_yaml_config(self, tmp_path): config_file = tmp_path / "config.yaml" - config_file.write_text("model_name: yaml-model\nepochs: 10\n") + config_file.write_text("model_name: yaml-model\nepochs: 10\n", encoding="utf-8") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -60,7 +60,7 @@ def test_yaml_config(self, tmp_path): def test_cli_overrides_yaml(self, tmp_path): config_file = tmp_path / "config.yaml" - config_file.write_text("model_name: yaml-model\nlearning_rate: 0.001\n") + config_file.write_text("model_name: yaml-model\nlearning_rate: 0.001\n", encoding="utf-8") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -71,7 +71,7 @@ def test_cli_overrides_yaml(self, tmp_path): def test_empty_yaml_config(self, tmp_path): config_file = tmp_path / "empty.yaml" - config_file.write_text("") + config_file.write_text("", encoding="utf-8") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -88,7 +88,7 @@ def test_generate_docs(self, tmp_path): parser.parse_args_into_dataclasses(args=["--generate_docs", str(output_path)]) assert exc_info.value.code == 0 - content = output_path.read_text() + content = output_path.read_text(encoding="utf-8") assert "## _ModelArgs" in content assert "## _TrainArgs" in content assert "--model_name" in content @@ -103,7 +103,7 @@ def test_generate_docs_default_path(self, tmp_path, monkeypatch): parser.parse_args_into_dataclasses(args=["--generate_docs"]) assert exc_info.value.code == 0 - content = Path("ARGUMENTS.md").read_text() + content = Path("ARGUMENTS.md").read_text(encoding="utf-8") assert "# Argument Reference" in content def test_docs_table_format(self, tmp_path): @@ -113,7 +113,7 @@ def test_docs_table_format(self, tmp_path): with pytest.raises(SystemExit): parser.parse_args_into_dataclasses(args=["--generate_docs", str(output_path)]) - content = output_path.read_text() + content = output_path.read_text(encoding="utf-8") # Check table headers assert "| Argument | Type | Default | Description |" in content # Check a specific row diff --git a/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py b/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py index 2a3712901a1..9e216435c61 100644 --- a/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py +++ b/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py @@ -54,8 +54,8 @@ def test_copy_auto_map_code_files_copies_valid_local_code_references(tmp_path, m checkpoint_dir = tmp_path / "checkpoint" source_dir.mkdir() checkpoint_dir.mkdir() - (source_dir / "modeling_custom.py").write_text("# modeling\n") - (source_dir / "tokenization_custom.py").write_text("# tokenizer\n") + (source_dir / "modeling_custom.py").write_text("# modeling\n", encoding="utf-8") + (source_dir / "tokenization_custom.py").write_text("# tokenizer\n", encoding="utf-8") monkeypatch.setattr(cuhf.inspect, "getfile", lambda _cls: source_dir / "configuration.py") diff --git a/tests/unit/torch/quantization/test_layerwise_calibrate.py b/tests/unit/torch/quantization/test_layerwise_calibrate.py index 07e14d50f2a..56c686e96df 100644 --- a/tests/unit/torch/quantization/test_layerwise_calibrate.py +++ b/tests/unit/torch/quantization/test_layerwise_calibrate.py @@ -1239,7 +1239,8 @@ def build_cfg(ckpt_dir): "save_every": save_every, "calib_mutates_weights": calib_mutates_weights, } - ) + ), + encoding="utf-8", ) torch.manual_seed(0) @@ -1290,7 +1291,7 @@ def crashing_torch_save(obj, path, *args, **kwargs): with pytest.raises(RuntimeError, match="simulated crash"): mtq.quantize(model, cfg, forward_loop=lambda m: [m(b) for b in calib_data]) - manifest = json.loads((tmp_path / "manifest.json").read_text()) + manifest = json.loads((tmp_path / "manifest.json").read_text(encoding="utf-8")) assert manifest["last_completed_layer"] == 1, f"manifest leaked mid-window state: {manifest}" @@ -1325,7 +1326,8 @@ def test_layerwise_checkpoint_mismatch_save_every_raises(monkeypatch, tmp_path): "save_every": 2, "calib_mutates_weights": True, } - ) + ), + encoding="utf-8", ) cfg_mismatched = _int8_cfg_with_algorithm( { diff --git a/tests/unit/torch/quantization/test_sequential_checkpoint.py b/tests/unit/torch/quantization/test_sequential_checkpoint.py index 0e592a68c75..2252e8034ff 100644 --- a/tests/unit/torch/quantization/test_sequential_checkpoint.py +++ b/tests/unit/torch/quantization/test_sequential_checkpoint.py @@ -87,7 +87,7 @@ def test_full_run_creates_checkpoints(monkeypatch, tmp_path): manifest_path = os.path.join(ckpt_dir, "manifest.json") assert os.path.isfile(manifest_path) - with open(manifest_path) as f: + with open(manifest_path, encoding="utf-8") as f: manifest = json.load(f) assert manifest["last_completed_layer"] == 2 assert manifest["num_layers"] == 3 @@ -116,7 +116,7 @@ def test_resume_matches_full_run(monkeypatch, tmp_path): # Simulate crash after layer 0: truncate manifest manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w") as f: + with open(manifest_path, "w", encoding="utf-8") as f: json.dump({"last_completed_layer": 0, "num_layers": 3}, f) # Resume from a fresh model diff --git a/tests/unit/torch/speculative/plugins/test_fakebase.py b/tests/unit/torch/speculative/plugins/test_fakebase.py index cf6dfe1a6bc..7425237dc16 100644 --- a/tests/unit/torch/speculative/plugins/test_fakebase.py +++ b/tests/unit/torch/speculative/plugins/test_fakebase.py @@ -57,7 +57,7 @@ def fake_checkpoint(tmp_path, fake_config): shard = tmp_path / "model-00001-of-00001.safetensors" safetensors.torch.save_file(tensors, shard) index = {"weight_map": dict.fromkeys(tensors, shard.name)} - (tmp_path / "model.safetensors.index.json").write_text(json.dumps(index)) + (tmp_path / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") return tmp_path diff --git a/tests/unit/torch/speculative/plugins/test_hf_dflash.py b/tests/unit/torch/speculative/plugins/test_hf_dflash.py index 104a16b16a1..e3fab238bbc 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_dflash.py +++ b/tests/unit/torch/speculative/plugins/test_hf_dflash.py @@ -791,7 +791,7 @@ def test_export_config_fields(self, tmp_path): export_dir = tmp_path / "exported" exporter.export(export_dir) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] @@ -819,7 +819,7 @@ def test_export_swa_fields(self, tmp_path): export_dir = tmp_path / "exported" exporter.export(export_dir) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) # vLLM _resolve_layer_attention reads these; all-full layer_types + use_swa=True diff --git a/tests/unit/torch/speculative/plugins/test_hf_domino.py b/tests/unit/torch/speculative/plugins/test_hf_domino.py index 0030abe6f33..b02c71c6050 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_domino.py +++ b/tests/unit/torch/speculative/plugins/test_hf_domino.py @@ -227,7 +227,7 @@ def test_export_weight_keys_match_reference(self, tmp_path): def test_export_config_has_domino_fields(self, tmp_path): """config.json carries the dflash_config domino fields + top-level emb_dim.""" export_dir = self._export(tmp_path) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] diff --git a/tests/unit/torch/speculative/plugins/test_hf_dspark.py b/tests/unit/torch/speculative/plugins/test_hf_dspark.py index 3686788ad90..9a4c589a010 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_dspark.py +++ b/tests/unit/torch/speculative/plugins/test_hf_dspark.py @@ -306,7 +306,7 @@ def test_export_includes_confidence_weights(self, tmp_path): def test_export_config_has_dspark_fields(self, tmp_path): """config.json carries the dflash_config DSpark head fields.""" export_dir = self._export(tmp_path, head_type="gated") - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] @@ -410,7 +410,7 @@ def test_export_records_draft_attention(self, tmp_path): model = self._make_model(mode) export_dir = tmp_path / f"exp_{mode}" model.get_exporter().export(export_dir) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["dflash_config"]["causal"] is expected @@ -514,7 +514,7 @@ def test_export_includes_sink_weights_and_flag(self, tmp_path): assert key in sd, f"missing {key}" assert sd[key].shape == (heads,) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["dflash_config"]["attention_sink_bias"] is True assert cfg["attention_sink_bias"] is True @@ -525,7 +525,7 @@ def test_export_omits_sink_when_disabled(self, tmp_path): model.get_exporter().export(export_dir) sd = load_file(str(export_dir / "model.safetensors")) assert not any("attention_sink_bias" in k for k in sd) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert "attention_sink_bias" not in cfg["dflash_config"] @@ -767,6 +767,6 @@ def test_negative_id_raises(self): def test_export_round_trips_explicit_ids(self, tmp_path): model = self._make_model(target_layer_ids=[0, 7]) model.get_exporter().export(tmp_path / "exp") - with open(tmp_path / "exp" / "config.json") as f: + with open(tmp_path / "exp" / "config.json", encoding="utf-8") as f: cfg = json.load(f) assert cfg["dflash_config"]["target_layer_ids"] == [0, 7] diff --git a/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py b/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py index f9bdbbca355..a4491782b14 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py +++ b/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py @@ -479,7 +479,7 @@ def test_export_config_declares_the_lilicorr_architecture(self, tmp_path): A checkpoint that declares DFlashDraftModel loads as plain DFlash and silently ignores the head, which reads as a small acceptance delta rather than an error. """ - with open(self._export(tmp_path) / "config.json") as f: + with open(self._export(tmp_path) / "config.json", encoding="utf-8") as f: config = json.load(f) assert config["architectures"] == ["LiLiCorrDraftModel"] @@ -502,7 +502,7 @@ def test_exported_geometry_matches_the_exported_weights(self, tmp_path): """Geometry is read off the built head, so it cannot drift from the tensors.""" export_dir = self._export(tmp_path) state_dict = load_file(str(export_dir / "model.safetensors")) - with open(export_dir / "config.json") as f: + with open(export_dir / "config.json", encoding="utf-8") as f: dflash_config = json.load(f)["dflash_config"] out_head = state_dict["lilicorr.out_head.weight"] assert out_head.shape == ( diff --git a/tests/unit/torch/utils/test_mlflow.py b/tests/unit/torch/utils/test_mlflow.py index 49a9d23351e..4fe63a7a051 100644 --- a/tests/unit/torch/utils/test_mlflow.py +++ b/tests/unit/torch/utils/test_mlflow.py @@ -85,7 +85,7 @@ def log_text(self, text, artifact_file): def log_artifact(self, local_path, artifact_path=None): self.artifacts.append((Path(local_path).name, artifact_path)) - self.artifact_text[Path(local_path).name] = Path(local_path).read_text() + self.artifact_text[Path(local_path).name] = Path(local_path).read_text(encoding="utf-8") def log_metrics(self, metrics): self.metrics.update(metrics) @@ -248,7 +248,9 @@ def test_logger_is_inert_when_disabled(monkeypatch): def test_logger_logs_inputs_and_outputs(fake_mlflow, tmp_path, monkeypatch): monkeypatch.setattr(sys, "argv", ["hf_ptq.py", "--pyt_ckpt_path", "/models/Qwen3-0.6B"]) - (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers found in model\n") + (tmp_path / ".quant_summary.txt").write_text( + "706 TensorQuantizers found in model\n", encoding="utf-8" + ) logger = _logger(run_name="unit-test") logger.start( @@ -421,7 +423,7 @@ def test_capture_includes_preconfigured_library_logging(fake_mlflow, monkeypatch logger.start() log_path = logger._log_path library_logger.warning("Rate limited. Waiting 169.0s before retry") - captured = log_path.read_text() + captured = log_path.read_text(encoding="utf-8") logger.finish("FINISHED") finally: library_logger.removeHandler(handler) @@ -599,19 +601,19 @@ def test_git_sha_resolves_in_a_checkout_and_a_worktree(tmp_path, monkeypatch, in main checkout -- a directory-only reader silently reports "unknown" for every worktree.""" main = tmp_path / "repo" / ".git" (main / "refs" / "heads").mkdir(parents=True) - (main / "refs" / "heads" / "main").write_text("a" * 40 + "\n") + (main / "refs" / "heads" / "main").write_text("a" * 40 + "\n", encoding="utf-8") if in_worktree: checkout = tmp_path / "wt" wt_git = main / "worktrees" / "wt" wt_git.mkdir(parents=True) - (wt_git / "HEAD").write_text("ref: refs/heads/main\n") - (wt_git / "commondir").write_text("../..\n") + (wt_git / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") + (wt_git / "commondir").write_text("../..\n", encoding="utf-8") checkout.mkdir() - (checkout / ".git").write_text(f"gitdir: {wt_git}\n") + (checkout / ".git").write_text(f"gitdir: {wt_git}\n", encoding="utf-8") else: checkout = main.parent - (main / "HEAD").write_text("ref: refs/heads/main\n") + (main / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") # _git_sha locates .git relative to the module file, three parents up. fake_module = checkout / "modelopt" / "torch" / "utils" / "mlflow.py" @@ -625,7 +627,7 @@ def test_git_sha_resolves_in_a_checkout_and_a_worktree(tmp_path, monkeypatch, in def test_git_sha_handles_a_detached_head(tmp_path, monkeypatch): git_dir = tmp_path / "repo" / ".git" git_dir.mkdir(parents=True) - (git_dir / "HEAD").write_text("b" * 40 + "\n") + (git_dir / "HEAD").write_text("b" * 40 + "\n", encoding="utf-8") fake_module = tmp_path / "repo" / "modelopt" / "torch" / "utils" / "mlflow.py" fake_module.parent.mkdir(parents=True) fake_module.touch() @@ -655,7 +657,7 @@ def test_track_marks_a_raising_block_failed(fake_mlflow, monkeypatch, tmp_path): _logger().track(files=summary), ): # post_quantize writes the summary during the run, so the test must too. - (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers\n") + (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers\n", encoding="utf-8") raise RuntimeError("calibration exploded") assert fake_mlflow.status == "FAILED" @@ -699,13 +701,15 @@ def test_only_files_this_run_produced_are_uploaded(fake_mlflow, tmp_path, monkey summary must not upload the previous run's file as though it were its own.""" monkeypatch.setattr(sys, "argv", ["hf_ptq.py"]) stale = tmp_path / ".quant_summary.txt" - stale.write_text("from a previous run\n") + stale.write_text("from a previous run\n", encoding="utf-8") fresh = tmp_path / ".moe.html" logger = _logger() outputs = {"summary/quant_summary.txt": stale, "summary/moe.html": fresh} logger.start(files=outputs) - fresh.write_text("written by this run") # produced during the run + fresh.write_text( + "written by this run", encoding="utf-8" + ) # produced during the run logger.finish("FAILED", files=outputs) uploaded = [name for name, _ in fake_mlflow.artifacts] @@ -720,7 +724,7 @@ def test_stale_check_survives_unnormalized_string_paths(fake_mlflow, tmp_path, m monkeypatch.chdir(tmp_path) (tmp_path / "out").mkdir() stale = tmp_path / "out" / ".quant_summary.txt" - stale.write_text("from a previous run\n") + stale.write_text("from a previous run\n", encoding="utf-8") outputs = {"summary/quant_summary.txt": "./out/.quant_summary.txt"} logger = _logger() diff --git a/tests/unit/torch/utils/test_model_load_utils.py b/tests/unit/torch/utils/test_model_load_utils.py index 323fb92a568..7a1e8b96fbe 100644 --- a/tests/unit/torch/utils/test_model_load_utils.py +++ b/tests/unit/torch/utils/test_model_load_utils.py @@ -39,7 +39,8 @@ def test_weight_map_for_sharded(tmp_path): (tmp_path / "model.safetensors.index.json").write_text( json.dumps( {"weight_map": {"a.weight": "shard1.safetensors", "b.weight": "shard2.safetensors"}} - ) + ), + encoding="utf-8", ) assert weight_map_for(str(tmp_path)) == { diff --git a/tools/launcher/common/check_regression.py b/tools/launcher/common/check_regression.py index 17ca7980f59..c3c685cddb8 100644 --- a/tools/launcher/common/check_regression.py +++ b/tools/launcher/common/check_regression.py @@ -56,7 +56,7 @@ def find_trainer_state(output_dir): def get_final_metrics(trainer_state_path): """Extract final loss and accuracy from trainer_state.json.""" - with open(trainer_state_path) as f: + with open(trainer_state_path, encoding="utf-8") as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] diff --git a/tools/launcher/common/query.py b/tools/launcher/common/query.py index 27c41953d8a..5fa744a3cbd 100644 --- a/tools/launcher/common/query.py +++ b/tools/launcher/common/query.py @@ -254,7 +254,7 @@ def synthesize(data): shard = shard.map(disable_thinking_column, num_proc=num_proc) updated_shard = shard.map(synthesize, num_proc=num_proc) updated_shard.to_json(file_path) - with open(done_path, "w") as done_file: + with open(done_path, "w", encoding="utf-8") as done_file: done_file.write("done\n") print(updated_shard[0]) diff --git a/tools/launcher/core.py b/tools/launcher/core.py index 44f3efab86d..8ab56409742 100644 --- a/tools/launcher/core.py +++ b/tools/launcher/core.py @@ -163,7 +163,7 @@ def create_task_from_yaml(yaml_file, factory_lookup): yaml_file: Path to the YAML config. factory_lookup: Dict mapping factory names to callable factory functions. """ - with open(yaml_file) as file: + with open(yaml_file, encoding="utf-8") as file: config_from_yaml = yaml.safe_load(file) script = config_from_yaml["script"] @@ -191,7 +191,7 @@ def _explicit_slurm_fields_from_yaml(yaml_path: str | None, task_name: str) -> s if not yaml_path: return None try: - with open(yaml_path) as file: + with open(yaml_path, encoding="utf-8") as file: config = yaml.safe_load(file) or {} except (OSError, yaml.YAMLError): return None @@ -895,5 +895,5 @@ def run_jobs( } metadata_path = os.path.join("experiments", experiment_title, exp._id, "metadata.json") os.makedirs(os.path.dirname(metadata_path), exist_ok=True) - with open(metadata_path, "w") as f: + with open(metadata_path, "w", encoding="utf-8") as f: json.dump(metadata, f) diff --git a/tools/launcher/tests/conftest.py b/tools/launcher/tests/conftest.py index 1886f9bf9cd..834efcf4295 100644 --- a/tools/launcher/tests/conftest.py +++ b/tools/launcher/tests/conftest.py @@ -43,7 +43,7 @@ def tmp_yaml(tmp_path): def _write(content, name="test.yaml"): p = tmp_path / name - p.write_text(content) + p.write_text(content, encoding="utf-8") return str(p) return _write diff --git a/tools/launcher/tests/test_docker_execution.py b/tools/launcher/tests/test_docker_execution.py index 7b7b92850eb..beadc8d0f67 100644 --- a/tools/launcher/tests/test_docker_execution.py +++ b/tools/launcher/tests/test_docker_execution.py @@ -289,7 +289,7 @@ def test_metadata_written(self, mock_docker, mock_exp, tmp_path): metadata_path = os.path.join("experiments", "cicd", "test_exp_meta", "metadata.json") assert os.path.exists(metadata_path) - with open(metadata_path) as f: + with open(metadata_path, encoding="utf-8") as f: meta = json.load(f) assert meta["experiment_id"] == "test_exp_meta" assert meta["job_name"] == "meta_job" diff --git a/tools/launcher/tests/test_docker_launch.py b/tools/launcher/tests/test_docker_launch.py index 625d28b0822..c00b1015dad 100644 --- a/tools/launcher/tests/test_docker_launch.py +++ b/tools/launcher/tests/test_docker_launch.py @@ -40,7 +40,7 @@ def test_echo_script_via_launch(self, tmp_path): script_dir = tmp_path / "scripts" script_dir.mkdir() script = script_dir / "hello.sh" - script.write_text("#!/bin/bash\necho 'HELLO_FROM_DOCKER'\n") + script.write_text("#!/bin/bash\necho 'HELLO_FROM_DOCKER'\n", encoding="utf-8") script.chmod(0o755) # Create a YAML config @@ -54,7 +54,7 @@ def test_echo_script_via_launch(self, tmp_path): container: python:3.12-slim """ yaml_path = tmp_path / "test.yaml" - yaml_path.write_text(yaml_content) + yaml_path.write_text(yaml_content, encoding="utf-8") # Run launch.py as a subprocess (avoids pytest stdin capture issues) launcher_dir = os.path.join(os.path.dirname(__file__), "..") @@ -85,7 +85,7 @@ def test_failing_script_via_launch(self, tmp_path): script_dir = tmp_path / "scripts" script_dir.mkdir() script = script_dir / "fail.sh" - script.write_text("#!/bin/bash\necho 'FAILING'\nexit 1\n") + script.write_text("#!/bin/bash\necho 'FAILING'\nexit 1\n", encoding="utf-8") script.chmod(0o755) yaml_content = """ @@ -98,7 +98,7 @@ def test_failing_script_via_launch(self, tmp_path): container: python:3.12-slim """ yaml_path = tmp_path / "fail_test.yaml" - yaml_path.write_text(yaml_content) + yaml_path.write_text(yaml_content, encoding="utf-8") launcher_dir = os.path.join(os.path.dirname(__file__), "..") launcher_dir = os.path.abspath(launcher_dir) diff --git a/tools/launcher/tests/test_examples_resolve.py b/tools/launcher/tests/test_examples_resolve.py index 5d0bc7c2138..e3b38564348 100644 --- a/tools/launcher/tests/test_examples_resolve.py +++ b/tools/launcher/tests/test_examples_resolve.py @@ -70,7 +70,7 @@ def test_examples_present(): ) def test_example_yaml_valid(path): """Each example parses and every task has a valid script/factory/args shape.""" - with open(path) as f: + with open(path, encoding="utf-8") as f: cfg = yaml.safe_load(f) assert isinstance(cfg, (dict, list)), f"{path}: top-level YAML is not a mapping/list" diff --git a/tools/launcher/tests/test_yaml_formats.py b/tools/launcher/tests/test_yaml_formats.py index 9ba09550bb2..d373430519a 100644 --- a/tools/launcher/tests/test_yaml_formats.py +++ b/tools/launcher/tests/test_yaml_formats.py @@ -53,7 +53,7 @@ def test_yaml_format_with_job_name(self, tmp_yaml): - KEY: value """ path = tmp_yaml(content) - with open(path) as f: + with open(path, encoding="utf-8") as f: data = yaml.safe_load(f) assert data["job_name"] == "test_job" @@ -77,7 +77,7 @@ def test_bare_pipeline_format(self, tmp_yaml): skip: false """ path = tmp_yaml(content) - with open(path) as f: + with open(path, encoding="utf-8") as f: data = yaml.safe_load(f) # Verify the YAML parses into valid SandboxPipeline kwargs @@ -186,7 +186,7 @@ def test_target_with_overrides(self, tmp_yaml): allow_to_fail: false """ path = tmp_yaml(content) - with open(path) as f: + with open(path, encoding="utf-8") as f: data = yaml.safe_load(f) assert isinstance(data, list) diff --git a/tools/mcp/modelopt_mcp/bridge.py b/tools/mcp/modelopt_mcp/bridge.py index 5ac251e83b1..8c84e843588 100644 --- a/tools/mcp/modelopt_mcp/bridge.py +++ b/tools/mcp/modelopt_mcp/bridge.py @@ -186,7 +186,7 @@ def _tail_docker_launch_log(log_path: Path, proc: subprocess.Popen) -> tuple[str text = "" while True: try: - text = log_path.read_text(errors="replace") + text = log_path.read_text(encoding="utf-8", errors="replace") except OSError: text = "" complete_text = text if text.endswith(("\n", "\r")) else text.rsplit("\n", 1)[0] @@ -635,7 +635,7 @@ def list_examples_impl() -> dict: # the path-derived defaults when present. Don't crash on a # malformed YAML. try: - with open(path) as f: + with open(path, encoding="utf-8") as f: doc = yaml.safe_load(f) or {} if isinstance(doc, dict): body_model = doc.get("model") or doc.get("base_model") or doc.get("job_name") diff --git a/tools/mcp/tests/test_bridge.py b/tools/mcp/tests/test_bridge.py index 69d57822119..46fda64d19e 100644 --- a/tools/mcp/tests/test_bridge.py +++ b/tools/mcp/tests/test_bridge.py @@ -46,11 +46,11 @@ def test_list_examples_returns_structured_metadata(tmp_path, monkeypatch): examples = tmp_path / "examples" (examples / "Qwen").mkdir(parents=True) (examples / "Qwen" / "ptq.yaml").write_text( - "job_name: qwen-ptq\nmodel: Qwen/Qwen3-8B\ndescription: PTQ test\n" + "job_name: qwen-ptq\nmodel: Qwen/Qwen3-8B\ndescription: PTQ test\n", encoding="utf-8" ) (examples / "moonshotai").mkdir(parents=True) (examples / "moonshotai" / "train.yaml").write_text( - "job_name: kimi-train\nbase_model: moonshotai/Kimi-K2\n" + "job_name: kimi-train\nbase_model: moonshotai/Kimi-K2\n", encoding="utf-8" ) monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(examples)) @@ -75,8 +75,8 @@ def test_list_examples_tolerates_malformed_yaml(tmp_path, monkeypatch): """A single malformed YAML doesn't crash list_examples — it lands with model=None.""" examples = tmp_path / "examples" examples.mkdir() - (examples / "good.yaml").write_text("job_name: g\nmodel: ok\n") - (examples / "bad.yaml").write_text("not: [unbalanced\n") + (examples / "good.yaml").write_text("job_name: g\nmodel: ok\n", encoding="utf-8") + (examples / "bad.yaml").write_text("not: [unbalanced\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(examples)) result = bridge.list_examples_impl() @@ -397,7 +397,7 @@ def test_submit_job_dry_run_uses_managed_source_checkout(monkeypatch, tmp_path): yaml_dir = checkout_root / "tools" / "launcher" / "examples" / "fam" / "model" yaml_dir.mkdir(parents=True) yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") checkout = bridge.SourceCheckout( repo="https://example.com/modelopt.git", ref="feature/ref", @@ -489,7 +489,7 @@ def test_submit_job_docker_captures_experiment_id_from_launcher_output(monkeypat yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setattr(bridge, "verify_docker_setup_impl", lambda: {"ok": True}) @@ -536,7 +536,7 @@ def test_submit_job_docker_no_experiment_id_returns_pid_and_log(monkeypatch, tmp yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setenv("MODELOPT_MCP_DOCKER_ID_TIMEOUT_SEC", "0") @@ -590,7 +590,7 @@ def test_submit_job_docker_log_creation_failure_is_structured(monkeypatch, tmp_p yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setattr(bridge, "verify_docker_setup_impl", lambda: {"ok": True}) @@ -623,7 +623,7 @@ def test_submit_job_slurm_zero_exit_without_ids_is_failure(monkeypatch, tmp_path yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -661,7 +661,7 @@ def test_submit_job_slurm_parses_nemo_job_id(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) exp_dir = tmp_path / "experiments" / "cicd" / "cicd_1782173197" @@ -700,7 +700,7 @@ def fake_run(argv, **kwargs): assert result["ok"] is True assert result["slurm_job_id"] == "13049989" assert result["experiment_id"] == "cicd_1782173197" - meta = json.loads((exp_dir / bridge._SLURM_STATUS_META).read_text()) + meta = json.loads((exp_dir / bridge._SLURM_STATUS_META).read_text(encoding="utf-8")) assert meta["slurm_job_id"] == "13049989" assert meta["cluster_host"] == "cluster.example.com" assert meta["cluster_user"] == "user" @@ -710,7 +710,7 @@ def test_submit_job_slurm_accepts_nmm_cluster_fields(monkeypatch, tmp_path): """nmm-sandbox resolved cluster config maps to launcher overrides and env.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n") + (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) verify_seen = {} @@ -769,7 +769,7 @@ def test_submit_job_slurm_job_id_without_experiment_id_is_failure(monkeypatch, t yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -808,7 +808,7 @@ def test_submit_job_slurm_zero_exit_with_launcher_error_is_failure(monkeypatch, yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -852,7 +852,7 @@ def test_submit_job_dry_run_yaml_validates(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" / "fam" / "model" yaml_dir.mkdir(parents=True) yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(tmp_path / "examples")) captured = {} @@ -892,7 +892,7 @@ def test_submit_job_dry_run_uses_slurm_inventory_fields(monkeypatch, tmp_path): """dry-run must mirror live submit Slurm overrides and env.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n") + (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) captured = {} @@ -940,7 +940,7 @@ def test_submit_job_dry_run_yaml_invalid(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "bad.yaml" - yaml_path.write_text("not: [unbalanced\n") + yaml_path.write_text("not: [unbalanced\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) def fake_run(argv, **kwargs): @@ -977,7 +977,7 @@ def test_submit_job_dry_run_zero_exit_with_launcher_error_is_invalid(monkeypatch yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "bad.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n") + yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) def fake_run(argv, **kwargs): @@ -1033,7 +1033,7 @@ def test_submit_job_dry_run_skips_verify(monkeypatch, tmp_path): """dry_run=True bypasses verify_setup even when skip_verify=False.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "ok.yaml").write_text("job_name: ok\npipeline: []\n") + (yaml_dir / "ok.yaml").write_text("job_name: ok\npipeline: []\n", encoding="utf-8") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) verify_called = {"n": 0} @@ -1082,8 +1082,8 @@ def test_job_status_done_success(tmp_path, monkeypatch): exp = tmp_path / "experiments" / "exp_1781000000" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n") - (exp / "status_task_1.out").write_text("succeeded\n") + (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") + (exp / "status_task_1.out").write_text("succeeded\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000000") @@ -1097,8 +1097,8 @@ def test_job_status_failed_task(tmp_path, monkeypatch): exp = tmp_path / "experiments" / "exp_1781000001" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n") - (exp / "status_task_1.out").write_text("failed (rc=1)\n") + (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") + (exp / "status_task_1.out").write_text("failed (rc=1)\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000001") @@ -1111,7 +1111,7 @@ def test_job_status_running(tmp_path, monkeypatch): """No _DONE marker → running.""" exp = tmp_path / "experiments" / "exp_1781000002" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n") + (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000002") @@ -1124,7 +1124,7 @@ def test_job_status_nested_nemo_title_dir(tmp_path, monkeypatch): """nemo_run stores experiments under experiments//<experiment_id>.""" exp = tmp_path / "experiments" / "cicd" / "exp_1781000006" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n") + (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000006") @@ -1147,7 +1147,8 @@ def test_job_status_slurm_sidecar_overrides_local_done_marker(tmp_path, monkeypa "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ) + ), + encoding="utf-8", ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1184,7 +1185,8 @@ def test_job_status_slurm_sidecar_reports_terminal_state(tmp_path, monkeypatch): "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ) + ), + encoding="utf-8", ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1222,7 +1224,8 @@ def test_job_status_slurm_not_found_falls_back_to_local_done_marker(tmp_path, mo "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ) + ), + encoding="utf-8", ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) monkeypatch.setattr( @@ -1247,7 +1250,7 @@ def test_job_status_slurm_not_found_falls_back_to_local_failed_marker(tmp_path, exp = tmp_path / "experiments" / "cicd" / "exp_slurm_aged_out_failed" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("failed (rc=1)\n") + (exp / "status_task_0.out").write_text("failed (rc=1)\n", encoding="utf-8") (exp / bridge._SLURM_STATUS_META).write_text( json.dumps( { @@ -1257,7 +1260,8 @@ def test_job_status_slurm_not_found_falls_back_to_local_failed_marker(tmp_path, "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ) + ), + encoding="utf-8", ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) monkeypatch.setattr( @@ -1281,7 +1285,7 @@ def test_job_status_launcher_experiments_fallback(tmp_path, monkeypatch): launcher_dir = tmp_path / "launcher" exp = launcher_dir / "experiments" / "cicd" / "exp_1781000007" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n") + (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") monkeypatch.delenv("NEMORUN_HOME", raising=False) other_cwd = tmp_path / "other" other_cwd.mkdir() @@ -1327,8 +1331,8 @@ def test_job_logs_all_tasks(tmp_path, monkeypatch): """task=None returns logs for every log_*.out under the experiment dir.""" exp = tmp_path / "experiments" / "exp_1781000003" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("hello\nworld\n") - (exp / "log_task_1.out").write_text("done\n") + (exp / "log_task_0.out").write_text("hello\nworld\n", encoding="utf-8") + (exp / "log_task_1.out").write_text("done\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000003", task=None, tail=None) @@ -1341,7 +1345,7 @@ def test_job_logs_with_tail(tmp_path, monkeypatch): """tail=N returns only the last N lines per task.""" exp = tmp_path / "experiments" / "exp_1781000004" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("line1\nline2\nline3\nline4\n") + (exp / "log_task_0.out").write_text("line1\nline2\nline3\nline4\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000004", task="task_0", tail=2) @@ -1354,7 +1358,7 @@ def test_job_logs_missing_task(tmp_path, monkeypatch): """Requested task name has no log file → task_log_not_found.""" exp = tmp_path / "experiments" / "exp_1781000005" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("only task 0\n") + (exp / "log_task_0.out").write_text("only task 0\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000005", task="task_99", tail=None) @@ -1379,7 +1383,7 @@ def test_wait_for_experiment_returns_terminal_immediately(tmp_path, monkeypatch) exp = tmp_path / "experiments" / "exp_already_done" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n") + (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.wait_for_experiment_impl( @@ -1396,7 +1400,7 @@ def test_wait_for_experiment_polls_until_done(tmp_path, monkeypatch): """Spin through running → done.""" exp = tmp_path / "experiments" / "exp_in_flight" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n") + (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) # Flip the marker after 2 polls via a counter side-effect @@ -1434,7 +1438,8 @@ def test_wait_for_experiment_polls_slurm_despite_local_done_marker(tmp_path, mon "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ) + ), + encoding="utf-8", ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1467,7 +1472,7 @@ def test_wait_for_experiment_timeout(tmp_path, monkeypatch): """Never reaches terminal → wait_timeout with last_status.""" exp = tmp_path / "experiments" / "exp_stuck" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n") + (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.wait_for_experiment_impl( @@ -1523,9 +1528,9 @@ def test_provision_ssh_key_present_emits_copy_id(tmp_path, monkeypatch): fake_home = tmp_path / "home" ssh = fake_home / ".ssh" ssh.mkdir(parents=True) - (ssh / "id_ed25519").write_text("PRIVKEY") + (ssh / "id_ed25519").write_text("PRIVKEY", encoding="utf-8") pubkey_content = "ssh-ed25519 AAAAC3NzaC... alice@host" - (ssh / "id_ed25519.pub").write_text(pubkey_content + "\n") + (ssh / "id_ed25519.pub").write_text(pubkey_content + "\n", encoding="utf-8") monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("IDENTITY", raising=False) @@ -1547,7 +1552,7 @@ def test_provision_ssh_priv_without_pub_surfaces_failure(tmp_path, monkeypatch): fake_home = tmp_path / "home" ssh = fake_home / ".ssh" ssh.mkdir(parents=True) - (ssh / "id_ed25519").write_text("PRIVKEY") + (ssh / "id_ed25519").write_text("PRIVKEY", encoding="utf-8") monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("IDENTITY", raising=False) @@ -1564,8 +1569,8 @@ def test_provision_ssh_priv_without_pub_surfaces_failure(tmp_path, monkeypatch): def test_provision_ssh_explicit_identity_overrides_default(tmp_path, monkeypatch): """Explicit identity arg wins over $IDENTITY and ~/.ssh/id_ed25519.""" explicit = tmp_path / "custom_key" - explicit.write_text("CUSTOM") - (tmp_path / "custom_key.pub").write_text("ssh-ed25519 AAAA alice\n") + explicit.write_text("CUSTOM", encoding="utf-8") + (tmp_path / "custom_key.pub").write_text("ssh-ed25519 AAAA alice\n", encoding="utf-8") monkeypatch.setenv("IDENTITY", "/wrong/path") # should be ignored result = bridge.provision_passwordless_ssh_dry_run_impl( diff --git a/tools/resource_monitor.py b/tools/resource_monitor.py index 9fb255a7f79..afda9efd511 100644 --- a/tools/resource_monitor.py +++ b/tools/resource_monitor.py @@ -356,7 +356,7 @@ def _write_summary(path, duration, metrics: _Metrics): print(text, flush=True) if path: Path(path).parent.mkdir(parents=True, exist_ok=True) - Path(path).write_text(text + "\n") + Path(path).write_text(text + "\n", encoding="utf-8") def main() -> None: @@ -402,7 +402,7 @@ def _request_stop(signum, frame): "proc_cpu_util_pct", ] start = time.monotonic() - with open(args.out, "w", newline="") as f: + with open(args.out, "w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() while not stop: From 23833f8510c1fb0b5151eba2d268c7981e76e819 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 12:52:49 -0700 Subject: [PATCH 04/20] Record the encoding change, and restore two mangled terms CHANGELOG entry for the previous commit: explicit `encoding` on all text I/O. It is user-visible, not only a CI fix -- `modelopt.recipe.loader` read recipe YAML through the locale codec, so a Windows user with a cp1252 locale hit `UnicodeDecodeError` on a UTF-8 recipe without ModelOpt being involved in any test run. This commit also carries two terms that a shell ate from c581ab96a, where backticks were substituted before git saw them: "259 calls" should read "259 `open` calls" "plain also switches" should read "plain `preview = true` also switches" Neither changes what that commit says, but both name the thing being discussed: PLW1514 covers `open` and nothing else, and it is `preview = true` -- not the rule itself -- that would drag 3755 findings in from preview behaviour in the already-selected stable rules. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 22d4681c3ad..04a60956566 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -38,6 +38,8 @@ Changelog **Bug Fixes** +- Text I/O across ModelOpt now passes an explicit ``encoding``. Without it Python uses the locale codepage, which on Windows is cp1252: reading a UTF-8 file raised ``UnicodeDecodeError`` on the first non-Latin-1 byte and writing non-ASCII raised ``UnicodeEncodeError``, on platforms where no such failure appears elsewhere. This affected library code a user reaches directly -- ``modelopt.recipe.loader`` read recipe YAML through the locale codec -- so it was not only a CI concern. Ruff's ``PLW1514`` now guards ``open`` calls, and a pre-commit hook guards ``Path.read_text``/``write_text``, which that rule does not implement. + - Fix ``examples/megatron_bridge/export_quantized_megatron_to_hf.py`` storing the MoE router at Megatron's ``moe_router_dtype``, which is a routing *compute* dtype, not a storage one. The router now exports at the export ``dtype`` like every other unquantized weight, matching what ``hf_ptq.py`` and the released NVFP4 checkpoints contain; pass ``moe_router_dtype`` to ``export_mcore_gpt_to_hf`` explicitly if you want the old fp32 storage. - Fix unified Megatron export writing a second, unreferenced copy of the vocab embedding when a model with MTP layers is exported with pipeline parallelism. The duplicate was never loaded but inflated the checkpoint by the size of the embedding (about 1 GB for Qwen3.6-35B-A3B); re-export to reclaim the space. - Fail fast on non-finite AutoQuantize output gradients with an actionable error before accumulating sensitivity scores, without changing attention backend settings. From 134b1081eac614d8f127b52b2e157908df2c9229 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 13:04:06 -0700 Subject: [PATCH 05/20] Cover plugins/ too, which the first encoding pass missed The fix script walked a hardcoded directory list -- modelopt, tests, examples, tools -- so plugins/ was never visited and kept 31 encoding-less calls. The AST hook added in the previous commit is what caught it: run over git ls-files it failed on exactly the files the fix script had skipped. Driving the fix from git ls-files rather than a curated list closes the gap and removes the possibility of a new top-level directory quietly reintroducing it. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .pre-commit-config.yaml | 8 ++-- .../scripts/benchmark_via_builtin.py | 2 +- .../tests/test_benchmark_via_builtin.py | 26 +++++------ .../skills/day0-release/tests/test_gates.py | 30 ++++++------- .../evaluation/tests/test_nel_gdpval.py | 4 +- tools/check_text_encoding.py | 43 +++++++++++++++++++ 6 files changed, 79 insertions(+), 34 deletions(-) create mode 100755 tools/check_text_encoding.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90e12a67674..3d9ee5b92fb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,10 +6,12 @@ repos: name: read_text/write_text must pass encoding description: > ruff PLW1514 only covers `open`, so Path.read_text/write_text can silently use the - locale codepage -- cp1252 on Windows -- and fail on UTF-8 content. Keep them explicit. - language: pygrep + locale codepage -- cp1252 on Windows -- and fail on UTF-8 content. AST-based, because a + line-oriented regex reports a false positive when `encoding=` sits on a later line of a + multi-line call. + language: python types: [python] - entry: '\.(read|write)_text\((?![^)]*encoding=)' + entry: python tools/check_text_encoding.py - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py index 633e1ba4fe4..d5faa591ba7 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py @@ -839,7 +839,7 @@ def main(argv: list[str] | None = None) -> None: parser.error(f"{args.workdir} already contains results; choose a fresh --workdir") testlist.write_text( "\n".join(shlex.join([*case.argv, "--case_tag", case.tag]) for case in cases) + "\n" - ) + , encoding="utf-8") header = _environment_header(args.flashinfer_repo) rows = _execute_cases(cases, benchmarks_dir, args.workdir, driver_log, header) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py index 2088765899b..61a5fca141f 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py @@ -219,9 +219,9 @@ def test_unavailable_fp8_quantization_is_written_as_an_error(monkeypatch, capsys assert case.quant_result == benchmark._FP8_QUANT_UNAVAILABLE assert "[WARN] vLLM is unavailable for FP8 activation quantization" in capsys.readouterr().out - assert "32x64,1,32,64,fp8_cutlass,False,1.000\n" in output.read_text() + assert "32x64,1,32,64,fp8_cutlass,False,1.000\n" in output.read_text(encoding="utf-8") assert f"32x64,1,32,64,fp8_cutlass,True,{benchmark._FP8_QUANT_UNAVAILABLE}\n" in ( - output.read_text() + output.read_text(encoding="utf-8") ) @@ -248,8 +248,8 @@ def test_driver_errors_are_added_to_kernel_and_with_quant_rows(tmp_path): benchmark._write_results(csv_path, [case], {(1280, 2880): ["1280x2880"]}) expected = "ERROR: K must be divisible by 128; got 2880" - assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text() - assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text() + assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text(encoding="utf-8") + assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text(encoding="utf-8") def test_empty_driver_error_has_no_synthetic_reason(): @@ -303,7 +303,7 @@ def test_write_results_emits_long_form_rows(tmp_path): moe_shape=benchmark._MoeShape(32, 50, 4, 2, "Relu2", "model.layers.*.mlp.experts"), ) - assert output.read_text() == ( + assert output.read_text(encoding="utf-8") == ( "flashinfer test-header\n" "GEMM\n" "module_name,M,N,K,backend,with_quant,runtime\n" @@ -356,7 +356,7 @@ def test_missing_builtin_results_still_writes_combined_errors( ): benchmarks_dir = tmp_path / "flashinfer" / "benchmarks" benchmarks_dir.mkdir(parents=True) - (benchmarks_dir / "flashinfer_benchmark.py").write_text("") + (benchmarks_dir / "flashinfer_benchmark.py").write_text("", encoding="utf-8") workdir = tmp_path / "results" monkeypatch.setattr(benchmark, "_run_case", lambda *_: (returncode, [])) monkeypatch.setattr( @@ -379,18 +379,18 @@ def test_missing_builtin_results_still_writes_combined_errors( benchmark.main() assert not (workdir / "builtin_results.csv").exists() - combined = (workdir / "combined_results.csv").read_text() + combined = (workdir / "combined_results.csv").read_text(encoding="utf-8") assert f"2x3,1,2,3,bf16,False,ERROR: {expected_reason}" in combined assert "driver.log" in combined # The reproducibility header leads both the combined CSV and driver.log. assert combined.splitlines()[0].startswith("flashinfer ") - assert (workdir / "driver.log").read_text().startswith("flashinfer ") + assert (workdir / "driver.log").read_text(encoding="utf-8").startswith("flashinfer ") def test_case_rows_with_foreign_tags_are_treated_as_failures(monkeypatch, tmp_path): benchmarks_dir = tmp_path / "flashinfer" / "benchmarks" benchmarks_dir.mkdir(parents=True) - (benchmarks_dir / "flashinfer_benchmark.py").write_text("") + (benchmarks_dir / "flashinfer_benchmark.py").write_text("", encoding="utf-8") workdir = tmp_path / "results" def fake_run_case(benchmarks_dir, argv, log): @@ -418,7 +418,7 @@ def fake_run_case(benchmarks_dir, argv, log): with pytest.raises(RuntimeError, match="FlashInfer failed benchmark cases"): benchmark.main() - combined = (workdir / "combined_results.csv").read_text() + combined = (workdir / "combined_results.csv").read_text(encoding="utf-8") assert "no result row" in combined @@ -427,7 +427,7 @@ def test_run_case_streams_and_appends_to_the_driver_log(tmp_path, capsys): benchmarks_dir.mkdir() (benchmarks_dir / "flashinfer_benchmark.py").write_text( "print('line one')\nprint('line two')\n" - ) + , encoding="utf-8") driver_log = tmp_path / "driver.log" with driver_log.open("w") as log: @@ -435,7 +435,7 @@ def test_run_case_streams_and_appends_to_the_driver_log(tmp_path, capsys): assert returncode == 0 assert lines == ["line one\n", "line two\n"] - assert driver_log.read_text() == "line one\nline two\n" + assert driver_log.read_text(encoding="utf-8") == "line one\nline two\n" assert "line one" in capsys.readouterr().out @@ -450,6 +450,6 @@ def test_write_builtin_merges_heterogeneous_row_columns(tmp_path): ], ) - assert path.read_text() == ( + assert path.read_text(encoding="utf-8") == ( "routine,median_time,case_tag,num_experts\nmm_bf16,0.004,a,\ncutlass_fused_moe,,b,8\n" ) diff --git a/plugins/modelopt/skills/day0-release/tests/test_gates.py b/plugins/modelopt/skills/day0-release/tests/test_gates.py index b8217883e17..f89dd38f674 100644 --- a/plugins/modelopt/skills/day0-release/tests/test_gates.py +++ b/plugins/modelopt/skills/day0-release/tests/test_gates.py @@ -362,7 +362,7 @@ def test_harvest_keys_by_task_not_harness(tmp_path): d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 100.0, "successful_count": 10}}) - ) + , encoding="utf-8") # Harness is kept: two harnesses can expose the same task name, and pooling them # would average different generation conditions together. assert set(harvest(str(tmp_path))) == { @@ -378,17 +378,17 @@ def test_harvest_reports_what_it_dropped(tmp_path): good.mkdir(parents=True) good.joinpath("eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") bad = tmp_path / "eval_run" / "inv" / "h.notok" / "artifacts" bad.mkdir(parents=True) bad.joinpath("eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"successful_count": 2}}) # no token count - ) + , encoding="utf-8") skipped = tmp_path / "eval_high" / "inv" / "h.excl" / "artifacts" skipped.mkdir(parents=True) skipped.joinpath("eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") diag = {} out = harvest(str(tmp_path), exclude="_high", diagnostics=diag) assert set(out) == {"h.good"} @@ -409,7 +409,7 @@ def _write_metrics(d, tokens=100.0, count=10): d.mkdir(parents=True) d.joinpath("eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": tokens, "successful_count": count}}) - ) + , encoding="utf-8") def test_harvest_handles_both_documented_depths(tmp_path): @@ -476,11 +476,11 @@ def test_every_emitted_failure_class_has_a_triage_row(): # (ACCEPT/REGRESSION) and SLURM states (PENDING/RUNNING) are not failure classes. emitted = set() for f in scripts.glob("gate_*.py"): - src = f.read_text() + src = f.read_text(encoding="utf-8") emitted |= set(re.findall(r'"failure_class":\s*"([A-Z_]+)"', src)) emitted |= set(re.findall(r'failures\.append\(\s*\(\s*\n?\s*"([A-Z_]+)"', src)) rows = set( - re.findall(r"^\| `([A-Z_]+)` \|", (scripts.parent / "SKILL.md").read_text(), re.MULTILINE) + re.findall(r"^\| `([A-Z_]+)` \|", (scripts.parent / "SKILL.md").read_text(encoding="utf-8"), re.MULTILINE) ) # Subtract only declared exemptions: intersecting with an allowlist would filter out # exactly the newly-emitted class this test exists to catch. @@ -520,10 +520,10 @@ def test_harvest_prefers_the_task_name_from_metadata(tmp_path): for job, name in ((0, "simple_evals.gpqa"), (1, "tau2.telecom")): d = tmp_path / "eval_run" / f"inv123.{job}" / "artifacts" d.mkdir(parents=True) - (d / "metadata.yaml").write_text(f"evaluation:\n tasks:\n - name: {name}\n") + (d / "metadata.yaml").write_text(f"evaluation:\n tasks:\n - name: {name}\n", encoding="utf-8") (d / "eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") assert set(harvest(str(tmp_path))) == {"simple_evals.gpqa", "tau2.telecom"} @@ -534,7 +534,7 @@ def test_harvest_flags_collapsed_task_keys(tmp_path): d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") diag = {} harvest(str(tmp_path), diagnostics=diag) assert "collapsed_keys" in diag and diag["collapsed_keys"]["inv123"] == [ @@ -549,7 +549,7 @@ def test_dropped_tasks_covers_the_excluded_channel(tmp_path): d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") diag = {} assert harvest(str(tmp_path), exclude="_high", diagnostics=diag) == {} assert diag["excluded_tasks"] == ["h.only_high"] @@ -595,12 +595,12 @@ def _mk_run(root, leaf, cfg=None, meta=None): d = root / "eval_run" / leaf / "artifacts" d.mkdir(parents=True) if cfg: - (d / "config.yml").write_text(cfg) + (d / "config.yml").write_text(cfg, encoding="utf-8") if meta: - (d / "metadata.yaml").write_text(meta) + (d / "metadata.yaml").write_text(meta, encoding="utf-8") (d / "eval_factory_metrics.json").write_text( json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - ) + , encoding="utf-8") return d @@ -655,7 +655,7 @@ def test_unreadable_artifact_does_not_count_toward_collapse(tmp_path): good = _mk_run(tmp_path, "inv1.0") bad = tmp_path / "eval_run" / "inv1.1" / "artifacts" bad.mkdir(parents=True) - (bad / "eval_factory_metrics.json").write_text("{truncated") + (bad / "eval_factory_metrics.json").write_text("{truncated", encoding="utf-8") assert good.exists() diag = {} out = harvest(str(tmp_path), diagnostics=diag) diff --git a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py index ce9e367a058..88a6bfc75ef 100644 --- a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py +++ b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py @@ -23,7 +23,7 @@ def test_launcher_uses_validated_pin_despite_environment_override(tmp_path): args_file = tmp_path / "uvx-args" uvx = tmp_path / "uvx" - uvx.write_text('#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n') + uvx.write_text('#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n', encoding="utf-8") uvx.chmod(0o755) env = os.environ.copy() @@ -38,7 +38,7 @@ def test_launcher_uses_validated_pin_despite_environment_override(tmp_path): subprocess.run([SCRIPT, "run", "--config", "gdpval.yaml"], env=env, check=True) - assert args_file.read_text().splitlines() == [ + assert args_file.read_text(encoding="utf-8").splitlines() == [ "--python", "3.10", "--from", diff --git a/tools/check_text_encoding.py b/tools/check_text_encoding.py new file mode 100755 index 00000000000..6d2d8d9a95e --- /dev/null +++ b/tools/check_text_encoding.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Flag Path.read_text()/write_text() calls that do not pass an explicit encoding. + +Ruff's PLW1514 covers ``open`` only, so these slip through it. They matter for the same reason: +without an encoding Python uses the locale codepage, which is cp1252 on Windows, and a UTF-8 file +then fails to decode. Parsed rather than grepped -- a regex cannot tell whether ``encoding=`` sits +on a later line of a multi-line call, and reports it as a violation. +""" + +from __future__ import annotations + +import ast +import sys + +TARGETS = {"read_text", "write_text"} + + +def violations(path: str) -> list[tuple[int, str]]: + try: + tree = ast.parse(open(path, encoding="utf-8").read(), filename=path) + except (SyntaxError, UnicodeDecodeError): + return [] # not ours to police + found = [] + for node in ast.walk(tree): + if ( + isinstance(node, ast.Call) + and isinstance(node.func, ast.Attribute) + and node.func.attr in TARGETS + and not any(kw.arg == "encoding" for kw in node.keywords) + ): + found.append((node.lineno, node.func.attr)) + return found + + +def main(argv: list[str]) -> int: + bad = [(p, ln, name) for p in argv for ln, name in violations(p)] + for path, lineno, name in bad: + print(f"{path}:{lineno}: {name}() without an explicit encoding= (locale codepage on Windows)") + return 1 if bad else 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) From 683d451087aa945df7ef7f38b866b6e1ae125b01 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 13:08:53 -0700 Subject: [PATCH 06/20] Format the plugins edits, and document the checker itself ruff wanted five files reformatted after the plugins/ encoding pass -- the added kwarg pushed lines over the limit -- and flagged D103 on tools/check_text_encoding.py: the script added to enforce a standard did not meet the repos own docstring rule. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .../scripts/benchmark_via_builtin.py | 5 +- .../tests/test_benchmark_via_builtin.py | 12 ++-- .../skills/day0-release/tests/test_gates.py | 59 ++++++++++++------- .../evaluation/tests/test_nel_gdpval.py | 4 +- tools/check_text_encoding.py | 10 +++- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py index d5faa591ba7..a8e09ac9547 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py @@ -838,8 +838,9 @@ def main(argv: list[str] | None = None) -> None: if builtin_csv.exists() or combined_csv.exists(): parser.error(f"{args.workdir} already contains results; choose a fresh --workdir") testlist.write_text( - "\n".join(shlex.join([*case.argv, "--case_tag", case.tag]) for case in cases) + "\n" - , encoding="utf-8") + "\n".join(shlex.join([*case.argv, "--case_tag", case.tag]) for case in cases) + "\n", + encoding="utf-8", + ) header = _environment_header(args.flashinfer_repo) rows = _execute_cases(cases, benchmarks_dir, args.workdir, driver_log, header) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py index 61a5fca141f..b299112cd10 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py @@ -248,8 +248,12 @@ def test_driver_errors_are_added_to_kernel_and_with_quant_rows(tmp_path): benchmark._write_results(csv_path, [case], {(1280, 2880): ["1280x2880"]}) expected = "ERROR: K must be divisible by 128; got 2880" - assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text(encoding="utf-8") - assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text(encoding="utf-8") + assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text( + encoding="utf-8" + ) + assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text( + encoding="utf-8" + ) def test_empty_driver_error_has_no_synthetic_reason(): @@ -426,8 +430,8 @@ def test_run_case_streams_and_appends_to_the_driver_log(tmp_path, capsys): benchmarks_dir = tmp_path / "benchmarks" benchmarks_dir.mkdir() (benchmarks_dir / "flashinfer_benchmark.py").write_text( - "print('line one')\nprint('line two')\n" - , encoding="utf-8") + "print('line one')\nprint('line two')\n", encoding="utf-8" + ) driver_log = tmp_path / "driver.log" with driver_log.open("w") as log: diff --git a/plugins/modelopt/skills/day0-release/tests/test_gates.py b/plugins/modelopt/skills/day0-release/tests/test_gates.py index f89dd38f674..8e3cd53cec0 100644 --- a/plugins/modelopt/skills/day0-release/tests/test_gates.py +++ b/plugins/modelopt/skills/day0-release/tests/test_gates.py @@ -361,8 +361,11 @@ def test_harvest_keys_by_task_not_harness(tmp_path): d = tmp_path / "eval_run" / "inv123" / name / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 100.0, "successful_count": 10}}) - , encoding="utf-8") + json.dumps( + {"response_stats": {"avg_completion_tokens": 100.0, "successful_count": 10}} + ), + encoding="utf-8", + ) # Harness is kept: two harnesses can expose the same task name, and pooling them # would average different generation conditions together. assert set(harvest(str(tmp_path))) == { @@ -377,18 +380,21 @@ def test_harvest_reports_what_it_dropped(tmp_path): good = tmp_path / "eval_run" / "inv" / "h.good" / "artifacts" good.mkdir(parents=True) good.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) bad = tmp_path / "eval_run" / "inv" / "h.notok" / "artifacts" bad.mkdir(parents=True) bad.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"successful_count": 2}}) # no token count - , encoding="utf-8") + json.dumps({"response_stats": {"successful_count": 2}}), # no token count + encoding="utf-8", + ) skipped = tmp_path / "eval_high" / "inv" / "h.excl" / "artifacts" skipped.mkdir(parents=True) skipped.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) diag = {} out = harvest(str(tmp_path), exclude="_high", diagnostics=diag) assert set(out) == {"h.good"} @@ -408,8 +414,11 @@ def test_ptq_waiver_requires_canonical_values(): def _write_metrics(d, tokens=100.0, count=10): d.mkdir(parents=True) d.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": tokens, "successful_count": count}}) - , encoding="utf-8") + json.dumps( + {"response_stats": {"avg_completion_tokens": tokens, "successful_count": count}} + ), + encoding="utf-8", + ) def test_harvest_handles_both_documented_depths(tmp_path): @@ -480,7 +489,11 @@ def test_every_emitted_failure_class_has_a_triage_row(): emitted |= set(re.findall(r'"failure_class":\s*"([A-Z_]+)"', src)) emitted |= set(re.findall(r'failures\.append\(\s*\(\s*\n?\s*"([A-Z_]+)"', src)) rows = set( - re.findall(r"^\| `([A-Z_]+)` \|", (scripts.parent / "SKILL.md").read_text(encoding="utf-8"), re.MULTILINE) + re.findall( + r"^\| `([A-Z_]+)` \|", + (scripts.parent / "SKILL.md").read_text(encoding="utf-8"), + re.MULTILINE, + ) ) # Subtract only declared exemptions: intersecting with an allowlist would filter out # exactly the newly-emitted class this test exists to catch. @@ -520,10 +533,13 @@ def test_harvest_prefers_the_task_name_from_metadata(tmp_path): for job, name in ((0, "simple_evals.gpqa"), (1, "tau2.telecom")): d = tmp_path / "eval_run" / f"inv123.{job}" / "artifacts" d.mkdir(parents=True) - (d / "metadata.yaml").write_text(f"evaluation:\n tasks:\n - name: {name}\n", encoding="utf-8") + (d / "metadata.yaml").write_text( + f"evaluation:\n tasks:\n - name: {name}\n", encoding="utf-8" + ) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) assert set(harvest(str(tmp_path))) == {"simple_evals.gpqa", "tau2.telecom"} @@ -533,8 +549,9 @@ def test_harvest_flags_collapsed_task_keys(tmp_path): d = tmp_path / "eval_run" / f"inv123.{job}" / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) diag = {} harvest(str(tmp_path), diagnostics=diag) assert "collapsed_keys" in diag and diag["collapsed_keys"]["inv123"] == [ @@ -548,8 +565,9 @@ def test_dropped_tasks_covers_the_excluded_channel(tmp_path): d = tmp_path / "eval_high" / "inv" / "h.only_high" / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) diag = {} assert harvest(str(tmp_path), exclude="_high", diagnostics=diag) == {} assert diag["excluded_tasks"] == ["h.only_high"] @@ -599,8 +617,9 @@ def _mk_run(root, leaf, cfg=None, meta=None): if meta: (d / "metadata.yaml").write_text(meta, encoding="utf-8") (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) - , encoding="utf-8") + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), + encoding="utf-8", + ) return d diff --git a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py index 88a6bfc75ef..b24a3559347 100644 --- a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py +++ b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py @@ -23,7 +23,9 @@ def test_launcher_uses_validated_pin_despite_environment_override(tmp_path): args_file = tmp_path / "uvx-args" uvx = tmp_path / "uvx" - uvx.write_text('#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n', encoding="utf-8") + uvx.write_text( + '#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n', encoding="utf-8" + ) uvx.chmod(0o755) env = os.environ.copy() diff --git a/tools/check_text_encoding.py b/tools/check_text_encoding.py index 6d2d8d9a95e..aa8f45dcbf2 100755 --- a/tools/check_text_encoding.py +++ b/tools/check_text_encoding.py @@ -16,6 +16,11 @@ def violations(path: str) -> list[tuple[int, str]]: + """Return ``(lineno, method)`` for each offending call in ``path``. + + A file that will not parse is not ours to police -- ruff and the formatter already have + an opinion about it, and a syntax error reported from here would only be noise. + """ try: tree = ast.parse(open(path, encoding="utf-8").read(), filename=path) except (SyntaxError, UnicodeDecodeError): @@ -33,9 +38,12 @@ def violations(path: str) -> list[tuple[int, str]]: def main(argv: list[str]) -> int: + """Report every offending call across ``argv``; exit non-zero when any is found.""" bad = [(p, ln, name) for p in argv for ln, name in violations(p)] for path, lineno, name in bad: - print(f"{path}:{lineno}: {name}() without an explicit encoding= (locale codepage on Windows)") + print( + f"{path}:{lineno}: {name}() without an explicit encoding= (locale codepage on Windows)" + ) return 1 if bad else 0 From 555cec1c5eaf7366ddfcd1caa2ac1c1a0c6bf862 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 13:23:05 -0700 Subject: [PATCH 07/20] Revert the explicit-encoding sweep; keep UTF-8 mode for the windows job The sweep was the wrong trade. Annotating ~600 call sites, adding a preview-gated ruff rule, and carrying a custom AST pre-commit hook is a large permanent tax on every future change, to solve a problem that PYTHONUTF8=1 already solves for the process that actually fails. Reverted: the encoding= additions, the PLW1514 rule and its preview/ explicit-preview-rules configuration, the per-file-ignores that scoped it, the read_text/write_text pre-commit hook, tools/check_text_encoding.py, the CHANGELOG entry, and the C419/F401 fixes that were only needed because enabling preview surfaced them. Kept: PYTHONUTF8=1 on the windows unit job, which is what makes the failing platform read UTF-8 regardless of locale. The known limitation, stated rather than papered over: PEP 540 mode is per process, so this covers our CI and not a user's. modelopt code that reads text without an encoding still uses the locale codepage in a user process on Windows -- modelopt/recipe/loader.py reading recipe YAML is the clearest example. If that turns out to bite someone, the fix is a handful of targeted call sites at the public entry points, not a repo-wide sweep. Python 3.15 makes UTF-8 mode the default (PEP 686), which removes the issue at the source. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .pre-commit-config.yaml | 13 - CHANGELOG.rst | 2 - examples/alpamayo/quantize.py | 4 +- examples/deepseek/deepseek_v3/ptq.py | 4 +- .../deepseek/deepseek_v3/quantize_to_nvfp4.py | 12 +- examples/deepseek/deepseek_v4/ptq.py | 6 +- .../deepseek/deepseek_v4/quantize_to_nvfp4.py | 12 +- .../distillation/distillation_trainer.py | 8 +- examples/diffusers/fastgen/dmd2_recipe.py | 4 +- .../fastgen/export_diffusers_qwen_image.py | 4 +- .../fastgen/inference_dmd2_qwen_image.py | 2 +- .../preprocess/preprocessing_multiprocess.py | 4 +- examples/diffusers/quantization/utils.py | 2 +- .../gpt-oss/convert_oai_mxfp4_weight_only.py | 4 +- examples/hf_ptq/example_utils.py | 8 +- examples/kimi/kimi_k3/quantize_to_nvfp4.py | 24 +- examples/llm_eval/lm_eval_hf.py | 2 +- examples/llm_eval/modeling.py | 3 +- examples/llm_eval/simple_evals.py | 6 +- examples/llm_qat/dataset_utils.py | 2 +- examples/llm_qat/export.py | 4 +- .../llm_sparsity/weight_sparsity/data_prep.py | 4 +- examples/llm_sparsity/weight_sparsity/eval.py | 2 +- .../minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py | 13 +- examples/onnx_ptq/evaluate.py | 2 +- examples/onnx_ptq/evaluation.py | 2 +- .../scenario2_puzzletron.ipynb | 4 +- examples/specdec_bench/run.py | 2 +- .../specdec_bench/datasets/mtbench.py | 2 +- .../specdec_bench/datasets/specbench.py | 2 +- .../specdec_bench/metrics/aa_timing.py | 4 +- .../specdec_bench/metrics/acceptance_rate.py | 4 +- .../specdec_bench/metrics/base.py | 4 +- .../specdec_bench/metrics/mtbench.py | 4 +- .../specdec_bench/metrics/specbench.py | 8 +- .../specdec_bench/metrics/timing.py | 8 +- examples/specdec_bench/specdec_bench/utils.py | 8 +- examples/specdec_bench/upload_to_s3.py | 2 +- .../collect_hidden_states/common.py | 2 +- .../send_conversations_for_hiddens.py | 2 +- examples/speculative_decoding/eagle_utils.py | 4 +- examples/speculative_decoding/example.ipynb | 8 +- examples/speculative_decoding/medusa_utils.py | 4 +- .../scripts/calibrate_draft_vocab.py | 2 +- .../scripts/quantize_drafter.py | 8 +- .../scripts/send_conversation_vllm.py | 2 +- .../scripts/server_generate.py | 12 +- examples/torch_trt/torch_tensorrt_accuracy.py | 2 +- examples/torch_trt/torch_tensorrt_ptq.py | 2 +- .../fvd_metrics/compute_fvd.py | 2 +- .../compute_kl_divergence.py | 2 +- .../accuracy_benchmark/mmlu_benchmark.py | 4 +- .../perplexity_metrics/perplexity_metrics.py | 2 +- .../accuracy_benchmark/trtllm_utils.py | 6 +- .../sample_example_qad_diffusers.py | 8 +- experimental/dms/models/qwen3/train.py | 8 +- modelopt/deploy/llm/generate.py | 2 +- .../onnx/graph_surgery/utils/whisper_utils.py | 12 +- .../onnx/llm_export_utils/export_utils.py | 2 +- .../quantization/autotune/autotuner_base.py | 4 +- .../onnx/quantization/autotune/benchmark.py | 2 +- modelopt/onnx/quantization/autotune/common.py | 4 +- .../quantization/autotune/region_search.py | 4 +- modelopt/onnx/quantization/autotune/utils.py | 2 +- modelopt/onnx/quantization/calib_utils.py | 2 +- modelopt/onnx/trt_utils.py | 4 +- modelopt/recipe/loader.py | 4 +- modelopt/torch/_deploy/_runtime/common.py | 4 +- modelopt/torch/_deploy/_runtime/ort_client.py | 2 +- modelopt/torch/distill/plugins/megatron.py | 2 +- modelopt/torch/export/layerwise_export.py | 2 +- .../export/plugins/hf_checkpoint_utils.py | 4 +- .../torch/export/plugins/hf_spec_export.py | 10 +- modelopt/torch/export/plugins/mcore_custom.py | 14 +- .../export/trtllm/model_config_export.py | 4 +- modelopt/torch/export/unified_export_hf.py | 22 +- .../export/unified_export_hf_streaming.py | 2 +- .../torch/export/unified_export_megatron.py | 12 +- modelopt/torch/nas/hparams/concat.py | 2 +- modelopt/torch/opt/plugins/transformers.py | 6 +- modelopt/torch/opt/searcher.py | 4 +- modelopt/torch/prune/fastnas.py | 10 +- .../prune/importance_hooks/base_hooks.py | 2 +- .../compare_module_outputs.py | 2 +- .../puzzletron/anymodel/converter/base.py | 2 +- .../models/gpt_oss/gpt_oss_pruned_to_mxfp4.py | 10 +- modelopt/torch/puzzletron/mip/run_puzzle.py | 20 +- modelopt/torch/puzzletron/mip/sweep.py | 8 +- .../torch/puzzletron/pruning/pruning_utils.py | 2 +- .../build_replacement_library.py | 2 +- .../puzzletron/replacement_library/library.py | 2 +- .../calc_subblock_params_and_memory.py | 2 +- .../subblock_stats/calc_subblock_stats.py | 6 +- .../subblock_stats/runtime_utils.py | 8 +- .../puzzletron/subblock_stats/runtime_vllm.py | 6 +- .../tools/bypassed_training/child_init.py | 6 +- .../puzzletron/tools/checkpoint_utils.py | 6 +- .../tools/sharded_checkpoint_utils.py | 2 +- .../torch/puzzletron/tools/validate_model.py | 4 +- ...validate_puzzle_with_multi_replacements.py | 5 +- .../puzzletron/utils/checkpoint_manager.py | 4 +- modelopt/torch/puzzletron/utils/misc.py | 2 +- .../torch/quantization/plugins/attention.py | 2 +- .../quantization/utils/layerwise_calib.py | 4 +- .../calibration/ruler_dataset.py | 6 +- .../speculative/plugins/modeling_fakebase.py | 2 +- modelopt/torch/utils/logging.py | 6 +- modelopt/torch/utils/mlflow.py | 10 +- .../torch/utils/plugins/model_load_utils.py | 2 +- modelopt/torch/utils/robust_json.py | 4 +- .../scripts/benchmark_via_builtin.py | 9 +- .../tests/test_benchmark_via_builtin.py | 32 +-- .../day0-release/scripts/gate_compare.py | 4 +- .../skills/day0-release/scripts/gate_ptq.py | 2 +- .../skills/day0-release/scripts/gate_run.py | 2 +- .../day0-release/scripts/gate_verbosity.py | 4 +- .../tests/test_agent_definitions.py | 2 +- .../skills/day0-release/tests/test_gates.py | 49 +--- .../evaluation/tests/test_nel_gdpval.py | 6 +- pyproject.toml | 11 - tests/_test_utils/deploy_utils.py | 4 +- .../examples/megatron_example_runner.py | 1 + .../examples/onnx_ptq/aggregate_results.py | 4 +- tests/_test_utils/torch/diffusers_models.py | 4 +- .../torch/export/unified_checkpoint.py | 2 +- .../torch/quantization/quant_utils.py | 2 +- .../fastgen/test_vendored_migration.py | 8 +- .../diffusers/sparsity/test_sparsity.py | 4 +- .../test_export_diffusers_hf_ckpt.py | 2 +- tests/examples/gpt-oss/test_gpt_oss_qat.py | 2 +- .../hf_ptq/test_cast_mxfp4_to_nvfp4.py | 8 +- tests/examples/hf_ptq/test_example_utils.py | 41 +-- tests/examples/hf_ptq/test_hf_ptq_args.py | 30 +- tests/examples/llm_qat/test_llm_qat.py | 4 +- .../examples/megatron_bridge/test_distill.py | 2 +- tests/examples/megatron_bridge/test_qad.py | 4 +- .../specdec_bench/test_upload_to_s3.py | 18 +- .../examples/speculative_decoding/conftest.py | 2 +- .../torch_onnx/test_torch_quant_to_onnx.py | 6 +- .../vllm_serve/test_vllm_mlflow_utils.py | 9 +- tests/gpu/onnx/quantization/test_plugin.py | 10 +- tests/gpu/onnx/test_ort_patching.py | 2 +- tests/gpu/onnx/test_simplify.py | 2 +- tests/gpu/torch/export/test_export.py | 2 +- .../gpu/torch/export/test_export_diffusers.py | 2 +- tests/gpu/torch/export/test_fsdp2_export.py | 4 +- .../gpu/torch/export/test_layerwise_export.py | 22 +- tests/gpu/torch/export/test_offload_export.py | 4 +- tests/gpu/torch/puzzletron/test_puzzletron.py | 2 +- .../tools/test_save_ckpt_from_shards.py | 8 +- .../plugins/test_accelerate_gpu.py | 8 +- .../test_gpt_oss_mxfp4_nvfp4_cast_cuda.py | 3 +- .../gpu/torch/utils/test_model_load_utils.py | 2 +- .../export/test_unified_export_megatron.py | 12 +- .../test_vllm_fakequant_megatron_export.py | 2 +- .../quantization/plugins/test_megatron.py | 6 +- .../torch/speculative/test_dflash.py | 4 +- .../torch/speculative/test_dflash_offline.py | 2 +- .../test_kimi_k3_quantize_to_nvfp4.py | 19 +- .../onnx/autocast/test_referencerunner.py | 8 +- .../quantization/autotune/test_autotuner.py | 4 +- .../autotune/test_pattern_cache.py | 4 +- tests/unit/recipe/test_loader.py | 258 +++++++----------- tests/unit/test_example_run_command.py | 2 +- tests/unit/tools/test_resource_monitor.py | 6 +- .../_runtime/tensorrt/test_engine_builder.py | 4 +- .../torch/export/test_export_diffusers.py | 6 +- .../export/test_fsdp2_parallel_export.py | 4 +- .../torch/export/test_hf_checkpoint_utils.py | 40 ++- .../export/test_mcore_save_safetensors.py | 4 +- tests/unit/torch/export/test_nvfp4_utils.py | 2 +- .../unit/torch/export/test_offload_export.py | 6 +- .../torch/export/test_shard_cast_utils.py | 18 +- .../unit/torch/opt/plugins/test_lr_config.py | 6 +- .../opt/plugins/test_modelopt_arg_parser.py | 12 +- .../puzzletron/test_checkpoint_utils_hf.py | 4 +- .../quantization/test_layerwise_calibrate.py | 8 +- .../test_sequential_checkpoint.py | 4 +- .../speculative/plugins/test_fakebase.py | 2 +- .../speculative/plugins/test_hf_dflash.py | 4 +- .../speculative/plugins/test_hf_domino.py | 2 +- .../speculative/plugins/test_hf_dspark.py | 10 +- .../speculative/plugins/test_hf_lilicorr.py | 4 +- tests/unit/torch/utils/test_mlflow.py | 30 +- .../unit/torch/utils/test_model_load_utils.py | 3 +- tools/check_text_encoding.py | 51 ---- tools/launcher/common/check_regression.py | 2 +- tools/launcher/common/query.py | 2 +- tools/launcher/core.py | 6 +- tools/launcher/tests/conftest.py | 2 +- tools/launcher/tests/test_docker_execution.py | 2 +- tools/launcher/tests/test_docker_launch.py | 8 +- tools/launcher/tests/test_examples_resolve.py | 2 +- tools/launcher/tests/test_yaml_formats.py | 6 +- tools/mcp/modelopt_mcp/bridge.py | 4 +- tools/mcp/tests/test_bridge.py | 93 +++---- tools/resource_monitor.py | 4 +- 197 files changed, 659 insertions(+), 936 deletions(-) delete mode 100755 tools/check_text_encoding.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d9ee5b92fb..2f4fdd595e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,5 @@ # NOTE: Make sure to update version in dev requirements (pyproject.toml) as well! repos: - - repo: local - hooks: - - id: explicit-text-encoding - name: read_text/write_text must pass encoding - description: > - ruff PLW1514 only covers `open`, so Path.read_text/write_text can silently use the - locale codepage -- cp1252 on Windows -- and fail on UTF-8 content. AST-based, because a - line-oriented regex reports a false positive when `encoding=` sits on a later line of a - multi-line call. - language: python - types: [python] - entry: python tools/check_text_encoding.py - - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 04a60956566..22d4681c3ad 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -38,8 +38,6 @@ Changelog **Bug Fixes** -- Text I/O across ModelOpt now passes an explicit ``encoding``. Without it Python uses the locale codepage, which on Windows is cp1252: reading a UTF-8 file raised ``UnicodeDecodeError`` on the first non-Latin-1 byte and writing non-ASCII raised ``UnicodeEncodeError``, on platforms where no such failure appears elsewhere. This affected library code a user reaches directly -- ``modelopt.recipe.loader`` read recipe YAML through the locale codec -- so it was not only a CI concern. Ruff's ``PLW1514`` now guards ``open`` calls, and a pre-commit hook guards ``Path.read_text``/``write_text``, which that rule does not implement. - - Fix ``examples/megatron_bridge/export_quantized_megatron_to_hf.py`` storing the MoE router at Megatron's ``moe_router_dtype``, which is a routing *compute* dtype, not a storage one. The router now exports at the export ``dtype`` like every other unquantized weight, matching what ``hf_ptq.py`` and the released NVFP4 checkpoints contain; pass ``moe_router_dtype`` to ``export_mcore_gpt_to_hf`` explicitly if you want the old fp32 storage. - Fix unified Megatron export writing a second, unreferenced copy of the vocab embedding when a model with MTP layers is exported with pipeline parallelism. The duplicate was never loaded but inflated the checkpoint by the size of the embedding (about 1 GB for Qwen3.6-35B-A3B); re-export to reclaim the space. - Fail fast on non-finite AutoQuantize output gradients with an actionable error before accumulating sensitivity scores, without changing attention backend settings. diff --git a/examples/alpamayo/quantize.py b/examples/alpamayo/quantize.py index 316af8b53a6..6a7b3d56f61 100644 --- a/examples/alpamayo/quantize.py +++ b/examples/alpamayo/quantize.py @@ -641,9 +641,7 @@ def main(): model.config.save_pretrained(args.output_dir) quant_cfg = get_quant_config(model) - with open( - os.path.join(args.output_dir, "hf_quant_config.json"), "w", encoding="utf-8" - ) as f: + with open(os.path.join(args.output_dir, "hf_quant_config.json"), "w") as f: json.dump(quant_cfg, f) print(f"Quantized checkpoint saved to {args.output_dir}") diff --git a/examples/deepseek/deepseek_v3/ptq.py b/examples/deepseek/deepseek_v3/ptq.py index 1ee77e4cfca..96e99624da9 100644 --- a/examples/deepseek/deepseek_v3/ptq.py +++ b/examples/deepseek/deepseek_v3/ptq.py @@ -326,7 +326,7 @@ def load_deepseek_model( torch.set_default_dtype(torch.bfloat16) # get config and build model - with open(model_config, encoding="utf-8") as f: + with open(model_config) as f: model_args = deekseep_model.ModelArgs(**json.load(f)) model_args.max_batch_size = max(batch_size, model_args.max_batch_size) with torch.device("cuda"): @@ -536,7 +536,7 @@ def state_dict_filter(state_dict): if quantized_layers: quant_config["quantization"]["quantized_layers"] = quantized_layers - with open(os.path.join(output_path, "hf_quant_config.json"), "w", encoding="utf-8") as f: + with open(os.path.join(output_path, "hf_quant_config.json"), "w") as f: json.dump(quant_config, f, indent=4) diff --git a/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py b/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py index a21ace015e9..e54fdbebf46 100644 --- a/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py +++ b/examples/deepseek/deepseek_v3/quantize_to_nvfp4.py @@ -88,10 +88,10 @@ def remove_quantization_config_from_original_config(export_dir: str) -> None: Assumes the exported checkpoint directory has a `config.json` containing `quantization_config`. """ config_path = os.path.join(export_dir, "config.json") - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: cfg = json.load(f) del cfg["quantization_config"] - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(cfg, f, indent=2, sort_keys=True) f.write("\n") @@ -129,7 +129,7 @@ def load_and_preprocess_state_dict(modelopt_state_root, world_size=8): def process_quant_config(quant_config_path: str, save_path: str) -> dict[str, Any]: - with open(quant_config_path, encoding="utf-8") as f: + with open(quant_config_path) as f: quant_config = json.load(f) if "exclude_modules" in quant_config["quantization"]: @@ -142,7 +142,7 @@ def process_quant_config(quant_config_path: str, save_path: str) -> dict[str, An _remap_key(quant_config["quantization"]["quantized_layers"]) per_layer_quant_config = quant_config["quantization"]["quantized_layers"] - with open(save_path, "w", encoding="utf-8") as f: + with open(save_path, "w") as f: json.dump(quant_config, f, indent=4) return per_layer_quant_config @@ -173,7 +173,7 @@ def amax_to_fp8_scaling_factor(amax): torch.set_default_dtype(torch.bfloat16) model_index_file = os.path.join(fp8_root, "model.safetensors.index.json") os.makedirs(save_root, exist_ok=True) - with open(model_index_file, encoding="utf-8") as f: + with open(model_index_file) as f: model_index = json.load(f) weight_map = model_index["weight_map"] @@ -286,7 +286,7 @@ def get_tensor(tensor_name): scale_inv_name = f"{weight_name}_scale_inv" if scale_inv_name in weight_map: weight_map.pop(scale_inv_name) - with open(new_model_index_file, "w", encoding="utf-8") as f: + with open(new_model_index_file, "w") as f: json.dump({"metadata": {}, "weight_map": weight_map}, f, indent=2) diff --git a/examples/deepseek/deepseek_v4/ptq.py b/examples/deepseek/deepseek_v4/ptq.py index da16e96efed..89debc5d8a6 100644 --- a/examples/deepseek/deepseek_v4/ptq.py +++ b/examples/deepseek/deepseek_v4/ptq.py @@ -247,7 +247,7 @@ def load_deepseek_v4( torch.cuda.set_device(local_rank) torch.set_default_dtype(torch.bfloat16) - with open(model_config, encoding="utf-8") as f: + with open(model_config) as f: margs = deekseep_v4_model.ModelArgs(**json.load(f)) margs.max_batch_size = max(batch_size, margs.max_batch_size) with torch.device("cuda"): @@ -534,9 +534,7 @@ def _trace(msg): assert m is not None merged.update(m["quantized_layers"]) manifest["quantized_layers"] = sorted(merged) - with open( - os.path.join(output_path, "quantized_layers_manifest.json"), "w", encoding="utf-8" - ) as f: + with open(os.path.join(output_path, "quantized_layers_manifest.json"), "w") as f: json.dump(manifest, f, indent=2) diff --git a/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py b/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py index 8380e9f9158..ac164d7c7c0 100644 --- a/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py +++ b/examples/deepseek/deepseek_v4/quantize_to_nvfp4.py @@ -433,7 +433,7 @@ def _rewrite_config_json( sibling ``hf_quant_config.json``. """ dst = dst_dir / "config.json" - cfg = json.loads(src.read_text(encoding="utf-8")) + cfg = json.loads(src.read_text()) quant_cfg = cfg.get("quantization_config") if not isinstance(quant_cfg, dict): quant_cfg = {} @@ -453,7 +453,7 @@ def _rewrite_config_json( quant_cfg.pop("exclude_modules", None) quant_cfg["ignore"] = moe_quantization["exclude_modules"] cfg["quantization_config"] = quant_cfg - dst.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding="utf-8") + dst.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n") def _write_index_and_manifest( @@ -471,13 +471,11 @@ def _write_index_and_manifest( for k in added: weight_map[k] = shard_name new_index = {"metadata": src_index.get("metadata", {}), "weight_map": weight_map} - (output_ckpt / "model.safetensors.index.json").write_text( - json.dumps(new_index, indent=2), encoding="utf-8" - ) + (output_ckpt / "model.safetensors.index.json").write_text(json.dumps(new_index, indent=2)) _log(f"[index] wrote model.safetensors.index.json ({len(weight_map)} keys)") cfg = _build_hf_quant_config(quantized_layer_names) - (output_ckpt / "hf_quant_config.json").write_text(json.dumps(cfg, indent=2), encoding="utf-8") + (output_ckpt / "hf_quant_config.json").write_text(json.dumps(cfg, indent=2)) def _routed_experts_prefix(expert_proj: str) -> str: @@ -540,7 +538,7 @@ def main(): "model.safetensors.index.json", ) src_config_path = resolve_checkpoint_file(args.source_ckpt, "config.json") - src_index = json.loads(src_index_path.read_text(encoding="utf-8")) + src_index = json.loads(src_index_path.read_text()) amax, input_fallback = _load_merged_amax(args.amax_path, world_size=args.world_size) diff --git a/examples/diffusers/distillation/distillation_trainer.py b/examples/diffusers/distillation/distillation_trainer.py index 4f7e835e4cc..38908f5c8f3 100644 --- a/examples/diffusers/distillation/distillation_trainer.py +++ b/examples/diffusers/distillation/distillation_trainer.py @@ -711,7 +711,7 @@ def _load_calibration_prompts(self) -> list[str]: if not prompts_path.exists(): raise FileNotFoundError(f"Calibration prompts file not found: {prompts_path}") logger.info(f"Loading calibration prompts from {prompts_path}") - with open(prompts_path, encoding="utf-8") as f: + with open(prompts_path) as f: prompts = [line.strip() for line in f if line.strip()] else: logger.info( @@ -1153,7 +1153,7 @@ def _save_config(self) -> None: import yaml config_path = Path(self._config.output_dir) / "training_config.yaml" - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: yaml.dump(self._config.model_dump(), f, default_flow_style=False, indent=2) logger.info( f"Training configuration saved to: {config_path.relative_to(self._config.output_dir)}" @@ -1230,7 +1230,7 @@ def _save_training_state(self) -> Path | None: "quant_cfg": self._distillation_config.quant_cfg, } metadata_path = tmp_dir / "distillation_metadata.json" - with open(metadata_path, "w", encoding="utf-8") as f: + with open(metadata_path, "w") as f: json.dump(metadata, f, indent=2) # Barrier: ensure all ranks finished writing before rename @@ -1376,7 +1376,7 @@ def _load_training_state(self, checkpoint_dir: Path) -> int: # Load custom metadata to get global_step metadata_path = checkpoint_dir / "distillation_metadata.json" if metadata_path.exists(): - with open(metadata_path, encoding="utf-8") as f: + with open(metadata_path) as f: metadata = json.load(f) resumed_step = metadata.get("global_step", 0) logger.info(f"Restored global_step={resumed_step} from metadata") diff --git a/examples/diffusers/fastgen/dmd2_recipe.py b/examples/diffusers/fastgen/dmd2_recipe.py index b8ebb394350..7934a07cf13 100644 --- a/examples/diffusers/fastgen/dmd2_recipe.py +++ b/examples/diffusers/fastgen/dmd2_recipe.py @@ -669,7 +669,7 @@ def _write_dmd_complete_marker(self, path: str) -> None: "checkpoint": os.path.basename(os.path.realpath(path)), "dmd_iteration": int(self._dmd_pipeline._iteration), } - with open(marker_path, "w", encoding="utf-8") as f: + with open(marker_path, "w") as f: json.dump(payload, f) f.write("\n") logging.info("[DMD2] marked checkpoint complete -> %s", marker_path) @@ -833,7 +833,7 @@ def _resolve_checkpoint_pointer(self, pointer: str) -> str | None: return None elif os.path.isfile(pointer + ".txt"): try: - with open(pointer + ".txt", encoding="utf-8") as f: + with open(pointer + ".txt") as f: resolved = f.read().strip() except OSError: return None diff --git a/examples/diffusers/fastgen/export_diffusers_qwen_image.py b/examples/diffusers/fastgen/export_diffusers_qwen_image.py index 930801b1d56..65a17c9c3c0 100644 --- a/examples/diffusers/fastgen/export_diffusers_qwen_image.py +++ b/examples/diffusers/fastgen/export_diffusers_qwen_image.py @@ -117,9 +117,9 @@ def export_diffusers( # 1. model_index.json — copy verbatim (the class registry is the same # whether the transformer weights are live or DMD-distilled). dst_index = os.path.join(output_dir, "model_index.json") - with open(base_index, encoding="utf-8") as f: + with open(base_index) as f: index = json.load(f) - with open(dst_index, "w", encoding="utf-8") as f: + with open(dst_index, "w") as f: json.dump(index, f, indent=2) logger.info("[Diffusers-Export] Wrote %s", dst_index) diff --git a/examples/diffusers/fastgen/inference_dmd2_qwen_image.py b/examples/diffusers/fastgen/inference_dmd2_qwen_image.py index df1a5b72009..5907d0f1b86 100644 --- a/examples/diffusers/fastgen/inference_dmd2_qwen_image.py +++ b/examples/diffusers/fastgen/inference_dmd2_qwen_image.py @@ -483,7 +483,7 @@ def _smoke_test( os.makedirs(os.path.dirname(output_png), exist_ok=True) image.save(output_png) sidecar = output_png.replace(".png", "_stats.json") - with open(sidecar, "w", encoding="utf-8") as f: + with open(sidecar, "w") as f: json.dump(stats, f, indent=2) print(json.dumps(stats, indent=2)) print(f"\nImage saved to: {output_png}") diff --git a/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py b/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py index cd8b46baf15..d11efe30f9e 100644 --- a/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py +++ b/examples/diffusers/fastgen/preprocess/preprocessing_multiprocess.py @@ -122,7 +122,7 @@ def _save_metadata_shards( chunk_data = all_metadata[chunk_start : chunk_start + shard_size] chunk_idx = chunk_start // shard_size shard_file = output_dir / f"metadata_shard_{shard_prefix}s{chunk_idx:04d}.json" - with open(shard_file, "w", encoding="utf-8") as f: + with open(shard_file, "w") as f: json.dump(chunk_data, f, indent=2) shard_files.append(shard_file.name) @@ -140,7 +140,7 @@ def _save_metadata_shards( metadata["shard_rank"] = shard_rank metadata["shard_world"] = shard_world - with open(output_dir / index_filename, "w", encoding="utf-8") as f: + with open(output_dir / index_filename, "w") as f: json.dump(metadata, f, indent=2) diff --git a/examples/diffusers/quantization/utils.py b/examples/diffusers/quantization/utils.py index e7e016ca456..c3cfdcd5cdd 100644 --- a/examples/diffusers/quantization/utils.py +++ b/examples/diffusers/quantization/utils.py @@ -143,7 +143,7 @@ def load_calib_prompts( ) -> list[list[str]]: prompt_list: list[str] = [] if isinstance(calib_data_path, Path): - with open(calib_data_path, encoding="utf-8") as f: + with open(calib_data_path) as f: prompt_list = f.readlines() else: dataset = load_dataset(calib_data_path) diff --git a/examples/gpt-oss/convert_oai_mxfp4_weight_only.py b/examples/gpt-oss/convert_oai_mxfp4_weight_only.py index 41a42b1150d..cb4f03ae553 100644 --- a/examples/gpt-oss/convert_oai_mxfp4_weight_only.py +++ b/examples/gpt-oss/convert_oai_mxfp4_weight_only.py @@ -71,7 +71,7 @@ def convert_and_save(model, tokenizer, output_path: str): config_path = os.path.join(output_path, "config.json") config_data = {} - with open(config_path, encoding="utf-8") as file: + with open(config_path) as file: config_data = json.load(file) config_data["quantization_config"] = { @@ -86,7 +86,7 @@ def convert_and_save(model, tokenizer, output_path: str): config_data.pop("torch_dtype", None) - with open(config_path, "w", encoding="utf-8") as file: + with open(config_path, "w") as file: json.dump(config_data, file, indent=4) # Save tokenizer diff --git a/examples/hf_ptq/example_utils.py b/examples/hf_ptq/example_utils.py index 7b1f25d11fa..be8300d4d67 100755 --- a/examples/hf_ptq/example_utils.py +++ b/examples/hf_ptq/example_utils.py @@ -462,7 +462,7 @@ def _load_tensors_matching( index_file = model_dir / "model.safetensors.index.json" if index_file.exists(): - with open(index_file, encoding="utf-8") as f: + with open(index_file) as f: weight_map = json.load(f)["weight_map"] per_shard: dict[str, list[str]] = {} for key, shard_name in weight_map.items(): @@ -506,7 +506,7 @@ def mtp_layer_prefixes_from_checkpoint(model_path: str) -> list[str]: index_file = Path(model_path) / "model.safetensors.index.json" if not index_file.exists(): return [] - weight_map = json.load(open(index_file, encoding="utf-8"))["weight_map"] + weight_map = json.load(open(index_file))["weight_map"] mtp_keys = [k for k, v in weight_map.items() if "mtp" in k or "mtp" in v] return list(_keys_to_prefixes(mtp_keys)) @@ -607,7 +607,7 @@ def _resolve_file(filename): checkpoint_weights = {} index_file = _resolve_file("model.safetensors.index.json") if index_file: - with open(index_file, encoding="utf-8") as f: + with open(index_file) as f: index = json.load(f) st_filenames = list(set(index.get("weight_map", {}).values())) else: @@ -1449,7 +1449,7 @@ def _log_experiment_json( if not args.checkpoint_exported: return try: - (export_path / _EXPERIMENT_JSON).write_text(text, encoding="utf-8") + (export_path / _EXPERIMENT_JSON).write_text(text) except OSError as e: print(f"[mlflow] WARNING: could not write {export_path / _EXPERIMENT_JSON}: {e}") diff --git a/examples/kimi/kimi_k3/quantize_to_nvfp4.py b/examples/kimi/kimi_k3/quantize_to_nvfp4.py index 6ad617fd1a3..5eb612c029b 100644 --- a/examples/kimi/kimi_k3/quantize_to_nvfp4.py +++ b/examples/kimi/kimi_k3/quantize_to_nvfp4.py @@ -663,7 +663,7 @@ def _rewrite_config_json(src: Path, dst_dir: Path, hf_quant_config: dict[str, An would make a loader dequantize the NVFP4 experts as MXFP4. It is replaced wholesale by the ModelOpt mixed-precision manifest. """ - cfg = json.loads(src.read_text(encoding="utf-8")) + cfg = json.loads(src.read_text()) quant_cfg = convert_hf_quant_config_format(hf_quant_config) # ``convert_hf_quant_config_format`` targets the llm-compressor layout and # stamps ``quant_method="modelopt"``. Loaders gate their mixed-precision @@ -679,9 +679,7 @@ def _rewrite_config_json(src: Path, dst_dir: Path, hf_quant_config: dict[str, An if isinstance(text_cfg, dict): text_cfg.pop("quantization_config", None) cfg["quantization_config"] = quant_cfg - (dst_dir / "config.json").write_text( - json.dumps(cfg, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) + (dst_dir / "config.json").write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n") def _write_index_and_manifest( @@ -726,19 +724,15 @@ def _write_index_and_manifest( metadata = dict(src_index.get("metadata", {})) metadata["total_size"] = sum(r["tensor_bytes"] for r in results) new_index = {"metadata": metadata, "weight_map": weight_map} - (output_ckpt / "model.safetensors.index.json").write_text( - json.dumps(new_index, indent=2), encoding="utf-8" - ) + (output_ckpt / "model.safetensors.index.json").write_text(json.dumps(new_index, indent=2)) _log(f"[index] wrote model.safetensors.index.json ({len(weight_map)} keys)") - (output_ckpt / "hf_quant_config.json").write_text( - json.dumps(hf_quant_config, indent=2), encoding="utf-8" - ) + (output_ckpt / "hf_quant_config.json").write_text(json.dumps(hf_quant_config, indent=2)) def _write_json_atomic(path: Path, value: Any) -> None: tmp = path.with_suffix(path.suffix + ".tmp") - tmp.write_text(json.dumps(value, indent=2, sort_keys=True), encoding="utf-8") + tmp.write_text(json.dumps(value, indent=2, sort_keys=True)) os.replace(tmp, path) @@ -792,7 +786,7 @@ def _rank0_ready( """Check that rank 0 published matching rendezvous settings.""" if not ready_path.exists(): return False - ready = json.loads(ready_path.read_text(encoding="utf-8")) + ready = json.loads(ready_path.read_text()) if ready.get("run_id") != run_id: return False published_world_size = ready.get("world_size") @@ -815,7 +809,7 @@ def _rank_report_ready( if not report_path.exists(): return False try: - report = json.loads(report_path.read_text(encoding="utf-8")) + report = json.loads(report_path.read_text()) except (json.JSONDecodeError, OSError): return False if report.get("run_id") != run_id: @@ -964,7 +958,7 @@ def main(): "model.safetensors.index.json", ) src_config_path = resolve_checkpoint_file(args.source_ckpt, "config.json") - src_index = json.loads(src_index_path.read_text(encoding="utf-8")) + src_index = json.loads(src_index_path.read_text()) shards = sorted(args.source_ckpt.glob("model-*-of-*.safetensors")) assert shards, f"no HF-style shards in {args.source_ckpt}" @@ -1080,7 +1074,7 @@ def all_ranks_done() -> bool: ) _wait_for(all_ranks_done, f"{args.world_size} rank reports", args.sync_timeout) - rank_reports = [json.loads(path.read_text(encoding="utf-8")) for path in rank_paths] + rank_reports = [json.loads(path.read_text()) for path in rank_paths] for rank, report in enumerate(rank_reports): if report.get("run_id") != args.run_id or report.get("rank") != rank: raise RuntimeError(f"rank {rank} report changed after rendezvous validation") diff --git a/examples/llm_eval/lm_eval_hf.py b/examples/llm_eval/lm_eval_hf.py index 6439272c36a..a96a730c16a 100755 --- a/examples/llm_eval/lm_eval_hf.py +++ b/examples/llm_eval/lm_eval_hf.py @@ -301,7 +301,7 @@ def _enforce_accuracy_gate(output_path, task, lower_bound): raise FileNotFoundError(f"No results*.json under {output_path}") # Sort by mtime, not path: a reused output_path nests results under a # <model_name>/ dir, and lexical order would pick the wrong run's file. - with open(max(files, key=os.path.getmtime), encoding="utf-8") as f: + with open(max(files, key=os.path.getmtime)) as f: scores = json.load(f)["results"].get(task, {}) # lm-eval keys metrics by filter, e.g. "acc,none"; take acc (never acc_stderr). acc = next((float(v) for k, v in scores.items() if k == "acc" or k.startswith("acc,")), None) diff --git a/examples/llm_eval/modeling.py b/examples/llm_eval/modeling.py index 3a03c8a9f5a..341e21d956e 100644 --- a/examples/llm_eval/modeling.py +++ b/examples/llm_eval/modeling.py @@ -48,6 +48,7 @@ from pathlib import Path import openai +import rwkv import rwkv.utils import tiktoken import torch @@ -104,7 +105,7 @@ def load(self): if self.tokenizer is None: self.tokenizer = tiktoken.get_encoding("cl100k_base") # chatgpt/gpt-4 - with open(self.model_path, encoding="utf-8") as f: + with open(self.model_path) as f: info = json.load(f) openai.api_key = info["key"] self.engine = info["engine"] diff --git a/examples/llm_eval/simple_evals.py b/examples/llm_eval/simple_evals.py index bcde367969b..2ef12bd51d8 100644 --- a/examples/llm_eval/simple_evals.py +++ b/examples/llm_eval/simple_evals.py @@ -121,19 +121,19 @@ def get_evals(eval_name, debug_mode): file_stem = f"{eval_name}_{model_name}" report_filename = f"/tmp/{file_stem}{debug_suffix}.html" print(f"Writing report to {report_filename}") - with open(report_filename, "w", encoding="utf-8") as fh: + with open(report_filename, "w") as fh: fh.write(common.make_report(result)) metrics = result.metrics | {"score": result.score} print(metrics) result_filename = f"/tmp/{file_stem}{debug_suffix}.json" - with open(result_filename, "w", encoding="utf-8") as f: + with open(result_filename, "w") as f: f.write(json.dumps(metrics, indent=2)) print(f"Writing results to {result_filename}") mergekey2resultpath[f"{file_stem}"] = result_filename merge_metrics = [] for eval_model_name, result_filename in mergekey2resultpath.items(): try: - result = json.load(open(result_filename, "r+", encoding="utf-8")) + result = json.load(open(result_filename, "r+")) except Exception as e: print(e, result_filename) continue diff --git a/examples/llm_qat/dataset_utils.py b/examples/llm_qat/dataset_utils.py index eef5a797092..eaf067026df 100644 --- a/examples/llm_qat/dataset_utils.py +++ b/examples/llm_qat/dataset_utils.py @@ -151,7 +151,7 @@ def is_distributed(self) -> bool: def load_blend_config(config_path: str) -> BlendConfig: """Parse a dataset blend YAML file into a :class:`BlendConfig`.""" - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: raw = yaml.safe_load(f) sources = [DatasetSourceConfig(**s) for s in raw.get("sources", [])] diff --git a/examples/llm_qat/export.py b/examples/llm_qat/export.py index 24a26bcce28..afe2bd4d1cf 100644 --- a/examples/llm_qat/export.py +++ b/examples/llm_qat/export.py @@ -86,7 +86,7 @@ def main(args): model, is_modelopt_qlora=is_qlora ) - with open(f"{base_model_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: + with open(f"{base_model_dir}/hf_quant_config.json", "w") as file: json.dump(hf_quant_config, file, indent=4) hf_quant_config = convert_hf_quant_config_format(hf_quant_config) @@ -104,7 +104,7 @@ def main(args): config_data["quantization_config"] = hf_quant_config - with open(config_path, "w", encoding="utf-8") as file: + with open(config_path, "w") as file: json.dump(config_data, file, indent=4) # Save tokenizer diff --git a/examples/llm_sparsity/weight_sparsity/data_prep.py b/examples/llm_sparsity/weight_sparsity/data_prep.py index 2aa00d90c2b..62be755eeca 100644 --- a/examples/llm_sparsity/weight_sparsity/data_prep.py +++ b/examples/llm_sparsity/weight_sparsity/data_prep.py @@ -71,9 +71,9 @@ def main(): # save dataset to disk os.makedirs(args.save_path, exist_ok=True) - with open(os.path.join(args.save_path, "cnn_train.json"), "w", encoding="utf-8") as write_f: + with open(os.path.join(args.save_path, "cnn_train.json"), "w") as write_f: json.dump(list(tokenized_dataset["train"]["text"]), write_f, indent=4, ensure_ascii=False) - with open(os.path.join(args.save_path, "cnn_eval.json"), "w", encoding="utf-8") as write_f: + with open(os.path.join(args.save_path, "cnn_eval.json"), "w") as write_f: json.dump(list(tokenized_dataset["test"]["text"]), write_f, indent=4, ensure_ascii=False) diff --git a/examples/llm_sparsity/weight_sparsity/eval.py b/examples/llm_sparsity/weight_sparsity/eval.py index 5c1e1f160f1..a5f2fb91b2d 100644 --- a/examples/llm_sparsity/weight_sparsity/eval.py +++ b/examples/llm_sparsity/weight_sparsity/eval.py @@ -81,7 +81,7 @@ def prepare_tokenizer(accelerator, checkpoint_path, model_max_length, padding_si def preprocess_cnndailymail(accelerator, data_path, calib=False): # Load from CNN dailymail - with open(data_path, encoding="utf-8") as fh: + with open(data_path) as fh: list_data_dict = json.load(fh) sources = [G_PROMPT_INPUT.format_map(example) for example in list_data_dict] diff --git a/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py b/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py index 97afbd21d4f..80cb473cc63 100644 --- a/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py +++ b/examples/minimax_m3/hf_ptq_mixed_mxfp8_nvfp4.py @@ -65,7 +65,7 @@ def _log(message: str) -> None: def _load_index(checkpoint: Path) -> dict[str, str]: - index = json.loads((checkpoint / "model.safetensors.index.json").read_text(encoding="utf-8")) + index = json.loads((checkpoint / "model.safetensors.index.json").read_text()) return index["weight_map"] @@ -332,7 +332,7 @@ def main() -> None: _copy_mxfp8_base(mxfp8, destination, mxfp8_map, new_index) new_index = _rename_checkpoint_shards(destination, new_index) - mxfp8_config = json.loads((mxfp8 / "config.json").read_text(encoding="utf-8")) + mxfp8_config = json.loads((mxfp8 / "config.json").read_text()) vendor_quantization = mxfp8_config.get("quantization_config", {}) mixed_quant_config = _build_quant_config( mxfp8_map, @@ -341,13 +341,10 @@ def main() -> None: ) mxfp8_config["quantization_config"] = mixed_quant_config["quantization"] - (destination / "config.json").write_text(json.dumps(mxfp8_config, indent=2), encoding="utf-8") - (destination / "hf_quant_config.json").write_text( - json.dumps(mixed_quant_config, indent=2), encoding="utf-8" - ) + (destination / "config.json").write_text(json.dumps(mxfp8_config, indent=2)) + (destination / "hf_quant_config.json").write_text(json.dumps(mixed_quant_config, indent=2)) (destination / "model.safetensors.index.json").write_text( - json.dumps({"metadata": {"format": "pt"}, "weight_map": new_index}, indent=2), - encoding="utf-8", + json.dumps({"metadata": {"format": "pt"}, "weight_map": new_index}, indent=2) ) _copy_ancillary_files(mxfp8, destination) _log(f"[mixed] done -> {destination}") diff --git a/examples/onnx_ptq/evaluate.py b/examples/onnx_ptq/evaluate.py index c6803821b35..89d6daca070 100644 --- a/examples/onnx_ptq/evaluate.py +++ b/examples/onnx_ptq/evaluate.py @@ -125,7 +125,7 @@ def main(): ["Top 5", top5_accuracy], ["Latency", latency], ] - with open(args.results_path, "w", encoding="utf-8", newline="") as csvfile: + with open(args.results_path, "w", newline="") as csvfile: writer = csv.writer(csvfile) writer.writerows(results) diff --git a/examples/onnx_ptq/evaluation.py b/examples/onnx_ptq/evaluation.py index 5d8d1287524..0fdcfd18b9a 100644 --- a/examples/onnx_ptq/evaluation.py +++ b/examples/onnx_ptq/evaluation.py @@ -77,7 +77,7 @@ def __init__(self, root, transform=None): transform: Optional transform to apply to images. """ img_dir = Path(root) / "validation" - with open(Path(root) / "val.txt", encoding="utf-8") as f: + with open(Path(root) / "val.txt") as f: entries = [line.strip().split() for line in f] self.samples = [(img_dir / name, int(label)) for name, label in entries] self.transform = transform diff --git a/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb b/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb index b9d9b8e57d2..85a79aa8836 100644 --- a/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb +++ b/examples/pruning/minitron_vs_puzzletron/scenario2_puzzletron.ipynb @@ -384,7 +384,7 @@ "\n", "config_path = \"/opt/Model-Optimizer/examples/puzzletron/configs/qwen3-8b_pruneffn_memory/qwen3_8b_pruneffn_memory.yaml\"\n", "\n", - "with open(config_path, encoding=\"utf-8\") as f:\n", + "with open(config_path) as f:\n", " config = yaml.safe_load(f)\n", "\n", "# Add sweep configuration\n", @@ -394,7 +394,7 @@ " \"output_csv\": \"/workspace/puzzle_dir/mip_sweep_results.csv\",\n", "}\n", "\n", - "with open(config_path, \"w\", encoding=\"utf-8\") as f:\n", + "with open(config_path, \"w\") as f:\n", " yaml.dump(config, f, default_flow_style=False)\n", "\n", "print(\"Sweep config added. Compression rates: [0.5, 0.6, 0.7, 0.8, 0.9, 1.0]\")" diff --git a/examples/specdec_bench/run.py b/examples/specdec_bench/run.py index 2343690f2ba..9ed7ec44272 100644 --- a/examples/specdec_bench/run.py +++ b/examples/specdec_bench/run.py @@ -405,7 +405,7 @@ def run_simple(args): args = parser.parse_args() if args.runtime_params is not None: - with open(args.runtime_params, encoding="utf-8") as f: + with open(args.runtime_params) as f: args.runtime_params = yaml.safe_load(f) else: args.runtime_params = {} diff --git a/examples/specdec_bench/specdec_bench/datasets/mtbench.py b/examples/specdec_bench/specdec_bench/datasets/mtbench.py index 7485b2182d6..cb58dd21038 100644 --- a/examples/specdec_bench/specdec_bench/datasets/mtbench.py +++ b/examples/specdec_bench/specdec_bench/datasets/mtbench.py @@ -36,7 +36,7 @@ def __init__(self, path, num_samples=80, **kwargs): self._preprocess(path) def _preprocess(self, path): - with open(path, encoding="utf-8") as f: + with open(path) as f: for json_line in f: line = json.loads(json_line) key = "turns" if "turns" in line else "prompt" diff --git a/examples/specdec_bench/specdec_bench/datasets/specbench.py b/examples/specdec_bench/specdec_bench/datasets/specbench.py index cc5b3a3ab01..a14d3403903 100644 --- a/examples/specdec_bench/specdec_bench/datasets/specbench.py +++ b/examples/specdec_bench/specdec_bench/datasets/specbench.py @@ -25,7 +25,7 @@ def __init__(self, path, num_samples=480, **kwargs): self._preprocess(path) def _preprocess(self, path): - with open(path, encoding="utf-8") as f: + with open(path) as f: for json_line in f: line = json.loads(json_line) self.data.append( diff --git a/examples/specdec_bench/specdec_bench/metrics/aa_timing.py b/examples/specdec_bench/specdec_bench/metrics/aa_timing.py index 40c780e6b96..cce735d5f1c 100644 --- a/examples/specdec_bench/specdec_bench/metrics/aa_timing.py +++ b/examples/specdec_bench/specdec_bench/metrics/aa_timing.py @@ -45,8 +45,8 @@ def process_step(self, step_outputs, request_id, turn_id): def process_final(self, text_outputs): gen_tp_time = [] - start_time = min(t[0] for t in self.timing) - end_time = max(t[-1] for t in self.timing) + start_time = min([t[0] for t in self.timing]) + end_time = max([t[-1] for t in self.timing]) self.out["AA Output TPS"] = sum(self.total_tokens) / (end_time - start_time) for tokens, times in zip(self.total_tokens, self.timing): if len(times) > 2: diff --git a/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py b/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py index 4eb8ef35024..819f251a3d8 100644 --- a/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py +++ b/examples/specdec_bench/specdec_bench/metrics/acceptance_rate.py @@ -88,9 +88,7 @@ def clear(self): self.prompt_ar = [] def _format_write_output(self, outputs): - with open( - os.path.join(self.directory, "responses.jsonl"), "w", encoding="utf-8" - ) as outfile: + with open(os.path.join(self.directory, "responses.jsonl"), "w") as outfile: for i, messages in enumerate(outputs): q_id = i out_line = {} diff --git a/examples/specdec_bench/specdec_bench/metrics/base.py b/examples/specdec_bench/specdec_bench/metrics/base.py index cc45b690c30..62a2fbf2cf3 100644 --- a/examples/specdec_bench/specdec_bench/metrics/base.py +++ b/examples/specdec_bench/specdec_bench/metrics/base.py @@ -38,13 +38,13 @@ def write(self): if self.out: filename = os.path.join(self.directory, f"{self.name}.json") if os.path.exists(filename): - with open(filename, encoding="utf-8") as json_file: + with open(filename) as json_file: existing_data = json.load(json_file) existing_data.append(self.out) else: existing_data = [self.out] - with open(filename, "w", encoding="utf-8") as json_file: + with open(filename, "w") as json_file: json.dump(existing_data, json_file, indent=4) @classmethod diff --git a/examples/specdec_bench/specdec_bench/metrics/mtbench.py b/examples/specdec_bench/specdec_bench/metrics/mtbench.py index dfa97c5a08c..48655de1fba 100644 --- a/examples/specdec_bench/specdec_bench/metrics/mtbench.py +++ b/examples/specdec_bench/specdec_bench/metrics/mtbench.py @@ -62,9 +62,7 @@ def process_final(self, text_outputs): self._format_write_output(text_outputs) def _format_write_output(self, outputs): - with open( - os.path.join(self.directory, "mtbench_responses.jsonl"), "w", encoding="utf-8" - ) as outfile: + with open(os.path.join(self.directory, "mtbench_responses.jsonl"), "w") as outfile: for i, messages in enumerate(outputs): q_id = i + 81 out_line = {} diff --git a/examples/specdec_bench/specdec_bench/metrics/specbench.py b/examples/specdec_bench/specdec_bench/metrics/specbench.py index 0f39a45e602..5364e719d24 100644 --- a/examples/specdec_bench/specdec_bench/metrics/specbench.py +++ b/examples/specdec_bench/specdec_bench/metrics/specbench.py @@ -72,9 +72,7 @@ def process_final(self, text_outputs): self._create_visualizations(text_outputs) def _format_write_output(self, outputs): - with open( - os.path.join(self.directory, "specbench_responses.jsonl"), "w", encoding="utf-8" - ) as outfile: + with open(os.path.join(self.directory, "specbench_responses.jsonl"), "w") as outfile: for i, messages in enumerate(outputs): out_line = {} out_line["question_id"] = self.requests[i].question_id @@ -108,9 +106,7 @@ def _pretty_print_results(self): console.print(table) def _dump_results(self): - with open( - os.path.join(self.directory, "specbench_results.json"), "w", encoding="utf-8" - ) as outfile: + with open(os.path.join(self.directory, "specbench_results.json"), "w") as outfile: json.dump(self.out, outfile, indent=4) def _create_visualizations( diff --git a/examples/specdec_bench/specdec_bench/metrics/timing.py b/examples/specdec_bench/specdec_bench/metrics/timing.py index 49c5b002648..5bf33c604e0 100644 --- a/examples/specdec_bench/specdec_bench/metrics/timing.py +++ b/examples/specdec_bench/specdec_bench/metrics/timing.py @@ -28,15 +28,17 @@ def __init__(self, tp_size): def process_step(self, step_outputs, request_id, turn_id): self.timing.append(step_outputs["token_times"]) - self.total_tokens.append(sum(sum(len(j) for j in i) for i in step_outputs["output_ids"])) + self.total_tokens.append( + sum([sum([len(j) for j in i]) for i in step_outputs["output_ids"]]) + ) def process_final(self, text_outputs): e2e_time = [] ttft_time = [] tpot_time = [] gen_tp_time = [] - start_time = min(t[0] for t in self.timing) - end_time = max(t[-1] for t in self.timing) + start_time = min([t[0] for t in self.timing]) + end_time = max([t[-1] for t in self.timing]) self.out["Output TPS"] = sum(self.total_tokens) / (end_time - start_time) self.out["Output TPS/gpu"] = self.out["Output TPS"] / self.tp_size for tokens, times in zip(self.total_tokens, self.timing): diff --git a/examples/specdec_bench/specdec_bench/utils.py b/examples/specdec_bench/specdec_bench/utils.py index e3c294c6cec..1f7a7b9bef9 100644 --- a/examples/specdec_bench/specdec_bench/utils.py +++ b/examples/specdec_bench/specdec_bench/utils.py @@ -38,7 +38,7 @@ def get_tokenizer(path, trust_remote_code=False): extra_special_tokens = None tokenizer_config_path = os.path.join(path, "tokenizer_config.json") if os.path.exists(tokenizer_config_path): - with open(tokenizer_config_path, encoding="utf-8") as f: + with open(tokenizer_config_path) as f: tokenizer_config = json.load(f) extra_special_tokens = tokenizer_config.get("extra_special_tokens") @@ -68,7 +68,7 @@ def decode_chat(tokenizer, out_tokens): def read_json(path): if path is not None: - with open(path, encoding="utf-8") as f: + with open(path) as f: data = json.load(f) return data return {} @@ -149,7 +149,7 @@ def _git_sha(path): def _shard_files_from_index(index_path): """Return the set of shard filenames referenced by a safetensors index JSON.""" try: - with open(index_path, encoding="utf-8") as f: + with open(index_path) as f: wm = json.load(f).get("weight_map", {}) or {} return set(wm.values()) except Exception: @@ -320,5 +320,5 @@ def dump_env(args, save_dir, overrides=None): config["huggingface_model_id"] = os.environ.get("HUGGINGFACE_MODEL_ID") or None os.makedirs(save_dir, exist_ok=True) - with open(os.path.join(save_dir, "configuration.json"), "w", encoding="utf-8") as f: + with open(os.path.join(save_dir, "configuration.json"), "w") as f: json.dump(config, f, indent=4, default=str) diff --git a/examples/specdec_bench/upload_to_s3.py b/examples/specdec_bench/upload_to_s3.py index 4a6910f68db..067ea25bce3 100644 --- a/examples/specdec_bench/upload_to_s3.py +++ b/examples/specdec_bench/upload_to_s3.py @@ -63,7 +63,7 @@ def _check_provenance(run_dir: Path) -> list[str]: if not cfg_path.is_file(): return list(_REQUIRED_PROVENANCE_FIELDS) try: - with open(cfg_path, encoding="utf-8") as f: + with open(cfg_path) as f: cfg = json.load(f) except (OSError, json.JSONDecodeError): return list(_REQUIRED_PROVENANCE_FIELDS) diff --git a/examples/speculative_decoding/collect_hidden_states/common.py b/examples/speculative_decoding/collect_hidden_states/common.py index e113169e8b5..78b317853b6 100644 --- a/examples/speculative_decoding/collect_hidden_states/common.py +++ b/examples/speculative_decoding/collect_hidden_states/common.py @@ -106,7 +106,7 @@ def load_chat_template(path: Path | None) -> str | None: """Read a Jinja chat template from ``path``, or return ``None`` if not provided.""" if path is None: return None - with open(path, encoding="utf-8") as f: + with open(path) as f: return f.read() diff --git a/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py b/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py index 528681eaa62..f0bbe4f951e 100644 --- a/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py +++ b/examples/speculative_decoding/collect_hidden_states/send_conversations_for_hiddens.py @@ -142,7 +142,7 @@ async def main(args: argparse.Namespace) -> None: # Use /tmp/meta.json to communicate with the local serving engine. # See usage guide for more details - with temp_meta_file.open("w", encoding="utf-8") as f: + with temp_meta_file.open("w") as f: json.dump( { "conversation_id": conversation_id, diff --git a/examples/speculative_decoding/eagle_utils.py b/examples/speculative_decoding/eagle_utils.py index 6c29f07dccb..0691ca06f52 100644 --- a/examples/speculative_decoding/eagle_utils.py +++ b/examples/speculative_decoding/eagle_utils.py @@ -74,7 +74,7 @@ def make_speculative_data_module( chat_template = None if getattr(data_args, "chat_template", None): template_path = data_args.chat_template - with open(template_path, encoding="utf-8") as f: + with open(template_path) as f: chat_template = f.read() print_rank_0(f"Loaded chat template from {template_path}") @@ -347,7 +347,7 @@ def on_save(self, args, state, control, **kwargs): save_file(drafter_sd, os.path.join(export_dir, "model.safetensors")) config = exporter._export_config() - with open(os.path.join(export_dir, "config.json"), "w", encoding="utf-8") as f: + with open(os.path.join(export_dir, "config.json"), "w") as f: json.dump(config, f, indent=2) total_mb = sum(v.nbytes for v in drafter_sd.values()) / 1024 / 1024 diff --git a/examples/speculative_decoding/example.ipynb b/examples/speculative_decoding/example.ipynb index 0f91387358e..d9d9be3a668 100644 --- a/examples/speculative_decoding/example.ipynb +++ b/examples/speculative_decoding/example.ipynb @@ -92,7 +92,7 @@ "from eagle_utils import DataCollatorWithPadding, LazySupervisedDataset\n", "from transformers import Trainer\n", "\n", - "with open(\"/tmp/Daring-Anteater/train.jsonl\", encoding=\"utf-8\") as f:\n", + "with open(\"/tmp/Daring-Anteater/train.jsonl\") as f:\n", " data_json = [json.loads(line) for line in f]\n", "train_dataset = LazySupervisedDataset(data_json[: int(len(data_json) * 0.95)], tokenizer=tokenizer)\n", "eval_dataset = LazySupervisedDataset(data_json[int(len(data_json) * 0.95) :], tokenizer=tokenizer)\n", @@ -196,10 +196,10 @@ "\"\"\"\n", "\n", "# Dump the two scripts into /tmp\n", - "with open(\"/tmp/trtllm_serve.sh\", \"w\", encoding=\"utf-8\") as f:\n", + "with open(\"/tmp/trtllm_serve.sh\", \"w\") as f:\n", " f.write(trtllm_serve_script)\n", "\n", - "with open(\"/tmp/extra-llm-api-config.yml\", \"w\", encoding=\"utf-8\") as f:\n", + "with open(\"/tmp/extra-llm-api-config.yml\", \"w\") as f:\n", " f.write(extra_llm_api_config)" ] }, @@ -349,7 +349,7 @@ " --dtype float16\n", "\"\"\"\n", "\n", - "with open(\"/tmp/sglang_serve.sh\", \"w\", encoding=\"utf-8\") as f:\n", + "with open(\"/tmp/sglang_serve.sh\", \"w\") as f:\n", " f.write(sglang_serve_script)" ] }, diff --git a/examples/speculative_decoding/medusa_utils.py b/examples/speculative_decoding/medusa_utils.py index a73fb39650e..30dc238c35a 100644 --- a/examples/speculative_decoding/medusa_utils.py +++ b/examples/speculative_decoding/medusa_utils.py @@ -209,10 +209,10 @@ def make_medusa_supervised_data_module( print_rank_0("Loading data...") if data_args.data_path.endswith("jsonl"): - with open(data_args.data_path, encoding="utf-8") as f: + with open(data_args.data_path) as f: data_json = [json.loads(line) for line in f] else: - data_json = json.load(open(data_args.data_path, encoding="utf-8")) + data_json = json.load(open(data_args.data_path)) train_dataset = dataset_cls(data_json[: int(len(data_json) * 0.95)], tokenizer=tokenizer) eval_dataset = dataset_cls(data_json[int(len(data_json) * 0.95) :], tokenizer=tokenizer) diff --git a/examples/speculative_decoding/scripts/calibrate_draft_vocab.py b/examples/speculative_decoding/scripts/calibrate_draft_vocab.py index 90eb9c9a0a7..19f387a6546 100644 --- a/examples/speculative_decoding/scripts/calibrate_draft_vocab.py +++ b/examples/speculative_decoding/scripts/calibrate_draft_vocab.py @@ -47,7 +47,7 @@ def main(): print("Calibrating vocab...") tokenizer = AutoTokenizer.from_pretrained(args.model) - with open(args.data, encoding="utf-8") as f: + with open(args.data) as f: lines = islice(f, args.calibrate_size) if args.calibrate_size else f conversations = [ (d := json.loads(line)).get("messages") or d["conversations"] for line in lines diff --git a/examples/speculative_decoding/scripts/quantize_drafter.py b/examples/speculative_decoding/scripts/quantize_drafter.py index 26a23d5aae5..0f4237d22ab 100644 --- a/examples/speculative_decoding/scripts/quantize_drafter.py +++ b/examples/speculative_decoding/scripts/quantize_drafter.py @@ -304,7 +304,7 @@ def main(): export_dir.mkdir(parents=True, exist_ok=True) save_file(export_sd, export_dir / "model.safetensors", metadata={"format": "pt"}) - config = json.loads((source_dir / "config.json").read_text(encoding="utf-8")) + config = json.loads((source_dir / "config.json").read_text()) hf_quant_config = get_quant_config(root) # ``get_quant_config`` only knows the linear view, so tensors it never saw (norms, 1-D # weights) are missing and a loader walking the checkpoint expects a scale for them. @@ -351,10 +351,8 @@ def main(): # ``ignore``, not ``exclude_modules``. config["quantization_config"]["ignore"] = list(exclude_modules) config["torch_dtype"] = args.dtype - (export_dir / "config.json").write_text(json.dumps(config, indent=2), encoding="utf-8") - (export_dir / "hf_quant_config.json").write_text( - json.dumps(hf_quant_config, indent=2), encoding="utf-8" - ) + (export_dir / "config.json").write_text(json.dumps(config, indent=2)) + (export_dir / "hf_quant_config.json").write_text(json.dumps(hf_quant_config, indent=2)) for extra in SIDECAR_FILES: if (source_dir / extra).is_file(): diff --git a/examples/speculative_decoding/scripts/send_conversation_vllm.py b/examples/speculative_decoding/scripts/send_conversation_vllm.py index 33f60533e41..9271af121bc 100644 --- a/examples/speculative_decoding/scripts/send_conversation_vllm.py +++ b/examples/speculative_decoding/scripts/send_conversation_vllm.py @@ -192,7 +192,7 @@ async def main(args: argparse.Namespace) -> None: # Use /tmp/meta.json to communicate with the local serving engine. # See usage guide for more details - with temp_meta_file.open("w", encoding="utf-8") as f: + with temp_meta_file.open("w") as f: json.dump( { "conversation_id": conversation_id, diff --git a/examples/speculative_decoding/scripts/server_generate.py b/examples/speculative_decoding/scripts/server_generate.py index 6a16f464e8d..a0516bc3922 100644 --- a/examples/speculative_decoding/scripts/server_generate.py +++ b/examples/speculative_decoding/scripts/server_generate.py @@ -58,10 +58,10 @@ if args.data_path.endswith("jsonl"): - with open(args.data_path, encoding="utf-8") as f: + with open(args.data_path) as f: data = [json.loads(line) for line in f] else: - data = json.load(open(args.data_path, encoding="utf-8")) + data = json.load(open(args.data_path)) client = OpenAI( base_url=args.url, @@ -129,7 +129,7 @@ def generate_data(messages, idx, system_prompt): to_write = {"conversation_id": idx, "conversations": output_messages} if truncated: to_write["truncated"] = True - with open(args.output_path, "a", encoding="utf-8") as f: + with open(args.output_path, "a") as f: # write in share gpt format f.write(json.dumps(to_write) + "\n") else: @@ -150,7 +150,7 @@ def generate_data(messages, idx, system_prompt): spaces_between_special_tokens=False, ) response = response.choices[0].text.strip() - with open(args.output_path, "a", encoding="utf-8") as f: + with open(args.output_path, "a") as f: # write in share gpt format if args.log_empty_conversations: to_write = {"conversation_id": idx, "text": prompt + response} @@ -167,7 +167,7 @@ def generate_data(messages, idx, system_prompt): finished_ids = [] done = False if os.path.exists(args.output_path): - with open(args.output_path, encoding="utf-8") as f: + with open(args.output_path) as f: for line in f: outdata = json.loads(line) finished_ids.append(outdata.get("conversation_id", -1)) @@ -199,5 +199,5 @@ def generate_data(messages, idx, system_prompt): future.result() if args.log_empty_conversations: - with open(args.output_path, "a", encoding="utf-8") as f: + with open(args.output_path, "a") as f: f.write(json.dumps({"finished": True}) + "\n") diff --git a/examples/torch_trt/torch_tensorrt_accuracy.py b/examples/torch_trt/torch_tensorrt_accuracy.py index 40453faba36..c450b458993 100644 --- a/examples/torch_trt/torch_tensorrt_accuracy.py +++ b/examples/torch_trt/torch_tensorrt_accuracy.py @@ -219,7 +219,7 @@ def to_eval_model(m: torch.nn.Module, what: str) -> torch.nn.Module: results.append([tag, top1, top5]) if args.results_path: - with open(args.results_path, "w", encoding="utf-8", newline="") as f: + with open(args.results_path, "w", newline="") as f: csv.writer(f).writerows(results) print(f"\nWrote results to {args.results_path}") diff --git a/examples/torch_trt/torch_tensorrt_ptq.py b/examples/torch_trt/torch_tensorrt_ptq.py index 8b66e01f10a..60f8bd6ec23 100644 --- a/examples/torch_trt/torch_tensorrt_ptq.py +++ b/examples/torch_trt/torch_tensorrt_ptq.py @@ -175,7 +175,7 @@ def dump_trt_layer_info(trt_model: torch.nn.Module, path: Path) -> None: print("No TorchTensorRTModule found; nothing to dump (whole graph fell back to PyTorch?).") return path.parent.mkdir(parents=True, exist_ok=True) - path.write_text("\n".join(infos), encoding="utf-8") + path.write_text("\n".join(infos)) print(f"Wrote TRT layer info ({len(infos)} engine(s)) to {path}") diff --git a/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py b/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py index 0c909eecf20..4513ae87955 100644 --- a/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py +++ b/examples/windows/accuracy_benchmark/fvd_metrics/compute_fvd.py @@ -405,7 +405,7 @@ def main(): out_dir = os.path.dirname(args.output) if out_dir: os.makedirs(out_dir, exist_ok=True) - with open(args.output, "w", encoding="utf-8") as f: + with open(args.output, "w") as f: json.dump(result, f, indent=2) log.info(f"Results saved to {args.output}") diff --git a/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py b/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py index aa2d4e886f4..3a3fbbdd2b9 100644 --- a/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py +++ b/examples/windows/accuracy_benchmark/kl_divergence_metrics/compute_kl_divergence.py @@ -854,7 +854,7 @@ def main(): # Save results if output file specified if args.output: print(f"\n[INFO] Saving results to: {args.output}") - with open(args.output, "w", encoding="utf-8") as f: + with open(args.output, "w") as f: json.dump(final_results, f, indent=2) print("[INFO] Results saved successfully") diff --git a/examples/windows/accuracy_benchmark/mmlu_benchmark.py b/examples/windows/accuracy_benchmark/mmlu_benchmark.py index 9732608baf5..54573e6425e 100644 --- a/examples/windows/accuracy_benchmark/mmlu_benchmark.py +++ b/examples/windows/accuracy_benchmark/mmlu_benchmark.py @@ -409,7 +409,7 @@ def evaluate_ort_native(args, subject, sess, tokenizer, dev_df, test_df, config) def save_results_to_json(results, output_file="results.json"): os.makedirs(os.path.dirname(output_file), exist_ok=True) - with open(output_file, "w", encoding="utf-8") as f: + with open(output_file, "w") as f: json.dump(results, f, indent=4) @@ -487,7 +487,7 @@ def evaluate_func(args, subject, dev_df, test_df): # Create the InferenceSession with the selected provider sess = rt.InferenceSession(os.path.join(onnx_model_path, "model.onnx"), providers=providers) - with open(os.path.join(onnx_model_path, "config.json"), encoding="utf-8") as config_file: + with open(os.path.join(onnx_model_path, "config.json")) as config_file: config = json.load(config_file) tokenizer = AutoTokenizer.from_pretrained(onnx_model_path, local_files_only=True) diff --git a/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py b/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py index 0f900e50a4c..899cc810bf5 100644 --- a/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py +++ b/examples/windows/accuracy_benchmark/perplexity_metrics/perplexity_metrics.py @@ -336,7 +336,7 @@ def perplexity_eval(model_dir, input_len=1024, chunk_size=None): # Load model configuration from JSON file (optional) model_cfg_json = None try: - with open(f"{model_dir}/genai_config.json", encoding="utf-8") as file: + with open(f"{model_dir}/genai_config.json") as file: model_cfg_json = json.load(file) if DEBUG: print( diff --git a/examples/windows/accuracy_benchmark/trtllm_utils.py b/examples/windows/accuracy_benchmark/trtllm_utils.py index 71981ad35cd..6977b12935c 100644 --- a/examples/windows/accuracy_benchmark/trtllm_utils.py +++ b/examples/windows/accuracy_benchmark/trtllm_utils.py @@ -86,7 +86,7 @@ def supports_inflight_batching(engine_dir): def read_decoder_start_token_id(engine_dir): - with open(Path(engine_dir) / "config.json", encoding="utf-8") as f: + with open(Path(engine_dir) / "config.json") as f: config = json.load(f) return config["pretrained_config"]["decoder_start_token_id"] @@ -94,7 +94,7 @@ def read_decoder_start_token_id(engine_dir): def read_model_name(engine_dir: str): engine_version = get_engine_version(engine_dir) - with open(Path(engine_dir) / "config.json", encoding="utf-8") as f: + with open(Path(engine_dir) / "config.json") as f: config = json.load(f) if engine_version is None: @@ -163,7 +163,7 @@ def load_tokenizer( if "qwen" in model_name.lower() and model_version == "qwen": if tokenizer_dir is None: raise ValueError("tokenizer_dir must be provided for QWEN models") - with open(Path(tokenizer_dir) / "generation_config.json", encoding="utf-8") as f: + with open(Path(tokenizer_dir) / "generation_config.json") as f: gen_config = json.load(f) pad_id = gen_config["pad_token_id"] end_id = gen_config["eos_token_id"] diff --git a/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py b/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py index c085a0c3fae..855136bbd32 100644 --- a/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py +++ b/examples/windows/diffusers/qad_example/sample_example_qad_diffusers.py @@ -548,7 +548,7 @@ def _save_checkpoint(self) -> Path: amax_dict = extract_amax_values(state_dict) if amax_dict: amax_path = save_dir / f"amax_step_{self._global_step:05d}.json" - with open(amax_path, "w", encoding="utf-8") as f: + with open(amax_path, "w") as f: json.dump( {"total_amax_keys": len(amax_dict), "amax_values": amax_dict}, f, @@ -678,7 +678,7 @@ def create_inference_checkpoint( if amax_dict: amax_path = output_path.parent / (output_path.stem + "_amax.json") output_path.parent.mkdir(parents=True, exist_ok=True) - with open(amax_path, "w", encoding="utf-8") as f: + with open(amax_path, "w") as f: json.dump( {"total_amax_keys": len(amax_dict), "amax_values": amax_dict}, f, @@ -750,7 +750,7 @@ def create_inference_checkpoint( print(f" {conv}: {cnt} tensors") dtype_log_path = output_path.parent / (output_path.stem + "_dtype_fixes.json") - with open(dtype_log_path, "w", encoding="utf-8") as f: + with open(dtype_log_path, "w") as f: json.dump({"total": dtype_fixed, "fixes": dtype_mismatches}, f, indent=2) print(f" Dtype fix log saved to: {dtype_log_path}") @@ -948,7 +948,7 @@ def main(): # ── Train ── import yaml - with open(args.config, encoding="utf-8") as f: + with open(args.config) as f: config_dict = yaml.safe_load(f) # Extract QAD-specific config (not part of LtxTrainerConfig) diff --git a/experimental/dms/models/qwen3/train.py b/experimental/dms/models/qwen3/train.py index ffa960a058d..78fbb19a2fb 100644 --- a/experimental/dms/models/qwen3/train.py +++ b/experimental/dms/models/qwen3/train.py @@ -66,7 +66,7 @@ def load_config(path: str) -> dict: """Load a YAML configuration file.""" - with open(path, encoding="utf-8") as f: + with open(path) as f: return yaml.safe_load(f) @@ -74,7 +74,7 @@ def save_config(cfg: dict, output_dir: str) -> None: """Save the configuration to the output directory for reproducibility.""" os.makedirs(output_dir, exist_ok=True) config_path = os.path.join(output_dir, "config.yaml") - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: yaml.dump(cfg, f, default_flow_style=False, sort_keys=False) logger.info(f"Saved config to {config_path}") @@ -238,11 +238,11 @@ def extract_student_model( # Update config.json with auto_map config_path = Path(save_path) / "config.json" - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config = json.load(f) config.pop("architectures", None) config["auto_map"] = AUTO_MAP_CONFIG - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(config, f, indent=2) # Copy model implementation files for trust_remote_code diff --git a/modelopt/deploy/llm/generate.py b/modelopt/deploy/llm/generate.py index 8d8062973eb..39306504137 100644 --- a/modelopt/deploy/llm/generate.py +++ b/modelopt/deploy/llm/generate.py @@ -79,7 +79,7 @@ def __init__( reuse, shared-prefix requests only return logits for the recomputed suffix, which breaks per-token logprob computation. """ - with open(Path(checkpoint_dir) / "config.json", encoding="utf-8") as config_file: + with open(Path(checkpoint_dir) / "config.json") as config_file: config = json.load(config_file) assert medusa_choices is None, "medusa_choices is not supported with the torch llmapi" diff --git a/modelopt/onnx/graph_surgery/utils/whisper_utils.py b/modelopt/onnx/graph_surgery/utils/whisper_utils.py index 3987c74d1e9..012355af3a1 100644 --- a/modelopt/onnx/graph_surgery/utils/whisper_utils.py +++ b/modelopt/onnx/graph_surgery/utils/whisper_utils.py @@ -129,7 +129,7 @@ def save_audio_processor_config( # Save to file os.makedirs(output_dir, exist_ok=True) - with open(output_path, "w", encoding="utf-8") as f: + with open(output_path, "w") as f: json.dump(audio_processor_cfg, f, indent=4) logger.info(f"Saved audio_processor_config.json to {output_dir}") @@ -379,7 +379,7 @@ def save_genai_config( # Save to file os.makedirs(output_dir, exist_ok=True) - with open(output_path, "w", encoding="utf-8") as f: + with open(output_path, "w") as f: json.dump(genai_cfg, f, indent=4) logger.info(f"Saved genai_config.json to {output_dir}") @@ -406,7 +406,7 @@ def update_genai_config_encoder( Returns: Updated configuration dictionary. """ - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config = json.load(f) # Update encoder section @@ -420,7 +420,7 @@ def update_genai_config_encoder( ) # Save updated config - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(config, f, indent=4) logger.info(f"Updated encoder section in {config_path}") @@ -451,7 +451,7 @@ def update_genai_config_decoder( Returns: Updated configuration dictionary. """ - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config = json.load(f) # Update decoder section @@ -463,7 +463,7 @@ def update_genai_config_decoder( config["model"]["decoder"]["outputs"]["present_value_names"] = decoder_present_value_pattern # Save updated config - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(config, f, indent=4) logger.info(f"Updated decoder section in {config_path}") diff --git a/modelopt/onnx/llm_export_utils/export_utils.py b/modelopt/onnx/llm_export_utils/export_utils.py index 592d5637010..2016e872e28 100644 --- a/modelopt/onnx/llm_export_utils/export_utils.py +++ b/modelopt/onnx/llm_export_utils/export_utils.py @@ -46,7 +46,7 @@ def __init__(self, hf_model_path: str, config_path: str): def get_model_type(self): """Get model type from config file.""" - with open(self.config_path, encoding="utf-8") as f: + with open(self.config_path) as f: return json.load(f).get("model_type") def load_model(self, trust_remote_code: bool = False) -> AutoModelForCausalLM: diff --git a/modelopt/onnx/quantization/autotune/autotuner_base.py b/modelopt/onnx/quantization/autotune/autotuner_base.py index fd3d41d0e76..22ddf0ea098 100644 --- a/modelopt/onnx/quantization/autotune/autotuner_base.py +++ b/modelopt/onnx/quantization/autotune/autotuner_base.py @@ -737,7 +737,7 @@ def save_state(self, output_path: str) -> None: "patterns": [pattern_schemes.to_dict() for pattern_schemes in self.profiled_patterns], } - with open(output_path, "w", encoding="utf-8") as f: + with open(output_path, "w") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) num_patterns = len(self.profiled_patterns) @@ -775,7 +775,7 @@ def load_state(self, input_path: str) -> None: AutotunerNotInitializedError: If initialize() hasn't been called FileNotFoundError: If the input_path doesn't exist """ - with open(input_path, encoding="utf-8") as f: + with open(input_path) as f: state = yaml.safe_load(f) if state.get("baseline_latency_ms") is not None: diff --git a/modelopt/onnx/quantization/autotune/benchmark.py b/modelopt/onnx/quantization/autotune/benchmark.py index b25f7f82156..ba5cf1142bf 100644 --- a/modelopt/onnx/quantization/autotune/benchmark.py +++ b/modelopt/onnx/quantization/autotune/benchmark.py @@ -138,7 +138,7 @@ def _write_log_file(self, file: Path | str | None, content: str) -> None: file = Path(file) try: file.parent.mkdir(parents=True, exist_ok=True) - file.write_text(content, encoding="utf-8") + file.write_text(content) self.logger.debug(f"Saved logs to: {file}") except Exception as e: self.logger.warning(f"Failed to save logs to {file}: {e}") diff --git a/modelopt/onnx/quantization/autotune/common.py b/modelopt/onnx/quantization/autotune/common.py index a0aa6744cca..31983423cd9 100644 --- a/modelopt/onnx/quantization/autotune/common.py +++ b/modelopt/onnx/quantization/autotune/common.py @@ -739,7 +739,7 @@ def save(self, output_path: str) -> None: """ state = self.to_dict() - with open(output_path, "w", encoding="utf-8") as f: + with open(output_path, "w") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) logger.info( @@ -768,7 +768,7 @@ def load(cls, input_path: str) -> "PatternCache": Raises: FileNotFoundError: If the input_path doesn't exist """ - with open(input_path, encoding="utf-8") as f: + with open(input_path) as f: state = yaml.safe_load(f) cache = cls.from_dict(state) diff --git a/modelopt/onnx/quantization/autotune/region_search.py b/modelopt/onnx/quantization/autotune/region_search.py index a6f73a0247e..02f8282a014 100644 --- a/modelopt/onnx/quantization/autotune/region_search.py +++ b/modelopt/onnx/quantization/autotune/region_search.py @@ -884,7 +884,9 @@ def _split_sequence_regions(self, root: Region) -> list[Region]: nodes_after_merge.update(consumer.get_nodes()) nodes_after_merge.update(common_use_region.get_nodes()) node_ops = [self.graph.nodes[idx].op for idx in nodes_after_merge] - boundary_op_count = sum(1 if op in self.boundary_op_types else 0 for op in node_ops) + boundary_op_count = sum( + [1 if op in self.boundary_op_types else 0 for op in node_ops] + ) if boundary_op_count > 3: can_merge = False continue diff --git a/modelopt/onnx/quantization/autotune/utils.py b/modelopt/onnx/quantization/autotune/utils.py index d451afb6c4b..8782f004da7 100644 --- a/modelopt/onnx/quantization/autotune/utils.py +++ b/modelopt/onnx/quantization/autotune/utils.py @@ -87,7 +87,7 @@ def get_node_filter_list(node_filter_list_path: str) -> list | None: if node_filter_list_path: filter_file = validate_file_path(node_filter_list_path, "Node filter list file") if filter_file: - with open(filter_file, encoding="utf-8") as f: + with open(filter_file) as f: node_filter_list = [ line.strip() for line in f if line.strip() and not line.strip().startswith("#") ] diff --git a/modelopt/onnx/quantization/calib_utils.py b/modelopt/onnx/quantization/calib_utils.py index cb0f9fe2970..82f3af16d2c 100644 --- a/modelopt/onnx/quantization/calib_utils.py +++ b/modelopt/onnx/quantization/calib_utils.py @@ -161,7 +161,7 @@ def import_scales_from_calib_cache(cache_path: str) -> dict[str, float]: Dictionary with scales in the format {tensor_name: float_scale}. """ logger.info(f"Importing scales from calibration cache: {cache_path}") - with open(cache_path, encoding="utf-8") as f: + with open(cache_path) as f: scales_dict = {} lines = f.readlines() for i, line in enumerate(lines): diff --git a/modelopt/onnx/trt_utils.py b/modelopt/onnx/trt_utils.py index d1e12e3e606..b407fdc5411 100644 --- a/modelopt/onnx/trt_utils.py +++ b/modelopt/onnx/trt_utils.py @@ -503,8 +503,8 @@ def interpret_trt_plugins_precision_flag( if not custom_op_nodes: logger.warning(f"No nodes of type {op_type} were found. Skipping.") continue - num_inps = max(len(node.inputs) for node in custom_op_nodes) - num_outs = max(len(node.outputs) for node in custom_op_nodes) + num_inps = max([len(node.inputs) for node in custom_op_nodes]) + num_outs = max([len(node.outputs) for node in custom_op_nodes]) # Now split the remainder of the string to get the I/O precisions if trt_plugin_precision.count(":") == 1: diff --git a/modelopt/recipe/loader.py b/modelopt/recipe/loader.py index 7f8591b0123..91e2bac75de 100644 --- a/modelopt/recipe/loader.py +++ b/modelopt/recipe/loader.py @@ -171,7 +171,7 @@ def _peek_recipe_type(recipe_file: Path | Traversable) -> RecipeType | None: import yaml try: - raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) + raw = yaml.safe_load(recipe_file.read_text()) return RecipeType(raw["metadata"]["recipe_type"]) except (TypeError, KeyError, ValueError): return None @@ -201,7 +201,7 @@ def _load_recipe_from_file( if required_section is not None: import yaml - raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) or {} + raw = yaml.safe_load(recipe_file.read_text()) or {} if not isinstance(raw, dict) or required_section not in raw: # Strip only the ``speculative_`` prefix so multi-word non-speculative types # (e.g. ``auto_quantize``) keep their full name: AUTO_QUANTIZE, not QUANTIZE. diff --git a/modelopt/torch/_deploy/_runtime/common.py b/modelopt/torch/_deploy/_runtime/common.py index 3d1f2136492..6e82c7dd294 100644 --- a/modelopt/torch/_deploy/_runtime/common.py +++ b/modelopt/torch/_deploy/_runtime/common.py @@ -59,7 +59,7 @@ def read_bytes(file_path: str | Path) -> bytes: def read_string(file_path: str | Path) -> str: path = Path(file_path) - return path.read_text(encoding="utf-8") + return path.read_text() def write_bytes(data: bytes, file_path: str | Path) -> None: @@ -69,4 +69,4 @@ def write_bytes(data: bytes, file_path: str | Path) -> None: def write_string(data: str, file_path: str | Path) -> None: path = Path(file_path) - path.write_text(data, encoding="utf-8") + path.write_text(data) diff --git a/modelopt/torch/_deploy/_runtime/ort_client.py b/modelopt/torch/_deploy/_runtime/ort_client.py index 41dfef454a6..be025cef9d9 100644 --- a/modelopt/torch/_deploy/_runtime/ort_client.py +++ b/modelopt/torch/_deploy/_runtime/ort_client.py @@ -114,7 +114,7 @@ def _profile( # end profiling and load results prof_file = ort_session.end_profiling() - with open(prof_file, encoding="utf-8") as p_file: + with open(prof_file) as p_file: results = json.load(p_file) # get latency from profiling results (latencies are in nano-seconds) diff --git a/modelopt/torch/distill/plugins/megatron.py b/modelopt/torch/distill/plugins/megatron.py index 581f670759c..c93f0961d1f 100644 --- a/modelopt/torch/distill/plugins/megatron.py +++ b/modelopt/torch/distill/plugins/megatron.py @@ -120,7 +120,7 @@ def setup_distillation_config( elif isinstance(config_or_path, DistillationConfig): cfg = config_or_path else: - with open(config_or_path, encoding="utf-8") as f: + with open(config_or_path) as f: cfg = yaml.safe_load(f) cfg = DistillationConfig(**cfg) diff --git a/modelopt/torch/export/layerwise_export.py b/modelopt/torch/export/layerwise_export.py index 04178e2e761..9767d5635a4 100644 --- a/modelopt/torch/export/layerwise_export.py +++ b/modelopt/torch/export/layerwise_export.py @@ -512,7 +512,7 @@ def _write_index(self) -> None: weight_map[key] = shard.name total_size += _shard_data_bytes(shard) index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} - (self._export_dir / _INDEX_FILE).write_text(json.dumps(index, indent=2), encoding="utf-8") + (self._export_dir / _INDEX_FILE).write_text(json.dumps(index, indent=2)) def _holds_meta_tensor(module: nn.Module) -> bool: diff --git a/modelopt/torch/export/plugins/hf_checkpoint_utils.py b/modelopt/torch/export/plugins/hf_checkpoint_utils.py index 1c4f65d570a..9d508e58904 100644 --- a/modelopt/torch/export/plugins/hf_checkpoint_utils.py +++ b/modelopt/torch/export/plugins/hf_checkpoint_utils.py @@ -237,7 +237,7 @@ def load_multimodal_components( index_file = index_dir / "model.safetensors.index.json" if index_file.is_file(): try: - weight_map = json.loads(index_file.read_text(encoding="utf-8"))["weight_map"] + weight_map = json.loads(index_file.read_text())["weight_map"] except (json.JSONDecodeError, KeyError) as exc: raise ValueError(f"Malformed safetensors index in {repo_id}.") from exc wanted = sorted( @@ -273,7 +273,7 @@ def load_multimodal_components( elif safetensors_index_file.is_file(): print(f"Loading multimodal components from sharded model: {hf_checkpoint_path}") - with open(safetensors_index_file, encoding="utf-8") as f: + with open(safetensors_index_file) as f: safetensors_index = json.load(f) all_shard_files = sorted( diff --git a/modelopt/torch/export/plugins/hf_spec_export.py b/modelopt/torch/export/plugins/hf_spec_export.py index 2a95c950203..06be11b8a57 100644 --- a/modelopt/torch/export/plugins/hf_spec_export.py +++ b/modelopt/torch/export/plugins/hf_spec_export.py @@ -260,7 +260,7 @@ def _export_lora(self, export_dir: Path, full_sd: dict): target_modules=target_modules, bias="none", ) - with open(export_dir / "adapter_config.json", "w", encoding="utf-8") as f: + with open(export_dir / "adapter_config.json", "w") as f: json.dump( lora_config.to_dict(), f, @@ -294,12 +294,12 @@ def export( drafter_config = self._export_config() if hf_quant_config is not None: drafter_config["quantization_config"] = hf_quant_config - with open(f"{export_dir}/config.json", "w", encoding="utf-8") as file: + with open(f"{export_dir}/config.json", "w") as file: json.dump(drafter_config, file, indent=4) # Export hf_quant_config for backward compatibility if hf_quant_config is not None: - with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: + with open(f"{export_dir}/hf_quant_config.json", "w") as file: json.dump(hf_quant_config, file, indent=4) # Export LoRA adapter weights separately @@ -483,11 +483,11 @@ def export(self, export_dir: Path | str, dtype: torch.dtype | None = None): drafter_config["torch_dtype"] = str(dtype).replace("torch.", "") if hf_quant_config is not None: drafter_config["quantization_config"] = hf_quant_config - with open(f"{export_dir}/config.json", "w", encoding="utf-8") as f: + with open(f"{export_dir}/config.json", "w") as f: json.dump(drafter_config, f, indent=2) if hf_quant_config is not None: - with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as f: + with open(f"{export_dir}/hf_quant_config.json", "w") as f: json.dump(hf_quant_config, f, indent=2) print( diff --git a/modelopt/torch/export/plugins/mcore_custom.py b/modelopt/torch/export/plugins/mcore_custom.py index d3a5cf623aa..ed3e00fd962 100644 --- a/modelopt/torch/export/plugins/mcore_custom.py +++ b/modelopt/torch/export/plugins/mcore_custom.py @@ -310,7 +310,7 @@ def save_safetensors(state_dict, save_directory: str | os.PathLike): local_total_size += val.numel() * val.element_size() weight_map[key] = ckpt_filename - with open(save_directory + "/" + meta_filename, "w", encoding="utf-8") as f: + with open(save_directory + "/" + meta_filename, "w") as f: json.dump( {"metadata": {"total_size": local_total_size}, "weight_map": weight_map}, f, @@ -328,12 +328,12 @@ def save_safetensors(state_dict, save_directory: str | os.PathLike): } for global_idx in range(global_count): meta_filename = f"model-{global_idx + 1:05d}-of-{global_count:05d}.json" - with open(save_directory + "/" + meta_filename, encoding="utf-8") as f: + with open(save_directory + "/" + meta_filename) as f: shard = json.load(f) safetensor_index["metadata"]["total_size"] += shard["metadata"]["total_size"] safetensor_index["weight_map"].update(shard["weight_map"]) - with open(save_directory + "/model.safetensors.index.json", "w", encoding="utf-8") as f: + with open(save_directory + "/model.safetensors.index.json", "w") as f: json.dump(safetensor_index, f, indent=4) @@ -371,7 +371,7 @@ def save_safetensors_by_layer_index( layer_total_size += tensor_size weight_map[key] = ckpt_filename - with open(save_directory + "/" + meta_filename, "w", encoding="utf-8") as f: + with open(save_directory + "/" + meta_filename, "w") as f: json.dump( {"metadata": {"total_size": layer_total_size}, "weight_map": weight_map}, f, @@ -388,12 +388,12 @@ def save_safetensors_by_layer_index( } for layer_index in range(total_layers): meta_filename = name_template.format(layer_index + 1, total_layers) + ".json" - with open(save_directory + "/" + meta_filename, encoding="utf-8") as f: + with open(save_directory + "/" + meta_filename) as f: shard = json.load(f) safetensor_index["metadata"]["total_size"] += shard["metadata"]["total_size"] safetensor_index["weight_map"].update(shard["weight_map"]) - with open(save_directory + "/model.safetensors.index.json", "w", encoding="utf-8") as f: + with open(save_directory + "/model.safetensors.index.json", "w") as f: json.dump(safetensor_index, f, indent=4) @@ -409,7 +409,7 @@ def _get_safetensors_file(pretrained_model_path: str | Path, key: str) -> Path | if safetensors_file.is_file(): pass elif safetensors_index_file.is_file(): - with open(safetensors_index_file, encoding="utf-8") as f: + with open(safetensors_index_file) as f: safetensors_index = json.load(f) safetensors_file = ( (Path(pretrained_model_path) / safetensors_index["weight_map"][key]) diff --git a/modelopt/torch/export/trtllm/model_config_export.py b/modelopt/torch/export/trtllm/model_config_export.py index 89f03a8f35d..8eae2aa94e4 100644 --- a/modelopt/torch/export/trtllm/model_config_export.py +++ b/modelopt/torch/export/trtllm/model_config_export.py @@ -570,13 +570,13 @@ def export_tensorrt_llm_checkpoint( tensorrt_llm_config["quantization"] = { k: quant_config[k] for k in ("quant_algo", "kv_cache_quant_algo") } - with open(export_dir / "quant_cfg.json", "w", encoding="utf-8") as f: + with open(export_dir / "quant_cfg.json", "w") as f: json.dump(quant_config, f, indent=4) else: # Excluded modules information is only included in non auto_quant case tensorrt_llm_config["quantization"]["exclude_modules"] = list(exclude_modules) - with open(export_dir / "config.json", "w", encoding="utf-8") as f: + with open(export_dir / "config.json", "w") as f: json.dump(tensorrt_llm_config, f, indent=4) # Hacky implementation for Encoder-Decoder for now diff --git a/modelopt/torch/export/unified_export_hf.py b/modelopt/torch/export/unified_export_hf.py index 7210f9e4ff5..8670af77403 100644 --- a/modelopt/torch/export/unified_export_hf.py +++ b/modelopt/torch/export/unified_export_hf.py @@ -160,7 +160,7 @@ def _save_component_state_dict_safetensors( metadata=metadata, ) - with open(component_export_dir / "config.json", "w", encoding="utf-8") as f: + with open(component_export_dir / "config.json", "w") as f: json.dump(metadata, f, indent=4) @@ -1354,10 +1354,10 @@ def _export_diffusers_checkpoint( if hf_quant_config is not None: config_path = component_export_dir / "config.json" if config_path.exists(): - with open(config_path, encoding="utf-8") as file: + with open(config_path) as file: config_data = json.load(file) config_data["quantization_config"] = hf_quant_config - with open(config_path, "w", encoding="utf-8") as file: + with open(config_path, "w") as file: json.dump(config_data, file, indent=4) finally: # Drop the temporary promoted export buffers so the live module is @@ -1375,10 +1375,10 @@ def _export_diffusers_checkpoint( if sparse_attn_config is not None: config_path = component_export_dir / "config.json" if config_path.exists(): - with open(config_path, encoding="utf-8") as file: + with open(config_path) as file: config_data = json.load(file) config_data["sparse_attention_config"] = sparse_attn_config - with open(config_path, "w", encoding="utf-8") as file: + with open(config_path, "w") as file: json.dump(config_data, file, indent=4) print(f" Added sparse_attention_config to {config_path.name}") @@ -1426,9 +1426,9 @@ def _export_diffusers_checkpoint( if source_path: candidate_model_index = Path(source_path) / "model_index.json" if candidate_model_index.exists(): - with open(candidate_model_index, encoding="utf-8") as file: + with open(candidate_model_index) as file: model_index = json.load(file) - with open(model_index_path, "w", encoding="utf-8") as file: + with open(model_index_path, "w") as file: json.dump(model_index, file, indent=4) # Full-export fallback to Diffusers-native config serialization. @@ -1447,7 +1447,7 @@ def _export_diffusers_checkpoint( library = module.split(".")[0] model_index[name] = [library, type(comp).__name__] - with open(model_index_path, "w", encoding="utf-8") as file: + with open(model_index_path, "w") as file: json.dump(model_index, file, indent=4) print(f"Export complete. Saved to: {export_dir}") @@ -1561,12 +1561,12 @@ def _write_hf_export_config( ) quantization_config = None if hf_quant_config is not None and is_quantized_export: - with open(f"{export_dir}/hf_quant_config.json", "w", encoding="utf-8") as file: + with open(f"{export_dir}/hf_quant_config.json", "w") as file: json.dump(hf_quant_config, file, indent=4) quantization_config = convert_hf_quant_config_format(hf_quant_config) original_config = f"{export_dir}/config.json" - with open(original_config, encoding="utf-8") as file: + with open(original_config) as file: config_data = json.load(file) sanitize_hf_config_for_deployment(config_data, model) if quantization_config is not None: @@ -1575,7 +1575,7 @@ def _write_hf_export_config( sparse_attn_config = export_sparse_attention_config(model) if sparse_attn_config is not None: config_data["sparse_attention_config"] = sparse_attn_config - with open(original_config, "w", encoding="utf-8") as file: + with open(original_config, "w") as file: json.dump(config_data, file, indent=4) diff --git a/modelopt/torch/export/unified_export_hf_streaming.py b/modelopt/torch/export/unified_export_hf_streaming.py index 1e8ba0f0153..32477173e51 100644 --- a/modelopt/torch/export/unified_export_hf_streaming.py +++ b/modelopt/torch/export/unified_export_hf_streaming.py @@ -178,7 +178,7 @@ def name_shards_and_write_index( weight_map = {key: shard_names[part_idx] for key, part_idx in key_to_part.items()} index_path = export_dir / "model.safetensors.index.json" - with open(index_path, "w", encoding="utf-8") as f: + with open(index_path, "w") as f: json.dump({"metadata": {"total_size": total_size}, "weight_map": weight_map}, f) return weight_map diff --git a/modelopt/torch/export/unified_export_megatron.py b/modelopt/torch/export/unified_export_megatron.py index 38a9d316a34..7ac06e73e17 100644 --- a/modelopt/torch/export/unified_export_megatron.py +++ b/modelopt/torch/export/unified_export_megatron.py @@ -402,7 +402,7 @@ def save_pretrained( }, "quantization": quantization_config, } - with open(save_directory + "/hf_quant_config.json", "w", encoding="utf-8") as f: + with open(save_directory + "/hf_quant_config.json", "w") as f: json.dump(self._hf_quant_config, f, indent=4) # Add multimodal components to state_dict. Since only support decoder model quantization, @@ -427,12 +427,12 @@ def save_pretrained( torch.distributed.barrier() config_json_file = save_directory + "/config.json" if is_writer_rank and self._hf_quant_config and os.path.exists(config_json_file): - with open(config_json_file, encoding="utf-8") as f: + with open(config_json_file) as f: config_dict = json.load(f) config_dict["quantization_config"] = convert_hf_quant_config_format( self._hf_quant_config ) - with open(config_json_file, "w", encoding="utf-8") as f: + with open(config_json_file, "w") as f: json.dump(config_dict, f, indent=4) torch.distributed.barrier() @@ -491,7 +491,7 @@ def _verify_exported_keys(self, save_directory, pretrained_model_name_or_path) - with safe_open(str(single), framework="pt", device="cpu") as f: exported = set(f.keys()) else: - with open(index_file, encoding="utf-8") as f: + with open(index_file) as f: exported = set(json.load(f)["weight_map"]) if not source: warn_rank_0(f"Export self-check skipped: no tensor index found in {source_dir}.") @@ -838,7 +838,7 @@ def _copy_mtp_state_dict_from_pretrained(self) -> dict[str, torch.Tensor]: return mtp_state_dict if safetensors_index_file is not None and safetensors_index_file.exists(): - with open(safetensors_index_file, encoding="utf-8") as f: + with open(safetensors_index_file) as f: safetensors_index = json.load(f) model_dir = safetensors_index_file.parent for key in safetensors_index["weight_map"]: @@ -2023,7 +2023,7 @@ def _read_checkpoint_keys(checkpoint_dir) -> set[str]: directory = Path(checkpoint_dir) index_file = directory / "model.safetensors.index.json" if index_file.exists(): - with open(index_file, encoding="utf-8") as f: + with open(index_file) as f: return set(json.load(f)["weight_map"]) single_file = directory / "model.safetensors" if single_file.exists(): diff --git a/modelopt/torch/nas/hparams/concat.py b/modelopt/torch/nas/hparams/concat.py index 2e3e00e33ae..31274ab052e 100644 --- a/modelopt/torch/nas/hparams/concat.py +++ b/modelopt/torch/nas/hparams/concat.py @@ -178,7 +178,7 @@ def _get_importance(self) -> TracedHp.Importance: # We need to aggregate between split importances when the come from the same hparam! imps = [ - sum(imp_ for imp_, hp in zip(imps, self._inputs) if hp is self._inputs[i]) + sum([imp_ for imp_, hp in zip(imps, self._inputs) if hp is self._inputs[i]]) for i, imp in enumerate(imps) ] diff --git a/modelopt/torch/opt/plugins/transformers.py b/modelopt/torch/opt/plugins/transformers.py index 517bb97ed22..a291b5abf36 100644 --- a/modelopt/torch/opt/plugins/transformers.py +++ b/modelopt/torch/opt/plugins/transformers.py @@ -344,7 +344,7 @@ def parse_args_into_dataclasses(self, args=None, **kwargs): args = args[:idx] + args[idx + 2 :] # strip --config <path> from argv import yaml - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config = yaml.safe_load(f) if config: known_by_parser = {a.dest for a in self._actions} @@ -446,7 +446,7 @@ def _sort_key(dc): # Remove trailing blank lines so markdownlint won't modify the file while lines and lines[-1] == "": lines.pop() - Path(output_path).write_text("\n".join(lines) + "\n", encoding="utf-8") + Path(output_path).write_text("\n".join(lines) + "\n") print(f"Generated {output_path}") @staticmethod @@ -676,7 +676,7 @@ def load_lr_config(path: str) -> dict[str, dict[str, Any]]: """ import yaml - with open(path, encoding="utf-8") as f: + with open(path) as f: cfg = yaml.safe_load(f) if not isinstance(cfg, dict): raise ValueError(f"lr_config must be a YAML mapping, got {type(cfg).__name__}") diff --git a/modelopt/torch/opt/searcher.py b/modelopt/torch/opt/searcher.py index 9a3cd2fd5b3..386948cb4a6 100644 --- a/modelopt/torch/opt/searcher.py +++ b/modelopt/torch/opt/searcher.py @@ -359,7 +359,7 @@ def _build_objective_problem( objective_value = 0 for layer_id, layer_vars in enumerate(selection_vars): objective_value += sum( - z * a for z, a in zip(layer_vars, self.candidate_scores[layer_id]) + [z * a for z, a in zip(layer_vars, self.candidate_scores[layer_id])] ) problem += (objective_value, "L") return problem @@ -375,7 +375,7 @@ def _build_budget_constraints(self, selection_vars: list[list[pulp.LpVariable]]) ) in self.constraints_to_candidate_costs.items(): cost = 0 for layer_vars, candidate_costs in zip(selection_vars, candidate_costs_list): - cost += sum(z * b for z, b in zip(layer_vars, candidate_costs)) + cost += sum([z * b for z, b in zip(layer_vars, candidate_costs)]) if isinstance(self.constraints[constraint_name], tuple): lower_bound, upper_bound = self.constraints[constraint_name] # type: ignore[misc] else: diff --git a/modelopt/torch/prune/fastnas.py b/modelopt/torch/prune/fastnas.py index b8511bfa1a9..4852efdad2b 100644 --- a/modelopt/torch/prune/fastnas.py +++ b/modelopt/torch/prune/fastnas.py @@ -114,8 +114,8 @@ def before_search(self) -> None: # compute and register the construction of sensitivity map self._build_sensitivity_map(self.config["verbose"]) - self.max_degrade = max(max(v.values()) for v in self.sensitivity_map.values()) - self.min_degrade = min(min(v.values()) for v in self.sensitivity_map.values()) + self.max_degrade = max([max(v.values()) for v in self.sensitivity_map.values()]) + self.min_degrade = min([min(v.values()) for v in self.sensitivity_map.values()]) # overwrite the score function to be a fake function, returning the -max degrade def max_degrade(_model): @@ -133,7 +133,7 @@ def before_step(self) -> None: def _apply_fastnas_according_to_threshold(self, threshold): cfg = { - name: min(k for k, v in sensitivity.items() if v <= threshold) + name: min([k for k, v in sensitivity.items() if v <= threshold]) for name, sensitivity in self.sensitivity_map.items() } select(self.model, cfg, strict=False) @@ -208,7 +208,7 @@ def _build_sensitivity_map(self, verbose=False) -> None: # Getting the number of choices needed to validate total_choices_to_validate = sum( - len(hparam.choices) for hparam in binary_search_hps.values() + [len(hparam.choices) for hparam in binary_search_hps.values()] ) assert total_choices_to_validate != 0, f"{type(self).__name__}: no searchable hparams found" @@ -223,7 +223,7 @@ def _build_sensitivity_map(self, verbose=False) -> None: } remaining_choices_to_validate = sum( - len(hparam.choices) for hparam in binary_search_hps.values() + [len(hparam.choices) for hparam in binary_search_hps.values()] ) if remaining_choices_to_validate == 0: diff --git a/modelopt/torch/prune/importance_hooks/base_hooks.py b/modelopt/torch/prune/importance_hooks/base_hooks.py index e74fbc9e6ed..5eccd033d65 100644 --- a/modelopt/torch/prune/importance_hooks/base_hooks.py +++ b/modelopt/torch/prune/importance_hooks/base_hooks.py @@ -778,7 +778,7 @@ def _save_channel_importance_results( # Save the output output_path = activations_log_dir / "channel_importance_results.json" print(f"Saving channel importance data to {output_path}") - with open(output_path, "w", encoding="utf-8") as f: + with open(output_path, "w") as f: json.dump(output_data, f, indent=2) # Print summary statistics diff --git a/modelopt/torch/prune/importance_hooks/compare_module_outputs.py b/modelopt/torch/prune/importance_hooks/compare_module_outputs.py index d86f66ffcc4..37e7ef69340 100644 --- a/modelopt/torch/prune/importance_hooks/compare_module_outputs.py +++ b/modelopt/torch/prune/importance_hooks/compare_module_outputs.py @@ -297,7 +297,7 @@ def compare_multi_layer(ref_data: dict, comp_data: dict, output_json: str | None results["aggregated"].pop("rmse", None) results["aggregated"].pop("cosine_sim_mean", None) - with open(output_json, "w", encoding="utf-8") as f: + with open(output_json, "w") as f: json.dump(results, f, indent=2) print(f"Saved comparison results to {output_json}") diff --git a/modelopt/torch/puzzletron/anymodel/converter/base.py b/modelopt/torch/puzzletron/anymodel/converter/base.py index c8762876fa4..c8e01ffe289 100644 --- a/modelopt/torch/puzzletron/anymodel/converter/base.py +++ b/modelopt/torch/puzzletron/anymodel/converter/base.py @@ -50,7 +50,7 @@ def _get_weight_map(input_dir: Path) -> Dict[str, str]: if index_path.exists(): # Sharded model - with open(index_path, "r", encoding="utf-8") as f: + with open(index_path, "r") as f: index = json.load(f) return index["weight_map"] elif single_file_path.exists(): diff --git a/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py b/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py index 760a119735d..85355146aba 100644 --- a/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py +++ b/modelopt/torch/puzzletron/anymodel/models/gpt_oss/gpt_oss_pruned_to_mxfp4.py @@ -186,7 +186,7 @@ def deduce_experts_for_layer( def load_original_index(path: str) -> Dict[str, Any]: """Load the original model's safetensors index.""" - with open(path, "r", encoding="utf-8") as f: + with open(path, "r") as f: return json.load(f) @@ -381,7 +381,7 @@ def copy_config_files(student_path: str, output_path: str): if not os.path.exists(src_config): raise FileNotFoundError(f"config.json not found at {src_config}") - with open(src_config, "r", encoding="utf-8") as f: + with open(src_config, "r") as f: config = json.load(f) # type: ignore[arg-type] # Set architecture to DeciGptOssForCausalLM for MXFP4 support @@ -399,7 +399,7 @@ def copy_config_files(student_path: str, output_path: str): } dst_config = os.path.join(output_path, "config.json") - with open(dst_config, "w", encoding="utf-8") as f: + with open(dst_config, "w") as f: json.dump(config, f, indent=2) # type: ignore[arg-type] @@ -475,7 +475,7 @@ def main(): # Save experts_to_keep.json experts_to_keep_output = os.path.join(args.output_path, "experts_to_keep.json") - with open(experts_to_keep_output, "w", encoding="utf-8") as f: + with open(experts_to_keep_output, "w") as f: json.dump(experts_to_keep, f, indent=2) print(f" Saved experts_to_keep mapping to {experts_to_keep_output}") @@ -515,7 +515,7 @@ def main(): index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} index_path = os.path.join(args.output_path, "model.safetensors.index.json") - with open(index_path, "w", encoding="utf-8") as f: + with open(index_path, "w") as f: json.dump(index, f, indent=2) print(f"\nCheckpoint created successfully at: {args.output_path}") diff --git a/modelopt/torch/puzzletron/mip/run_puzzle.py b/modelopt/torch/puzzletron/mip/run_puzzle.py index 6c9bf668b2e..22c8b471546 100644 --- a/modelopt/torch/puzzletron/mip/run_puzzle.py +++ b/modelopt/torch/puzzletron/mip/run_puzzle.py @@ -328,7 +328,7 @@ def run_single_puzzle_config( solution_repr_0 = solutions[0]["solution_repr"] mprint(f"\n{solution_repr_0}") mprint(f"Total costs: {solutions[0]['total_costs']}") - (output_folder / "solution_repr_0.txt").write_text(solution_repr_0, encoding="utf-8") + (output_folder / "solution_repr_0.txt").write_text(solution_repr_0) solutions_file = output_folder / "solutions.json" json_dump(solutions, solutions_file) @@ -439,7 +439,7 @@ def _get_minimal_unique_names(dicts: list[dict]) -> list[str]: def run_puzzle(args: DictConfig) -> list[str]: # Loads config from args/puzzle_profile if args.puzzle_profile is not None: - with open(args.puzzle_profile, encoding="utf-8") as f: + with open(args.puzzle_profile) as f: puzzle_profile = yaml.safe_load(f) _override_args_from_profile(args, puzzle_profile) mprint(f"Loaded Puzzle profile from {args.puzzle_profile}") @@ -449,7 +449,7 @@ def run_puzzle(args: DictConfig) -> list[str]: # Read Metrics and Stats if args.gathered_metrics_path is not None: - gathered_metrics = json.loads(args.gathered_metrics_path.read_text(encoding="utf-8")) + gathered_metrics = json.loads(args.gathered_metrics_path.read_text()) else: gathered_metrics = gather_multi_layer_puzzle_metrics( args.single_block_replacement_validation_dir @@ -458,7 +458,7 @@ def run_puzzle(args: DictConfig) -> list[str]: if args.metric_overrides is not None: gathered_metrics = {**gathered_metrics, **args.metric_overrides} - subblock_stats = json.loads(args.subblock_stats_path.read_text(encoding="utf-8")) + subblock_stats = json.loads(args.subblock_stats_path.read_text()) all_subblock_args = _load_all_subblock_stats_args(args, puzzle_profile) all_subblock_output_folders = [ @@ -533,7 +533,7 @@ def gather_multi_layer_puzzle_metrics( def _parse_single_block_replacement_metrics(metrics_path: Path) -> dict: - raw_metrics = json.loads(metrics_path.read_text(encoding="utf-8")) + raw_metrics = json.loads(metrics_path.read_text()) single_block_replacement = raw_metrics["puzzle_solution"]["single_block_replacement"] variant_metrics = { "block_config": BlockConfig(**single_block_replacement["block_config"]), @@ -544,7 +544,7 @@ def _parse_single_block_replacement_metrics(metrics_path: Path) -> dict: def _parse_single_sequence_replacement_metrics(metrics_path: Path) -> dict: - raw_metrics = json.loads(metrics_path.read_text(encoding="utf-8")) + raw_metrics = json.loads(metrics_path.read_text()) single_sequence_replacement = raw_metrics["puzzle_solution"]["single_sequence_replacement"] if len(single_sequence_replacement["child_block_configs"]) > 1: raise NotImplementedError( @@ -565,9 +565,7 @@ def _parse_teacher_block_metrics( single_block_replacement_validation_dir: Path, all_metric_names: Iterable[str] = ("kl_div_loss",), ) -> list[dict]: - raw_metrics = json.loads( - (single_block_replacement_validation_dir / "teacher.json").read_text(encoding="utf-8") - ) + raw_metrics = json.loads((single_block_replacement_validation_dir / "teacher.json").read_text()) teacher_checkpoint_dir = Path(raw_metrics["args"]["teacher_dir"]).resolve() descriptor_name = raw_metrics["args"]["descriptor"] descriptor = ModelDescriptorFactory.get(descriptor_name) @@ -580,9 +578,7 @@ def _parse_teacher_block_metrics( replacement_library_path = raw_metrics["args"].get("replacement_library_path") if replacement_library_path is not None: teacher_replacements = dict() - all_layer_replacements = json.loads( - Path(replacement_library_path).read_text(encoding="utf-8") - ) + all_layer_replacements = json.loads(Path(replacement_library_path).read_text()) for layer_replacement in all_layer_replacements: layer_replacement = parse_layer_replacement(layer_replacement) if replacement_is_teacher( diff --git a/modelopt/torch/puzzletron/mip/sweep.py b/modelopt/torch/puzzletron/mip/sweep.py index dcc89efac28..ea4e95dc3ed 100644 --- a/modelopt/torch/puzzletron/mip/sweep.py +++ b/modelopt/torch/puzzletron/mip/sweep.py @@ -69,7 +69,7 @@ def _load_teacher_subblock_stats(hydra_cfg: DictConfig) -> tuple[dict[str, Any], "Please run the full pipeline first without --mip-only flag." ) - with open(subblock_stats_path, encoding="utf-8") as f: + with open(subblock_stats_path) as f: subblock_stats_list = json.load(f) try: @@ -158,7 +158,7 @@ def extract_solution_results( # Load solutions.json for actual memory and parameters solutions_file = solution_dir / "solutions.json" - with open(solutions_file, encoding="utf-8") as f: + with open(solutions_file) as f: solutions_data = json.load(f) solution = solutions_data[0] # First solution total_costs = solution.get("total_costs", {}) @@ -170,7 +170,7 @@ def extract_solution_results( # TODO: There could be multiple solutions, but we only need the first one. Is it the best solution? solution_0_file = validation_dir / "solution_0.json" - with open(solution_0_file, encoding="utf-8") as f: + with open(solution_0_file) as f: validation_data = json.load(f) result["lm_loss"] = validation_data.get("lm_loss", {}).get("avg", None) result["token_accuracy_top_1"] = validation_data.get("token_accuracy_top_1", {}).get( @@ -212,7 +212,7 @@ def write_results_to_csv(results: list, output_csv: str): output_path = Path(output_csv) output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_path, "w", encoding="utf-8", newline="") as f: + with open(output_path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=columns) writer.writeheader() writer.writerows(results) diff --git a/modelopt/torch/puzzletron/pruning/pruning_utils.py b/modelopt/torch/puzzletron/pruning/pruning_utils.py index 5ed1d317aaf..38ab7a2e0be 100644 --- a/modelopt/torch/puzzletron/pruning/pruning_utils.py +++ b/modelopt/torch/puzzletron/pruning/pruning_utils.py @@ -652,7 +652,7 @@ def _load_expert_scores( assert mlp_init_config is not None if "expert_scores_file" in mlp_init_config: expert_scores_file = mlp_init_config["expert_scores_file"] - with open(expert_scores_file, "r", encoding="utf-8") as f: + with open(expert_scores_file, "r") as f: expert_scores = json.load(f) elif "activations_log_dir" in mlp_init_config: _cache_activations_log(mlp_init_config) diff --git a/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py b/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py index fb1cd347e67..ae156ad8e19 100644 --- a/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py +++ b/modelopt/torch/puzzletron/replacement_library/build_replacement_library.py @@ -521,7 +521,7 @@ def _gather_layer_replacements_from_checkpoints( ) for checkpoint_dir in checkpoint_dirs: if (layer_replacements_path := checkpoint_dir / "replacement_library.json").exists(): - layer_replacements = json.loads(layer_replacements_path.read_text(encoding="utf-8")) + layer_replacements = json.loads(layer_replacements_path.read_text()) for layer_replacement in layer_replacements: layer_replacement["child_block_configs"] = [ BlockConfig(**block_config_dict) diff --git a/modelopt/torch/puzzletron/replacement_library/library.py b/modelopt/torch/puzzletron/replacement_library/library.py index fb3b27331e6..d6012f596a2 100644 --- a/modelopt/torch/puzzletron/replacement_library/library.py +++ b/modelopt/torch/puzzletron/replacement_library/library.py @@ -61,7 +61,7 @@ def __init__( @staticmethod def _load_replacement_library(replacement_library_path: str | Path) -> list[dict]: - replacement_library = json.loads(Path(replacement_library_path).read_text(encoding="utf-8")) + replacement_library = json.loads(Path(replacement_library_path).read_text()) replacement_library = [ parse_layer_replacement(layer_replacement) for layer_replacement in replacement_library ] diff --git a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py index 0142740738a..531f7a3f0a1 100644 --- a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py +++ b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_params_and_memory.py @@ -252,7 +252,7 @@ def load_moe_stats(stats_file: str) -> dict: distribution over experts for the corresponding block. If a block's expert list is empty, its entry is 0. """ - with open(stats_file, encoding="utf-8") as f: + with open(stats_file) as f: stats = json.load(f) return [ np.array(expert_probs) / np.sum(expert_probs) if len(expert_probs) > 0 else 0 diff --git a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py index fd7d380a15a..1d04cc01add 100644 --- a/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py +++ b/modelopt/torch/puzzletron/subblock_stats/calc_subblock_stats.py @@ -295,7 +295,7 @@ def calculate_subblock_stats_for_puzzle_dir( ) if subblock_stats_file.exists(): - with open(subblock_stats_file, encoding="utf-8") as f: + with open(subblock_stats_file) as f: subblock_stats = json.load(f) else: subblock_stats = [] @@ -416,9 +416,7 @@ def _load_subblock_configs_from_replacement_library( Args: master_puzzle_dir: Directory with "replacement_library.json" file """ - replacement_library = json.loads( - (master_puzzle_dir / "replacement_library.json").read_text(encoding="utf-8") - ) + replacement_library = json.loads((master_puzzle_dir / "replacement_library.json").read_text()) subblock_configs = set() for layer_replacement in replacement_library: layer_replacement = parse_layer_replacement(layer_replacement) diff --git a/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py b/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py index a8d5c491f91..204c2a74305 100644 --- a/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py +++ b/modelopt/torch/puzzletron/subblock_stats/runtime_utils.py @@ -84,10 +84,10 @@ def save_model_as_anymodel(model, output_dir: Path, descriptor): config_path = output_dir / "config.json" if config_path.exists(): - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config_data = json.load(f) config_data["architectures"] = ["AnyModel"] - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(config_data, f, indent=2) @@ -105,7 +105,7 @@ def convert_config_to_vllm_anymodel(config_dir: Path): shutil.copy(config_path, backup_config_path) try: - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config_data = json.load(f) except json.JSONDecodeError as e: raise ValueError(f"Error loading config file: {e}") from e @@ -118,7 +118,7 @@ def convert_config_to_vllm_anymodel(config_dir: Path): mprint("Converted block configs to per-layer config") else: mprint("No block configs to convert") - with open(config_path, "w", encoding="utf-8") as f: + with open(config_path, "w") as f: json.dump(vars(config), f, indent=2) diff --git a/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py b/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py index 0935b182332..386f3615d49 100644 --- a/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py +++ b/modelopt/torch/puzzletron/subblock_stats/runtime_vllm.py @@ -46,13 +46,13 @@ def run_vllm_latency_benchmark(model_path: Path, runtime_config: RuntimeConfig) output_json_path = model_path / "vllm_latency_benchmark.json" max_model_len = runtime_config.prefill_seq_len + runtime_config.generation_seq_len - with open(model_path / "config.json", encoding="utf-8") as f: + with open(model_path / "config.json") as f: config = json.load(f) config = SimpleNamespace(**config) if convert_block_configs_to_per_layer_config(config): mprint("Converted block configs to per-layer config") - with open(model_path / "config.json", "w", encoding="utf-8") as f: + with open(model_path / "config.json", "w") as f: json.dump(vars(config), f, indent=2) else: mprint("No block configs to convert") @@ -109,7 +109,7 @@ def run_vllm_latency_benchmark(model_path: Path, runtime_config: RuntimeConfig) raise RuntimeError(exc.stderr or exc.stdout or "vLLM latency benchmark failed") from exc if output_json_path.exists(): - with open(output_json_path, encoding="utf-8") as f: + with open(output_json_path) as f: vllm_results = json.load(f) if "avg_latency" in vllm_results: return vllm_results["avg_latency"] * 1000 # seconds -> milliseconds diff --git a/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py b/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py index cd131dc3b2a..3979f305261 100644 --- a/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py +++ b/modelopt/torch/puzzletron/tools/bypassed_training/child_init.py @@ -918,9 +918,7 @@ def _parse_model_config_overrides( if os.path.exists( model_config_overrides_json ): # using os.path.exists, because Path.exists throws an exception on long strings - model_config_overrides_json = Path(model_config_overrides_json).read_text( - encoding="utf-8" - ) + model_config_overrides_json = Path(model_config_overrides_json).read_text() print(f"I'm json loadsing over here. {model_config_overrides_json=}") model_config_overrides_dict = json.loads(model_config_overrides_json) @@ -977,7 +975,7 @@ def _apply_hidden_size_pruning( channel_ranking = None if hidden_size_init_mode == HiddenSizeInitMode.PruneByChannelRanking: if channel_importance_path is not None: - with open(channel_importance_path, "r", encoding="utf-8") as f: + with open(channel_importance_path, "r") as f: channel_ranking = json.load(f)["channel_importance_ranking"] else: raise ValueError( diff --git a/modelopt/torch/puzzletron/tools/checkpoint_utils.py b/modelopt/torch/puzzletron/tools/checkpoint_utils.py index 162733d7679..becabf04314 100644 --- a/modelopt/torch/puzzletron/tools/checkpoint_utils.py +++ b/modelopt/torch/puzzletron/tools/checkpoint_utils.py @@ -182,9 +182,7 @@ def copy_tokenizer( """ source_tokenizer_name_path = Path(source_dir_or_tokenizer_name) / "tokenizer_name.txt" if source_tokenizer_name_path.exists(): - source_dir_or_tokenizer_name = source_tokenizer_name_path.read_text( - encoding="utf-8" - ).strip() + source_dir_or_tokenizer_name = source_tokenizer_name_path.read_text().strip() tokenizer = None try: @@ -206,4 +204,4 @@ def copy_tokenizer( target_tokenizer_name_path = target_dir / "tokenizer_name.txt" is_given_tokenizer_name_as_argument = not Path(source_dir_or_tokenizer_name).exists() if is_given_tokenizer_name_as_argument: - target_tokenizer_name_path.write_text(source_dir_or_tokenizer_name, encoding="utf-8") + target_tokenizer_name_path.write_text(source_dir_or_tokenizer_name) diff --git a/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py b/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py index 047b26e093a..9a9ebbaade1 100644 --- a/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py +++ b/modelopt/torch/puzzletron/tools/sharded_checkpoint_utils.py @@ -367,7 +367,7 @@ def save_sharded_model( index = {"metadata": metadata, "weight_map": weight_map} index_path = Path(str(out_path) + ".index.json") - index_path.write_text(json.dumps(index, indent=2), encoding="utf-8") + index_path.write_text(json.dumps(index, indent=2)) else: torch.distributed.gather_object(shard_metadata, dst=0) diff --git a/modelopt/torch/puzzletron/tools/validate_model.py b/modelopt/torch/puzzletron/tools/validate_model.py index a13cbe76cc9..b5d997286f9 100644 --- a/modelopt/torch/puzzletron/tools/validate_model.py +++ b/modelopt/torch/puzzletron/tools/validate_model.py @@ -198,9 +198,7 @@ def validate_model( results_str = textwrap.dedent(results_str) aprint(results_str) if args.write_results: - Path(f"{args.model_name_or_path}/validate_model_results.txt").write_text( - results_str, encoding="utf-8" - ) + Path(f"{args.model_name_or_path}/validate_model_results.txt").write_text(results_str) if activation_hooks is not None: hook_class.dump_activations_logs(activation_hooks, args.activations_log_dir, args) diff --git a/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py b/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py index 60183377d4b..3ed4b517b3e 100644 --- a/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py +++ b/modelopt/torch/puzzletron/tools/validate_puzzle_with_multi_replacements.py @@ -277,13 +277,12 @@ def load_puzzle_solutions( assert solutions_path.exists(), f"{solutions_path=} does not exist" if solutions_path.is_file(): - puzzle_solutions = json.loads(solutions_path.read_text(encoding="utf-8")) + puzzle_solutions = json.loads(solutions_path.read_text()) if isinstance(puzzle_solutions, dict): puzzle_solutions = [puzzle_solutions] else: puzzle_solutions = [ - json.loads(p.read_text(encoding="utf-8")) - for p in solutions_path.glob("*solution*.json") + json.loads(p.read_text()) for p in solutions_path.glob("*solution*.json") ] if len(puzzle_solutions) == 0: diff --git a/modelopt/torch/puzzletron/utils/checkpoint_manager.py b/modelopt/torch/puzzletron/utils/checkpoint_manager.py index 342b5ac0ddc..e0b90deaeac 100644 --- a/modelopt/torch/puzzletron/utils/checkpoint_manager.py +++ b/modelopt/torch/puzzletron/utils/checkpoint_manager.py @@ -75,7 +75,7 @@ def load_checkpoint(self) -> dict[str, Any] | None: return None try: - with open(self.progress_file, encoding="utf-8") as f: + with open(self.progress_file) as f: checkpoint_data = json.load(f) # Validate checkpoint @@ -222,7 +222,7 @@ def save_checkpoint(self): # Write progress atomically temp_file = self.progress_file.with_suffix(".tmp") - with open(temp_file, "w", encoding="utf-8") as f: + with open(temp_file, "w") as f: json.dump(progress_data, f, indent=2) temp_file.replace(self.progress_file) diff --git a/modelopt/torch/puzzletron/utils/misc.py b/modelopt/torch/puzzletron/utils/misc.py index 36ef1d336b4..68751d1e07e 100644 --- a/modelopt/torch/puzzletron/utils/misc.py +++ b/modelopt/torch/puzzletron/utils/misc.py @@ -98,7 +98,7 @@ def load_json(file_path: str): print("file does not exist {file_path}") return None - with open(encoding="utf-8", file=file_path) as f: + with open(file=file_path) as f: return json.load(f) diff --git a/modelopt/torch/quantization/plugins/attention.py b/modelopt/torch/quantization/plugins/attention.py index ec5d635e248..2113edea8a7 100644 --- a/modelopt/torch/quantization/plugins/attention.py +++ b/modelopt/torch/quantization/plugins/attention.py @@ -255,7 +255,7 @@ def _create_quantized_class_from_ast( temp_file_name = temp_file.name print(f"Definition of {new_class_name} saved to {temp_file_name}") else: - with open(temp_file_name, "w", encoding="utf-8") as f: + with open(temp_file_name, "w") as f: f.write(module_code_str) # Exec with python runtime and extract the new class diff --git a/modelopt/torch/quantization/utils/layerwise_calib.py b/modelopt/torch/quantization/utils/layerwise_calib.py index 56070b2d11e..56e7554f522 100644 --- a/modelopt/torch/quantization/utils/layerwise_calib.py +++ b/modelopt/torch/quantization/utils/layerwise_calib.py @@ -568,7 +568,7 @@ def _read_manifest(checkpoint_dir: str) -> dict | None: if not os.path.isfile(path): return None try: - with open(path, encoding="utf-8") as f: + with open(path) as f: return json.load(f) except (json.JSONDecodeError, OSError): return None @@ -585,7 +585,7 @@ def _write_manifest( """Atomically write manifest.json. Config keys are persisted so resume can detect drift.""" path = os.path.join(checkpoint_dir, "manifest.json") tmp = path + ".tmp" - with open(tmp, "w", encoding="utf-8") as f: + with open(tmp, "w") as f: json.dump( { "last_completed_layer": last_completed_layer, diff --git a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py index 74a2ba5fc7e..abbbc399d6b 100644 --- a/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py +++ b/modelopt/torch/sparsity/attention_sparsity/calibration/ruler_dataset.py @@ -142,7 +142,7 @@ def _load_paul_graham_essays_from_files(data_dir: Path) -> str: all_essays = [] for filepath in essay_files: - text = filepath.read_text(encoding="utf-8") + text = filepath.read_text() all_essays.append(text) combined_text = " ".join(all_essays) @@ -740,7 +740,7 @@ def _load_cached_data(self, cache_path: Path) -> list[dict[str, Any]] | None: """Load calibration data from cache if it exists.""" if cache_path.exists(): try: - with open(cache_path, encoding="utf-8") as f: + with open(cache_path) as f: data = json.load(f) print(f"Loaded {len(data)} cached calibration samples from {cache_path}") return data @@ -752,7 +752,7 @@ def _save_cached_data(self, cache_path: Path, data: list[dict[str, Any]]) -> Non """Save calibration data to cache.""" try: cache_path.parent.mkdir(parents=True, exist_ok=True) - with open(cache_path, "w", encoding="utf-8") as f: + with open(cache_path, "w") as f: json.dump(data, f) print(f"Saved calibration samples to cache: {cache_path}") except Exception as e: diff --git a/modelopt/torch/speculative/plugins/modeling_fakebase.py b/modelopt/torch/speculative/plugins/modeling_fakebase.py index 6731f9721b2..2b5fe989c03 100644 --- a/modelopt/torch/speculative/plugins/modeling_fakebase.py +++ b/modelopt/torch/speculative/plugins/modeling_fakebase.py @@ -242,7 +242,7 @@ def _try_fetch(name: str) -> str | None: return None if (index_path := _try_fetch(_SAFETENSORS_INDEX_FILENAME)) is not None: - with open(index_path, encoding="utf-8") as f: + with open(index_path) as f: return json.load(f).get("weight_map", {}) for single_name in _SAFETENSORS_SINGLE_FILENAMES: if (single_path := _try_fetch(single_name)) is not None: diff --git a/modelopt/torch/utils/logging.py b/modelopt/torch/utils/logging.py index 1137f737b50..85d3b9df18f 100644 --- a/modelopt/torch/utils/logging.py +++ b/modelopt/torch/utils/logging.py @@ -102,11 +102,7 @@ def _new_init(self, *args, **kwargs): def no_stdout(): """Silences stdout within the invoked context.""" # Special disable for tqdm - with ( - open(os.devnull, "w", encoding="utf-8") as f, - contextlib.redirect_stdout(f), - _disable_tqdm(), - ): + with open(os.devnull, "w") as f, contextlib.redirect_stdout(f), _disable_tqdm(): yield diff --git a/modelopt/torch/utils/mlflow.py b/modelopt/torch/utils/mlflow.py index 5322640f095..13740aeb858 100644 --- a/modelopt/torch/utils/mlflow.py +++ b/modelopt/torch/utils/mlflow.py @@ -168,10 +168,10 @@ def _git_sha() -> str: try: git_path = Path(__file__).resolve().parents[3] / ".git" if git_path.is_file(): - git_dir = Path(git_path.read_text(encoding="utf-8").split("gitdir:", 1)[1].strip()) + git_dir = Path(git_path.read_text().split("gitdir:", 1)[1].strip()) else: git_dir = git_path - head = (git_dir / "HEAD").read_text(encoding="utf-8").strip() + head = (git_dir / "HEAD").read_text().strip() if not head.startswith("ref: "): return head[:9] # detached HEAD ref = head.removeprefix("ref: ") @@ -179,13 +179,13 @@ def _git_sha() -> str: bases = [git_dir] commondir = git_dir / "commondir" if commondir.is_file(): - bases.append((git_dir / commondir.read_text(encoding="utf-8").strip()).resolve()) + bases.append((git_dir / commondir.read_text().strip()).resolve()) for base in bases: if (base / ref).is_file(): - return (base / ref).read_text(encoding="utf-8").strip()[:9] + return (base / ref).read_text().strip()[:9] packed = base / "packed-refs" if packed.is_file(): - for line in packed.read_text(encoding="utf-8").splitlines(): + for line in packed.read_text().splitlines(): sha, _, name = line.partition(" ") if name.strip() == ref: return sha[:9] diff --git a/modelopt/torch/utils/plugins/model_load_utils.py b/modelopt/torch/utils/plugins/model_load_utils.py index 0eaa8dff241..cd66567fa9a 100644 --- a/modelopt/torch/utils/plugins/model_load_utils.py +++ b/modelopt/torch/utils/plugins/model_load_utils.py @@ -83,7 +83,7 @@ def weight_map_for(ckpt_path: str) -> dict[str, str]: index_path = os.path.join(ckpt_path, "model.safetensors.index.json") single_file = os.path.join(ckpt_path, "model.safetensors") if os.path.exists(index_path): - with open(index_path, encoding="utf-8") as f: + with open(index_path) as f: return json.load(f)["weight_map"] if os.path.exists(single_file): with safe_open(single_file, framework="pt", device="cpu") as f: diff --git a/modelopt/torch/utils/robust_json.py b/modelopt/torch/utils/robust_json.py index c3cd7116660..23a3091637f 100644 --- a/modelopt/torch/utils/robust_json.py +++ b/modelopt/torch/utils/robust_json.py @@ -75,11 +75,11 @@ def json_dump(obj: Any, path: Path | str) -> None: path = Path(path) path.parent.mkdir(exist_ok=True, parents=True) json_text = json_dumps(obj) - path.write_text(json_text, encoding="utf-8") + path.write_text(json_text) def json_load(path: Path | str) -> dict: """Load JSON from file and return as dictionary.""" path = Path(path) - text = path.read_text(encoding="utf-8") + text = path.read_text() return json.loads(text) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py index a8e09ac9547..71563d2534b 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/scripts/benchmark_via_builtin.py @@ -315,7 +315,7 @@ def _write_builtin(path: Path, rows: list[dict[str, str]]) -> None: for row in rows: for key in row: fieldnames.setdefault(key, None) - with path.open("w", encoding="utf-8", newline="") as stream: + with path.open("w", newline="") as stream: writer = csv.DictWriter( stream, fieldnames=list(fieldnames), restval="", lineterminator="\n" ) @@ -645,7 +645,7 @@ def _write_results( columns = ["module_name", "M", "N", "K", "backend", "with_quant", "runtime"] gemm = [case for case in cases if case.section == "gemm" and case.result is not None] moe = [case for case in cases if case.section == "moe" and case.result is not None] - with path.open("w", encoding="utf-8", newline="") as stream: + with path.open("w", newline="") as stream: writer = csv.writer(stream, lineterminator="\n") if header: writer.writerow([header]) @@ -752,7 +752,7 @@ def _execute_cases( """ case_csv = workdir / "case_result.csv" rows: list[dict[str, str]] = [] - with driver_log.open("w", encoding="utf-8") as log: + with driver_log.open("w") as log: print(header, flush=True) log.write(header + "\n") for case in cases: @@ -838,8 +838,7 @@ def main(argv: list[str] | None = None) -> None: if builtin_csv.exists() or combined_csv.exists(): parser.error(f"{args.workdir} already contains results; choose a fresh --workdir") testlist.write_text( - "\n".join(shlex.join([*case.argv, "--case_tag", case.tag]) for case in cases) + "\n", - encoding="utf-8", + "\n".join(shlex.join([*case.argv, "--case_tag", case.tag]) for case in cases) + "\n" ) header = _environment_header(args.flashinfer_repo) diff --git a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py index b299112cd10..d40d97bb5c5 100644 --- a/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py +++ b/plugins/modelopt/skills/benchmark-model-kernels/tests/test_benchmark_via_builtin.py @@ -219,9 +219,9 @@ def test_unavailable_fp8_quantization_is_written_as_an_error(monkeypatch, capsys assert case.quant_result == benchmark._FP8_QUANT_UNAVAILABLE assert "[WARN] vLLM is unavailable for FP8 activation quantization" in capsys.readouterr().out - assert "32x64,1,32,64,fp8_cutlass,False,1.000\n" in output.read_text(encoding="utf-8") + assert "32x64,1,32,64,fp8_cutlass,False,1.000\n" in output.read_text() assert f"32x64,1,32,64,fp8_cutlass,True,{benchmark._FP8_QUANT_UNAVAILABLE}\n" in ( - output.read_text(encoding="utf-8") + output.read_text() ) @@ -248,12 +248,8 @@ def test_driver_errors_are_added_to_kernel_and_with_quant_rows(tmp_path): benchmark._write_results(csv_path, [case], {(1280, 2880): ["1280x2880"]}) expected = "ERROR: K must be divisible by 128; got 2880" - assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text( - encoding="utf-8" - ) - assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text( - encoding="utf-8" - ) + assert f"1280x2880,8,1280,2880,fp8_trtllm,False,{expected}\n" in csv_path.read_text() + assert f"1280x2880,8,1280,2880,fp8_trtllm,True,{expected}\n" in csv_path.read_text() def test_empty_driver_error_has_no_synthetic_reason(): @@ -307,7 +303,7 @@ def test_write_results_emits_long_form_rows(tmp_path): moe_shape=benchmark._MoeShape(32, 50, 4, 2, "Relu2", "model.layers.*.mlp.experts"), ) - assert output.read_text(encoding="utf-8") == ( + assert output.read_text() == ( "flashinfer test-header\n" "GEMM\n" "module_name,M,N,K,backend,with_quant,runtime\n" @@ -360,7 +356,7 @@ def test_missing_builtin_results_still_writes_combined_errors( ): benchmarks_dir = tmp_path / "flashinfer" / "benchmarks" benchmarks_dir.mkdir(parents=True) - (benchmarks_dir / "flashinfer_benchmark.py").write_text("", encoding="utf-8") + (benchmarks_dir / "flashinfer_benchmark.py").write_text("") workdir = tmp_path / "results" monkeypatch.setattr(benchmark, "_run_case", lambda *_: (returncode, [])) monkeypatch.setattr( @@ -383,23 +379,23 @@ def test_missing_builtin_results_still_writes_combined_errors( benchmark.main() assert not (workdir / "builtin_results.csv").exists() - combined = (workdir / "combined_results.csv").read_text(encoding="utf-8") + combined = (workdir / "combined_results.csv").read_text() assert f"2x3,1,2,3,bf16,False,ERROR: {expected_reason}" in combined assert "driver.log" in combined # The reproducibility header leads both the combined CSV and driver.log. assert combined.splitlines()[0].startswith("flashinfer ") - assert (workdir / "driver.log").read_text(encoding="utf-8").startswith("flashinfer ") + assert (workdir / "driver.log").read_text().startswith("flashinfer ") def test_case_rows_with_foreign_tags_are_treated_as_failures(monkeypatch, tmp_path): benchmarks_dir = tmp_path / "flashinfer" / "benchmarks" benchmarks_dir.mkdir(parents=True) - (benchmarks_dir / "flashinfer_benchmark.py").write_text("", encoding="utf-8") + (benchmarks_dir / "flashinfer_benchmark.py").write_text("") workdir = tmp_path / "results" def fake_run_case(benchmarks_dir, argv, log): output = Path(argv[argv.index("--output_path") + 1]) - output.write_text("case_tag,median_time\nsomeone_else,0.001\n", encoding="utf-8") + output.write_text("case_tag,median_time\nsomeone_else,0.001\n") return 0, [] monkeypatch.setattr(benchmark, "_run_case", fake_run_case) @@ -422,7 +418,7 @@ def fake_run_case(benchmarks_dir, argv, log): with pytest.raises(RuntimeError, match="FlashInfer failed benchmark cases"): benchmark.main() - combined = (workdir / "combined_results.csv").read_text(encoding="utf-8") + combined = (workdir / "combined_results.csv").read_text() assert "no result row" in combined @@ -430,7 +426,7 @@ def test_run_case_streams_and_appends_to_the_driver_log(tmp_path, capsys): benchmarks_dir = tmp_path / "benchmarks" benchmarks_dir.mkdir() (benchmarks_dir / "flashinfer_benchmark.py").write_text( - "print('line one')\nprint('line two')\n", encoding="utf-8" + "print('line one')\nprint('line two')\n" ) driver_log = tmp_path / "driver.log" @@ -439,7 +435,7 @@ def test_run_case_streams_and_appends_to_the_driver_log(tmp_path, capsys): assert returncode == 0 assert lines == ["line one\n", "line two\n"] - assert driver_log.read_text(encoding="utf-8") == "line one\nline two\n" + assert driver_log.read_text() == "line one\nline two\n" assert "line one" in capsys.readouterr().out @@ -454,6 +450,6 @@ def test_write_builtin_merges_heterogeneous_row_columns(tmp_path): ], ) - assert path.read_text(encoding="utf-8") == ( + assert path.read_text() == ( "routine,median_time,case_tag,num_experts\nmm_bf16,0.004,a,\ncutlass_fused_moe,,b,8\n" ) diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_compare.py b/plugins/modelopt/skills/day0-release/scripts/gate_compare.py index 767ca50c133..16767630f5e 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_compare.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_compare.py @@ -201,9 +201,9 @@ def main(argv=None): args = p.parse_args(argv) try: - with open(args.baseline, encoding="utf-8") as f: + with open(args.baseline) as f: baseline = json.load(f) - with open(args.candidate, encoding="utf-8") as f: + with open(args.candidate) as f: candidate = json.load(f) scales = json.loads(args.scales) if args.scales else None except (OSError, json.JSONDecodeError) as e: diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py b/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py index 9005e273039..5fa61862bc8 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_ptq.py @@ -309,7 +309,7 @@ def main(argv=None): return 2 try: - with open(args.summary, encoding="utf-8") as f: + with open(args.summary) as f: summary = json.load(f) except (OSError, json.JSONDecodeError) as e: print( diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_run.py b/plugins/modelopt/skills/day0-release/scripts/gate_run.py index ce333417cc0..d5dcbe94a70 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_run.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_run.py @@ -144,7 +144,7 @@ def main(argv=None): args = p.parse_args(argv) try: - with open(args.run, encoding="utf-8") as f: + with open(args.run) as f: summary = json.load(f) except (OSError, json.JSONDecodeError) as e: print(json.dumps({"pass": False, "failure_class": "USER_CONFIG_ERROR", "detail": str(e)})) diff --git a/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py b/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py index 2742fbcda74..fbfb86649f7 100644 --- a/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py +++ b/plugins/modelopt/skills/day0-release/scripts/gate_verbosity.py @@ -274,7 +274,7 @@ def _task_from_metadata(artifacts_dir): """ for fname in ("metadata.yaml", "config.yml"): try: - with open(os.path.join(artifacts_dir, fname), encoding="utf-8") as f: + with open(os.path.join(artifacts_dir, fname)) as f: text = f.read() except OSError: continue @@ -342,7 +342,7 @@ def harvest(side, glob="eval_*", exclude="", diagnostics=None): head, _, tail = name.rpartition(".") task = head if head and re.fullmatch(r"\d+", tail) else name try: - with open(path, encoding="utf-8") as f: + with open(path) as f: stats = json.load(f).get("response_stats", {}) except (OSError, json.JSONDecodeError) as e: unreadable.append(f"{path}: {e}") diff --git a/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py b/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py index 3f03016ac00..883195bf491 100644 --- a/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py +++ b/plugins/modelopt/skills/day0-release/tests/test_agent_definitions.py @@ -30,7 +30,7 @@ def _load_claude_agent(path: Path) -> tuple[str, str]: - text = path.read_text(encoding="utf-8") + text = path.read_text() assert text.startswith("---\n"), f"{path} has no YAML frontmatter" frontmatter, body = text.removeprefix("---\n").split("\n---\n", 1) names = [ diff --git a/plugins/modelopt/skills/day0-release/tests/test_gates.py b/plugins/modelopt/skills/day0-release/tests/test_gates.py index 8e3cd53cec0..b8217883e17 100644 --- a/plugins/modelopt/skills/day0-release/tests/test_gates.py +++ b/plugins/modelopt/skills/day0-release/tests/test_gates.py @@ -361,10 +361,7 @@ def test_harvest_keys_by_task_not_harness(tmp_path): d = tmp_path / "eval_run" / "inv123" / name / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps( - {"response_stats": {"avg_completion_tokens": 100.0, "successful_count": 10}} - ), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 100.0, "successful_count": 10}}) ) # Harness is kept: two harnesses can expose the same task name, and pooling them # would average different generation conditions together. @@ -380,20 +377,17 @@ def test_harvest_reports_what_it_dropped(tmp_path): good = tmp_path / "eval_run" / "inv" / "h.good" / "artifacts" good.mkdir(parents=True) good.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) bad = tmp_path / "eval_run" / "inv" / "h.notok" / "artifacts" bad.mkdir(parents=True) bad.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"successful_count": 2}}), # no token count - encoding="utf-8", + json.dumps({"response_stats": {"successful_count": 2}}) # no token count ) skipped = tmp_path / "eval_high" / "inv" / "h.excl" / "artifacts" skipped.mkdir(parents=True) skipped.joinpath("eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) diag = {} out = harvest(str(tmp_path), exclude="_high", diagnostics=diag) @@ -414,10 +408,7 @@ def test_ptq_waiver_requires_canonical_values(): def _write_metrics(d, tokens=100.0, count=10): d.mkdir(parents=True) d.joinpath("eval_factory_metrics.json").write_text( - json.dumps( - {"response_stats": {"avg_completion_tokens": tokens, "successful_count": count}} - ), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": tokens, "successful_count": count}}) ) @@ -485,15 +476,11 @@ def test_every_emitted_failure_class_has_a_triage_row(): # (ACCEPT/REGRESSION) and SLURM states (PENDING/RUNNING) are not failure classes. emitted = set() for f in scripts.glob("gate_*.py"): - src = f.read_text(encoding="utf-8") + src = f.read_text() emitted |= set(re.findall(r'"failure_class":\s*"([A-Z_]+)"', src)) emitted |= set(re.findall(r'failures\.append\(\s*\(\s*\n?\s*"([A-Z_]+)"', src)) rows = set( - re.findall( - r"^\| `([A-Z_]+)` \|", - (scripts.parent / "SKILL.md").read_text(encoding="utf-8"), - re.MULTILINE, - ) + re.findall(r"^\| `([A-Z_]+)` \|", (scripts.parent / "SKILL.md").read_text(), re.MULTILINE) ) # Subtract only declared exemptions: intersecting with an allowlist would filter out # exactly the newly-emitted class this test exists to catch. @@ -533,12 +520,9 @@ def test_harvest_prefers_the_task_name_from_metadata(tmp_path): for job, name in ((0, "simple_evals.gpqa"), (1, "tau2.telecom")): d = tmp_path / "eval_run" / f"inv123.{job}" / "artifacts" d.mkdir(parents=True) - (d / "metadata.yaml").write_text( - f"evaluation:\n tasks:\n - name: {name}\n", encoding="utf-8" - ) + (d / "metadata.yaml").write_text(f"evaluation:\n tasks:\n - name: {name}\n") (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) assert set(harvest(str(tmp_path))) == {"simple_evals.gpqa", "tau2.telecom"} @@ -549,8 +533,7 @@ def test_harvest_flags_collapsed_task_keys(tmp_path): d = tmp_path / "eval_run" / f"inv123.{job}" / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) diag = {} harvest(str(tmp_path), diagnostics=diag) @@ -565,8 +548,7 @@ def test_dropped_tasks_covers_the_excluded_channel(tmp_path): d = tmp_path / "eval_high" / "inv" / "h.only_high" / "artifacts" d.mkdir(parents=True) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) diag = {} assert harvest(str(tmp_path), exclude="_high", diagnostics=diag) == {} @@ -613,12 +595,11 @@ def _mk_run(root, leaf, cfg=None, meta=None): d = root / "eval_run" / leaf / "artifacts" d.mkdir(parents=True) if cfg: - (d / "config.yml").write_text(cfg, encoding="utf-8") + (d / "config.yml").write_text(cfg) if meta: - (d / "metadata.yaml").write_text(meta, encoding="utf-8") + (d / "metadata.yaml").write_text(meta) (d / "eval_factory_metrics.json").write_text( - json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}), - encoding="utf-8", + json.dumps({"response_stats": {"avg_completion_tokens": 10.0, "successful_count": 2}}) ) return d @@ -674,7 +655,7 @@ def test_unreadable_artifact_does_not_count_toward_collapse(tmp_path): good = _mk_run(tmp_path, "inv1.0") bad = tmp_path / "eval_run" / "inv1.1" / "artifacts" bad.mkdir(parents=True) - (bad / "eval_factory_metrics.json").write_text("{truncated", encoding="utf-8") + (bad / "eval_factory_metrics.json").write_text("{truncated") assert good.exists() diag = {} out = harvest(str(tmp_path), diagnostics=diag) diff --git a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py index b24a3559347..ce9e367a058 100644 --- a/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py +++ b/plugins/modelopt/skills/evaluation/tests/test_nel_gdpval.py @@ -23,9 +23,7 @@ def test_launcher_uses_validated_pin_despite_environment_override(tmp_path): args_file = tmp_path / "uvx-args" uvx = tmp_path / "uvx" - uvx.write_text( - '#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n', encoding="utf-8" - ) + uvx.write_text('#!/usr/bin/env bash\nprintf "%s\\n" "$@" > "$UVX_ARGS_FILE"\n') uvx.chmod(0o755) env = os.environ.copy() @@ -40,7 +38,7 @@ def test_launcher_uses_validated_pin_despite_environment_override(tmp_path): subprocess.run([SCRIPT, "run", "--config", "gdpval.yaml"], env=env, check=True) - assert args_file.read_text(encoding="utf-8").splitlines() == [ + assert args_file.read_text().splitlines() == [ "--python", "3.10", "--from", diff --git a/pyproject.toml b/pyproject.toml index 410ec6ee1ae..0886e0380b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -197,12 +197,6 @@ docstring-code-line-length = "dynamic" [tool.ruff.lint] # See available rules at https://docs.astral.sh/ruff/rules/ # Flake8 is equivalent to pycodestyle + pyflakes + mccabe. -# PLW1514 (unspecified-encoding) is still a preview rule, and plain `preview = true` would also -# switch on preview BEHAVIOUR for the stable rules above -- 3755 findings on this tree. -# explicit-preview-rules keeps that contained: only preview rules named exactly here are enabled. -preview = true -explicit-preview-rules = true - select = [ "C4", # Flake8 comprehensions "D", # pydocstyle @@ -216,11 +210,6 @@ select = [ "PGH", # pygrep-hooks "PIE", # flake8-pie "PLE", # pylint errors - # Text I/O without an explicit encoding uses the locale codepage, which is cp1252 on the - # Windows runners -- a UTF-8 file then dies with UnicodeDecodeError on the first non-Latin-1 - # byte, a failure no other platform sees. Covers `open` only; see the read_text/write_text - # pre-commit hook for the half ruff does not implement. - "PLW1514", # pylint: unspecified encoding "PLR", # pylint refactor "PT", # flake8-pytest-style "RUF", # ruff diff --git a/tests/_test_utils/deploy_utils.py b/tests/_test_utils/deploy_utils.py index d8979c3865a..00abcdf0b74 100644 --- a/tests/_test_utils/deploy_utils.py +++ b/tests/_test_utils/deploy_utils.py @@ -178,9 +178,7 @@ def _run_deploy_via_subprocess( cmd = [sys.executable, "-c", code] if backend == "trtllm": - with tempfile.NamedTemporaryFile( - encoding="utf-8", mode="w", suffix=".py", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write(code) tmp_path = f.name try: diff --git a/tests/_test_utils/examples/megatron_example_runner.py b/tests/_test_utils/examples/megatron_example_runner.py index d01d9fc11e8..ac974a5a07e 100644 --- a/tests/_test_utils/examples/megatron_example_runner.py +++ b/tests/_test_utils/examples/megatron_example_runner.py @@ -31,6 +31,7 @@ import contextlib import gc +import importlib import importlib.util import logging import os diff --git a/tests/_test_utils/examples/onnx_ptq/aggregate_results.py b/tests/_test_utils/examples/onnx_ptq/aggregate_results.py index 4181e515d6e..4a25de9e8fe 100644 --- a/tests/_test_utils/examples/onnx_ptq/aggregate_results.py +++ b/tests/_test_utils/examples/onnx_ptq/aggregate_results.py @@ -19,7 +19,7 @@ def get_metrics_from_csv(file_path): - with open(file_path, encoding="utf-8") as csv_file: + with open(file_path) as csv_file: csv_reader = csv.reader(csv_file) next(csv_reader) top1_accuracy, top5_accuracy, latency = None, None, None @@ -171,7 +171,7 @@ def main(): output_file_path = os.path.join(build_folder_path, "aggregated_results.csv") # Write aggregated data to a new CSV file - with open(output_file_path, encoding="utf-8", mode="w", newline="") as output_file: + with open(output_file_path, mode="w", newline="") as output_file: csv_writer = csv.writer(output_file) # Write header csv_writer.writerow( diff --git a/tests/_test_utils/torch/diffusers_models.py b/tests/_test_utils/torch/diffusers_models.py index d938c85265d..c680c64bc31 100644 --- a/tests/_test_utils/torch/diffusers_models.py +++ b/tests/_test_utils/torch/diffusers_models.py @@ -355,8 +355,8 @@ def _build_local_qwen2_tokenizer(out_dir: Path): vocab = {token: idx for idx, token in enumerate(_byte_level_unicode_chars())} for special in ("<|endoftext|>", "<|im_start|>", "<|im_end|>"): vocab.setdefault(special, len(vocab)) - (out_dir / "vocab.json").write_text(json.dumps(vocab), encoding="utf-8") - (out_dir / "merges.txt").write_text("#version: 0.2\n", encoding="utf-8") + (out_dir / "vocab.json").write_text(json.dumps(vocab)) + (out_dir / "merges.txt").write_text("#version: 0.2\n") special_kwargs = { "unk_token": "<|endoftext|>", diff --git a/tests/_test_utils/torch/export/unified_checkpoint.py b/tests/_test_utils/torch/export/unified_checkpoint.py index 50f3d946624..bfa0647c69b 100644 --- a/tests/_test_utils/torch/export/unified_checkpoint.py +++ b/tests/_test_utils/torch/export/unified_checkpoint.py @@ -61,7 +61,7 @@ def assert_safetensors_index_consistent(export_dir: Path | str) -> None: index_file = export_dir / "model.safetensors.index.json" if not index_file.exists(): # single unsharded file: nothing to cross-check return - weight_map = json.loads(index_file.read_text(encoding="utf-8"))["weight_map"] + weight_map = json.loads(index_file.read_text())["weight_map"] missing_files = {f for f in set(weight_map.values()) if not (export_dir / f).exists()} assert not missing_files, f"index.json references missing shards: {sorted(missing_files)}" exported = set(load_safetensors_dir(export_dir)) diff --git a/tests/_test_utils/torch/quantization/quant_utils.py b/tests/_test_utils/torch/quantization/quant_utils.py index 38abeaf5a59..5c997b86c97 100644 --- a/tests/_test_utils/torch/quantization/quant_utils.py +++ b/tests/_test_utils/torch/quantization/quant_utils.py @@ -33,7 +33,7 @@ def quant(x, amax, num_bits=8, fake=False, narrow_range=True): def get_model_size(model): - return sum(p.element_size() * p.nelement() for p in model.parameters()) + return sum([p.element_size() * p.nelement() for p in model.parameters()]) def nvfp4_static_amax_dtypes(model): diff --git a/tests/examples/diffusers/fastgen/test_vendored_migration.py b/tests/examples/diffusers/fastgen/test_vendored_migration.py index e5c3cdfcb01..d881a6e49a6 100644 --- a/tests/examples/diffusers/fastgen/test_vendored_migration.py +++ b/tests/examples/diffusers/fastgen/test_vendored_migration.py @@ -77,7 +77,7 @@ def test_all_configs_target_vendored_builders(): configs = sorted((_FASTGEN_DIR / "configs").glob("*.yaml")) assert configs, "no configs found under configs/" for cfg in configs: - text = cfg.read_text(encoding="utf-8") + text = cfg.read_text() assert "nemo_automodel.components.datasets.diffusion.build_" not in text, ( f"{cfg.name} still targets the upstream dataloader builder (breaks on stock upstream)" ) @@ -93,7 +93,7 @@ def test_no_tools_star_imports_in_vendored_code(): str(py.relative_to(_FASTGEN_DIR)) for sub in ("fastgen_data", "preprocess") for py in (_FASTGEN_DIR / sub).rglob("*.py") - if pat.search(py.read_text(encoding="utf-8")) + if pat.search(py.read_text()) ] assert not offenders, f"tools.* imports found in vendored code: {offenders}" @@ -133,7 +133,7 @@ def test_all_staged_automodel_files_are_removable(): def test_formerly_vendored_files_use_standard_nvidia_header(): """They carry only the standard NVIDIA SPDX header — no provenance note, no duplicate license.""" for target in FORMERLY_VENDORED: - text = (_FASTGEN_DIR / target).read_text(encoding="utf-8") + text = (_FASTGEN_DIR / target).read_text() assert text.startswith( "# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES" ), f"{target}: must start with the standard NVIDIA SPDX header" @@ -220,7 +220,7 @@ def test_partial_load_checkpointer_overrides_only_load_optimizer(): def test_recipe_injects_partial_load_checkpointer_in_load_checkpoint(): """The recipe upgrades self.checkpointer in load_checkpoint (before the parent restore).""" - src = (_FASTGEN_DIR / "dmd2_recipe.py").read_text(encoding="utf-8") + src = (_FASTGEN_DIR / "dmd2_recipe.py").read_text() assert "from fastgen_checkpoint import make_optimizer_partial_load_tolerant" in src assert "make_optimizer_partial_load_tolerant(self.checkpointer)" in src diff --git a/tests/examples/diffusers/sparsity/test_sparsity.py b/tests/examples/diffusers/sparsity/test_sparsity.py index 86689bc0fbc..bca94e3dafb 100644 --- a/tests/examples/diffusers/sparsity/test_sparsity.py +++ b/tests/examples/diffusers/sparsity/test_sparsity.py @@ -137,7 +137,7 @@ def test_wan22_export_sparse_checkpoint(tiny_wan22_path, tmp_path): assert component_dir.exists(), f"Missing component dir: {component}" config_path = component_dir / "config.json" assert config_path.exists(), f"Missing config.json for {component}" - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config_data = json.load(f) # Fixed (uncalibrated) threshold has nothing to export. assert "sparse_attention_config" not in config_data, ( @@ -208,7 +208,7 @@ def test_wan22_calibrated_export(tiny_wan22_path, tmp_path): for component in ["transformer", "transformer_2"]: config_path = export_dir / component / "config.json" assert config_path.exists(), f"Missing config.json for {component}" - with open(config_path, encoding="utf-8") as f: + with open(config_path) as f: config_data = json.load(f) assert "sparse_attention_config" in config_data, ( f"No sparse_attention_config in {component}/config.json" diff --git a/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py b/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py index 9f75957ed4f..c0385c088c1 100644 --- a/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py +++ b/tests/examples/diffusers/test_export_diffusers_hf_ckpt.py @@ -234,7 +234,7 @@ def test_qwen_image_hf_ckpt_export( transformer_dir = hf_ckpt_dir / "transformer" config_path = transformer_dir / "config.json" assert config_path.exists(), f"no transformer/config.json in {hf_ckpt_dir}" - quant_config = json.loads(config_path.read_text(encoding="utf-8")).get("quantization_config") + quant_config = json.loads(config_path.read_text()).get("quantization_config") assert quant_config is not None, "missing quantization_config" assert quant_config.get("quant_method") == "modelopt" diff --git a/tests/examples/gpt-oss/test_gpt_oss_qat.py b/tests/examples/gpt-oss/test_gpt_oss_qat.py index ac7cd332432..34b9ab4a2ca 100644 --- a/tests/examples/gpt-oss/test_gpt_oss_qat.py +++ b/tests/examples/gpt-oss/test_gpt_oss_qat.py @@ -235,7 +235,7 @@ def deploy_gpt_oss_trtllm(self, tmp_path, model_path_override=None): if not os.path.exists(benchmark_file) or os.path.getsize(benchmark_file) == 0: print(f"Creating dataset file '{benchmark_file}'...") - with open(benchmark_file, "w", encoding="utf-8") as fp: + with open(benchmark_file, "w") as fp: subprocess.run( f"python {script} --stdout --tokenizer={self.model_path} token-norm-dist --input-mean 128 \ --output-mean 128 --input-stdev 0 --output-stdev 0 --num-requests 1400", diff --git a/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py b/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py index 973f95f533e..6ab49eabf0a 100644 --- a/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py +++ b/tests/examples/hf_ptq/test_cast_mxfp4_to_nvfp4.py @@ -77,7 +77,7 @@ def _write_synthetic_mxfp4_checkpoint( "metadata": {"total_size": sum(t.numel() * t.element_size() for t in state.values())}, "weight_map": dict.fromkeys(state, shard_name), } - (ckpt_dir / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") + (ckpt_dir / "model.safetensors.index.json").write_text(json.dumps(index)) return ckpt_dir @@ -135,8 +135,7 @@ def test_build_amax_map_no_scales_raises(tmp_path): "metadata": {}, "weight_map": {"model.layers.0.weight": "model-00001-of-00001.safetensors"}, } - ), - encoding="utf-8", + ) ) with pytest.raises(SystemExit, match="No '\\*_scales'"): cast.build_amax_map(empty) @@ -230,8 +229,7 @@ def test_apply_to_model_raises_on_missing_blocks_pair(tmp_path): "metadata": {}, "weight_map": {"experts.gate_up_proj_scales": "model-00001-of-00001.safetensors"}, } - ), - encoding="utf-8", + ) ) model = _FakeModel(num_blocks=4) with pytest.raises(AssertionError, match=r"no paired '.*_blocks' tensor"): diff --git a/tests/examples/hf_ptq/test_example_utils.py b/tests/examples/hf_ptq/test_example_utils.py index a6b778a5610..e532af09fed 100644 --- a/tests/examples/hf_ptq/test_example_utils.py +++ b/tests/examples/hf_ptq/test_example_utils.py @@ -73,17 +73,13 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): "model.gguf": "source weights\n", } for file_name, contents in source_files.items(): - (source_dir / file_name).write_text(contents, encoding="utf-8") + (source_dir / file_name).write_text(contents) - (export_dir / "config.json").write_text('{"export": "config"}\n', encoding="utf-8") - (export_dir / "generation_config.json").write_text( - '{"export": "generation"}\n', encoding="utf-8" - ) - (export_dir / "hf_quant_config.json").write_text('{"export": "quant"}\n', encoding="utf-8") - (export_dir / "chat_template.jinja").write_text("{{ exported_messages }}\n", encoding="utf-8") - (export_dir / "tokenizer_config.json").write_text( - '{"chat_template": "export"}\n', encoding="utf-8" - ) + (export_dir / "config.json").write_text('{"export": "config"}\n') + (export_dir / "generation_config.json").write_text('{"export": "generation"}\n') + (export_dir / "hf_quant_config.json").write_text('{"export": "quant"}\n') + (export_dir / "chat_template.jinja").write_text("{{ exported_messages }}\n") + (export_dir / "tokenizer_config.json").write_text('{"chat_template": "export"}\n') example_utils.copy_custom_model_files(str(source_dir), str(export_dir), trust_remote_code=False) @@ -95,15 +91,11 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): "chat_template.jinja", "generation_config.json", ]: - assert (export_dir / file_name).read_text(encoding="utf-8") == source_files[file_name] - - assert (export_dir / "config.json").read_text(encoding="utf-8") == '{"export": "config"}\n' - assert (export_dir / "hf_quant_config.json").read_text( - encoding="utf-8" - ) == '{"export": "quant"}\n' - assert (export_dir / "tokenizer_config.json").read_text( - encoding="utf-8" - ) == '{"chat_template": "export"}\n' + assert (export_dir / file_name).read_text() == source_files[file_name] + + assert (export_dir / "config.json").read_text() == '{"export": "config"}\n' + assert (export_dir / "hf_quant_config.json").read_text() == '{"export": "quant"}\n' + assert (export_dir / "tokenizer_config.json").read_text() == '{"chat_template": "export"}\n' assert not (export_dir / "quant_config.json").exists() assert not (export_dir / "quantize_config.json").exists() assert not (export_dir / "recipe.yaml").exists() @@ -111,17 +103,13 @@ def test_copy_custom_model_files_preserves_non_weight_sidecars(tmp_path): assert not (export_dir / "model-00001-of-00001.safetensors").exists() assert not (export_dir / "model.gguf").exists() - (export_dir / "generation_config.json").write_text( - '{"export": "generation"}\n', encoding="utf-8" - ) + (export_dir / "generation_config.json").write_text('{"export": "generation"}\n') example_utils.copy_custom_model_files( str(source_dir), str(export_dir), exclude_files={"generation_config.json"}, ) - assert (export_dir / "generation_config.json").read_text( - encoding="utf-8" - ) == '{"export": "generation"}\n' + assert (export_dir / "generation_config.json").read_text() == '{"export": "generation"}\n' def test_resolve_model_path_snapshot_download_stays_allowlisted(monkeypatch, tmp_path): @@ -227,8 +215,7 @@ def test_load_mtp_weights_separate_indexed_shard(tmp_path): **dict.fromkeys(mtp_tensors, mtp_shard), } } - ), - encoding="utf-8", + ) ) cfg = SimpleNamespace(num_hidden_layers=4, num_nextn_predict_layers=0) diff --git a/tests/examples/hf_ptq/test_hf_ptq_args.py b/tests/examples/hf_ptq/test_hf_ptq_args.py index 7103cef4416..2f5c860437e 100644 --- a/tests/examples/hf_ptq/test_hf_ptq_args.py +++ b/tests/examples/hf_ptq/test_hf_ptq_args.py @@ -682,7 +682,7 @@ def test_experiment_json_lands_in_the_checkpoint_and_on_the_server( with example_utils.mlflow_run(args): _exported(args) - written = json.loads((tmp_path / ".experiment.json").read_text(encoding="utf-8")) + written = json.loads((tmp_path / ".experiment.json").read_text()) assert written["experiment_name"] == "tester/hf_ptq/Qwen3-0.6B-fp8" assert written["run_id"] == "deadbeef" assert written["run_url"] == "https://mlflow.example.com/#/experiments/7/runs/deadbeef" @@ -702,10 +702,7 @@ def test_experiment_json_is_written_when_a_run_fails_after_exporting( _exported(args) raise RuntimeError("crashed while cleaning up") - assert ( - json.loads((tmp_path / ".experiment.json").read_text(encoding="utf-8"))["run_id"] - == "deadbeef" - ) + assert json.loads((tmp_path / ".experiment.json").read_text())["run_id"] == "deadbeef" assert fake_mlflow.status == "FAILED" @@ -716,19 +713,14 @@ def test_no_local_pointer_when_the_export_never_completed( already hold a valid checkpoint from an earlier attempt. Neither is evidence that this run wrote the weights, so a run that fails before export must not claim them.""" args = _tracked_run(monkeypatch, tmp_path) - (tmp_path / ".quant_summary.txt").write_text( - "706 TensorQuantizers found in model\n", encoding="utf-8" - ) + (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers found in model\n") previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "the-run-that-really-wrote-this"}\n', encoding="utf-8") + previous.write_text('{"run_id": "the-run-that-really-wrote-this"}\n') with pytest.raises(RuntimeError), example_utils.mlflow_run(args): raise RuntimeError("OOM during calibration") - assert ( - json.loads(previous.read_text(encoding="utf-8"))["run_id"] - == "the-run-that-really-wrote-this" - ) + assert json.loads(previous.read_text())["run_id"] == "the-run-that-really-wrote-this" # Still traceable from the server side: the run opened, it just produced no checkpoint. assert json.loads(fake_mlflow.texts["experiment.json"])["run_id"] == "deadbeef" assert fake_mlflow.status == "FAILED" @@ -752,13 +744,13 @@ def explode(name): ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "from-an-earlier-run"}\n', encoding="utf-8") + previous.write_text('{"run_id": "from-an-earlier-run"}\n') with example_utils.mlflow_run(args): _exported(args) assert args.mlflow_required is False - assert json.loads(previous.read_text(encoding="utf-8"))["run_id"] == "from-an-earlier-run" + assert json.loads(previous.read_text())["run_id"] == "from-an-earlier-run" def test_untracked_export_drops_a_pointer_it_would_otherwise_inherit( @@ -771,7 +763,7 @@ def test_untracked_export_drops_a_pointer_it_would_otherwise_inherit( ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) inherited = tmp_path / ".experiment.json" - inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n', encoding="utf-8") + inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n') with example_utils.mlflow_run(args): _exported(args) @@ -786,12 +778,12 @@ def test_untracked_failure_leaves_an_existing_pointer_alone(monkeypatch, example ) args.dist_state = SimpleNamespace(is_main=True, world_size=1) previous = tmp_path / ".experiment.json" - previous.write_text('{"run_id": "still-valid"}\n', encoding="utf-8") + previous.write_text('{"run_id": "still-valid"}\n') with pytest.raises(RuntimeError), example_utils.mlflow_run(args): raise RuntimeError("died before export") - assert json.loads(previous.read_text(encoding="utf-8"))["run_id"] == "still-valid" + assert json.loads(previous.read_text())["run_id"] == "still-valid" def test_only_the_main_rank_clears_an_inherited_pointer(monkeypatch, example_utils, tmp_path): @@ -802,7 +794,7 @@ def test_only_the_main_rank_clears_an_inherited_pointer(monkeypatch, example_uti ) args.dist_state = SimpleNamespace(is_main=False, world_size=8) inherited = tmp_path / ".experiment.json" - inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n', encoding="utf-8") + inherited.write_text('{"run_id": "a-run-that-quantized-something-else"}\n') with example_utils.mlflow_run(args): _exported(args) diff --git a/tests/examples/llm_qat/test_llm_qat.py b/tests/examples/llm_qat/test_llm_qat.py index 7736b70aeac..a7b610a807c 100644 --- a/tests/examples/llm_qat/test_llm_qat.py +++ b/tests/examples/llm_qat/test_llm_qat.py @@ -188,7 +188,7 @@ def test_qwen3_lora_qat_nvfp4(tiny_qwen3_path, tmp_path): _run_export(str(lora_qat_output_dir), str(export_dir)) base_model_dir = export_dir / "base_model" - with open(base_model_dir / "hf_quant_config.json", encoding="utf-8") as f: + with open(base_model_dir / "hf_quant_config.json") as f: assert json.load(f)["quantization"]["quant_algo"] == "NVFP4" base_weights = load_file(base_model_dir / "model.safetensors") @@ -283,7 +283,7 @@ def test_qwen3_qlora_nvfp4(tiny_qwen3_path, tmp_path): assert (export_dir / "adapter_model.safetensors").is_file() assert (base_model_dir / "hf_quant_config.json").is_file() - with open(base_model_dir / "hf_quant_config.json", encoding="utf-8") as f: + with open(base_model_dir / "hf_quant_config.json") as f: assert json.load(f)["quantization"]["quant_algo"] == "NVFP4" # NVFP4 needs the packed weight and *both* scales to be dequantizable downstream. diff --git a/tests/examples/megatron_bridge/test_distill.py b/tests/examples/megatron_bridge/test_distill.py index a4522f6bfe2..75c3e430958 100644 --- a/tests/examples/megatron_bridge/test_distill.py +++ b/tests/examples/megatron_bridge/test_distill.py @@ -94,7 +94,7 @@ def test_distill_llm_sft(tmp_path, num_gpus): records = [{"input": f"Q: what follows {i}?\nA:", "output": f" {i + 1}"} for i in range(64)] for split in ("training", "validation"): (dataset_root / f"{split}.jsonl").write_text( - "\n".join(json.dumps(r) for r in records) + "\n", encoding="utf-8" + "\n".join(json.dumps(r) for r in records) + "\n" ) distill_output_dir = tmp_path / "distill_output" diff --git a/tests/examples/megatron_bridge/test_qad.py b/tests/examples/megatron_bridge/test_qad.py index 8e7bb8dd75e..ddaa6538ef2 100644 --- a/tests/examples/megatron_bridge/test_qad.py +++ b/tests/examples/megatron_bridge/test_qad.py @@ -57,7 +57,7 @@ def test_qad(tmp_path: Path, num_gpus, create_student): export is covered more cheaply by test_quantize_export.py, so keep this to one LLM and one VLM. """ hf_model_path = create_student(tmp_path) - is_vlm = "vision_config" in (hf_model_path / "config.json").read_text(encoding="utf-8") + is_vlm = "vision_config" in (hf_model_path / "config.json").read_text() quantized_megatron_path = tmp_path / "quantized_megatron" distill_output_dir = tmp_path / "qad_output" train_iters = 3 @@ -131,7 +131,7 @@ def test_qad(tmp_path: Path, num_gpus, create_student): assert (hf_export_path / "hf_quant_config.json").exists() # A quantized export writes routed experts one per expert while the BF16 reference packs # them, so both sides of that expansion differ from the reference. - text_config = json.loads((hf_model_path / "config.json").read_text(encoding="utf-8")) + text_config = json.loads((hf_model_path / "config.json").read_text()) is_moe = bool(text_config.get("text_config", text_config).get("num_experts")) # QAD trains the student, so language-model weights drift from the reference; the vision # tower is never trained and must still come through byte for byte. diff --git a/tests/examples/specdec_bench/test_upload_to_s3.py b/tests/examples/specdec_bench/test_upload_to_s3.py index 9e9d7505cc4..bfc663e90e6 100644 --- a/tests/examples/specdec_bench/test_upload_to_s3.py +++ b/tests/examples/specdec_bench/test_upload_to_s3.py @@ -51,8 +51,8 @@ def test_parsing(self, path, expected): def _make_run_dir(path: Path) -> Path: """Create a directory shaped like a specdec_bench run output.""" path.mkdir(parents=True, exist_ok=True) - (path / "configuration.json").write_text("{}", encoding="utf-8") - (path / "timing.json").write_text("{}", encoding="utf-8") + (path / "configuration.json").write_text("{}") + (path / "timing.json").write_text("{}") return path @@ -65,7 +65,7 @@ def test_empty_dir(self, tmp_path): assert upload_to_s3._is_run_dir(tmp_path) is False def test_non_sentinel_files(self, tmp_path): - (tmp_path / "results.txt").write_text("", encoding="utf-8") + (tmp_path / "results.txt").write_text("") assert upload_to_s3._is_run_dir(tmp_path) is False @@ -116,7 +116,7 @@ def test_empty_prefix_flat_layout(self, tmp_path): def test_ignores_non_run_files(self, tmp_path): root = tmp_path / "mixed" _make_run_dir(root / "a") - (root / "notes.txt").write_text("ignore me", encoding="utf-8") + (root / "notes.txt").write_text("ignore me") queue = upload_to_s3._discover_runs(root, "results") assert len(queue) == 1 assert queue[0][0].name == "a" @@ -126,15 +126,13 @@ class TestCheckProvenance: def test_complete(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text( - '{"container_image": "vllm/vllm-openai:nightly"}', encoding="utf-8" - ) + (run / "configuration.json").write_text('{"container_image": "vllm/vllm-openai:nightly"}') assert upload_to_s3._check_provenance(run) == [] def test_missing_container_image(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text('{"container_image": null}', encoding="utf-8") + (run / "configuration.json").write_text('{"container_image": null}') assert upload_to_s3._check_provenance(run) == ["container_image"] def test_no_configuration_json(self, tmp_path): @@ -145,11 +143,11 @@ def test_no_configuration_json(self, tmp_path): def test_malformed_configuration_json(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text("{ not valid json", encoding="utf-8") + (run / "configuration.json").write_text("{ not valid json") assert upload_to_s3._check_provenance(run) == list(upload_to_s3._REQUIRED_PROVENANCE_FIELDS) def test_empty_string_is_missing(self, tmp_path): run = tmp_path / "r" run.mkdir() - (run / "configuration.json").write_text('{"container_image": ""}', encoding="utf-8") + (run / "configuration.json").write_text('{"container_image": ""}') assert upload_to_s3._check_provenance(run) == ["container_image"] diff --git a/tests/examples/speculative_decoding/conftest.py b/tests/examples/speculative_decoding/conftest.py index a8fc774d58a..3b487805be9 100644 --- a/tests/examples/speculative_decoding/conftest.py +++ b/tests/examples/speculative_decoding/conftest.py @@ -39,7 +39,7 @@ def tiny_conversations_path(tmp_path_factory): } for i in range(5) ] - with open(output_file, "w", encoding="utf-8") as f: + with open(output_file, "w") as f: f.writelines(json.dumps(conv) + "\n" for conv in conversations) return output_file diff --git a/tests/examples/torch_onnx/test_torch_quant_to_onnx.py b/tests/examples/torch_onnx/test_torch_quant_to_onnx.py index f5c4a207095..d2748942972 100644 --- a/tests/examples/torch_onnx/test_torch_quant_to_onnx.py +++ b/tests/examples/torch_onnx/test_torch_quant_to_onnx.py @@ -107,8 +107,7 @@ def test_torch_onnx_recipe_flag(tmp_path): " algorithm: max\n" " quant_cfg:\n" " - quantizer_name: '*'\n" - " enable: false\n", - encoding="utf-8", + " enable: false\n" ) cmd_parts = extend_cmd_parts( @@ -151,8 +150,7 @@ def test_torch_onnx_auto_quantize_recipe(tmp_path): " - $import: fp8\n" " - $import: int8\n" " auto_quantize_method: gradient\n" - " score_size: 1\n", - encoding="utf-8", + " score_size: 1\n" ) cmd_parts = extend_cmd_parts( diff --git a/tests/examples/vllm_serve/test_vllm_mlflow_utils.py b/tests/examples/vllm_serve/test_vllm_mlflow_utils.py index 65222874b56..04b97c31580 100644 --- a/tests/examples/vllm_serve/test_vllm_mlflow_utils.py +++ b/tests/examples/vllm_serve/test_vllm_mlflow_utils.py @@ -99,10 +99,7 @@ def log_text(self, text, artifact_file): self.texts[artifact_file] = text def log_artifact(self, local_path, artifact_path=None): - self.artifacts[Path(local_path).name] = ( - artifact_path, - Path(local_path).read_text(encoding="utf-8"), - ) + self.artifacts[Path(local_path).name] = (artifact_path, Path(local_path).read_text()) def log_metrics(self, metrics): self.metrics.update(metrics) @@ -465,9 +462,7 @@ def test_quant_summary_is_uploaded_from_the_staging_directory( # Stand in for mtq.print_quant_summary(model, output_dir=...), which is what writes it. def write_summary(model, output_dir): - Path(output_dir, ".quant_summary.txt").write_text( - "2 TensorQuantizers found in model\n", encoding="utf-8" - ) + Path(output_dir, ".quant_summary.txt").write_text("2 TensorQuantizers found in model\n") monkeypatch.setattr( importlib.import_module("modelopt.torch.quantization"), diff --git a/tests/gpu/onnx/quantization/test_plugin.py b/tests/gpu/onnx/quantization/test_plugin.py index 7c938570da2..f15f4ecf4cd 100755 --- a/tests/gpu/onnx/quantization/test_plugin.py +++ b/tests/gpu/onnx/quantization/test_plugin.py @@ -104,7 +104,7 @@ def _create_test_model_trt(): def test_trt_plugin_quantization(tmp_path): model = _create_test_model_trt() - with open(os.path.join(tmp_path, "model_with_trt_plugin.onnx"), "w", encoding="utf-8") as f: + with open(os.path.join(tmp_path, "model_with_trt_plugin.onnx"), "w") as f: onnx.save_model(model, f.name) # Check that the model contains TRT custom op @@ -130,9 +130,7 @@ def test_trt_plugin_quantization(tmp_path): def test_trt_plugin_quantization_int4_awq(tmp_path): model = _create_test_model_trt() - with open( - os.path.join(tmp_path, "model_with_trt_plugin_int4.onnx"), "w", encoding="utf-8" - ) as f: + with open(os.path.join(tmp_path, "model_with_trt_plugin_int4.onnx"), "w") as f: onnx.save_model(model, f.name) # Quantize at int4 with awq_clip (the path that forces opset >= 21). @@ -188,9 +186,7 @@ def test_get_custom_layers_file_backed_matches_in_memory(tmp_path, monkeypatch): def test_trt_plugin_autocast(tmp_path): model = _create_test_model_trt() - with open( - os.path.join(tmp_path, "model_with_trt_plugin_autocast.onnx"), "w", encoding="utf-8" - ) as f: + with open(os.path.join(tmp_path, "model_with_trt_plugin_autocast.onnx"), "w") as f: onnx.save_model(model, f.name) # Check that the model contains TRT custom op diff --git a/tests/gpu/onnx/test_ort_patching.py b/tests/gpu/onnx/test_ort_patching.py index 9f936db9b3d..84224dcffa0 100644 --- a/tests/gpu/onnx/test_ort_patching.py +++ b/tests/gpu/onnx/test_ort_patching.py @@ -334,7 +334,7 @@ class TestInferenceSession: def test_create_inference_session_with_ep_config(self, mock_calibrator, tmp_path): """Test inference session creation with EP configuration.""" model_path = tmp_path / "test_model.onnx" - model_path.write_text("dummy", encoding="utf-8") + model_path.write_text("dummy") with patch("onnxruntime.InferenceSession") as mock_session: mock_inference_session = Mock() diff --git a/tests/gpu/onnx/test_simplify.py b/tests/gpu/onnx/test_simplify.py index 5f59666461d..5ca8449b391 100644 --- a/tests/gpu/onnx/test_simplify.py +++ b/tests/gpu/onnx/test_simplify.py @@ -38,7 +38,7 @@ def test_onnx_simplification(tmp_path): onnx_filename = os.path.join(tmp_path, "model_non_simplified.onnx") _create_test_model(onnx_filename) - with open(onnx_filename, encoding="utf-8") as f: + with open(onnx_filename) as f: graph = gs.import_onnx(onnx.load(f.name)) # Check that the model contains Identity nodes, indicating that constant folding did not happen. diff --git a/tests/gpu/torch/export/test_export.py b/tests/gpu/torch/export/test_export.py index 136f4388f1e..6f90c9104c7 100644 --- a/tests/gpu/torch/export/test_export.py +++ b/tests/gpu/torch/export/test_export.py @@ -533,7 +533,7 @@ def test_qwen3_moe_nvfp4_experts_only_export_exclude_modules(tmp_path): # Load the generated hf_quant_config.json hf_quant_config_path = export_dir / "hf_quant_config.json" assert hf_quant_config_path.exists(), "hf_quant_config.json should be generated" - with open(hf_quant_config_path, encoding="utf-8") as f: + with open(hf_quant_config_path) as f: hf_quant_config = json.load(f) quant_section = hf_quant_config["quantization"] diff --git a/tests/gpu/torch/export/test_export_diffusers.py b/tests/gpu/torch/export/test_export_diffusers.py index a0fa9e798a6..392eaf92b6c 100644 --- a/tests/gpu/torch/export/test_export_diffusers.py +++ b/tests/gpu/torch/export/test_export_diffusers.py @@ -25,7 +25,7 @@ def _load_config(config_path): - with open(config_path, encoding="utf-8") as file: + with open(config_path) as file: return json.load(file) diff --git a/tests/gpu/torch/export/test_fsdp2_export.py b/tests/gpu/torch/export/test_fsdp2_export.py index e42d6b4735f..939fe3e581f 100644 --- a/tests/gpu/torch/export/test_fsdp2_export.py +++ b/tests/gpu/torch/export/test_fsdp2_export.py @@ -428,9 +428,7 @@ def calib_fn(m): export_dir = Path(export_dir) assert not list(export_dir.glob("__shard_part*")), "part files left behind after the merge" - index = json.loads( - (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") - ) + index = json.loads((export_dir / "model.safetensors.index.json").read_text()) weight_map = index["weight_map"] assert len(set(weight_map.values())) >= 2, ( "every key landed in one shard file, so the ranks did not each write their own share" diff --git a/tests/gpu/torch/export/test_layerwise_export.py b/tests/gpu/torch/export/test_layerwise_export.py index a0c7f9b0e8c..84244a8fec1 100644 --- a/tests/gpu/torch/export/test_layerwise_export.py +++ b/tests/gpu/torch/export/test_layerwise_export.py @@ -90,7 +90,7 @@ def _layerwise_cfg(export_dir, checkpoint_dir, base=None): def _load_checkpoint(export_dir): index = export_dir / "model.safetensors.index.json" shards = ( - set(json.loads(index.read_text(encoding="utf-8"))["weight_map"].values()) + set(json.loads(index.read_text())["weight_map"].values()) if index.exists() else ["model.safetensors"] ) @@ -128,8 +128,8 @@ def _assert_same_quant_config(baseline_dir, export_dir): ) if not want.is_file(): continue - expected = json.loads(want.read_text(encoding="utf-8")).get(key) - actual = json.loads(got.read_text(encoding="utf-8")).get(key) + expected = json.loads(want.read_text()).get(key) + actual = json.loads(got.read_text()).get(key) assert actual == expected, ( f"{name}[{key}] differs:\n baseline={expected}\n fused={actual}" ) @@ -397,9 +397,7 @@ def test_orphaned_tensors_reach_the_tail_shard(tmp_path): for key, value in orphans.items(): assert key in exported, f"{key} missing from the exported checkpoint" assert torch.equal(exported[key].cpu(), value) - weight_map = json.loads( - (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") - )["weight_map"] + weight_map = json.loads((export_dir / "model.safetensors.index.json").read_text())["weight_map"] assert set(orphans) <= set(weight_map), "orphans written but left out of the index" @@ -458,9 +456,7 @@ def test_index_resolves_every_key_to_the_shard_holding_it(tmp_path): export_dir = tmp_path / "fused" _layerwise_quantize(_build_model(), _layerwise_cfg(export_dir, tmp_path / "ckpt")) - weight_map = json.loads( - (export_dir / "model.safetensors.index.json").read_text(encoding="utf-8") - )["weight_map"] + weight_map = json.loads((export_dir / "model.safetensors.index.json").read_text())["weight_map"] on_disk = {} for shard in sorted(set(weight_map.values())): assert (export_dir / shard).is_file(), f"index names a missing shard {shard}" @@ -510,9 +506,9 @@ def test_resume_without_matching_shards_fails_fast(tmp_path): _layerwise_quantize(_build_model(), _layerwise_cfg(tmp_path / "fused", checkpoint_dir)) manifest_path = checkpoint_dir / "manifest.json" - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest = json.loads(manifest_path.read_text()) manifest["last_completed_layer"] = 1 - manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + manifest_path.write_text(json.dumps(manifest)) with pytest.raises(RuntimeError, match="shards are missing"): _layerwise_quantize( @@ -559,9 +555,9 @@ def test_shards_without_resume_record_refuse(tmp_path, damage): if damage == "deleted": manifest.unlink() else: - record = json.loads(manifest.read_text(encoding="utf-8")) + record = json.loads(manifest.read_text()) record.pop("last_completed_layer") - manifest.write_text(json.dumps(record), encoding="utf-8") + manifest.write_text(json.dumps(record)) with pytest.raises(RuntimeError, match="no usable resume record"): _layerwise_quantize(_build_model(), _layerwise_cfg(export_dir, checkpoint_dir)) diff --git a/tests/gpu/torch/export/test_offload_export.py b/tests/gpu/torch/export/test_offload_export.py index 406737842ac..9ee6139077e 100644 --- a/tests/gpu/torch/export/test_offload_export.py +++ b/tests/gpu/torch/export/test_offload_export.py @@ -95,7 +95,7 @@ def forward_loop(m): # 1. hf_quant_config.json must exist and declare fp8 quant_config_path = export_dir / "hf_quant_config.json" assert quant_config_path.exists(), "hf_quant_config.json not written" - with open(quant_config_path, encoding="utf-8") as f: + with open(quant_config_path) as f: quant_config = json.load(f) assert quant_config["quantization"]["quant_algo"] == "FP8", ( f"Expected FP8, got {quant_config['quantization'].get('quant_algo')}" @@ -132,7 +132,7 @@ def _read_shards(export_dir): index_path = export_dir / "model.safetensors.index.json" if index_path.exists(): - with open(index_path, encoding="utf-8") as f: + with open(index_path) as f: weight_map = json.load(f)["weight_map"] assert set(weight_map) == set(tensors), "index weight_map disagrees with shard contents" for shard_name in set(weight_map.values()): diff --git a/tests/gpu/torch/puzzletron/test_puzzletron.py b/tests/gpu/torch/puzzletron/test_puzzletron.py index 6924fbb5f40..4953db2c737 100644 --- a/tests/gpu/torch/puzzletron/test_puzzletron.py +++ b/tests/gpu/torch/puzzletron/test_puzzletron.py @@ -236,7 +236,7 @@ def _check_lm_loss(puzzle_dir: Path, hf_model_name: str, tolerance: float = 0.15 if not solution_0_path.exists(): errors.append(f"Expected {solution_0_path} to exist for lm_loss check") return errors - with open(solution_0_path, encoding="utf-8") as f: + with open(solution_0_path) as f: validation = json.load(f) actual_lm_loss = validation["lm_loss"]["avg"] diff --git a/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py b/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py index e75c8a00511..a31c687cc1e 100644 --- a/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py +++ b/tests/gpu/torch/puzzletron/tools/test_save_ckpt_from_shards.py @@ -45,7 +45,7 @@ def test_creates_config_index_and_subblocks(self, tmp_path): # test safetensors index file exists and contains weight map index_path = tmp_path / SAFE_WEIGHTS_INDEX_NAME assert index_path.exists(), "safetensors index file was not written" - index = json.loads(index_path.read_text(encoding="utf-8")) + index = json.loads(index_path.read_text()) assert "weight_map" in index assert set(index["weight_map"].keys()) == expected_keys @@ -59,7 +59,7 @@ def test_creates_config_index_and_subblocks(self, tmp_path): # test config.json saved config_path = tmp_path / "config.json" assert config_path.exists(), "config.json was not saved" - cfg = json.loads(config_path.read_text(encoding="utf-8")) + cfg = json.loads(config_path.read_text()) assert cfg["num_hidden_layers"] == get_tiny_llama().config.num_hidden_layers # test subblock filenames follow descriptor groups @@ -72,7 +72,7 @@ def test_tie_word_embeddings_excluded(self, tmp_path): model = get_tiny_llama(tie_word_embeddings=True) save_checkpoint_from_shards(model, tmp_path, LlamaModelDescriptor) - index = json.loads((tmp_path / SAFE_WEIGHTS_INDEX_NAME).read_text(encoding="utf-8")) + index = json.loads((tmp_path / SAFE_WEIGHTS_INDEX_NAME).read_text()) assert "lm_head.weight" not in index["weight_map"] reloaded_sd = {} @@ -122,7 +122,7 @@ def test_distributed_save_creates_valid_checkpoint(self, tmp_path): index_path = tmp_path / SAFE_WEIGHTS_INDEX_NAME assert index_path.exists() - index = json.loads(index_path.read_text(encoding="utf-8")) + index = json.loads(index_path.read_text()) model = get_tiny_llama() expected_keys = set(model.state_dict().keys()) diff --git a/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py b/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py index 5a9e86a53ed..6aab8fe48c8 100644 --- a/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py +++ b/tests/gpu/torch/quantization/plugins/test_accelerate_gpu.py @@ -187,7 +187,7 @@ def test_layerwise_calibrate_cpu_offloaded(tmp_path, use_checkpoint): if use_checkpoint: manifest_path = os.path.join(ckpt_dir, "manifest.json") assert os.path.isfile(manifest_path) - with open(manifest_path, encoding="utf-8") as f: + with open(manifest_path) as f: manifest = json.load(f) assert manifest["last_completed_layer"] == num_layers - 1 assert manifest["num_layers"] == num_layers @@ -233,7 +233,7 @@ def test_sequential_checkpoint_resume_cpu_offloaded(tmp_path): # Simulate crash after layer 0 by truncating the manifest and removing later layers last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w", encoding="utf-8") as f: + with open(manifest_path, "w") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) @@ -288,7 +288,7 @@ def _make_multi_offload_model(): # Simulate crash after layer 0 last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w", encoding="utf-8") as f: + with open(manifest_path, "w") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) @@ -378,7 +378,7 @@ def test_sequential_gptq_checkpoint_resume_cpu_offloaded(tmp_path): # Simulate crash after layer 0 last_completed_layer = 0 manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w", encoding="utf-8") as f: + with open(manifest_path, "w") as f: json.dump({"last_completed_layer": last_completed_layer, "num_layers": num_layers}, f) for i in range(last_completed_layer + 1, num_layers): d = _layer_dir(ckpt_dir, i) diff --git a/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py b/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py index 4e704997f4d..14cc68dbec2 100644 --- a/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py +++ b/tests/gpu/torch/quantization/test_gpt_oss_mxfp4_nvfp4_cast_cuda.py @@ -74,8 +74,7 @@ def _write_lossless_mxfp4_source(model, ckpt_dir: Path) -> None: (ckpt_dir / "model.safetensors.index.json").write_text( json.dumps( {"metadata": {}, "weight_map": dict.fromkeys(state, "model-00001-of-00001.safetensors")} - ), - encoding="utf-8", + ) ) diff --git a/tests/gpu/torch/utils/test_model_load_utils.py b/tests/gpu/torch/utils/test_model_load_utils.py index ed402181067..7c972319d97 100644 --- a/tests/gpu/torch/utils/test_model_load_utils.py +++ b/tests/gpu/torch/utils/test_model_load_utils.py @@ -82,7 +82,7 @@ def _test_parallel_load_and_export(rank, size, ckpt_dir, export_dir, cpu_offload export_hf_checkpoint(model, export_dir=export_dir, dtype=torch.bfloat16) if rank == 0: - with open(os.path.join(export_dir, "config.json"), encoding="utf-8") as f: + with open(os.path.join(export_dir, "config.json")) as f: cfg = json.load(f) assert cfg["architectures"] == ["LlamaForCausalLM"] diff --git a/tests/gpu_megatron/torch/export/test_unified_export_megatron.py b/tests/gpu_megatron/torch/export/test_unified_export_megatron.py index 8cc59abd0a1..614b5d96e2a 100644 --- a/tests/gpu_megatron/torch/export/test_unified_export_megatron.py +++ b/tests/gpu_megatron/torch/export/test_unified_export_megatron.py @@ -55,8 +55,8 @@ def _verify_model_quant_config( export_dir: Path, quant_config: str | None = None, kv_cache_quant_cfg: str | None = None ): """Verify config.json and hf_quant_config.json""" - config_dict = json.load(open(export_dir / "config.json", encoding="utf-8")) - hf_quant_config_dict = json.load(open(export_dir / "hf_quant_config.json", encoding="utf-8")) + config_dict = json.load(open(export_dir / "config.json")) + hf_quant_config_dict = json.load(open(export_dir / "hf_quant_config.json")) # Make sure config.json and hf_quant_config.json are consistent assert ( config_dict["quantization_config"]["quant_algo"] @@ -430,7 +430,7 @@ def _test_qkv_slicing_gqa_tp2(tmp_path, rank, size): "num_key_value_heads": num_query_groups, "torch_dtype": "bfloat16", } - with open(tmp_path / "config.json", "w", encoding="utf-8") as f: + with open(tmp_path / "config.json", "w") as f: json.dump(pretrained_config, f) export_dir = tmp_path / "export" @@ -507,7 +507,7 @@ def _fake_get_mtp_state_dict(self): shard_keys_cache = {} all_weight_map_keys = set() for shard_json_file in shard_json_files: - with open(shard_json_file, encoding="utf-8") as f: + with open(shard_json_file) as f: shard_meta = json.load(f) for key, shard_file in shard_meta["weight_map"].items(): all_weight_map_keys.add(key) @@ -679,7 +679,7 @@ def test_mtp_state_dict_index_file(tmp_path): "mtp.0.hnorm.weight": "model-00002-of-00002.safetensors", } } - with open(model_dir / "model.safetensors.index.json", "w", encoding="utf-8") as f: + with open(model_dir / "model.safetensors.index.json", "w") as f: json.dump(index, f) exporter = _make_exporter_for_mtp(model_dir) @@ -908,7 +908,7 @@ def _make_exporter_for_key_check(num_layers: int) -> GPTModelExporter: def _write_index(dir_path: Path, keys) -> None: dir_path.mkdir(parents=True, exist_ok=True) (dir_path / "model.safetensors.index.json").write_text( - json.dumps({"weight_map": dict.fromkeys(keys, "model-00001.safetensors")}), encoding="utf-8" + json.dumps({"weight_map": dict.fromkeys(keys, "model-00001.safetensors")}) ) diff --git a/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py b/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py index e8b376bb04f..61fdabed8b3 100644 --- a/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py +++ b/tests/gpu_megatron/torch/export/test_vllm_fakequant_megatron_export.py @@ -79,7 +79,7 @@ def forward_loop(model): "torch_dtype": "bfloat16", } - with open(tmp_path / "config.json", "w", encoding="utf-8") as f: + with open(tmp_path / "config.json", "w") as f: json.dump(pretrained_config, f) # Export directory diff --git a/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py b/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py index 8a5f497cadb..041a0bd3fca 100644 --- a/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py +++ b/tests/gpu_megatron/torch/quantization/plugins/test_megatron.py @@ -1518,7 +1518,7 @@ def forward_backward_step(model, batch): assert summed_cost == pytest.approx(local_total, rel=1e-6) if rank == 0: - Path(result_path).write_text(repr(summed_cost), encoding="utf-8") + Path(result_path).write_text(repr(summed_cost)) @pytest.mark.skipif(not HAS_MAMBA, reason="Mamba not installed") @@ -1543,8 +1543,8 @@ def test_auto_quantize_mamba_hybrid_ep_cost(dist_workers, tmp_path): result_path=str(ep2_path), ) ) - cost_ep1 = float(ep1_path.read_text(encoding="utf-8")) - cost_ep2 = float(ep2_path.read_text(encoding="utf-8")) + cost_ep1 = float(ep1_path.read_text()) + cost_ep2 = float(ep2_path.read_text()) assert cost_ep1 == pytest.approx(cost_ep2, rel=1e-6) diff --git a/tests/regression/torch/speculative/test_dflash.py b/tests/regression/torch/speculative/test_dflash.py index 4ee5a69d114..18e95148fcd 100644 --- a/tests/regression/torch/speculative/test_dflash.py +++ b/tests/regression/torch/speculative/test_dflash.py @@ -100,7 +100,7 @@ def test_dflash_training(qwen3_model_name, dflash_output_dir): # Regression: verify loss decreased trainer_state = os.path.join(output_dir, "trainer_state.json") assert os.path.exists(trainer_state), "trainer_state.json not found" - with open(trainer_state, encoding="utf-8") as f: + with open(trainer_state) as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] assert len(logs) >= 2, f"Expected at least 2 log entries, got {len(logs)}" @@ -149,7 +149,7 @@ def test_dflash_export(dflash_output_dir): assert os.path.exists(os.path.join(export_dir, "model.safetensors")) assert os.path.exists(os.path.join(export_dir, "config.json")) - with open(os.path.join(export_dir, "config.json"), encoding="utf-8") as f: + with open(os.path.join(export_dir, "config.json")) as f: config = json.load(f) assert config["architectures"] == ["DFlashDraftModel"] assert config["model_type"] == "qwen3" diff --git a/tests/regression/torch/speculative/test_dflash_offline.py b/tests/regression/torch/speculative/test_dflash_offline.py index 837142b448e..678742686cb 100644 --- a/tests/regression/torch/speculative/test_dflash_offline.py +++ b/tests/regression/torch/speculative/test_dflash_offline.py @@ -132,7 +132,7 @@ def test_dflash_offline_training( trainer_state = os.path.join(output_dir, "trainer_state.json") assert os.path.exists(trainer_state), "trainer_state.json not found" - with open(trainer_state, encoding="utf-8") as f: + with open(trainer_state) as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] assert len(logs) >= 2, f"Expected at least 2 log entries, got {len(logs)}" diff --git a/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py b/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py index 901c4f6a44f..ce63b1a96c2 100644 --- a/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py +++ b/tests/unit/examples/test_kimi_k3_quantize_to_nvfp4.py @@ -112,8 +112,7 @@ def test_rank0_rendezvous_rejects_mismatched_configuration(tmp_path): ready_path = tmp_path / "ready.json" fingerprint = {"source_ckpt": "/models/Kimi-K3", "shards": ["model-1.safetensors"]} ready_path.write_text( - json.dumps({"run_id": "run-1", "world_size": 4, "fingerprint": fingerprint}), - encoding="utf-8", + json.dumps({"run_id": "run-1", "world_size": 4, "fingerprint": fingerprint}) ) assert not k3_cast._rank0_ready( @@ -142,8 +141,7 @@ def test_rank_report_rejects_mismatched_fingerprint(tmp_path): "rank": 1, "fingerprint": {"cast_mxfp4_to_nvfp4": False}, } - ), - encoding="utf-8", + ) ) with pytest.raises(ValueError, match="rank 1 report conversion fingerprint"): @@ -197,7 +195,7 @@ def _write_source_checkpoint(tmp_path: Path) -> tuple[Path, str, dict[str, torch "metadata": {"total_size": sum(t.numel() * t.element_size() for t in state.values())}, "weight_map": dict.fromkeys(state, shard_name), } - (source / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") + (source / "model.safetensors.index.json").write_text(json.dumps(index)) (source / "config.json").write_text( json.dumps( { @@ -209,10 +207,9 @@ def _write_source_checkpoint(tmp_path: Path) -> tuple[Path, str, dict[str, torch } }, } - ), - encoding="utf-8", + ) ) - (source / "tokenizer_config.json").write_text("{}", encoding="utf-8") + (source / "tokenizer_config.json").write_text("{}") return source, shard_name, state @@ -414,7 +411,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): hf_quant_config = k3_cast._build_hf_quant_config( report["banks"], report["attn_modules"], attn_fp8=True ) - source_index = json.loads((source / "model.safetensors.index.json").read_text(encoding="utf-8")) + source_index = json.loads((source / "model.safetensors.index.json").read_text()) k3_cast._write_index_and_manifest( output, source_index, @@ -424,7 +421,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): ) k3_cast._rewrite_config_json(source / "config.json", output, hf_quant_config) - index = json.loads((output / "model.safetensors.index.json").read_text(encoding="utf-8")) + index = json.loads((output / "model.safetensors.index.json").read_text()) weight_map = index["weight_map"] expert = "language_model.model.layers.1.block_sparse_moe.experts.0.w1" assert expert + ".weight_packed" not in weight_map @@ -434,7 +431,7 @@ def test_manifest_and_index_replace_source_mxfp4_schema(tmp_path): assert weight_map[expert + ".input_scale"] == shard_name assert index["metadata"]["total_size"] == report["tensor_bytes"] - config = json.loads((output / "config.json").read_text(encoding="utf-8")) + config = json.loads((output / "config.json").read_text()) assert "quantization_config" not in config["text_config"] quant = config["quantization_config"] assert quant["quant_method"] == "modelopt_mixed" diff --git a/tests/unit/onnx/autocast/test_referencerunner.py b/tests/unit/onnx/autocast/test_referencerunner.py index cfeb90e2a72..5c5c3c00ab2 100644 --- a/tests/unit/onnx/autocast/test_referencerunner.py +++ b/tests/unit/onnx/autocast/test_referencerunner.py @@ -204,7 +204,7 @@ def test_mismatched_input_names(reference_runner): "wrong_name2": np.array([[4.0, 5.0, 6.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name @@ -221,7 +221,7 @@ def test_invalid_json(reference_runner): """Test error handling for non-Polygraphy JSON format.""" inputs = {"X1": [[1.0, 2.0, 3.0]], "X2": [[4.0, 5.0, 6.0]]} - with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: json.dump(inputs, f) input_path = f.name try: @@ -253,7 +253,7 @@ def test_compare_outputs(reference_runner): "X2": np.array([[4.0, 5.0, 6.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name @@ -275,7 +275,7 @@ def test_compare_outputs(reference_runner): "X2": np.array([[1.0, 2.0, 3.0]], dtype=np.float32), } - with tempfile.NamedTemporaryFile(encoding="utf-8", suffix=".json", mode="w", delete=False) as f: + with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f: from polygraphy.json import save_json input_path = f.name diff --git a/tests/unit/onnx/quantization/autotune/test_autotuner.py b/tests/unit/onnx/quantization/autotune/test_autotuner.py index 22e83fc2d2b..26e390a2354 100644 --- a/tests/unit/onnx/quantization/autotune/test_autotuner.py +++ b/tests/unit/onnx/quantization/autotune/test_autotuner.py @@ -254,9 +254,7 @@ def test_save_and_load_state(self, simple_conv_model): # Submit some results autotuner.submit(10.5) # baseline - with tempfile.NamedTemporaryFile( - encoding="utf-8", mode="w", suffix=".yaml", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: state_path = f.name try: diff --git a/tests/unit/onnx/quantization/autotune/test_pattern_cache.py b/tests/unit/onnx/quantization/autotune/test_pattern_cache.py index b5f6a8d6779..a2d61c507b9 100644 --- a/tests/unit/onnx/quantization/autotune/test_pattern_cache.py +++ b/tests/unit/onnx/quantization/autotune/test_pattern_cache.py @@ -121,9 +121,7 @@ def test_yaml_round_trip(self): scheme.latency_ms = 15.0 ps.schemes.append(scheme) cache.add_pattern_schemes(ps) - with tempfile.NamedTemporaryFile( - encoding="utf-8", mode="w", suffix=".yaml", delete=False - ) as f: + with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f: yaml_path = f.name try: cache.save(yaml_path) diff --git a/tests/unit/recipe/test_loader.py b/tests/unit/recipe/test_loader.py index 7398fd67a3c..006e7d57927 100644 --- a/tests/unit/recipe/test_loader.py +++ b/tests/unit/recipe/test_loader.py @@ -94,15 +94,15 @@ def _write_quantizer_attribute(path, body: str): - path.write_text(QUANTIZER_ATTRIBUTE_SCHEMA + body, encoding="utf-8") + path.write_text(QUANTIZER_ATTRIBUTE_SCHEMA + body) def _write_quantizer_cfg_entry(path, body: str): - path.write_text(QUANTIZER_CFG_ENTRY_SCHEMA + body, encoding="utf-8") + path.write_text(QUANTIZER_CFG_ENTRY_SCHEMA + body) def _write_quantizer_cfg_list(path, body: str): - path.write_text(QUANTIZER_CFG_LIST_SCHEMA + body, encoding="utf-8") + path.write_text(QUANTIZER_CFG_LIST_SCHEMA + body) def _cfg_to_dict(cfg): @@ -123,13 +123,13 @@ def _cfg_to_dict(cfg): def test_load_config_plain(tmp_path): """A plain config is returned as-is.""" - (tmp_path / "cfg.yml").write_text(CFG_AB, encoding="utf-8") + (tmp_path / "cfg.yml").write_text(CFG_AB) assert load_config(tmp_path / "cfg.yml") == {"a": 1, "b": 2} def test_load_config_suffix_probe(tmp_path): """load_config finds a .yml file when suffix is omitted from a string path.""" - (tmp_path / "mycfg.yml").write_text(CFG_KEY_VAL, encoding="utf-8") + (tmp_path / "mycfg.yml").write_text(CFG_KEY_VAL) assert load_config(str(tmp_path / "mycfg")) == {"key": "val"} @@ -268,8 +268,7 @@ def test_load_recipe_local_tree_overrides_builtin_even_on_name_collision(tmp_pat local = tmp_path / old_rel local.parent.mkdir(parents=True) local.write_text( - "metadata:\n recipe_type: ptq\nquantize:\n quant_cfg: {}\n algorithm: max\n", - encoding="utf-8", + "metadata:\n recipe_type: ptq\nquantize:\n quant_cfg: {}\n algorithm: max\n" ) monkeypatch.chdir(tmp_path) @@ -447,7 +446,7 @@ def test_load_recipe_missing_raises(tmp_path): def test_load_recipe_missing_recipe_type_raises(tmp_path): """load_recipe raises ValueError when metadata.recipe_type is absent.""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_TYPE, encoding="utf-8") + bad.write_text(CFG_RECIPE_MISSING_TYPE) with pytest.raises(ValueError, match="recipe_type"): load_recipe(bad) @@ -455,7 +454,7 @@ def test_load_recipe_missing_recipe_type_raises(tmp_path): def test_load_recipe_missing_quantize_raises(tmp_path): """A PTQ recipe missing the ``quantize`` section is rejected (no silent default).""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_quantize, encoding="utf-8") + bad.write_text(CFG_RECIPE_MISSING_quantize) with pytest.raises(ValueError, match="quantize"): load_recipe(bad) @@ -463,7 +462,7 @@ def test_load_recipe_missing_quantize_raises(tmp_path): def test_load_recipe_missing_metadata_raises(tmp_path): """A recipe missing the ``metadata`` section is rejected (no silent default).""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_MISSING_METADATA, encoding="utf-8") + bad.write_text(CFG_RECIPE_MISSING_METADATA) with pytest.raises(ValueError, match="metadata"): load_recipe(bad) @@ -471,7 +470,7 @@ def test_load_recipe_missing_metadata_raises(tmp_path): def test_load_recipe_unsupported_type_raises(tmp_path): """load_recipe raises ValueError for an unknown recipe_type.""" bad = tmp_path / "bad.yml" - bad.write_text(CFG_RECIPE_UNSUPPORTED_TYPE, encoding="utf-8") + bad.write_text(CFG_RECIPE_UNSUPPORTED_TYPE) # Schema-driven validation reports the failure via the metadata schema's enum check. with pytest.raises(ValueError, match="recipe_type"): load_recipe(bad) @@ -484,10 +483,8 @@ def test_load_recipe_unsupported_type_raises(tmp_path): def test_load_recipe_dir(tmp_path): """load_recipe loads a recipe from a directory with metadata.yml + quantize.yml.""" - (tmp_path / "metadata.yml").write_text( - "recipe_type: ptq\ndescription: Dir test.\n", encoding="utf-8" - ) - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n", encoding="utf-8") + (tmp_path / "metadata.yml").write_text("recipe_type: ptq\ndescription: Dir test.\n") + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n") recipe = load_recipe(tmp_path) assert recipe.recipe_type == RecipeType.PTQ assert recipe.description == "Dir test." @@ -497,14 +494,14 @@ def test_load_recipe_dir(tmp_path): def test_load_recipe_dir_missing_metadata_raises(tmp_path): """load_recipe raises ValueError when metadata.yml is absent from the directory.""" - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: {}\n", encoding="utf-8") + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: {}\n") with pytest.raises(ValueError, match="metadata"): load_recipe(tmp_path) def test_load_recipe_dir_missing_quantize_raises(tmp_path): """load_recipe raises ValueError when quantize.yml is absent from the directory.""" - (tmp_path / "metadata.yml").write_text("recipe_type: ptq\n", encoding="utf-8") + (tmp_path / "metadata.yml").write_text("recipe_type: ptq\n") with pytest.raises(ValueError, match="quantize"): load_recipe(tmp_path) @@ -528,7 +525,7 @@ def test_load_recipe_eagle_builtin(): def test_load_recipe_eagle_missing_section_raises(tmp_path): """load_recipe raises ValueError when 'eagle' is absent for a SPECULATIVE_EAGLE recipe.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: speculative_eagle\n", encoding="utf-8") + bad.write_text("metadata:\n recipe_type: speculative_eagle\n") with pytest.raises(ValueError, match="eagle"): load_recipe(bad) @@ -537,8 +534,7 @@ def test_load_recipe_eagle_field_validation_raises(tmp_path): """Invalid EAGLE field values must fail Pydantic validation at load time.""" bad = tmp_path / "bad.yml" bad.write_text( - "metadata:\n recipe_type: speculative_eagle\neagle:\n eagle_ttt_steps: not_an_int\n", - encoding="utf-8", + "metadata:\n recipe_type: speculative_eagle\neagle:\n eagle_ttt_steps: not_an_int\n" ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(bad) @@ -563,7 +559,7 @@ def test_load_recipe_dflash_builtin(): def test_load_recipe_dflash_missing_section_raises(tmp_path): """load_recipe raises ValueError when 'dflash' is absent for a SPECULATIVE_DFLASH recipe.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: speculative_dflash\n", encoding="utf-8") + bad.write_text("metadata:\n recipe_type: speculative_dflash\n") with pytest.raises(ValueError, match="dflash"): load_recipe(bad) @@ -576,8 +572,7 @@ def test_load_recipe_eagle_with_training_sections(tmp_path): "model:\n model_name_or_path: TinyLlama/TinyLlama-1.1B-Chat-v1.0\n" "data:\n data_path: train.jsonl\n" "training:\n output_dir: ckpts/test\n" - "eagle:\n eagle_decoder_type: llama\n eagle_ttt_steps: 2\n", - encoding="utf-8", + "eagle:\n eagle_decoder_type: llama\n eagle_ttt_steps: 2\n" ) recipe = load_recipe(recipe_path) assert isinstance(recipe, ModelOptEagleRecipe) @@ -594,8 +589,7 @@ def test_typed_model_section_rejects_unknown_field(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "model:\n typo_name: oops\n" - "eagle:\n eagle_decoder_type: llama\n", - encoding="utf-8", + "eagle:\n eagle_decoder_type: llama\n" ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(recipe_path) @@ -610,8 +604,7 @@ def test_typed_training_section_accepts_hf_extras(tmp_path): " num_train_epochs: 3\n" # HF field — accepted as extra " learning_rate: 1.0e-4\n" # HF field — accepted as extra " training_seq_len: 4096\n" # our extension field — validated - "eagle:\n eagle_decoder_type: llama\n", - encoding="utf-8", + "eagle:\n eagle_decoder_type: llama\n" ) recipe = load_recipe(recipe_path) assert isinstance(recipe, ModelOptEagleRecipe) @@ -689,8 +682,7 @@ def test_load_recipe_with_overrides(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "model:\n trust_remote_code: false\n" - "eagle:\n eagle_ttt_steps: 3\n", - encoding="utf-8", + "eagle:\n eagle_ttt_steps: 3\n" ) recipe = load_recipe( recipe_path, @@ -703,8 +695,8 @@ def test_load_recipe_with_overrides(tmp_path): def test_load_recipe_overrides_rejected_for_dir(tmp_path): """Overrides are not allowed for directory-format recipes.""" - (tmp_path / "recipe.yml").write_text("metadata:\n recipe_type: ptq\n", encoding="utf-8") - (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n", encoding="utf-8") + (tmp_path / "recipe.yml").write_text("metadata:\n recipe_type: ptq\n") + (tmp_path / "quantize.yml").write_text("algorithm: max\nquant_cfg: []\n") with pytest.raises(ValueError, match="directory-format"): load_recipe(tmp_path, overrides=["quantize.algorithm=gptq"]) @@ -715,8 +707,7 @@ def test_typed_data_sample_size_validator(tmp_path): recipe_path.write_text( "metadata:\n recipe_type: speculative_eagle\n" "data:\n sample_size: 0\n" - "eagle:\n eagle_decoder_type: llama\n", - encoding="utf-8", + "eagle:\n eagle_decoder_type: llama\n" ) with pytest.raises(Exception, match="sample_size"): # pydantic.ValidationError load_recipe(recipe_path) @@ -726,8 +717,7 @@ def test_load_recipe_dflash_field_validation_raises(tmp_path): """Invalid DFlash field values must fail Pydantic validation at load time.""" bad = tmp_path / "bad.yml" bad.write_text( - "metadata:\n recipe_type: speculative_dflash\ndflash:\n dflash_block_size: not_an_int\n", - encoding="utf-8", + "metadata:\n recipe_type: speculative_dflash\ndflash:\n dflash_block_size: not_an_int\n" ) with pytest.raises(Exception): # pydantic.ValidationError load_recipe(bad) @@ -815,8 +805,7 @@ def test_import_resolves_cfg_reference(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) recipe = load_recipe(recipe_file) entry = recipe.quantize["quant_cfg"][0] @@ -840,8 +829,7 @@ def test_import_same_name_used_twice(tmp_path): f" $import: fp8\n" f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["cfg"] == recipe.quantize["quant_cfg"][1]["cfg"] @@ -866,8 +854,7 @@ def test_import_multiple_snippets(tmp_path): f" $import: nvfp4\n" f" - quantizer_name: '*[kv]_bmm_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["cfg"]["num_bits"] == (2, 1) @@ -892,8 +879,7 @@ def test_import_inline_cfg_not_affected(tmp_path): f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" f" num_bits: 8\n" - f" axis: 0\n", - encoding="utf-8", + f" axis: 0\n" ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][1]["cfg"].model_dump(exclude_unset=True) == { @@ -915,8 +901,7 @@ def test_import_unknown_reference_raises(tmp_path): " quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: nonexistent\n", - encoding="utf-8", + " $import: nonexistent\n" ) with pytest.raises(ValueError, match=r"Unknown \$import reference"): load_recipe(recipe_file) @@ -932,8 +917,7 @@ def test_import_empty_path_raises(tmp_path): " recipe_type: ptq\n" "quantize:\n" " algorithm: max\n" - " quant_cfg: []\n", - encoding="utf-8", + " quant_cfg: []\n" ) with pytest.raises(ValueError, match="empty config path"): load_recipe(recipe_file) @@ -941,7 +925,7 @@ def test_import_empty_path_raises(tmp_path): def test_import_snippet_without_schema_raises(tmp_path): """Every imported snippet must declare modelopt-schema, including dict snippets.""" - (tmp_path / "fp8.yml").write_text("num_bits: e4m3\n", encoding="utf-8") + (tmp_path / "fp8.yml").write_text("num_bits: e4m3\n") recipe_file = tmp_path / "ptq.yml" recipe_file.write_text( f"imports:\n" @@ -953,8 +937,7 @@ def test_import_snippet_without_schema_raises(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) with pytest.raises(ValueError, match="modelopt-schema"): load_recipe(recipe_file) @@ -970,8 +953,7 @@ def test_import_not_a_dict_raises(tmp_path): " recipe_type: ptq\n" "quantize:\n" " algorithm: max\n" - " quant_cfg: []\n", - encoding="utf-8", + " quant_cfg: []\n" ) with pytest.raises(ValueError, match="must be a dict"): load_recipe(recipe_file) @@ -987,8 +969,7 @@ def test_import_no_imports_section(tmp_path): " algorithm: max\n" " quant_cfg:\n" " - quantizer_name: '*'\n" - " enable: false\n", - encoding="utf-8", + " enable: false\n" ) recipe = load_recipe(recipe_file) assert recipe.quantize["quant_cfg"][0]["enable"] is False @@ -1016,8 +997,7 @@ def test_import_entry_single_element_list(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: disable_all\n", - encoding="utf-8", + f" - $import: disable_all\n" ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 1 @@ -1038,8 +1018,7 @@ def test_import_entry_element_schema_appends(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: disable_all\n", - encoding="utf-8", + f" - $import: disable_all\n" ) recipe = load_recipe(recipe_file) # Entry was loaded against the QuantizerCfgEntry pydantic schema, so it is now a @@ -1065,8 +1044,7 @@ def test_import_entry_wrong_schema_raises(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: fp8\n", - encoding="utf-8", + f" - $import: fp8\n" ) with pytest.raises(ValueError, match="expected either"): load_recipe(recipe_file) @@ -1090,8 +1068,7 @@ def test_import_entry_list_splice(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*'\n" f" enable: false\n" - f" - $import: disables\n", - encoding="utf-8", + f" - $import: disables\n" ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 3 @@ -1112,8 +1089,7 @@ def test_import_entry_sibling_keys_with_list_snippet_raises(tmp_path): f" algorithm: max\n" f" quant_cfg:\n" f" - $import: disable_all\n" - f" quantizer_name: '*extra*'\n", - encoding="utf-8", + f" quantizer_name: '*extra*'\n" ) with pytest.raises(ValueError, match="must resolve to a dict"): load_recipe(recipe_file) @@ -1134,8 +1110,7 @@ def test_import_cfg_extend(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" axis: 0\n", - encoding="utf-8", + f" axis: 0\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1157,8 +1132,7 @@ def test_import_cfg_inline_overrides_import(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" num_bits: 8\n", - encoding="utf-8", + f" num_bits: 8\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1178,8 +1152,7 @@ def test_import_in_non_cfg_dict_value(tmp_path): f"quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" my_field:\n" - f" $import: extra\n", - encoding="utf-8", + f" $import: extra\n" ) data = load_config(config_file) entry = data["quant_cfg"][0] @@ -1200,8 +1173,7 @@ def test_import_in_multiple_dict_values(tmp_path): f" cfg:\n" f" $import: fp8\n" f" my_field:\n" - f" $import: extra\n", - encoding="utf-8", + f" $import: extra\n" ) data = load_config(config_file) entry = data["quant_cfg"][0] @@ -1226,8 +1198,7 @@ def test_import_cfg_multi_import(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: [bits, axis]\n", - encoding="utf-8", + f" $import: [bits, axis]\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1250,8 +1221,7 @@ def test_import_cfg_multi_import_later_overrides_earlier(tmp_path): f" quant_cfg:\n" f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: [a, b]\n", - encoding="utf-8", + f" $import: [a, b]\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1277,8 +1247,7 @@ def test_import_cfg_multi_import_with_extend(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: [bits, extra]\n" - f" axis: 0\n", - encoding="utf-8", + f" axis: 0\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1292,9 +1261,7 @@ def test_import_cfg_multi_import_with_extend(tmp_path): def test_import_dir_format(tmp_path): """Imports in quantize.yml work with the directory recipe format.""" _write_quantizer_attribute(tmp_path / "fp8.yml", "num_bits: e4m3\naxis:\n") - (tmp_path / "metadata.yml").write_text( - "recipe_type: ptq\ndescription: Dir with imports.\n", encoding="utf-8" - ) + (tmp_path / "metadata.yml").write_text("recipe_type: ptq\ndescription: Dir with imports.\n") (tmp_path / "quantize.yml").write_text( f"imports:\n" f" fp8: {tmp_path / 'fp8.yml'}\n" @@ -1302,8 +1269,7 @@ def test_import_dir_format(tmp_path): "quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: fp8\n", - encoding="utf-8", + " $import: fp8\n" ) recipe = load_recipe(tmp_path) assert recipe.quantize["quant_cfg"][0]["cfg"].model_dump(exclude_unset=True) == { @@ -1316,15 +1282,14 @@ def test_import_dir_format_metadata_imports_do_not_apply_to_quantize(tmp_path): """metadata.yml imports are scoped to metadata.yml, not quantize.yml.""" _write_quantizer_attribute(tmp_path / "fp8.yml", "num_bits: e4m3\n") (tmp_path / "metadata.yml").write_text( - f"imports:\n fmt: {tmp_path / 'fp8.yml'}\nrecipe_type: ptq\n", encoding="utf-8" + f"imports:\n fmt: {tmp_path / 'fp8.yml'}\nrecipe_type: ptq\n" ) (tmp_path / "quantize.yml").write_text( "algorithm: max\n" "quant_cfg:\n" " - quantizer_name: '*weight_quantizer'\n" " cfg:\n" - " $import: fmt\n", - encoding="utf-8", + " $import: fmt\n" ) with pytest.raises(ValueError, match=r"Unknown \$import reference"): load_recipe(tmp_path) @@ -1339,8 +1304,7 @@ def test_import_multi_document_list_snippet(tmp_path): """List snippet using multi-document YAML (imports --- content) resolves $import.""" (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n", - encoding="utf-8", + "num_bits: e4m3\n" ) (tmp_path / "kv.yaml").write_text( f"# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig\n" @@ -1349,8 +1313,7 @@ def test_import_multi_document_list_snippet(tmp_path): f"---\n" f"- quantizer_name: '*[kv]_bmm_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) recipe_file = tmp_path / "ptq.yml" recipe_file.write_text( @@ -1361,8 +1324,7 @@ def test_import_multi_document_list_snippet(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: kv\n", - encoding="utf-8", + f" - $import: kv\n" ) recipe = load_recipe(recipe_file) assert len(recipe.quantize["quant_cfg"]) == 1 @@ -1392,8 +1354,7 @@ def test_import_in_top_level_dict_value(tmp_path): _write_quantizer_attribute(tmp_path / "algo.yml", "num_bits: 8\naxis: 0\n") config_file = tmp_path / "config.yml" config_file.write_text( - f"imports:\n algo: {tmp_path / 'algo.yml'}\nalgorithm:\n $import: algo\nquant_cfg: []\n", - encoding="utf-8", + f"imports:\n algo: {tmp_path / 'algo.yml'}\nalgorithm:\n $import: algo\nquant_cfg: []\n" ) data = load_config(config_file) assert data["algorithm"] == {"num_bits": 8, "axis": 0} @@ -1409,8 +1370,7 @@ def test_import_in_nested_dict(tmp_path): f"training:\n" f" optimizer:\n" f" params:\n" - f" $import: settings\n", - encoding="utf-8", + f" $import: settings\n" ) data = load_config(config_file) assert data["training"]["optimizer"]["params"] == {"num_bits": (4, 3)} @@ -1430,8 +1390,7 @@ def test_import_list_splice_outside_typed_list_raises(tmp_path): f"tasks:\n" f" - name: task_a\n" f" - $import: extra\n" - f" - name: task_d\n", - encoding="utf-8", + f" - name: task_d\n" ) with pytest.raises(ValueError, match="requires a typed list schema"): load_config(config_file) @@ -1451,8 +1410,7 @@ def test_import_in_nested_list_of_dicts(tmp_path): f" verbose: true\n" f" - name: test\n" f" config:\n" - f" $import: defaults\n", - encoding="utf-8", + f" $import: defaults\n" ) data = load_config(config_file) assert data["stages"][0]["config"] == {"num_bits": 8, "verbose": True} @@ -1463,14 +1421,12 @@ def test_import_mixed_tree(tmp_path): """$import resolves at multiple levels in the same config.""" (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n", - encoding="utf-8", + "num_bits: e4m3\n" ) (tmp_path / "disables.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerCfgListConfig\n" "- quantizer_name: '*lm_head*'\n" - " enable: false\n", - encoding="utf-8", + " enable: false\n" ) config_file = tmp_path / "config.yml" config_file.write_text( @@ -1483,8 +1439,7 @@ def test_import_mixed_tree(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" $import: fp8\n" - f" - $import: disables\n", - encoding="utf-8", + f" - $import: disables\n" ) data = load_config(config_file) # Dict import inside list entry @@ -1510,8 +1465,7 @@ def test_import_recursive(tmp_path): # base: dict snippet with FP8 attributes (tmp_path / "fp8.yml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n", - encoding="utf-8", + "num_bits: e4m3\n" ) # mid: list snippet that imports base and uses $import in cfg (tmp_path / "mid.yaml").write_text( @@ -1521,8 +1475,7 @@ def test_import_recursive(tmp_path): f"---\n" f"- quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) # recipe imports mid recipe_file = tmp_path / "ptq.yml" @@ -1534,8 +1487,7 @@ def test_import_recursive(tmp_path): f"quantize:\n" f" algorithm: max\n" f" quant_cfg:\n" - f" - $import: mid\n", - encoding="utf-8", + f" - $import: mid\n" ) recipe = load_recipe(recipe_file) cfg = recipe.quantize["quant_cfg"][0]["cfg"] @@ -1558,8 +1510,7 @@ def test_import_circular_raises(tmp_path): f" recipe_type: ptq\n" f"quantize:\n" f" algorithm: max\n" - f" quant_cfg: []\n", - encoding="utf-8", + f" quant_cfg: []\n" ) with pytest.raises(ValueError, match="Circular import"): load_recipe(recipe_file) @@ -1586,8 +1537,7 @@ def test_import_circular_via_path_aliases_raises(tmp_path): f" recipe_type: ptq\n" f"quantize:\n" f" algorithm: max\n" - f" quant_cfg: []\n", - encoding="utf-8", + f" quant_cfg: []\n" ) cwd = os.getcwd() os.chdir(tmp_path) @@ -1635,8 +1585,7 @@ def test_import_cross_file_same_name_no_conflict(tmp_path): f" $import: fmt\n" f" - quantizer_name: '*input_quantizer'\n" f" cfg:\n" - f" $import: child\n", - encoding="utf-8", + f" $import: child\n" ) recipe = load_recipe(recipe_file) # Parent's "fmt" resolves to fp8 (e4m3), not child's nvfp4. @@ -1684,8 +1633,7 @@ def test_modelopt_schema_comment_returns_instance(tmp_path): config_file.write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" "num_bits: e4m3\n" - "axis:\n", - encoding="utf-8", + "axis:\n" ) data = load_config(config_file) assert isinstance(data, QuantizerAttributeConfig) @@ -1698,8 +1646,7 @@ def test_modelopt_schema_comment_validation_error(tmp_path): config_file = tmp_path / "bad.yaml" config_file.write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "unknown_field: true\n", - encoding="utf-8", + "unknown_field: true\n" ) with pytest.raises(ValueError, match="does not match modelopt-schema"): load_config(config_file) @@ -1720,8 +1667,7 @@ def test_modelopt_schema_comment_validates_after_import_resolution(tmp_path): """Schema validation runs after nested imports have been resolved.""" (tmp_path / "fp8.yaml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n", - encoding="utf-8", + "num_bits: e4m3\n" ) config_file = tmp_path / "entry.yaml" config_file.write_text( @@ -1731,8 +1677,7 @@ def test_modelopt_schema_comment_validates_after_import_resolution(tmp_path): f"---\n" f"- quantizer_name: '*weight_quantizer'\n" f" cfg:\n" - f" $import: fp8\n", - encoding="utf-8", + f" $import: fp8\n" ) data = load_config(config_file) # data is a list of QuantizerCfgEntry pydantic instances, not raw dicts. Dump with @@ -1751,13 +1696,11 @@ def test_import_dict_snippet_imports_in_union_typed_list_field(tmp_path): "num_bits: 4\n" "block_sizes:\n" " -1: 128\n" - " type: static\n", - encoding="utf-8", + " type: static\n" ) (tmp_path / "fp8.yaml").write_text( "# modelopt-schema: modelopt.torch.quantization.config.QuantizerAttributeConfig\n" - "num_bits: e4m3\n", - encoding="utf-8", + "num_bits: e4m3\n" ) config_file = tmp_path / "config.yaml" config_file.write_text( @@ -1770,8 +1713,7 @@ def test_import_dict_snippet_imports_in_union_typed_list_field(tmp_path): f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" - $import: int4\n" - f" - $import: fp8\n", - encoding="utf-8", + f" - $import: fp8\n" ) data = load_config(config_file) @@ -1798,8 +1740,7 @@ def test_import_dict_snippet_in_union_typed_list_field_with_inline_item(tmp_path f" - quantizer_name: '*weight_quantizer'\n" f" cfg:\n" f" - $import: int4\n" - f" - num_bits: e4m3\n", - encoding="utf-8", + f" - num_bits: e4m3\n" ) data = load_config(config_file) assert _cfg_to_dict(data["quant_cfg"][0]["cfg"]) == [ @@ -1816,7 +1757,7 @@ def test_import_dict_snippet_in_union_typed_list_field_with_inline_item(tmp_path def test_load_config_path_object(tmp_path): """load_config accepts a Path object.""" cfg_file = tmp_path / "test.yaml" - cfg_file.write_text("key: value\n", encoding="utf-8") + cfg_file.write_text("key: value\n") data = load_config(cfg_file) assert data == {"key": "value"} @@ -1824,7 +1765,7 @@ def test_load_config_path_object(tmp_path): def test_load_config_path_without_suffix(tmp_path): """load_config probes .yml/.yaml suffixes for a Path without suffix.""" cfg_file = tmp_path / "test.yaml" - cfg_file.write_text("key: value\n", encoding="utf-8") + cfg_file.write_text("key: value\n") data = load_config(tmp_path / "test") # no suffix assert data == {"key": "value"} @@ -1832,7 +1773,7 @@ def test_load_config_path_without_suffix(tmp_path): def test_load_config_empty_yaml(tmp_path): """load_config returns empty dict for empty YAML file.""" cfg_file = tmp_path / "empty.yaml" - cfg_file.write_text("", encoding="utf-8") + cfg_file.write_text("") data = load_config(cfg_file) assert data == {} @@ -1840,7 +1781,7 @@ def test_load_config_empty_yaml(tmp_path): def test_load_config_null_yaml(tmp_path): """load_config returns empty dict for YAML file containing only null.""" cfg_file = tmp_path / "null.yaml" - cfg_file.write_text("---\n", encoding="utf-8") + cfg_file.write_text("---\n") data = load_config(cfg_file) assert data == {} @@ -1848,7 +1789,7 @@ def test_load_config_null_yaml(tmp_path): def test_load_config_multi_doc_dict_dict(tmp_path): """Multi-document YAML with two dicts merges them.""" cfg_file = tmp_path / "multi.yaml" - cfg_file.write_text("imports:\n fp8: some/path\n---\nalgorithm: max\n", encoding="utf-8") + cfg_file.write_text("imports:\n fp8: some/path\n---\nalgorithm: max\n") data = _load_raw_config(cfg_file) assert data["imports"] == {"fp8": "some/path"} assert data["algorithm"] == "max" @@ -1857,7 +1798,7 @@ def test_load_config_multi_doc_dict_dict(tmp_path): def test_load_config_multi_doc_null_content(tmp_path): """Multi-document YAML where second doc is null treats content as empty dict.""" cfg_file = tmp_path / "multi_null.yaml" - cfg_file.write_text("key: value\n---\n", encoding="utf-8") + cfg_file.write_text("key: value\n---\n") data = _load_raw_config(cfg_file) assert data == {"key": "value"} @@ -1865,7 +1806,7 @@ def test_load_config_multi_doc_null_content(tmp_path): def test_load_config_multi_doc_first_not_dict_raises(tmp_path): """Multi-document YAML with non-dict first document raises ValueError.""" cfg_file = tmp_path / "bad_multi.yaml" - cfg_file.write_text("- item1\n---\nkey: value\n", encoding="utf-8") + cfg_file.write_text("- item1\n---\nkey: value\n") with pytest.raises(ValueError, match="first YAML document must be a mapping"): load_config(cfg_file) @@ -1873,7 +1814,7 @@ def test_load_config_multi_doc_first_not_dict_raises(tmp_path): def test_load_config_multi_doc_second_not_dict_or_list_raises(tmp_path): """Multi-document YAML with scalar second document raises ValueError.""" cfg_file = tmp_path / "bad_multi2.yaml" - cfg_file.write_text("key: value\n---\njust a string\n", encoding="utf-8") + cfg_file.write_text("key: value\n---\njust a string\n") with pytest.raises(ValueError, match="second YAML document must be a mapping or list"): load_config(cfg_file) @@ -1881,7 +1822,7 @@ def test_load_config_multi_doc_second_not_dict_or_list_raises(tmp_path): def test_load_config_three_docs_raises(tmp_path): """YAML with 3+ documents raises ValueError.""" cfg_file = tmp_path / "three_docs.yaml" - cfg_file.write_text("a: 1\n---\nb: 2\n---\nc: 3\n", encoding="utf-8") + cfg_file.write_text("a: 1\n---\nb: 2\n---\nc: 3\n") with pytest.raises(ValueError, match="expected 1 or 2 YAML documents"): load_config(cfg_file) @@ -1901,8 +1842,7 @@ def test_load_config_list_valued_yaml(tmp_path): " cfg:\n" " num_bits: 8\n" "- quantizer_name: '*input_quantizer'\n" - " enable: false\n", - encoding="utf-8", + " enable: false\n" ) data = load_config(cfg_file) assert isinstance(data, list) @@ -1930,8 +1870,7 @@ def test_import_dict_value_resolves_to_list_raises(tmp_path): ) config_file = tmp_path / "config.yml" config_file.write_text( - f"imports:\n entries: {tmp_path / 'entries.yml'}\nmy_field:\n $import: entries\n", - encoding="utf-8", + f"imports:\n entries: {tmp_path / 'entries.yml'}\nmy_field:\n $import: entries\n" ) with pytest.raises(ValueError, match="must resolve to a dict"): load_config(config_file) @@ -1940,7 +1879,7 @@ def test_import_dict_value_resolves_to_list_raises(tmp_path): def test_import_imports_not_a_dict_raises(tmp_path): """imports section that is a list raises ValueError.""" config_file = tmp_path / "config.yml" - config_file.write_text("imports:\n - some/path\nkey: value\n", encoding="utf-8") + config_file.write_text("imports:\n - some/path\nkey: value\n") with pytest.raises(ValueError, match="must be a dict"): load_config(config_file) @@ -1966,7 +1905,7 @@ def test_import_imports_not_a_dict_raises(tmp_path): def test_load_recipe_autoquantize_minimal(tmp_path): """Minimal AutoQuantize recipe loads with the right type and field defaults.""" recipe_file = tmp_path / "aq.yml" - recipe_file.write_text(_AQ_MINIMAL_BODY, encoding="utf-8") + recipe_file.write_text(_AQ_MINIMAL_BODY) recipe = load_recipe(recipe_file) assert recipe.recipe_type == RecipeType.AUTO_QUANTIZE @@ -2014,8 +1953,7 @@ def test_load_recipe_autoquantize_active_moe_cost_roundtrip(tmp_path): " - algorithm: max\n" " quant_cfg: []\n" " - algorithm: max\n" - " quant_cfg: []\n", - encoding="utf-8", + " quant_cfg: []\n" ) constraints = load_recipe(recipe_file).auto_quantize.constraints assert constraints.cost_model == "active_moe" @@ -2030,7 +1968,7 @@ def test_load_recipe_autoquantize_active_moe_cost_roundtrip(tmp_path): def test_load_recipe_autoquantize_missing_section_raises(tmp_path): """Missing auto_quantize section gives the clean loader-level error.""" bad = tmp_path / "bad.yml" - bad.write_text("metadata:\n recipe_type: auto_quantize\n", encoding="utf-8") + bad.write_text("metadata:\n recipe_type: auto_quantize\n") with pytest.raises( ValueError, match=r"AUTO_QUANTIZE recipe file .* must contain 'auto_quantize'" ): @@ -2043,8 +1981,7 @@ def test_load_recipe_autoquantize_empty_candidates_raises(tmp_path): bad.write_text( "metadata:\n recipe_type: auto_quantize\n" "auto_quantize:\n constraints:\n effective_bits: 4.8\n" - " candidate_formats: []\n", - encoding="utf-8", + " candidate_formats: []\n" ) with pytest.raises(ValueError, match="candidate_formats or at least one"): load_recipe(bad) @@ -2056,8 +1993,7 @@ def test_load_recipe_autoquantize_single_candidate_ok(tmp_path): recipe_file.write_text( "metadata:\n recipe_type: auto_quantize\n" "auto_quantize:\n constraints:\n effective_bits: 6.0\n" - " candidate_formats:\n - algorithm: max\n quant_cfg: []\n", - encoding="utf-8", + " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" ) aq = load_recipe(recipe_file).auto_quantize assert len(aq.candidate_formats) == 1 @@ -2066,9 +2002,7 @@ def test_load_recipe_autoquantize_single_candidate_ok(tmp_path): def test_load_recipe_autoquantize_effective_bits_out_of_range_raises(tmp_path): """effective_bits outside (0, 16] is rejected.""" bad = tmp_path / "bad.yml" - bad.write_text( - _AQ_MINIMAL_BODY.replace("effective_bits: 4.8", "effective_bits: 20"), encoding="utf-8" - ) + bad.write_text(_AQ_MINIMAL_BODY.replace("effective_bits: 4.8", "effective_bits: 20")) with pytest.raises(ValueError, match="effective_bits"): load_recipe(bad) @@ -2118,8 +2052,7 @@ def test_load_recipe_autoquantize_fixed_baseline_rejects_global_fallback(tmp_pat " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" " module_search_spaces:\n" " - module_name_patterns: ['*mlp*']\n" - " candidate_formats:\n - algorithm: max\n quant_cfg: []\n", - encoding="utf-8", + " candidate_formats:\n - algorithm: max\n quant_cfg: []\n" ) with pytest.raises(ValueError, match="must omit top-level"): @@ -2131,8 +2064,7 @@ def test_load_recipe_autoquantize_fixed_baseline_requires_explicit_search(tmp_pa recipe_file.write_text( "metadata:\n recipe_type: auto_quantize\n" "quantize:\n algorithm: max\n quant_cfg: []\n" - "auto_quantize:\n constraints:\n effective_bits: 6.0\n", - encoding="utf-8", + "auto_quantize:\n constraints:\n effective_bits: 6.0\n" ) with pytest.raises(ValueError, match="candidate_formats or at least one"): diff --git a/tests/unit/test_example_run_command.py b/tests/unit/test_example_run_command.py index d8496fb55d0..09a1f681876 100644 --- a/tests/unit/test_example_run_command.py +++ b/tests/unit/test_example_run_command.py @@ -38,7 +38,7 @@ def test_run_capturing_does_not_block_on_a_survivor_holding_the_pipe( os.environ.copy(), ) - survivor = int(pid_file.read_text(encoding="utf-8")) + survivor = int(pid_file.read_text()) try: assert returncode == -9 assert "out" in output # captured despite the survivor diff --git a/tests/unit/tools/test_resource_monitor.py b/tests/unit/tools/test_resource_monitor.py index b9e8d806e58..79d9e7848ca 100644 --- a/tests/unit/tools/test_resource_monitor.py +++ b/tests/unit/tools/test_resource_monitor.py @@ -155,7 +155,7 @@ def test_standalone_writes_csv_and_summary(tmp_path): check=True, ) - with open(csv_path, encoding="utf-8", newline="") as f: + with open(csv_path, newline="") as f: rows = list(csv.DictReader(f)) assert rows, "expected at least one sample row" for col in ( @@ -169,7 +169,7 @@ def test_standalone_writes_csv_and_summary(tmp_path): ): assert col in rows[0] - summary = summary_path.read_text(encoding="utf-8") + summary = summary_path.read_text() assert "sys_cpu_total_mb:" in summary assert "peak_sys_cpu_used_mb:" in summary assert "min_sys_cpu_free_mb:" in summary @@ -210,6 +210,6 @@ def test_wrap_mode_propagates_exit_code(tmp_path): assert fail.returncode == 3 # Wrap mode tracks the child tree, so proc_rss is populated. - with open(tmp_path / "a.csv", encoding="utf-8", newline="") as f: + with open(tmp_path / "a.csv", newline="") as f: rows = list(csv.DictReader(f)) assert rows and rows[0]["proc_rss_mb"] != "" diff --git a/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py b/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py index 656c145374a..ff7f77cf617 100755 --- a/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py +++ b/tests/unit/torch/deploy/_runtime/tensorrt/test_engine_builder.py @@ -44,10 +44,10 @@ def setup_mocks(): (tmp_path / "model.engine").write_bytes(engine_bytes) (tmp_path / f"{dummy_hash}-profile.json").write_text( - json.dumps([{"count": 1}, {"name": "dummy_layer", "averageMs": 0.001}]), encoding="utf-8" + json.dumps([{"count": 1}, {"name": "dummy_layer", "averageMs": 0.001}]) ) (tmp_path / f"{dummy_hash}-layerInfo.json").write_text( - json.dumps({"Layers": [{"Name": "dummy_layer"}]}), encoding="utf-8" + json.dumps({"Layers": [{"Name": "dummy_layer"}]}) ) mock_onnx = mock.Mock() diff --git a/tests/unit/torch/export/test_export_diffusers.py b/tests/unit/torch/export/test_export_diffusers.py index f553bc9311a..753c81a4b0e 100644 --- a/tests/unit/torch/export/test_export_diffusers.py +++ b/tests/unit/torch/export/test_export_diffusers.py @@ -42,7 +42,7 @@ def _load_config(config_path): - with open(config_path, encoding="utf-8") as file: + with open(config_path) as file: return json.load(file) @@ -63,9 +63,7 @@ def _write_sharded_checkpoint(export_dir, shards): weight_map[key] = filename total_size += tensor.numel() * tensor.element_size() index = {"metadata": {"total_size": total_size}, "weight_map": weight_map} - with open( - export_dir / "diffusion_pytorch_model.safetensors.index.json", "w", encoding="utf-8" - ) as file: + with open(export_dir / "diffusion_pytorch_model.safetensors.index.json", "w") as file: json.dump(index, file) diff --git a/tests/unit/torch/export/test_fsdp2_parallel_export.py b/tests/unit/torch/export/test_fsdp2_parallel_export.py index b2406b6a34d..58baa7defae 100644 --- a/tests/unit/torch/export/test_fsdp2_parallel_export.py +++ b/tests/unit/torch/export/test_fsdp2_parallel_export.py @@ -59,7 +59,7 @@ def _tiny_quantized_llama(quant_cfg=None, tie=True): def _load_all(export_dir: Path) -> dict: index = export_dir / "model.safetensors.index.json" if index.exists(): - weight_map = json.loads(index.read_text(encoding="utf-8"))["weight_map"] + weight_map = json.loads(index.read_text())["weight_map"] out: dict = {} for fname in set(weight_map.values()): out.update(load_file(str(export_dir / fname))) @@ -127,7 +127,7 @@ def test_streaming_export_subsplits_by_max_shard_size(tmp_path): d = tmp_path _export_fsdp2_checkpoint_streaming(model, torch.bfloat16, export_dir=d, max_shard_size=2048) - index = json.loads((d / "model.safetensors.index.json").read_text(encoding="utf-8")) + index = json.loads((d / "model.safetensors.index.json").read_text()) assert len(set(index["weight_map"].values())) > 1 loaded = _load_all(d) assert set(loaded) == set(index["weight_map"]) diff --git a/tests/unit/torch/export/test_hf_checkpoint_utils.py b/tests/unit/torch/export/test_hf_checkpoint_utils.py index a387a721cd0..08292c65f9f 100644 --- a/tests/unit/torch/export/test_hf_checkpoint_utils.py +++ b/tests/unit/torch/export/test_hf_checkpoint_utils.py @@ -35,11 +35,11 @@ def test_copy_non_safetensor_files_from_ckpt_supports_additional_exclusions(tmp_path): src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "model.safetensors").write_text("weights", encoding="utf-8") - (src_dir / "model.safetensors.index.json").write_text('{"weight_map": {}}', encoding="utf-8") - (src_dir / "pytorch_model.bin").write_text("weights", encoding="utf-8") - (src_dir / "stats.npy").write_text("stats", encoding="utf-8") - (src_dir / "reasoning_parser.py").write_text("parser", encoding="utf-8") + (src_dir / "model.safetensors").write_text("weights") + (src_dir / "model.safetensors.index.json").write_text('{"weight_map": {}}') + (src_dir / "pytorch_model.bin").write_text("weights") + (src_dir / "stats.npy").write_text("stats") + (src_dir / "reasoning_parser.py").write_text("parser") default_dst = tmp_path / "default" copy_non_safetensor_files_from_ckpt(src_dir, default_dst) @@ -62,8 +62,8 @@ def test_copy_non_safetensor_files_from_ckpt_supports_additional_exclusions(tmp_ def test_copy_non_safetensor_files_from_ckpt_continues_after_copy_failure(tmp_path, monkeypatch): src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "bad.py").write_text("bad", encoding="utf-8") - (src_dir / "good.py").write_text("good", encoding="utf-8") + (src_dir / "bad.py").write_text("bad") + (src_dir / "good.py").write_text("good") original_copy2 = hf_checkpoint_utils.shutil.copy2 @@ -83,21 +83,19 @@ def test_copy_hf_ckpt_remote_code_local_dir(tmp_path): """copy_hf_ckpt_remote_code copies top-level .py files from a local directory.""" src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "modeling_custom.py").write_text("# custom model", encoding="utf-8") - (src_dir / "configuration_custom.py").write_text("# custom config", encoding="utf-8") - (src_dir / "not_python.txt").write_text("not python", encoding="utf-8") + (src_dir / "modeling_custom.py").write_text("# custom model") + (src_dir / "configuration_custom.py").write_text("# custom config") + (src_dir / "not_python.txt").write_text("not python") (src_dir / "subdir").mkdir() - (src_dir / "subdir" / "nested.py").write_text( - "# nested — should not be copied", encoding="utf-8" - ) + (src_dir / "subdir" / "nested.py").write_text("# nested — should not be copied") dst_dir = tmp_path / "dst" dst_dir.mkdir() copy_hf_ckpt_remote_code(src_dir, dst_dir) - assert (dst_dir / "modeling_custom.py").read_text(encoding="utf-8") == "# custom model" - assert (dst_dir / "configuration_custom.py").read_text(encoding="utf-8") == "# custom config" + assert (dst_dir / "modeling_custom.py").read_text() == "# custom model" + assert (dst_dir / "configuration_custom.py").read_text() == "# custom config" assert not (dst_dir / "not_python.txt").exists(), "non-.py files should not be copied" assert not (dst_dir / "nested.py").exists(), "nested .py files should not be copied" @@ -106,7 +104,7 @@ def test_copy_hf_ckpt_remote_code_local_dir_no_py_files(tmp_path): """copy_hf_ckpt_remote_code is a no-op when the local directory has no .py files.""" src_dir = tmp_path / "src" src_dir.mkdir() - (src_dir / "config.json").write_text("{}", encoding="utf-8") + (src_dir / "config.json").write_text("{}") dst_dir = tmp_path / "dst" dst_dir.mkdir() @@ -121,8 +119,8 @@ def test_copy_hf_ckpt_remote_code_hub_id(tmp_path, monkeypatch): dst_dir = tmp_path / "dst" snapshot_dir = tmp_path / "snapshot" snapshot_dir.mkdir() - (snapshot_dir / "modeling_custom.py").write_text("# custom model", encoding="utf-8") - (snapshot_dir / "not_python.txt").write_text("not python", encoding="utf-8") + (snapshot_dir / "modeling_custom.py").write_text("# custom model") + (snapshot_dir / "not_python.txt").write_text("not python") monkeypatch.delenv("HF_HUB_OFFLINE", raising=False) with patch( @@ -136,7 +134,7 @@ def test_copy_hf_ckpt_remote_code_hub_id(tmp_path, monkeypatch): allow_patterns=["*.py"], local_files_only=False, ) - assert (dst_dir / "modeling_custom.py").read_text(encoding="utf-8") == "# custom model" + assert (dst_dir / "modeling_custom.py").read_text() == "# custom model" assert not (dst_dir / "not_python.txt").exists(), "non-.py files should not be copied" @@ -145,7 +143,7 @@ def test_copy_hf_ckpt_remote_code_hub_id_offline_uses_cache(tmp_path, monkeypatc dst_dir = tmp_path / "dst" snapshot_dir = tmp_path / "snapshot" snapshot_dir.mkdir() - (snapshot_dir / "nemotron_reasoning_parser.py").write_text("# parser", encoding="utf-8") + (snapshot_dir / "nemotron_reasoning_parser.py").write_text("# parser") monkeypatch.setenv("HF_HUB_OFFLINE", "1") with patch( @@ -159,7 +157,7 @@ def test_copy_hf_ckpt_remote_code_hub_id_offline_uses_cache(tmp_path, monkeypatc allow_patterns=["*.py"], local_files_only=True, ) - assert (dst_dir / "nemotron_reasoning_parser.py").read_text(encoding="utf-8") == "# parser" + assert (dst_dir / "nemotron_reasoning_parser.py").read_text() == "# parser" def test_copy_hf_ckpt_remote_code_hub_id_offline_missing_cache_raises(tmp_path, monkeypatch): diff --git a/tests/unit/torch/export/test_mcore_save_safetensors.py b/tests/unit/torch/export/test_mcore_save_safetensors.py index 182c18c3efa..9f2050c405f 100644 --- a/tests/unit/torch/export/test_mcore_save_safetensors.py +++ b/tests/unit/torch/export/test_mcore_save_safetensors.py @@ -43,9 +43,9 @@ def _fake_save_file(tensors, path, metadata=None): ) shard_name = "model-00001-of-00001.safetensors" - with open(tmp_path / "model-00001-of-00001.json", encoding="utf-8") as f: + with open(tmp_path / "model-00001-of-00001.json") as f: shard_meta = json.load(f) - with open(tmp_path / "model.safetensors.index.json", encoding="utf-8") as f: + with open(tmp_path / "model.safetensors.index.json") as f: index_meta = json.load(f) json_keys = set(shard_meta["weight_map"].keys()) diff --git a/tests/unit/torch/export/test_nvfp4_utils.py b/tests/unit/torch/export/test_nvfp4_utils.py index ea46e8d7737..7aed23f0b7a 100644 --- a/tests/unit/torch/export/test_nvfp4_utils.py +++ b/tests/unit/torch/export/test_nvfp4_utils.py @@ -185,7 +185,7 @@ def test_padding_and_swizzle(self, tmp_path): def test_sharded_guard(self, tmp_path): save_file({"w": torch.randn(2, 2)}, str(tmp_path / "model.safetensors")) - (tmp_path / "model.safetensors.index.json").write_text("{}", encoding="utf-8") + (tmp_path / "model.safetensors.index.json").write_text("{}") with pytest.raises(NotImplementedError, match="sharded"): _postprocess_safetensors( diff --git a/tests/unit/torch/export/test_offload_export.py b/tests/unit/torch/export/test_offload_export.py index 9b3ef3c697a..09a4d353784 100644 --- a/tests/unit/torch/export/test_offload_export.py +++ b/tests/unit/torch/export/test_offload_export.py @@ -212,7 +212,7 @@ def test_streaming_shard_writer_multi_shard(): assert index_path.exists(), "model.safetensors.index.json not written" assert weight_map["x"] != weight_map["y"], "keys must be in different shards" - with open(index_path, encoding="utf-8") as f: + with open(index_path) as f: index = json.load(f) assert index["metadata"]["total_size"] > 0 @@ -322,9 +322,7 @@ def test_name_shards_and_write_index_merges_disjoint_writers(): weight_map = name_shards_and_write_index(tmpdir, closed) - index = json.loads( - (Path(tmpdir) / "model.safetensors.index.json").read_text(encoding="utf-8") - ) + index = json.loads((Path(tmpdir) / "model.safetensors.index.json").read_text()) assert set(index["weight_map"]) == set(ref) assert weight_map == index["weight_map"] assert not list(Path(tmpdir).glob("__shard_part*")), "every part should be renamed" diff --git a/tests/unit/torch/export/test_shard_cast_utils.py b/tests/unit/torch/export/test_shard_cast_utils.py index a0fd0e4b591..b93da516052 100644 --- a/tests/unit/torch/export/test_shard_cast_utils.py +++ b/tests/unit/torch/export/test_shard_cast_utils.py @@ -89,10 +89,10 @@ def test_link_aux_files_preserves_sidecars_and_applies_skips(tmp_path): output = tmp_path / "output" (source / "assets").mkdir(parents=True) (source / ".cache").mkdir() - (source / "tokenizer_config.json").write_text("{}", encoding="utf-8") + (source / "tokenizer_config.json").write_text("{}") (source / "model.safetensors").write_bytes(b"shard") - (source / "assets" / "config.txt").write_text("keep", encoding="utf-8") - (source / ".cache" / "stale.json").write_text("{}", encoding="utf-8") + (source / "assets" / "config.txt").write_text("keep") + (source / ".cache" / "stale.json").write_text("{}") link_aux_files( source, @@ -102,8 +102,8 @@ def test_link_aux_files_preserves_sidecars_and_applies_skips(tmp_path): skip_file=lambda path: path.suffix == ".safetensors", ) - assert (output / "tokenizer_config.json").read_text(encoding="utf-8") == "{}" - assert (output / "assets" / "config.txt").read_text(encoding="utf-8") == "keep" + assert (output / "tokenizer_config.json").read_text() == "{}" + assert (output / "assets" / "config.txt").read_text() == "keep" assert not (output / "model.safetensors").exists() assert not (output / ".cache").exists() @@ -115,7 +115,7 @@ def test_link_aux_files_accepts_huggingface_snapshot_blobs(tmp_path): blob = repository / "blobs" / "tokenizer-blob" snapshot.mkdir(parents=True) blob.parent.mkdir() - blob.write_text("tokenizer", encoding="utf-8") + blob.write_text("tokenizer") (snapshot / "tokenizer.json").symlink_to(os.path.relpath(blob, snapshot)) assert resolve_checkpoint_file(snapshot, "tokenizer.json") == blob.resolve() @@ -123,7 +123,7 @@ def test_link_aux_files_accepts_huggingface_snapshot_blobs(tmp_path): output = tmp_path / "output" link_aux_files(snapshot, output) - assert (output / "tokenizer.json").read_text(encoding="utf-8") == "tokenizer" + assert (output / "tokenizer.json").read_text() == "tokenizer" assert not (output / "tokenizer.json").is_symlink() @@ -138,7 +138,7 @@ def test_link_aux_files_rejects_unsafe_sources(tmp_path, source_kind, message): unsafe = source / "unsafe" if source_kind == "symlink": outside = tmp_path / "outside" - outside.write_text("secret", encoding="utf-8") + outside.write_text("secret") unsafe.symlink_to(outside) else: os.mkfifo(unsafe) @@ -154,7 +154,7 @@ def test_link_aux_files_rejects_unsafe_sources(tmp_path, source_kind, message): def test_resolve_checkpoint_file_rejects_oversized_metadata(tmp_path): source = tmp_path / "source" source.mkdir() - (source / "config.json").write_text("12345", encoding="utf-8") + (source / "config.json").write_text("12345") with pytest.raises(ValueError, match="4-byte size limit"): resolve_checkpoint_file(source, "config.json", max_bytes=4) diff --git a/tests/unit/torch/opt/plugins/test_lr_config.py b/tests/unit/torch/opt/plugins/test_lr_config.py index 09d3ce097f5..a3506e2f034 100644 --- a/tests/unit/torch/opt/plugins/test_lr_config.py +++ b/tests/unit/torch/opt/plugins/test_lr_config.py @@ -58,7 +58,7 @@ def dummy_dataset(): def _write_lr_config(tmp_path, cfg: dict) -> str: path = tmp_path / "lr_config.yaml" - path.write_text(yaml.dump(cfg), encoding="utf-8") + path.write_text(yaml.dump(cfg)) return str(path) @@ -114,13 +114,13 @@ def test_load_with_weight_decay_and_betas(self, tmp_path): def test_load_invalid_not_dict(self, tmp_path): path = tmp_path / "lr_config.yaml" - path.write_text("- item1\n- item2\n", encoding="utf-8") + path.write_text("- item1\n- item2\n") with pytest.raises(ValueError, match="YAML mapping"): ModelOptHFTrainer.load_lr_config(str(path)) def test_load_invalid_entry(self, tmp_path): path = tmp_path / "lr_config.yaml" - path.write_text('"*lm_head*": 0.001\n', encoding="utf-8") + path.write_text('"*lm_head*": 0.001\n') with pytest.raises(ValueError, match="str -> dict"): ModelOptHFTrainer.load_lr_config(str(path)) diff --git a/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py b/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py index a1189b0da47..78a89a27ff9 100644 --- a/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py +++ b/tests/unit/torch/opt/plugins/test_modelopt_arg_parser.py @@ -49,7 +49,7 @@ def test_cli_args_only(self): def test_yaml_config(self, tmp_path): config_file = tmp_path / "config.yaml" - config_file.write_text("model_name: yaml-model\nepochs: 10\n", encoding="utf-8") + config_file.write_text("model_name: yaml-model\nepochs: 10\n") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -60,7 +60,7 @@ def test_yaml_config(self, tmp_path): def test_cli_overrides_yaml(self, tmp_path): config_file = tmp_path / "config.yaml" - config_file.write_text("model_name: yaml-model\nlearning_rate: 0.001\n", encoding="utf-8") + config_file.write_text("model_name: yaml-model\nlearning_rate: 0.001\n") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -71,7 +71,7 @@ def test_cli_overrides_yaml(self, tmp_path): def test_empty_yaml_config(self, tmp_path): config_file = tmp_path / "empty.yaml" - config_file.write_text("", encoding="utf-8") + config_file.write_text("") parser = ModelOptArgParser((_ModelArgs, _TrainArgs)) model_args, train_args = parser.parse_args_into_dataclasses( @@ -88,7 +88,7 @@ def test_generate_docs(self, tmp_path): parser.parse_args_into_dataclasses(args=["--generate_docs", str(output_path)]) assert exc_info.value.code == 0 - content = output_path.read_text(encoding="utf-8") + content = output_path.read_text() assert "## _ModelArgs" in content assert "## _TrainArgs" in content assert "--model_name" in content @@ -103,7 +103,7 @@ def test_generate_docs_default_path(self, tmp_path, monkeypatch): parser.parse_args_into_dataclasses(args=["--generate_docs"]) assert exc_info.value.code == 0 - content = Path("ARGUMENTS.md").read_text(encoding="utf-8") + content = Path("ARGUMENTS.md").read_text() assert "# Argument Reference" in content def test_docs_table_format(self, tmp_path): @@ -113,7 +113,7 @@ def test_docs_table_format(self, tmp_path): with pytest.raises(SystemExit): parser.parse_args_into_dataclasses(args=["--generate_docs", str(output_path)]) - content = output_path.read_text(encoding="utf-8") + content = output_path.read_text() # Check table headers assert "| Argument | Type | Default | Description |" in content # Check a specific row diff --git a/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py b/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py index 9e216435c61..2a3712901a1 100644 --- a/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py +++ b/tests/unit/torch/puzzletron/test_checkpoint_utils_hf.py @@ -54,8 +54,8 @@ def test_copy_auto_map_code_files_copies_valid_local_code_references(tmp_path, m checkpoint_dir = tmp_path / "checkpoint" source_dir.mkdir() checkpoint_dir.mkdir() - (source_dir / "modeling_custom.py").write_text("# modeling\n", encoding="utf-8") - (source_dir / "tokenization_custom.py").write_text("# tokenizer\n", encoding="utf-8") + (source_dir / "modeling_custom.py").write_text("# modeling\n") + (source_dir / "tokenization_custom.py").write_text("# tokenizer\n") monkeypatch.setattr(cuhf.inspect, "getfile", lambda _cls: source_dir / "configuration.py") diff --git a/tests/unit/torch/quantization/test_layerwise_calibrate.py b/tests/unit/torch/quantization/test_layerwise_calibrate.py index 56c686e96df..07e14d50f2a 100644 --- a/tests/unit/torch/quantization/test_layerwise_calibrate.py +++ b/tests/unit/torch/quantization/test_layerwise_calibrate.py @@ -1239,8 +1239,7 @@ def build_cfg(ckpt_dir): "save_every": save_every, "calib_mutates_weights": calib_mutates_weights, } - ), - encoding="utf-8", + ) ) torch.manual_seed(0) @@ -1291,7 +1290,7 @@ def crashing_torch_save(obj, path, *args, **kwargs): with pytest.raises(RuntimeError, match="simulated crash"): mtq.quantize(model, cfg, forward_loop=lambda m: [m(b) for b in calib_data]) - manifest = json.loads((tmp_path / "manifest.json").read_text(encoding="utf-8")) + manifest = json.loads((tmp_path / "manifest.json").read_text()) assert manifest["last_completed_layer"] == 1, f"manifest leaked mid-window state: {manifest}" @@ -1326,8 +1325,7 @@ def test_layerwise_checkpoint_mismatch_save_every_raises(monkeypatch, tmp_path): "save_every": 2, "calib_mutates_weights": True, } - ), - encoding="utf-8", + ) ) cfg_mismatched = _int8_cfg_with_algorithm( { diff --git a/tests/unit/torch/quantization/test_sequential_checkpoint.py b/tests/unit/torch/quantization/test_sequential_checkpoint.py index 2252e8034ff..0e592a68c75 100644 --- a/tests/unit/torch/quantization/test_sequential_checkpoint.py +++ b/tests/unit/torch/quantization/test_sequential_checkpoint.py @@ -87,7 +87,7 @@ def test_full_run_creates_checkpoints(monkeypatch, tmp_path): manifest_path = os.path.join(ckpt_dir, "manifest.json") assert os.path.isfile(manifest_path) - with open(manifest_path, encoding="utf-8") as f: + with open(manifest_path) as f: manifest = json.load(f) assert manifest["last_completed_layer"] == 2 assert manifest["num_layers"] == 3 @@ -116,7 +116,7 @@ def test_resume_matches_full_run(monkeypatch, tmp_path): # Simulate crash after layer 0: truncate manifest manifest_path = os.path.join(ckpt_dir, "manifest.json") - with open(manifest_path, "w", encoding="utf-8") as f: + with open(manifest_path, "w") as f: json.dump({"last_completed_layer": 0, "num_layers": 3}, f) # Resume from a fresh model diff --git a/tests/unit/torch/speculative/plugins/test_fakebase.py b/tests/unit/torch/speculative/plugins/test_fakebase.py index 7425237dc16..cf6dfe1a6bc 100644 --- a/tests/unit/torch/speculative/plugins/test_fakebase.py +++ b/tests/unit/torch/speculative/plugins/test_fakebase.py @@ -57,7 +57,7 @@ def fake_checkpoint(tmp_path, fake_config): shard = tmp_path / "model-00001-of-00001.safetensors" safetensors.torch.save_file(tensors, shard) index = {"weight_map": dict.fromkeys(tensors, shard.name)} - (tmp_path / "model.safetensors.index.json").write_text(json.dumps(index), encoding="utf-8") + (tmp_path / "model.safetensors.index.json").write_text(json.dumps(index)) return tmp_path diff --git a/tests/unit/torch/speculative/plugins/test_hf_dflash.py b/tests/unit/torch/speculative/plugins/test_hf_dflash.py index e3fab238bbc..104a16b16a1 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_dflash.py +++ b/tests/unit/torch/speculative/plugins/test_hf_dflash.py @@ -791,7 +791,7 @@ def test_export_config_fields(self, tmp_path): export_dir = tmp_path / "exported" exporter.export(export_dir) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] @@ -819,7 +819,7 @@ def test_export_swa_fields(self, tmp_path): export_dir = tmp_path / "exported" exporter.export(export_dir) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) # vLLM _resolve_layer_attention reads these; all-full layer_types + use_swa=True diff --git a/tests/unit/torch/speculative/plugins/test_hf_domino.py b/tests/unit/torch/speculative/plugins/test_hf_domino.py index b02c71c6050..0030abe6f33 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_domino.py +++ b/tests/unit/torch/speculative/plugins/test_hf_domino.py @@ -227,7 +227,7 @@ def test_export_weight_keys_match_reference(self, tmp_path): def test_export_config_has_domino_fields(self, tmp_path): """config.json carries the dflash_config domino fields + top-level emb_dim.""" export_dir = self._export(tmp_path) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] diff --git a/tests/unit/torch/speculative/plugins/test_hf_dspark.py b/tests/unit/torch/speculative/plugins/test_hf_dspark.py index 9a4c589a010..3686788ad90 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_dspark.py +++ b/tests/unit/torch/speculative/plugins/test_hf_dspark.py @@ -306,7 +306,7 @@ def test_export_includes_confidence_weights(self, tmp_path): def test_export_config_has_dspark_fields(self, tmp_path): """config.json carries the dflash_config DSpark head fields.""" export_dir = self._export(tmp_path, head_type="gated") - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert cfg["architectures"] == ["DFlashDraftModel"] @@ -410,7 +410,7 @@ def test_export_records_draft_attention(self, tmp_path): model = self._make_model(mode) export_dir = tmp_path / f"exp_{mode}" model.get_exporter().export(export_dir) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert cfg["dflash_config"]["causal"] is expected @@ -514,7 +514,7 @@ def test_export_includes_sink_weights_and_flag(self, tmp_path): assert key in sd, f"missing {key}" assert sd[key].shape == (heads,) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert cfg["dflash_config"]["attention_sink_bias"] is True assert cfg["attention_sink_bias"] is True @@ -525,7 +525,7 @@ def test_export_omits_sink_when_disabled(self, tmp_path): model.get_exporter().export(export_dir) sd = load_file(str(export_dir / "model.safetensors")) assert not any("attention_sink_bias" in k for k in sd) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: cfg = json.load(f) assert "attention_sink_bias" not in cfg["dflash_config"] @@ -767,6 +767,6 @@ def test_negative_id_raises(self): def test_export_round_trips_explicit_ids(self, tmp_path): model = self._make_model(target_layer_ids=[0, 7]) model.get_exporter().export(tmp_path / "exp") - with open(tmp_path / "exp" / "config.json", encoding="utf-8") as f: + with open(tmp_path / "exp" / "config.json") as f: cfg = json.load(f) assert cfg["dflash_config"]["target_layer_ids"] == [0, 7] diff --git a/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py b/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py index a4491782b14..f9bdbbca355 100644 --- a/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py +++ b/tests/unit/torch/speculative/plugins/test_hf_lilicorr.py @@ -479,7 +479,7 @@ def test_export_config_declares_the_lilicorr_architecture(self, tmp_path): A checkpoint that declares DFlashDraftModel loads as plain DFlash and silently ignores the head, which reads as a small acceptance delta rather than an error. """ - with open(self._export(tmp_path) / "config.json", encoding="utf-8") as f: + with open(self._export(tmp_path) / "config.json") as f: config = json.load(f) assert config["architectures"] == ["LiLiCorrDraftModel"] @@ -502,7 +502,7 @@ def test_exported_geometry_matches_the_exported_weights(self, tmp_path): """Geometry is read off the built head, so it cannot drift from the tensors.""" export_dir = self._export(tmp_path) state_dict = load_file(str(export_dir / "model.safetensors")) - with open(export_dir / "config.json", encoding="utf-8") as f: + with open(export_dir / "config.json") as f: dflash_config = json.load(f)["dflash_config"] out_head = state_dict["lilicorr.out_head.weight"] assert out_head.shape == ( diff --git a/tests/unit/torch/utils/test_mlflow.py b/tests/unit/torch/utils/test_mlflow.py index 4fe63a7a051..49a9d23351e 100644 --- a/tests/unit/torch/utils/test_mlflow.py +++ b/tests/unit/torch/utils/test_mlflow.py @@ -85,7 +85,7 @@ def log_text(self, text, artifact_file): def log_artifact(self, local_path, artifact_path=None): self.artifacts.append((Path(local_path).name, artifact_path)) - self.artifact_text[Path(local_path).name] = Path(local_path).read_text(encoding="utf-8") + self.artifact_text[Path(local_path).name] = Path(local_path).read_text() def log_metrics(self, metrics): self.metrics.update(metrics) @@ -248,9 +248,7 @@ def test_logger_is_inert_when_disabled(monkeypatch): def test_logger_logs_inputs_and_outputs(fake_mlflow, tmp_path, monkeypatch): monkeypatch.setattr(sys, "argv", ["hf_ptq.py", "--pyt_ckpt_path", "/models/Qwen3-0.6B"]) - (tmp_path / ".quant_summary.txt").write_text( - "706 TensorQuantizers found in model\n", encoding="utf-8" - ) + (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers found in model\n") logger = _logger(run_name="unit-test") logger.start( @@ -423,7 +421,7 @@ def test_capture_includes_preconfigured_library_logging(fake_mlflow, monkeypatch logger.start() log_path = logger._log_path library_logger.warning("Rate limited. Waiting 169.0s before retry") - captured = log_path.read_text(encoding="utf-8") + captured = log_path.read_text() logger.finish("FINISHED") finally: library_logger.removeHandler(handler) @@ -601,19 +599,19 @@ def test_git_sha_resolves_in_a_checkout_and_a_worktree(tmp_path, monkeypatch, in main checkout -- a directory-only reader silently reports "unknown" for every worktree.""" main = tmp_path / "repo" / ".git" (main / "refs" / "heads").mkdir(parents=True) - (main / "refs" / "heads" / "main").write_text("a" * 40 + "\n", encoding="utf-8") + (main / "refs" / "heads" / "main").write_text("a" * 40 + "\n") if in_worktree: checkout = tmp_path / "wt" wt_git = main / "worktrees" / "wt" wt_git.mkdir(parents=True) - (wt_git / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") - (wt_git / "commondir").write_text("../..\n", encoding="utf-8") + (wt_git / "HEAD").write_text("ref: refs/heads/main\n") + (wt_git / "commondir").write_text("../..\n") checkout.mkdir() - (checkout / ".git").write_text(f"gitdir: {wt_git}\n", encoding="utf-8") + (checkout / ".git").write_text(f"gitdir: {wt_git}\n") else: checkout = main.parent - (main / "HEAD").write_text("ref: refs/heads/main\n", encoding="utf-8") + (main / "HEAD").write_text("ref: refs/heads/main\n") # _git_sha locates .git relative to the module file, three parents up. fake_module = checkout / "modelopt" / "torch" / "utils" / "mlflow.py" @@ -627,7 +625,7 @@ def test_git_sha_resolves_in_a_checkout_and_a_worktree(tmp_path, monkeypatch, in def test_git_sha_handles_a_detached_head(tmp_path, monkeypatch): git_dir = tmp_path / "repo" / ".git" git_dir.mkdir(parents=True) - (git_dir / "HEAD").write_text("b" * 40 + "\n", encoding="utf-8") + (git_dir / "HEAD").write_text("b" * 40 + "\n") fake_module = tmp_path / "repo" / "modelopt" / "torch" / "utils" / "mlflow.py" fake_module.parent.mkdir(parents=True) fake_module.touch() @@ -657,7 +655,7 @@ def test_track_marks_a_raising_block_failed(fake_mlflow, monkeypatch, tmp_path): _logger().track(files=summary), ): # post_quantize writes the summary during the run, so the test must too. - (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers\n", encoding="utf-8") + (tmp_path / ".quant_summary.txt").write_text("706 TensorQuantizers\n") raise RuntimeError("calibration exploded") assert fake_mlflow.status == "FAILED" @@ -701,15 +699,13 @@ def test_only_files_this_run_produced_are_uploaded(fake_mlflow, tmp_path, monkey summary must not upload the previous run's file as though it were its own.""" monkeypatch.setattr(sys, "argv", ["hf_ptq.py"]) stale = tmp_path / ".quant_summary.txt" - stale.write_text("from a previous run\n", encoding="utf-8") + stale.write_text("from a previous run\n") fresh = tmp_path / ".moe.html" logger = _logger() outputs = {"summary/quant_summary.txt": stale, "summary/moe.html": fresh} logger.start(files=outputs) - fresh.write_text( - "<html>written by this run</html>", encoding="utf-8" - ) # produced during the run + fresh.write_text("<html>written by this run</html>") # produced during the run logger.finish("FAILED", files=outputs) uploaded = [name for name, _ in fake_mlflow.artifacts] @@ -724,7 +720,7 @@ def test_stale_check_survives_unnormalized_string_paths(fake_mlflow, tmp_path, m monkeypatch.chdir(tmp_path) (tmp_path / "out").mkdir() stale = tmp_path / "out" / ".quant_summary.txt" - stale.write_text("from a previous run\n", encoding="utf-8") + stale.write_text("from a previous run\n") outputs = {"summary/quant_summary.txt": "./out/.quant_summary.txt"} logger = _logger() diff --git a/tests/unit/torch/utils/test_model_load_utils.py b/tests/unit/torch/utils/test_model_load_utils.py index 7a1e8b96fbe..323fb92a568 100644 --- a/tests/unit/torch/utils/test_model_load_utils.py +++ b/tests/unit/torch/utils/test_model_load_utils.py @@ -39,8 +39,7 @@ def test_weight_map_for_sharded(tmp_path): (tmp_path / "model.safetensors.index.json").write_text( json.dumps( {"weight_map": {"a.weight": "shard1.safetensors", "b.weight": "shard2.safetensors"}} - ), - encoding="utf-8", + ) ) assert weight_map_for(str(tmp_path)) == { diff --git a/tools/check_text_encoding.py b/tools/check_text_encoding.py deleted file mode 100755 index aa8f45dcbf2..00000000000 --- a/tools/check_text_encoding.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -"""Flag Path.read_text()/write_text() calls that do not pass an explicit encoding. - -Ruff's PLW1514 covers ``open`` only, so these slip through it. They matter for the same reason: -without an encoding Python uses the locale codepage, which is cp1252 on Windows, and a UTF-8 file -then fails to decode. Parsed rather than grepped -- a regex cannot tell whether ``encoding=`` sits -on a later line of a multi-line call, and reports it as a violation. -""" - -from __future__ import annotations - -import ast -import sys - -TARGETS = {"read_text", "write_text"} - - -def violations(path: str) -> list[tuple[int, str]]: - """Return ``(lineno, method)`` for each offending call in ``path``. - - A file that will not parse is not ours to police -- ruff and the formatter already have - an opinion about it, and a syntax error reported from here would only be noise. - """ - try: - tree = ast.parse(open(path, encoding="utf-8").read(), filename=path) - except (SyntaxError, UnicodeDecodeError): - return [] # not ours to police - found = [] - for node in ast.walk(tree): - if ( - isinstance(node, ast.Call) - and isinstance(node.func, ast.Attribute) - and node.func.attr in TARGETS - and not any(kw.arg == "encoding" for kw in node.keywords) - ): - found.append((node.lineno, node.func.attr)) - return found - - -def main(argv: list[str]) -> int: - """Report every offending call across ``argv``; exit non-zero when any is found.""" - bad = [(p, ln, name) for p in argv for ln, name in violations(p)] - for path, lineno, name in bad: - print( - f"{path}:{lineno}: {name}() without an explicit encoding= (locale codepage on Windows)" - ) - return 1 if bad else 0 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/launcher/common/check_regression.py b/tools/launcher/common/check_regression.py index c3c685cddb8..17ca7980f59 100644 --- a/tools/launcher/common/check_regression.py +++ b/tools/launcher/common/check_regression.py @@ -56,7 +56,7 @@ def find_trainer_state(output_dir): def get_final_metrics(trainer_state_path): """Extract final loss and accuracy from trainer_state.json.""" - with open(trainer_state_path, encoding="utf-8") as f: + with open(trainer_state_path) as f: state = json.load(f) logs = [h for h in state.get("log_history", []) if "loss" in h] diff --git a/tools/launcher/common/query.py b/tools/launcher/common/query.py index 5fa744a3cbd..27c41953d8a 100644 --- a/tools/launcher/common/query.py +++ b/tools/launcher/common/query.py @@ -254,7 +254,7 @@ def synthesize(data): shard = shard.map(disable_thinking_column, num_proc=num_proc) updated_shard = shard.map(synthesize, num_proc=num_proc) updated_shard.to_json(file_path) - with open(done_path, "w", encoding="utf-8") as done_file: + with open(done_path, "w") as done_file: done_file.write("done\n") print(updated_shard[0]) diff --git a/tools/launcher/core.py b/tools/launcher/core.py index 8ab56409742..44f3efab86d 100644 --- a/tools/launcher/core.py +++ b/tools/launcher/core.py @@ -163,7 +163,7 @@ def create_task_from_yaml(yaml_file, factory_lookup): yaml_file: Path to the YAML config. factory_lookup: Dict mapping factory names to callable factory functions. """ - with open(yaml_file, encoding="utf-8") as file: + with open(yaml_file) as file: config_from_yaml = yaml.safe_load(file) script = config_from_yaml["script"] @@ -191,7 +191,7 @@ def _explicit_slurm_fields_from_yaml(yaml_path: str | None, task_name: str) -> s if not yaml_path: return None try: - with open(yaml_path, encoding="utf-8") as file: + with open(yaml_path) as file: config = yaml.safe_load(file) or {} except (OSError, yaml.YAMLError): return None @@ -895,5 +895,5 @@ def run_jobs( } metadata_path = os.path.join("experiments", experiment_title, exp._id, "metadata.json") os.makedirs(os.path.dirname(metadata_path), exist_ok=True) - with open(metadata_path, "w", encoding="utf-8") as f: + with open(metadata_path, "w") as f: json.dump(metadata, f) diff --git a/tools/launcher/tests/conftest.py b/tools/launcher/tests/conftest.py index 834efcf4295..1886f9bf9cd 100644 --- a/tools/launcher/tests/conftest.py +++ b/tools/launcher/tests/conftest.py @@ -43,7 +43,7 @@ def tmp_yaml(tmp_path): def _write(content, name="test.yaml"): p = tmp_path / name - p.write_text(content, encoding="utf-8") + p.write_text(content) return str(p) return _write diff --git a/tools/launcher/tests/test_docker_execution.py b/tools/launcher/tests/test_docker_execution.py index beadc8d0f67..7b7b92850eb 100644 --- a/tools/launcher/tests/test_docker_execution.py +++ b/tools/launcher/tests/test_docker_execution.py @@ -289,7 +289,7 @@ def test_metadata_written(self, mock_docker, mock_exp, tmp_path): metadata_path = os.path.join("experiments", "cicd", "test_exp_meta", "metadata.json") assert os.path.exists(metadata_path) - with open(metadata_path, encoding="utf-8") as f: + with open(metadata_path) as f: meta = json.load(f) assert meta["experiment_id"] == "test_exp_meta" assert meta["job_name"] == "meta_job" diff --git a/tools/launcher/tests/test_docker_launch.py b/tools/launcher/tests/test_docker_launch.py index c00b1015dad..625d28b0822 100644 --- a/tools/launcher/tests/test_docker_launch.py +++ b/tools/launcher/tests/test_docker_launch.py @@ -40,7 +40,7 @@ def test_echo_script_via_launch(self, tmp_path): script_dir = tmp_path / "scripts" script_dir.mkdir() script = script_dir / "hello.sh" - script.write_text("#!/bin/bash\necho 'HELLO_FROM_DOCKER'\n", encoding="utf-8") + script.write_text("#!/bin/bash\necho 'HELLO_FROM_DOCKER'\n") script.chmod(0o755) # Create a YAML config @@ -54,7 +54,7 @@ def test_echo_script_via_launch(self, tmp_path): container: python:3.12-slim """ yaml_path = tmp_path / "test.yaml" - yaml_path.write_text(yaml_content, encoding="utf-8") + yaml_path.write_text(yaml_content) # Run launch.py as a subprocess (avoids pytest stdin capture issues) launcher_dir = os.path.join(os.path.dirname(__file__), "..") @@ -85,7 +85,7 @@ def test_failing_script_via_launch(self, tmp_path): script_dir = tmp_path / "scripts" script_dir.mkdir() script = script_dir / "fail.sh" - script.write_text("#!/bin/bash\necho 'FAILING'\nexit 1\n", encoding="utf-8") + script.write_text("#!/bin/bash\necho 'FAILING'\nexit 1\n") script.chmod(0o755) yaml_content = """ @@ -98,7 +98,7 @@ def test_failing_script_via_launch(self, tmp_path): container: python:3.12-slim """ yaml_path = tmp_path / "fail_test.yaml" - yaml_path.write_text(yaml_content, encoding="utf-8") + yaml_path.write_text(yaml_content) launcher_dir = os.path.join(os.path.dirname(__file__), "..") launcher_dir = os.path.abspath(launcher_dir) diff --git a/tools/launcher/tests/test_examples_resolve.py b/tools/launcher/tests/test_examples_resolve.py index e3b38564348..5d0bc7c2138 100644 --- a/tools/launcher/tests/test_examples_resolve.py +++ b/tools/launcher/tests/test_examples_resolve.py @@ -70,7 +70,7 @@ def test_examples_present(): ) def test_example_yaml_valid(path): """Each example parses and every task has a valid script/factory/args shape.""" - with open(path, encoding="utf-8") as f: + with open(path) as f: cfg = yaml.safe_load(f) assert isinstance(cfg, (dict, list)), f"{path}: top-level YAML is not a mapping/list" diff --git a/tools/launcher/tests/test_yaml_formats.py b/tools/launcher/tests/test_yaml_formats.py index d373430519a..9ba09550bb2 100644 --- a/tools/launcher/tests/test_yaml_formats.py +++ b/tools/launcher/tests/test_yaml_formats.py @@ -53,7 +53,7 @@ def test_yaml_format_with_job_name(self, tmp_yaml): - KEY: value """ path = tmp_yaml(content) - with open(path, encoding="utf-8") as f: + with open(path) as f: data = yaml.safe_load(f) assert data["job_name"] == "test_job" @@ -77,7 +77,7 @@ def test_bare_pipeline_format(self, tmp_yaml): skip: false """ path = tmp_yaml(content) - with open(path, encoding="utf-8") as f: + with open(path) as f: data = yaml.safe_load(f) # Verify the YAML parses into valid SandboxPipeline kwargs @@ -186,7 +186,7 @@ def test_target_with_overrides(self, tmp_yaml): allow_to_fail: false """ path = tmp_yaml(content) - with open(path, encoding="utf-8") as f: + with open(path) as f: data = yaml.safe_load(f) assert isinstance(data, list) diff --git a/tools/mcp/modelopt_mcp/bridge.py b/tools/mcp/modelopt_mcp/bridge.py index 8c84e843588..5ac251e83b1 100644 --- a/tools/mcp/modelopt_mcp/bridge.py +++ b/tools/mcp/modelopt_mcp/bridge.py @@ -186,7 +186,7 @@ def _tail_docker_launch_log(log_path: Path, proc: subprocess.Popen) -> tuple[str text = "" while True: try: - text = log_path.read_text(encoding="utf-8", errors="replace") + text = log_path.read_text(errors="replace") except OSError: text = "" complete_text = text if text.endswith(("\n", "\r")) else text.rsplit("\n", 1)[0] @@ -635,7 +635,7 @@ def list_examples_impl() -> dict: # the path-derived defaults when present. Don't crash on a # malformed YAML. try: - with open(path, encoding="utf-8") as f: + with open(path) as f: doc = yaml.safe_load(f) or {} if isinstance(doc, dict): body_model = doc.get("model") or doc.get("base_model") or doc.get("job_name") diff --git a/tools/mcp/tests/test_bridge.py b/tools/mcp/tests/test_bridge.py index 46fda64d19e..69d57822119 100644 --- a/tools/mcp/tests/test_bridge.py +++ b/tools/mcp/tests/test_bridge.py @@ -46,11 +46,11 @@ def test_list_examples_returns_structured_metadata(tmp_path, monkeypatch): examples = tmp_path / "examples" (examples / "Qwen").mkdir(parents=True) (examples / "Qwen" / "ptq.yaml").write_text( - "job_name: qwen-ptq\nmodel: Qwen/Qwen3-8B\ndescription: PTQ test\n", encoding="utf-8" + "job_name: qwen-ptq\nmodel: Qwen/Qwen3-8B\ndescription: PTQ test\n" ) (examples / "moonshotai").mkdir(parents=True) (examples / "moonshotai" / "train.yaml").write_text( - "job_name: kimi-train\nbase_model: moonshotai/Kimi-K2\n", encoding="utf-8" + "job_name: kimi-train\nbase_model: moonshotai/Kimi-K2\n" ) monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(examples)) @@ -75,8 +75,8 @@ def test_list_examples_tolerates_malformed_yaml(tmp_path, monkeypatch): """A single malformed YAML doesn't crash list_examples — it lands with model=None.""" examples = tmp_path / "examples" examples.mkdir() - (examples / "good.yaml").write_text("job_name: g\nmodel: ok\n", encoding="utf-8") - (examples / "bad.yaml").write_text("not: [unbalanced\n", encoding="utf-8") + (examples / "good.yaml").write_text("job_name: g\nmodel: ok\n") + (examples / "bad.yaml").write_text("not: [unbalanced\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(examples)) result = bridge.list_examples_impl() @@ -397,7 +397,7 @@ def test_submit_job_dry_run_uses_managed_source_checkout(monkeypatch, tmp_path): yaml_dir = checkout_root / "tools" / "launcher" / "examples" / "fam" / "model" yaml_dir.mkdir(parents=True) yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") checkout = bridge.SourceCheckout( repo="https://example.com/modelopt.git", ref="feature/ref", @@ -489,7 +489,7 @@ def test_submit_job_docker_captures_experiment_id_from_launcher_output(monkeypat yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setattr(bridge, "verify_docker_setup_impl", lambda: {"ok": True}) @@ -536,7 +536,7 @@ def test_submit_job_docker_no_experiment_id_returns_pid_and_log(monkeypatch, tmp yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setenv("MODELOPT_MCP_DOCKER_ID_TIMEOUT_SEC", "0") @@ -590,7 +590,7 @@ def test_submit_job_docker_log_creation_failure_is_structured(monkeypatch, tmp_p yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path / "nemo")) monkeypatch.setattr(bridge, "verify_docker_setup_impl", lambda: {"ok": True}) @@ -623,7 +623,7 @@ def test_submit_job_slurm_zero_exit_without_ids_is_failure(monkeypatch, tmp_path yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -661,7 +661,7 @@ def test_submit_job_slurm_parses_nemo_job_id(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) exp_dir = tmp_path / "experiments" / "cicd" / "cicd_1782173197" @@ -700,7 +700,7 @@ def fake_run(argv, **kwargs): assert result["ok"] is True assert result["slurm_job_id"] == "13049989" assert result["experiment_id"] == "cicd_1782173197" - meta = json.loads((exp_dir / bridge._SLURM_STATUS_META).read_text(encoding="utf-8")) + meta = json.loads((exp_dir / bridge._SLURM_STATUS_META).read_text()) assert meta["slurm_job_id"] == "13049989" assert meta["cluster_host"] == "cluster.example.com" assert meta["cluster_user"] == "user" @@ -710,7 +710,7 @@ def test_submit_job_slurm_accepts_nmm_cluster_fields(monkeypatch, tmp_path): """nmm-sandbox resolved cluster config maps to launcher overrides and env.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n", encoding="utf-8") + (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) verify_seen = {} @@ -769,7 +769,7 @@ def test_submit_job_slurm_job_id_without_experiment_id_is_failure(monkeypatch, t yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -808,7 +808,7 @@ def test_submit_job_slurm_zero_exit_with_launcher_error_is_failure(monkeypatch, yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) monkeypatch.setattr( bridge, @@ -852,7 +852,7 @@ def test_submit_job_dry_run_yaml_validates(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" / "fam" / "model" yaml_dir.mkdir(parents=True) yaml_path = yaml_dir / "config.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(tmp_path / "examples")) captured = {} @@ -892,7 +892,7 @@ def test_submit_job_dry_run_uses_slurm_inventory_fields(monkeypatch, tmp_path): """dry-run must mirror live submit Slurm overrides and env.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n", encoding="utf-8") + (yaml_dir / "config.yaml").write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) captured = {} @@ -940,7 +940,7 @@ def test_submit_job_dry_run_yaml_invalid(monkeypatch, tmp_path): yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "bad.yaml" - yaml_path.write_text("not: [unbalanced\n", encoding="utf-8") + yaml_path.write_text("not: [unbalanced\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) def fake_run(argv, **kwargs): @@ -977,7 +977,7 @@ def test_submit_job_dry_run_zero_exit_with_launcher_error_is_invalid(monkeypatch yaml_dir = tmp_path / "examples" yaml_dir.mkdir() yaml_path = yaml_dir / "bad.yaml" - yaml_path.write_text("job_name: t\npipeline: []\n", encoding="utf-8") + yaml_path.write_text("job_name: t\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) def fake_run(argv, **kwargs): @@ -1033,7 +1033,7 @@ def test_submit_job_dry_run_skips_verify(monkeypatch, tmp_path): """dry_run=True bypasses verify_setup even when skip_verify=False.""" yaml_dir = tmp_path / "examples" yaml_dir.mkdir() - (yaml_dir / "ok.yaml").write_text("job_name: ok\npipeline: []\n", encoding="utf-8") + (yaml_dir / "ok.yaml").write_text("job_name: ok\npipeline: []\n") monkeypatch.setenv("MODELOPT_LAUNCHER_EXAMPLES_DIR", str(yaml_dir)) verify_called = {"n": 0} @@ -1082,8 +1082,8 @@ def test_job_status_done_success(tmp_path, monkeypatch): exp = tmp_path / "experiments" / "exp_1781000000" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") - (exp / "status_task_1.out").write_text("succeeded\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("succeeded\n") + (exp / "status_task_1.out").write_text("succeeded\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000000") @@ -1097,8 +1097,8 @@ def test_job_status_failed_task(tmp_path, monkeypatch): exp = tmp_path / "experiments" / "exp_1781000001" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") - (exp / "status_task_1.out").write_text("failed (rc=1)\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("succeeded\n") + (exp / "status_task_1.out").write_text("failed (rc=1)\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000001") @@ -1111,7 +1111,7 @@ def test_job_status_running(tmp_path, monkeypatch): """No _DONE marker → running.""" exp = tmp_path / "experiments" / "exp_1781000002" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("running\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000002") @@ -1124,7 +1124,7 @@ def test_job_status_nested_nemo_title_dir(tmp_path, monkeypatch): """nemo_run stores experiments under experiments/<title>/<experiment_id>.""" exp = tmp_path / "experiments" / "cicd" / "exp_1781000006" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("running\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_status_impl("exp_1781000006") @@ -1147,8 +1147,7 @@ def test_job_status_slurm_sidecar_overrides_local_done_marker(tmp_path, monkeypa "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ), - encoding="utf-8", + ) ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1185,8 +1184,7 @@ def test_job_status_slurm_sidecar_reports_terminal_state(tmp_path, monkeypatch): "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ), - encoding="utf-8", + ) ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1224,8 +1222,7 @@ def test_job_status_slurm_not_found_falls_back_to_local_done_marker(tmp_path, mo "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ), - encoding="utf-8", + ) ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) monkeypatch.setattr( @@ -1250,7 +1247,7 @@ def test_job_status_slurm_not_found_falls_back_to_local_failed_marker(tmp_path, exp = tmp_path / "experiments" / "cicd" / "exp_slurm_aged_out_failed" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("failed (rc=1)\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("failed (rc=1)\n") (exp / bridge._SLURM_STATUS_META).write_text( json.dumps( { @@ -1260,8 +1257,7 @@ def test_job_status_slurm_not_found_falls_back_to_local_failed_marker(tmp_path, "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ), - encoding="utf-8", + ) ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) monkeypatch.setattr( @@ -1285,7 +1281,7 @@ def test_job_status_launcher_experiments_fallback(tmp_path, monkeypatch): launcher_dir = tmp_path / "launcher" exp = launcher_dir / "experiments" / "cicd" / "exp_1781000007" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("running\n") monkeypatch.delenv("NEMORUN_HOME", raising=False) other_cwd = tmp_path / "other" other_cwd.mkdir() @@ -1331,8 +1327,8 @@ def test_job_logs_all_tasks(tmp_path, monkeypatch): """task=None returns logs for every log_*.out under the experiment dir.""" exp = tmp_path / "experiments" / "exp_1781000003" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("hello\nworld\n", encoding="utf-8") - (exp / "log_task_1.out").write_text("done\n", encoding="utf-8") + (exp / "log_task_0.out").write_text("hello\nworld\n") + (exp / "log_task_1.out").write_text("done\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000003", task=None, tail=None) @@ -1345,7 +1341,7 @@ def test_job_logs_with_tail(tmp_path, monkeypatch): """tail=N returns only the last N lines per task.""" exp = tmp_path / "experiments" / "exp_1781000004" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("line1\nline2\nline3\nline4\n", encoding="utf-8") + (exp / "log_task_0.out").write_text("line1\nline2\nline3\nline4\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000004", task="task_0", tail=2) @@ -1358,7 +1354,7 @@ def test_job_logs_missing_task(tmp_path, monkeypatch): """Requested task name has no log file → task_log_not_found.""" exp = tmp_path / "experiments" / "exp_1781000005" exp.mkdir(parents=True) - (exp / "log_task_0.out").write_text("only task 0\n", encoding="utf-8") + (exp / "log_task_0.out").write_text("only task 0\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.job_logs_impl("exp_1781000005", task="task_99", tail=None) @@ -1383,7 +1379,7 @@ def test_wait_for_experiment_returns_terminal_immediately(tmp_path, monkeypatch) exp = tmp_path / "experiments" / "exp_already_done" exp.mkdir(parents=True) (exp / "_DONE").touch() - (exp / "status_task_0.out").write_text("succeeded\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("succeeded\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.wait_for_experiment_impl( @@ -1400,7 +1396,7 @@ def test_wait_for_experiment_polls_until_done(tmp_path, monkeypatch): """Spin through running → done.""" exp = tmp_path / "experiments" / "exp_in_flight" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("running\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) # Flip the marker after 2 polls via a counter side-effect @@ -1438,8 +1434,7 @@ def test_wait_for_experiment_polls_slurm_despite_local_done_marker(tmp_path, mon "cluster_host": "cluster.example.com", "cluster_user": "alice", } - ), - encoding="utf-8", + ) ) monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) @@ -1472,7 +1467,7 @@ def test_wait_for_experiment_timeout(tmp_path, monkeypatch): """Never reaches terminal → wait_timeout with last_status.""" exp = tmp_path / "experiments" / "exp_stuck" exp.mkdir(parents=True) - (exp / "status_task_0.out").write_text("running\n", encoding="utf-8") + (exp / "status_task_0.out").write_text("running\n") monkeypatch.setenv("NEMORUN_HOME", str(tmp_path)) result = bridge.wait_for_experiment_impl( @@ -1528,9 +1523,9 @@ def test_provision_ssh_key_present_emits_copy_id(tmp_path, monkeypatch): fake_home = tmp_path / "home" ssh = fake_home / ".ssh" ssh.mkdir(parents=True) - (ssh / "id_ed25519").write_text("PRIVKEY", encoding="utf-8") + (ssh / "id_ed25519").write_text("PRIVKEY") pubkey_content = "ssh-ed25519 AAAAC3NzaC... alice@host" - (ssh / "id_ed25519.pub").write_text(pubkey_content + "\n", encoding="utf-8") + (ssh / "id_ed25519.pub").write_text(pubkey_content + "\n") monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("IDENTITY", raising=False) @@ -1552,7 +1547,7 @@ def test_provision_ssh_priv_without_pub_surfaces_failure(tmp_path, monkeypatch): fake_home = tmp_path / "home" ssh = fake_home / ".ssh" ssh.mkdir(parents=True) - (ssh / "id_ed25519").write_text("PRIVKEY", encoding="utf-8") + (ssh / "id_ed25519").write_text("PRIVKEY") monkeypatch.setenv("HOME", str(fake_home)) monkeypatch.delenv("IDENTITY", raising=False) @@ -1569,8 +1564,8 @@ def test_provision_ssh_priv_without_pub_surfaces_failure(tmp_path, monkeypatch): def test_provision_ssh_explicit_identity_overrides_default(tmp_path, monkeypatch): """Explicit identity arg wins over $IDENTITY and ~/.ssh/id_ed25519.""" explicit = tmp_path / "custom_key" - explicit.write_text("CUSTOM", encoding="utf-8") - (tmp_path / "custom_key.pub").write_text("ssh-ed25519 AAAA alice\n", encoding="utf-8") + explicit.write_text("CUSTOM") + (tmp_path / "custom_key.pub").write_text("ssh-ed25519 AAAA alice\n") monkeypatch.setenv("IDENTITY", "/wrong/path") # should be ignored result = bridge.provision_passwordless_ssh_dry_run_impl( diff --git a/tools/resource_monitor.py b/tools/resource_monitor.py index afda9efd511..9fb255a7f79 100644 --- a/tools/resource_monitor.py +++ b/tools/resource_monitor.py @@ -356,7 +356,7 @@ def _write_summary(path, duration, metrics: _Metrics): print(text, flush=True) if path: Path(path).parent.mkdir(parents=True, exist_ok=True) - Path(path).write_text(text + "\n", encoding="utf-8") + Path(path).write_text(text + "\n") def main() -> None: @@ -402,7 +402,7 @@ def _request_stop(signum, frame): "proc_cpu_util_pct", ] start = time.monotonic() - with open(args.out, "w", encoding="utf-8", newline="") as f: + with open(args.out, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() while not stop: From aca4a7f83bbec58a2b4e4bcdaf4ed294f5547836 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 13:32:39 -0700 Subject: [PATCH 08/20] Read and write YAML config as UTF-8 YAML configs are the files most likely to carry non-ASCII -- comments, model names, paths -- and text I/O without an explicit encoding uses the locale codepage, which is cp1252 on Windows. modelopt/recipe/loader.py is the one that matters most: it reads recipe YAML inside a USER process, which will not have the PYTHONUTF8 the windows CI job now sets, so a UTF-8 recipe raises UnicodeDecodeError on the first non-Latin-1 byte with ModelOpt nowhere near a test run. Ten call sites: the recipe loader, the two ONNX autotune state files, the two transformers config readers, the distill config and the puzzletron profile. Deliberately not the ~600 elsewhere -- those are tests, examples, plugins and tooling, which only ever run under our CI and are covered by UTF-8 mode there. modelopt/torch/fastgen/loader.py already did this, so the convention predates the change. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- modelopt/onnx/quantization/autotune/autotuner_base.py | 4 ++-- modelopt/onnx/quantization/autotune/common.py | 4 ++-- modelopt/recipe/loader.py | 4 ++-- modelopt/torch/distill/plugins/megatron.py | 2 +- modelopt/torch/opt/plugins/transformers.py | 4 ++-- modelopt/torch/puzzletron/mip/run_puzzle.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modelopt/onnx/quantization/autotune/autotuner_base.py b/modelopt/onnx/quantization/autotune/autotuner_base.py index 22ddf0ea098..fd3d41d0e76 100644 --- a/modelopt/onnx/quantization/autotune/autotuner_base.py +++ b/modelopt/onnx/quantization/autotune/autotuner_base.py @@ -737,7 +737,7 @@ def save_state(self, output_path: str) -> None: "patterns": [pattern_schemes.to_dict() for pattern_schemes in self.profiled_patterns], } - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) num_patterns = len(self.profiled_patterns) @@ -775,7 +775,7 @@ def load_state(self, input_path: str) -> None: AutotunerNotInitializedError: If initialize() hasn't been called FileNotFoundError: If the input_path doesn't exist """ - with open(input_path) as f: + with open(input_path, encoding="utf-8") as f: state = yaml.safe_load(f) if state.get("baseline_latency_ms") is not None: diff --git a/modelopt/onnx/quantization/autotune/common.py b/modelopt/onnx/quantization/autotune/common.py index 31983423cd9..a0aa6744cca 100644 --- a/modelopt/onnx/quantization/autotune/common.py +++ b/modelopt/onnx/quantization/autotune/common.py @@ -739,7 +739,7 @@ def save(self, output_path: str) -> None: """ state = self.to_dict() - with open(output_path, "w") as f: + with open(output_path, "w", encoding="utf-8") as f: yaml.dump(state, f, default_flow_style=False, sort_keys=False) logger.info( @@ -768,7 +768,7 @@ def load(cls, input_path: str) -> "PatternCache": Raises: FileNotFoundError: If the input_path doesn't exist """ - with open(input_path) as f: + with open(input_path, encoding="utf-8") as f: state = yaml.safe_load(f) cache = cls.from_dict(state) diff --git a/modelopt/recipe/loader.py b/modelopt/recipe/loader.py index 91e2bac75de..7f8591b0123 100644 --- a/modelopt/recipe/loader.py +++ b/modelopt/recipe/loader.py @@ -171,7 +171,7 @@ def _peek_recipe_type(recipe_file: Path | Traversable) -> RecipeType | None: import yaml try: - raw = yaml.safe_load(recipe_file.read_text()) + raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) return RecipeType(raw["metadata"]["recipe_type"]) except (TypeError, KeyError, ValueError): return None @@ -201,7 +201,7 @@ def _load_recipe_from_file( if required_section is not None: import yaml - raw = yaml.safe_load(recipe_file.read_text()) or {} + raw = yaml.safe_load(recipe_file.read_text(encoding="utf-8")) or {} if not isinstance(raw, dict) or required_section not in raw: # Strip only the ``speculative_`` prefix so multi-word non-speculative types # (e.g. ``auto_quantize``) keep their full name: AUTO_QUANTIZE, not QUANTIZE. diff --git a/modelopt/torch/distill/plugins/megatron.py b/modelopt/torch/distill/plugins/megatron.py index c93f0961d1f..581f670759c 100644 --- a/modelopt/torch/distill/plugins/megatron.py +++ b/modelopt/torch/distill/plugins/megatron.py @@ -120,7 +120,7 @@ def setup_distillation_config( elif isinstance(config_or_path, DistillationConfig): cfg = config_or_path else: - with open(config_or_path) as f: + with open(config_or_path, encoding="utf-8") as f: cfg = yaml.safe_load(f) cfg = DistillationConfig(**cfg) diff --git a/modelopt/torch/opt/plugins/transformers.py b/modelopt/torch/opt/plugins/transformers.py index a291b5abf36..6665fdcaedc 100644 --- a/modelopt/torch/opt/plugins/transformers.py +++ b/modelopt/torch/opt/plugins/transformers.py @@ -344,7 +344,7 @@ def parse_args_into_dataclasses(self, args=None, **kwargs): args = args[:idx] + args[idx + 2 :] # strip --config <path> from argv import yaml - with open(config_path) as f: + with open(config_path, encoding="utf-8") as f: config = yaml.safe_load(f) if config: known_by_parser = {a.dest for a in self._actions} @@ -676,7 +676,7 @@ def load_lr_config(path: str) -> dict[str, dict[str, Any]]: """ import yaml - with open(path) as f: + with open(path, encoding="utf-8") as f: cfg = yaml.safe_load(f) if not isinstance(cfg, dict): raise ValueError(f"lr_config must be a YAML mapping, got {type(cfg).__name__}") diff --git a/modelopt/torch/puzzletron/mip/run_puzzle.py b/modelopt/torch/puzzletron/mip/run_puzzle.py index 22c8b471546..74643e2f592 100644 --- a/modelopt/torch/puzzletron/mip/run_puzzle.py +++ b/modelopt/torch/puzzletron/mip/run_puzzle.py @@ -439,7 +439,7 @@ def _get_minimal_unique_names(dicts: list[dict]) -> list[str]: def run_puzzle(args: DictConfig) -> list[str]: # Loads config from args/puzzle_profile if args.puzzle_profile is not None: - with open(args.puzzle_profile) as f: + with open(args.puzzle_profile, encoding="utf-8") as f: puzzle_profile = yaml.safe_load(f) _override_args_from_profile(args, puzzle_profile) mprint(f"Loaded Puzzle profile from {args.puzzle_profile}") From 706fbf48cf52f009e8f44ec0b5f61d1ce5ba5aff Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 13:41:47 -0700 Subject: [PATCH 09/20] Record what the windows runner is, and keep the crash dump The intermittent 0xc000001d (STATUS_ILLEGAL_INSTRUCTION) means a native module executed an opcode the host CPU lacks. The GitHub windows fleet is heterogeneous, so the same wheel passes on one machine and dies on another, which is why it comes and goes. The existing output cannot identify the module. Every frame it prints belongs to a parked background thread -- threading.wait -- while the main thread's native frame is lost as the process dies, and two threads writing at once leave the dump interleaved and truncated. Added, none of it changing what is tested: - the CPU model, and torch.backends.cpu.get_cpu_capability() after the run. Torch selects a vectorized kernel set at runtime; if what it chose exceeds what the recorded CPU supports, the fix is pinning ATEN_CPU_CAPABILITY, not anything in this repo. - WER local dumps, uploaded as an artifact on failure. A minidump names the faulting DLL and offset outright, which is the only way to identify the binary rather than narrow by elimination. - PYTHONUNBUFFERED and PYTHONFAULTHANDLER, so a crash does not interleave two threads' output and lose the main thread's frames. Every step is continue-on-error, so a runner that refuses the registry write or has no dump to collect cannot turn a passing run red. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index c561048f73a..29161d2cbec 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -91,6 +91,30 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.12" + # Diagnostics for the intermittent 0xc000001d (STATUS_ILLEGAL_INSTRUCTION) crash: a native + # module executes an opcode this host lacks. The runner fleet is heterogeneous, so the same + # wheel passes on one machine and dies on another. Nothing here changes what is tested; it + # records which CPU ran, and leaves a dump behind naming the faulting DLL if it happens. + - name: Record CPU and torch dispatch capability + continue-on-error: true + shell: pwsh + run: | + Get-CimInstance Win32_Processor | Select-Object Name, Description, NumberOfCores | Format-List + python -c "import platform; print('machine:', platform.machine(), platform.processor())" + - name: Enable crash dumps + continue-on-error: true + shell: pwsh + run: | + # WER local dumps. The Python-level faulthandler output does not identify the module -- + # it prints parked background threads while the main thread's native frame is lost as + # the process dies. A minidump names the faulting DLL and offset outright. + $d = "$env:GITHUB_WORKSPACE\crashdumps" + New-Item -ItemType Directory -Force -Path $d | Out-Null + $k = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" + New-Item -Path $k -Force | Out-Null + Set-ItemProperty -Path $k -Name DumpFolder -Value $d -Type ExpandString + Set-ItemProperty -Path $k -Name DumpType -Value 2 -Type DWord + Set-ItemProperty -Path $k -Name DumpCount -Value 5 -Type DWord - name: Run unit tests (without coverage) # PEP 540 UTF-8 mode. Windows defaults text I/O to the locale codepage (cp1252 on these # runners), so any read of a UTF-8 file without an explicit encoding= dies with @@ -99,7 +123,26 @@ jobs: # library code from passing encoding= explicitly: user processes will not have this set. env: PYTHONUTF8: "1" + # Unbuffered so a crash does not interleave two threads' faulthandler output and lose + # the main thread's frames, which is what made the last dump unreadable. + PYTHONUNBUFFERED: "1" + PYTHONFAULTHANDLER: "1" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" + - name: Report torch CPU capability + # Runs even on failure: torch picks a vectorized kernel set at runtime, so what it chose + # is the first thing to compare against the CPU recorded above. If they disagree, the + # fix is pinning ATEN_CPU_CAPABILITY rather than anything in this repo. + if: always() + continue-on-error: true + run: python -c "import torch; print('torch', torch.__version__, 'cpu_capability', torch.backends.cpu.get_cpu_capability())" + - name: Upload crash dumps + if: failure() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: windows-crashdumps + path: crashdumps/ + if-no-files-found: ignore multi-version: if: needs.check-file-changes.outputs.any_changed == 'true' needs: [linux, check-file-changes] From 4e24de85085794575c88bab002b02ba24e690c6f Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Thu, 17 Sep 2026 16:46:33 -0700 Subject: [PATCH 10/20] Stop windows gating merges; scope the prebuild; diagnose 0xc000001d Three pieces of review feedback. @kevalmorabia97: windows is flaky beyond these tests, so exclude it from the required check -- let it fail without blocking. Done: dropped from the unit-pr-required-check condition, kept in needs so it still runs and stays visible. The reasoning from that thread is worth keeping: what matters on that platform is the onnx surface, so blocking every PR on unrelated torch flakiness costs more than it catches. CodeRabbit: the torch diagnostic ran in the runner interpreter, which has only nox and uv, so it could not report the torch the tests use. My first repair -- re-invoking nox to print a version -- was worse than the problem; the report now comes from a session fixture inside the test process, windows-only. CodeRabbit: the prebuild fixture sat in tests/unit/conftest.py, so every focused run touching tests/unit paid the cppimport cache check, and a cold cache meant a multi-minute MSVC compile before unrelated tests. Moved to tests/unit/onnx/conftest.py. This was an open question I had already flagged without an answer; reaching it independently is good evidence it was the right concern. Also two steps to find the 0xc000001d root cause, from opposite directions: - ATEN_CPU_CAPABILITY=default vs unrestricted on the crashing test. torch picks a CPU kernel set at runtime; if the test passes pinned and dies unpinned, the fault is in torch's vectorized paths and no dump is needed. If it dies either way, torch is excluded. - procdump, which attaches as a debugger and therefore sees the exception regardless of WER policy or pytest's faulthandler plugin. That combination is why the earlier WER LocalDumps route produced no artifact despite the registry write succeeding. The CPU is already recorded: Intel Xeon Platinum 8573C, Emerald Rapids, which does support AVX-512 -- so the obvious "runner lacks AVX-512" explanation is already ruled out. Every diagnostic step is continue-on-error, and windows no longer gates merges, so these can experiment without risk to anyone's PR. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 49 ++++++++++++++++++++++++++++---- tests/unit/conftest.py | 40 +++++++++++++------------- tests/unit/onnx/conftest.py | 43 ++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 tests/unit/onnx/conftest.py diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 29161d2cbec..a995d543b20 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -128,13 +128,45 @@ jobs: PYTHONUNBUFFERED: "1" PYTHONFAULTHANDLER: "1" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" - - name: Report torch CPU capability - # Runs even on failure: torch picks a vectorized kernel set at runtime, so what it chose - # is the first thing to compare against the CPU recorded above. If they disagree, the - # fix is pinning ATEN_CPU_CAPABILITY rather than anything in this repo. + # --- 0xc000001d diagnosis ------------------------------------------------------------- + # The fault is an illegal instruction in a native module, and the python-level output names + # only parked threads. These two steps answer "which binary" from opposite directions, and + # both are continue-on-error: windows no longer gates the merge, so they can experiment. + - name: Probe whether torch's vectorized kernels are responsible + # torch selects a CPU kernel set at runtime; ATEN_CPU_CAPABILITY forces a lower one. If + # the crashing test passes with "default" and dies without it, the fault is in torch's + # AVX-512/AVX2 paths and the fix is pinning this variable -- no dump needed. If it dies + # either way, torch is excluded and the module is elsewhere (onnx, onnxruntime). if: always() continue-on-error: true - run: python -c "import torch; print('torch', torch.__version__, 'cpu_capability', torch.backends.cpu.get_cpu_capability())" + shell: pwsh + env: + PYTHONUTF8: "1" + run: | + $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion" + Write-Host "--- with ATEN_CPU_CAPABILITY=default ---" + $env:ATEN_CPU_CAPABILITY = "default" + nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler + Write-Host "exit(default)=$LASTEXITCODE" + Write-Host "--- unrestricted ---" + Remove-Item Env:\ATEN_CPU_CAPABILITY + nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler + Write-Host "exit(unrestricted)=$LASTEXITCODE" + - name: Capture a dump that names the faulting module + # procdump attaches as a debugger, so it sees the exception regardless of WER policy or + # pytest's faulthandler plugin -- which is why the WER LocalDumps route produced nothing. + # -e dumps on unhandled exception, -ma writes a full dump. + if: always() + continue-on-error: true + shell: pwsh + env: + PYTHONUTF8: "1" + run: | + choco install procdump -y --no-progress | Out-Null + $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion" + $py = (Get-Command python).Source + procdump -accepteula -e -ma -x "$env:GITHUB_WORKSPACE\crashdumps" $py -m nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler + Get-ChildItem "$env:GITHUB_WORKSPACE\crashdumps" -ErrorAction SilentlyContinue | Format-Table Name, Length - name: Upload crash dumps if: failure() continue-on-error: true @@ -251,9 +283,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Required unit tests did not succeed + # windows is deliberately absent from this condition. It still runs and is still visible, + # but it does not gate the merge: the job is flaky for reasons unrelated to the change + # under review -- an MSVC extension build racing the per-test timeout, and an + # 0xc000001d illegal instruction that depends on which CPU the runner draws. What we + # actually care about on that platform is the ONNX surface, so blocking every PR on + # unrelated torch flakiness costs more than it catches. if: >- ${{ needs.linux.result != 'success' || (needs.check-file-changes.outputs.any_changed == 'true' && ( - needs.windows.result != 'success' || needs.multi-version.result != 'success' || needs.partial-install.result != 'success' || needs.launcher.result != 'success' || diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index aa2a675b665..30a8e6063fc 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -15,6 +15,7 @@ import contextlib import os +import sys import pytest @@ -30,25 +31,24 @@ @pytest.fixture(scope="session", autouse=True) -def _prebuild_onnx_round_and_pack_ext(): - """Build the ONNX round-and-pack extension before per-test timeouts start. - - ``modelopt/onnx/quantization/extensions.py`` runs ``cppimport.imp`` at module import, and - that module is imported lazily from inside ``quant_utils.round_and_pack``. So the first test - to need it pays a full C++ compile INSIDE its own per-test timeout -- on the Windows runner - that is an MSVC build measured in minutes, and the test dies with pytest-timeout while - ``compiler.compile`` is still running. Which test pays is down to collection order, so the - failure appears to wander between runs. - - ``pyproject`` sets ``timeout_func_only``, so the per-test clock covers the call only; doing - the import here in session setup puts the build outside it. This mirrors - ``tests/gpu_megatron/conftest.py``, which prebuilds the quant CUDA extensions for the same - reason -- but it cannot reuse that helper: ``load_cpp_extension`` skips every quant extension - when CUDA is unavailable, which is exactly the case on the CPU-only Windows runner, so - ``precompile()`` would warm nothing here. - - Best-effort. The extension is an optimisation with a Python fallback -- ``extensions.py`` - already swallows its own build failures -- so a failure to prebuild must not fail the session. +def _report_cpu_dispatch(): + """On Windows, record what torch decided the CPU can do. + + The job intermittently dies with 0xc000001d (STATUS_ILLEGAL_INSTRUCTION): a native module + executing an opcode the host lacks. torch selects a vectorized kernel set at runtime, so what + it chose -- compared against the CPU the workflow records before the run -- is the first thing + to check. Reported from inside the test process because that is where the torch under test + lives; the runner interpreter has only nox and uv. + + Windows-only and best-effort: elsewhere it is noise, and a diagnostic must never fail a run. """ + if sys.platform != "win32": + return with contextlib.suppress(Exception): - import modelopt.onnx.quantization.extensions # noqa: F401 + import torch + + print( + f"\n[diag] torch {torch.__version__} " + f"cpu_capability={torch.backends.cpu.get_cpu_capability()}", + flush=True, + ) diff --git a/tests/unit/onnx/conftest.py b/tests/unit/onnx/conftest.py new file mode 100644 index 00000000000..5da1ad2d663 --- /dev/null +++ b/tests/unit/onnx/conftest.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 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. + +import contextlib + +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def _prebuild_onnx_round_and_pack_ext(): + """Build the ONNX round-and-pack extension before per-test timeouts start. + + ``modelopt/onnx/quantization/extensions.py`` runs ``cppimport.imp`` at module import, and + that module is imported lazily from inside ``quant_utils.round_and_pack``. So the first test + to need it pays a full C++ compile INSIDE its own per-test timeout -- on the Windows runner + that is an MSVC build measured in minutes, and the test dies with pytest-timeout while + ``compiler.compile`` is still running. Which test pays is down to collection order, so the + failure appears to wander between runs. + + ``pyproject`` sets ``timeout_func_only``, so the per-test clock covers the call only; doing + the import here in session setup puts the build outside it. This mirrors + ``tests/gpu_megatron/conftest.py``, which prebuilds the quant CUDA extensions for the same + reason -- but it cannot reuse that helper: ``load_cpp_extension`` skips every quant extension + when CUDA is unavailable, which is exactly the case on the CPU-only Windows runner, so + ``precompile()`` would warm nothing here. + + Best-effort. The extension is an optimisation with a Python fallback -- ``extensions.py`` + already swallows its own build failures -- so a failure to prebuild must not fail the session. + """ + with contextlib.suppress(Exception): + import modelopt.onnx.quantization.extensions # noqa: F401 From 5d065f938e6323576da45eb7b77d37ee8bc1eba2 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 00:51:36 +0000 Subject: [PATCH 11/20] Narrow 0xc000001d to a bf16 GEMM; probe the oneDNN ISA ceiling The last run's diagnostics ruled out my original hypothesis and reframed the bug, so both probes are replaced with one that tests what the evidence now points at. What the run established: - The crash is deterministic, not intermittent. It is always test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format], and the three sibling parametrizations pass in the 13 ms before it. - ATEN_CPU_CAPABILITY=default did not prevent it, so torch's own vectorized kernels are excluded. That probe was also broken: the `unit` nox session hardcodes `tests/unit` and drops posargs, so both arms ran the entire suite rather than the single test they named. - procdump wrote no dump ("Dump count not reached") -- it attached to the nox parent, while the crash was in the pytest child. - The runner is an Emerald Rapids Xeon 8573C, which does support AVX-512. That kills the simple "binary needs an opcode this CPU lacks" story. What is left is the one thing that distinguishes the crashing parametrization: it is the only case whose model is a bf16 128x128 Linear. The others quantize a 4x4 Linear. A bf16 GEMM at that size is where torch's CPU path hands off to oneDNN, which JIT-generates a kernel from runtime CPU detection rather than from compile-time flags -- which is exactly why ATEN_CPU_CAPABILITY had no effect on it. Emerald Rapids advertises AMX-BF16, and AMX raises #UD unless the hypervisor enabled its XSAVE tile state. #UD is STATUS_ILLEGAL_INSTRUCTION, and it would fire in several oneDNN worker threads at once -- which is why the faulthandler output was two threads' writes interleaved into one another instead of a readable main-thread traceback. The new step sweeps DNNL_MAX_CPU_ISA over descending ceilings against that one test, invoking pytest from the nox venv directly so it actually runs the test it names. The highest ceiling that passes identifies the opcode family and is the fix. If every ceiling crashes, oneDNN is excluded too and the remaining suspect is the ONNX export path. This is a hypothesis with a clean experiment attached, not a diagnosis. Windows no longer gates the merge, so the probe is free to be wrong. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 78 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index a995d543b20..c61454c7200 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -95,7 +95,10 @@ jobs: # module executes an opcode this host lacks. The runner fleet is heterogeneous, so the same # wheel passes on one machine and dies on another. Nothing here changes what is tested; it # records which CPU ran, and leaves a dump behind naming the faulting DLL if it happens. - - name: Record CPU and torch dispatch capability + - name: Record which CPU this runner got + # CPU model only -- torch is not installed in this interpreter yet, it lives in the nox + # venv created by the test step below. An earlier version of this step claimed to report + # torch's dispatch capability and could not; the ISA probe after the tests does that. continue-on-error: true shell: pwsh run: | @@ -129,44 +132,53 @@ jobs: PYTHONFAULTHANDLER: "1" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" # --- 0xc000001d diagnosis ------------------------------------------------------------- - # The fault is an illegal instruction in a native module, and the python-level output names - # only parked threads. These two steps answer "which binary" from opposite directions, and - # both are continue-on-error: windows no longer gates the merge, so they can experiment. - - name: Probe whether torch's vectorized kernels are responsible - # torch selects a CPU kernel set at runtime; ATEN_CPU_CAPABILITY forces a lower one. If - # the crashing test passes with "default" and dies without it, the fault is in torch's - # AVX-512/AVX2 paths and the fix is pinning this variable -- no dump needed. If it dies - # either way, torch is excluded and the module is elsewhere (onnx, onnxruntime). + # What the last run established: the fault is deterministic, not intermittent. It is always + # test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format], and the three sibling + # parametrizations pass in the milliseconds before it. That case is the only one whose model + # is a bf16 128x128 Linear; the others quantize a 4x4 Linear. A bf16 GEMM of that size is + # where torch's CPU path hands off to oneDNN, which JIT-generates a kernel from runtime CPU + # detection. This runner is an Emerald Rapids Xeon (Family 6 Model 207), which advertises + # AMX-BF16 -- and AMX faults with #UD unless the hypervisor enabled its XSAVE tile state. + # #UD is STATUS_ILLEGAL_INSTRUCTION, raised in several oneDNN worker threads at once, which + # is why the faulthandler output was two threads interleaved over each other. + # + # ATEN_CPU_CAPABILITY did not clear it and could not: it governs ATen's own vectorized + # kernels, not oneDNN's JIT. DNNL_MAX_CPU_ISA is the knob that does. This sweep runs the one + # crashing test at descending ISA ceilings -- the highest value that passes names the opcode + # family responsible, and becomes the fix. + # + # It calls pytest from the nox venv directly, because the `unit` session hardcodes + # `tests/unit` and drops posargs: that is why the previous probe silently ran the whole + # suite instead of the single test it named. exit=3221225501 is the crash, 0 pass, 1 fail. + - name: Which CPU ISA does the crash need if: always() continue-on-error: true shell: pwsh env: PYTHONUTF8: "1" run: | - $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion" - Write-Host "--- with ATEN_CPU_CAPABILITY=default ---" - $env:ATEN_CPU_CAPABILITY = "default" - nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler - Write-Host "exit(default)=$LASTEXITCODE" - Write-Host "--- unrestricted ---" - Remove-Item Env:\ATEN_CPU_CAPABILITY - nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler - Write-Host "exit(unrestricted)=$LASTEXITCODE" - - name: Capture a dump that names the faulting module - # procdump attaches as a debugger, so it sees the exception regardless of WER policy or - # pytest's faulthandler plugin -- which is why the WER LocalDumps route produced nothing. - # -e dumps on unhandled exception, -ma writes a full dump. - if: always() - continue-on-error: true - shell: pwsh - env: - PYTHONUTF8: "1" - run: | - choco install procdump -y --no-progress | Out-Null - $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion" - $py = (Get-Command python).Source - procdump -accepteula -e -ma -x "$env:GITHUB_WORKSPACE\crashdumps" $py -m nox -s "unit-3.12(torch_214, tf_latest)" -- $t -o addopts= -q -p no:faulthandler - Get-ChildItem "$env:GITHUB_WORKSPACE\crashdumps" -ErrorAction SilentlyContinue | Format-Table Name, Length + $venv = Get-ChildItem -Path "C:/tmp/.nox","/tmp/.nox","$env:GITHUB_WORKSPACE/.nox" -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "unit-3-12*" } | Select-Object -First 1 + if (-not $venv) { Write-Host "no nox venv found -- nothing to probe"; exit 0 } + $py = Join-Path $venv.FullName "Scripts\python.exe" + Write-Host "venv python: $py" + & $py -c "import torch; print('torch', torch.__version__, 'mkldnn', torch.backends.mkldnn.is_available()); print([l for l in torch.__config__.show().splitlines() if 'CPU capability' in l or 'oneDNN' in l])" + $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format]" + foreach ($isa in @("__unset__","AVX512_CORE_AMX","AVX512_CORE_BF16","AVX512_CORE","AVX2")) { + if ($isa -eq "__unset__") { + Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue + Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue + } else { + $env:DNNL_MAX_CPU_ISA = $isa + $env:ONEDNN_MAX_CPU_ISA = $isa + } + & $py -m pytest $t -o addopts= -q -p no:faulthandler > isa_out.txt 2>&1 + $code = $LASTEXITCODE + Get-Content isa_out.txt -Tail 3 -ErrorAction SilentlyContinue + Write-Host "ISA=$isa exit=$code" + } + Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue + Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue - name: Upload crash dumps if: failure() continue-on-error: true From 96e67a9bf8dcb768f36d9e53997073b69161f5d6 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 01:15:18 +0000 Subject: [PATCH 12/20] 0xc000001d tracks the runner CPU, not the test: retarget the diagnosis Sampling every windows failure back to July corrects most of what the previous commit assumed. The crash is not deterministic and not tied to one test. It has landed on test_peft_save_restore (four times, Jul-Aug), test_unet_save_restore, and now test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format], across five unrelated branches. It is neither new nor introduced by any PR, and the bf16-128x128 story in the last commit was an artifact of looking at one job: within a single job it reproduces every time, because every attempt shares one VM, and that is what made it look deterministic. What it does track is the CPU. The crashing run drew an Intel Xeon 8573C -- Emerald Rapids, with AVX-512 and AMX. The next run drew an AMD EPYC 7763 -- Zen 3, with neither -- and the whole suite passed, including the test that had just crashed three times consecutively, and including the ISA sweep the last commit added, whose control arm passed and therefore measured nothing. That sweep is removed; it ran on the one CPU that cannot exhibit the bug. That leaves native code taking an AVX-512 or AMX path on Intel hosts. AMX is the better fit: its tile instructions raise #UD -- precisely 0xc000001d -- unless the hypervisor enabled XSAVE tile state, and that enablement plausibly varies across a heterogeneous fleet. Three steps replace the sweep: - oneDNN's selected ISA, printed via ONEDNN_VERBOSE on a bf16 matmul. Says outright whether AMX is in play on whichever host we drew. - A full-suite rerun capped at ONEDNN_MAX_CPU_ISA=AVX2, gated on the suite having actually crashed. Only the full suite is a proven reproducer -- the single test passed in isolation -- so a single-test rerun could not settle anything. If this pass is clean, the cap is the fix. - A minidump parse that names the faulting module. procdump is now installed as the postmortem debugger (`-i`) rather than wrapping a process: the two earlier attempts caught nothing because WER LocalDumps never fired and procdump wrapped nox while the crash was in the pytest child. If the faulting address lands in no loaded module, that is memory corruption rather than a missing opcode, and the script says so. Windows still does not gate merges, so these can be wrong without cost. The job timeout goes to 30 minutes to fit the second suite pass; it reverts with the diagnostics. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/scripts/name_faulting_module.py | 59 ++++++++++++ .github/workflows/unit_tests.yml | 116 ++++++++++++++---------- 2 files changed, 125 insertions(+), 50 deletions(-) create mode 100644 .github/scripts/name_faulting_module.py diff --git a/.github/scripts/name_faulting_module.py b/.github/scripts/name_faulting_module.py new file mode 100644 index 00000000000..93d78bdd0ec --- /dev/null +++ b/.github/scripts/name_faulting_module.py @@ -0,0 +1,59 @@ +"""Name the module that executed the illegal instruction, from a minidump. + +The Python-level faulthandler cannot do this: it prints interpreter frames, and the fault is in +native code several frames below the interpreter. The minidump carries the exception record and +the loaded-module list, so the faulting address can be resolved to a DLL by containment. +""" + +import sys +from pathlib import Path + + +def main(folder: str) -> int: + dumps = sorted(Path(folder).glob("*.dmp")) + if not dumps: + print("no .dmp files -- the crash did not reach the postmortem debugger") + return 0 + try: + from minidump.minidumpfile import MinidumpFile + except ImportError: + print("minidump package unavailable; dumps are uploaded as an artifact instead") + return 0 + + for d in dumps: + print(f"=== {d.name} ({d.stat().st_size / 1e6:.1f} MB) ===") + try: + mf = MinidumpFile.parse(str(d)) + except Exception as exc: # noqa: BLE001 - diagnostic, never fatal + print(f" unreadable: {exc}") + continue + + addr = None + exc_rec = getattr(mf, "exception", None) + if exc_rec is not None: + for record in getattr(exc_rec, "exception_records", []) or [exc_rec]: + er = getattr(record, "ExceptionRecord", record) + code = getattr(er, "ExceptionCode", None) + addr = getattr(er, "ExceptionAddress", None) + code_s = f"0x{int(code):08x}" if isinstance(code, int) else str(code) + print(f" exception {code_s} at 0x{addr:x}" if addr else f" exception {code_s}") + break + + mods = getattr(getattr(mf, "modules", None), "modules", []) or [] + print(f" {len(mods)} modules loaded") + if addr is None: + continue + for m in mods: + base = getattr(m, "baseaddress", 0) + size = getattr(m, "size", 0) + if base <= addr < base + size: + print(f" >>> FAULTING MODULE: {getattr(m, 'name', '?')} (+0x{addr - base:x})") + break + else: + print(f" >>> address 0x{addr:x} is in NO loaded module " + "-- a jump into non-code memory, i.e. corruption, not a missing opcode") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "crashdumps")) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index c61454c7200..7863a6c6a69 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -83,7 +83,10 @@ jobs: if: needs.check-file-changes.outputs.any_changed == 'true' needs: [linux, check-file-changes] runs-on: windows-latest - timeout-minutes: 15 + # 15 while this job only ran the suite once. The 0xc000001d diagnosis reruns it at a capped + # oneDNN ISA when it crashes, which needs room for a second pass. Revert to 15 once the + # crash is understood and the diagnostic steps come out. + timeout-minutes: 30 permissions: contents: read steps: @@ -104,21 +107,20 @@ jobs: run: | Get-CimInstance Win32_Processor | Select-Object Name, Description, NumberOfCores | Format-List python -c "import platform; print('machine:', platform.machine(), platform.processor())" - - name: Enable crash dumps + - name: Install a postmortem debugger that catches any crashing process continue-on-error: true shell: pwsh run: | - # WER local dumps. The Python-level faulthandler output does not identify the module -- - # it prints parked background threads while the main thread's native frame is lost as - # the process dies. A minidump names the faulting DLL and offset outright. + # WER LocalDumps was tried here first and produced nothing, and procdump launched as a + # parent produced nothing either -- it wrapped the nox process while the crash was in + # the pytest child. `procdump -i` installs procdump as the system postmortem debugger + # (AeDebug), so it catches whichever process actually dies, anywhere in the tree. $d = "$env:GITHUB_WORKSPACE\crashdumps" New-Item -ItemType Directory -Force -Path $d | Out-Null - $k = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" - New-Item -Path $k -Force | Out-Null - Set-ItemProperty -Path $k -Name DumpFolder -Value $d -Type ExpandString - Set-ItemProperty -Path $k -Name DumpType -Value 2 -Type DWord - Set-ItemProperty -Path $k -Name DumpCount -Value 5 -Type DWord + choco install procdump -y --no-progress | Out-Null + procdump -accepteula -ma -i $d - name: Run unit tests (without coverage) + id: unit # PEP 540 UTF-8 mode. Windows defaults text I/O to the locale codepage (cp1252 on these # runners), so any read of a UTF-8 file without an explicit encoding= dies with # UnicodeDecodeError on the first non-Latin-1 byte -- a failure no other platform sees. @@ -132,55 +134,69 @@ jobs: PYTHONFAULTHANDLER: "1" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" # --- 0xc000001d diagnosis ------------------------------------------------------------- - # What the last run established: the fault is deterministic, not intermittent. It is always - # test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format], and the three sibling - # parametrizations pass in the milliseconds before it. That case is the only one whose model - # is a bf16 128x128 Linear; the others quantize a 4x4 Linear. A bf16 GEMM of that size is - # where torch's CPU path hands off to oneDNN, which JIT-generates a kernel from runtime CPU - # detection. This runner is an Emerald Rapids Xeon (Family 6 Model 207), which advertises - # AMX-BF16 -- and AMX faults with #UD unless the hypervisor enabled its XSAVE tile state. - # #UD is STATUS_ILLEGAL_INSTRUCTION, raised in several oneDNN worker threads at once, which - # is why the faulthandler output was two threads interleaved over each other. + # Corrected picture, after sampling runs back to July. The crash is NOT deterministic and + # NOT specific to one test: it has landed on test_peft_save_restore (four times, Jul-Aug), + # test_unet_save_restore, and now test_fp8_export_rejects[mixed-format]. It appeared on + # five unrelated branches, so it is neither new nor anything a PR introduced. Within a + # single job it reproduces every time -- which is what made it look deterministic -- but + # that is because all attempts share one VM. # - # ATEN_CPU_CAPABILITY did not clear it and could not: it governs ATen's own vectorized - # kernels, not oneDNN's JIT. DNNL_MAX_CPU_ISA is the knob that does. This sweep runs the one - # crashing test at descending ISA ceilings -- the highest value that passes names the opcode - # family responsible, and becomes the fix. + # What it tracks is the CPU. The run that crashed drew an Intel Xeon 8573C (Emerald + # Rapids: AVX-512 and AMX). The very next run drew an AMD EPYC 7763 (Zen 3: neither) and + # the whole suite passed, including the test that had just crashed three times in a row. + # So the suspect is native code dispatching to an AVX-512 or AMX path on Intel hosts, most + # likely AMX -- its tile instructions fault with #UD, which is exactly 0xc000001d, unless + # the hypervisor enabled XSAVE tile state, and that enablement varies across the fleet. # - # It calls pytest from the nox venv directly, because the `unit` session hardcodes - # `tests/unit` and drops posargs: that is why the previous probe silently ran the whole - # suite instead of the single test it named. exit=3221225501 is the crash, 0 pass, 1 fail. - - name: Which CPU ISA does the crash need + # ATEN_CPU_CAPABILITY was tried and did not help. It would not: it governs ATen's own + # kernels, while oneDNN JIT-generates its own from runtime detection. ONEDNN_MAX_CPU_ISA + # is the knob for that, and it is what the rerun below tests. + - name: What ISA did oneDNN actually select if: always() continue-on-error: true shell: pwsh - env: - PYTHONUTF8: "1" run: | - $venv = Get-ChildItem -Path "C:/tmp/.nox","/tmp/.nox","$env:GITHUB_WORKSPACE/.nox" -Directory -ErrorAction SilentlyContinue | + $venv = Get-ChildItem -Path "D:/tmp/.nox","C:/tmp/.nox","/tmp/.nox","$env:GITHUB_WORKSPACE/.nox" ` + -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "unit-3-12*" } | Select-Object -First 1 - if (-not $venv) { Write-Host "no nox venv found -- nothing to probe"; exit 0 } + if (-not $venv) { Write-Host "no nox venv -- nothing to report"; exit 0 } $py = Join-Path $venv.FullName "Scripts\python.exe" - Write-Host "venv python: $py" - & $py -c "import torch; print('torch', torch.__version__, 'mkldnn', torch.backends.mkldnn.is_available()); print([l for l in torch.__config__.show().splitlines() if 'CPU capability' in l or 'oneDNN' in l])" - $t = "tests/unit/torch/deploy/utils/test_torch_onnx_utils.py::test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format]" - foreach ($isa in @("__unset__","AVX512_CORE_AMX","AVX512_CORE_BF16","AVX512_CORE","AVX2")) { - if ($isa -eq "__unset__") { - Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue - Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue - } else { - $env:DNNL_MAX_CPU_ISA = $isa - $env:ONEDNN_MAX_CPU_ISA = $isa - } - & $py -m pytest $t -o addopts= -q -p no:faulthandler > isa_out.txt 2>&1 - $code = $LASTEXITCODE - Get-Content isa_out.txt -Tail 3 -ErrorAction SilentlyContinue - Write-Host "ISA=$isa exit=$code" - } - Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue - Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue + $env:ONEDNN_VERBOSE = "1" + # oneDNN prints its selected ISA once, when the first primitive is created. A bf16 + # matmul is the cheapest way to force that and is the shape of op both crash sites run. + & $py -c "import torch; a=torch.randn(256,256,dtype=torch.bfloat16); b=torch.randn(256,256,dtype=torch.bfloat16); print('result', float((a@b).sum()))" 2>&1 | + Select-String -Pattern "onednn_verbose.*info|result" | ForEach-Object { $_.Line } + Remove-Item Env:\ONEDNN_VERBOSE -ErrorAction SilentlyContinue + - name: Does lowering the oneDNN ISA stop the crash + # Only worth the five minutes when the suite actually crashed -- and only the full suite + # is a proven reproducer. Running the single test in isolation passed on the AMD host, + # so a green single-test result would prove nothing either way. + if: always() && steps.unit.outcome == 'failure' + continue-on-error: true + shell: pwsh + env: + PYTHONUTF8: "1" + PYTHONUNBUFFERED: "1" + ONEDNN_MAX_CPU_ISA: "AVX2" + DNNL_MAX_CPU_ISA: "AVX2" + run: | + nox -s "unit-3.12(torch_214, tf_latest)" + # 3221225501 is 0xc000001d. 0 means capping the ISA fixed it, and the fix is this env + # var; 1 means ordinary test failures and the crash did not recur; 3221225501 means the + # ISA ceiling is not the mechanism and the dump below is the remaining lead. + Write-Host "full-suite-at-AVX2 exit=$LASTEXITCODE" + - name: Name the faulting module from the dump + if: always() + continue-on-error: true + shell: pwsh + run: | + Get-ChildItem "$env:GITHUB_WORKSPACE\crashdumps" -ErrorAction SilentlyContinue | + Format-Table Name, Length + python -m pip install --quiet minidump + python .github/scripts/name_faulting_module.py "$env:GITHUB_WORKSPACE\crashdumps" - name: Upload crash dumps - if: failure() + # always(): the dump is the point of the run even when a later diagnostic step passes. + if: always() continue-on-error: true uses: actions/upload-artifact@v4 with: From 774439d140d9b198b7f8dc84922b3002a00ee03a Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 01:19:49 +0000 Subject: [PATCH 13/20] Cap oneDNN's ISA on windows: 0xc000001d is a bf16 GEMM fault, not our bug Root cause. bf16 linear/matmul on CPU dispatch through oneDNN, which by default selects the highest instruction set the host advertises -- Intel AMX on the Emerald Rapids machines in the Actions fleet. On those hosts that path executes an instruction that faults with #UD, and #UD is STATUS_ILLEGAL_INSTRUCTION: 0xc000001d, exit code 3221225501, taking the whole pytest process with it. This is a torch/oneDNN Windows issue. Nothing in ModelOpt causes it and no PR introduced it. The evidence: - Six crashes across five unrelated branches between 2026-07-04 and 2026-09-17. Not new, and not attributable to any change of ours. - The crash sites are test_peft_save_restore (four times), test_unet_save_restore, and test_fp8_export_rejects_unsupported_dtype_conversion[mixed-format]. Five of the six run a bf16 forward on CPU: create_tiny_llama_dir sets dtype=torch.bfloat16, and mixed-format is the only parametrization in its file built on a bf16 128x128 Linear rather than a 4x4 one. The sixth, the UNet test from July, is fp32 and remains unexplained by this mechanism. - It tracks the host, not the test. Within one job it reproduces every time, which is what made it look deterministic; across jobs it follows the CPU. The crashing job drew an Intel Xeon 8573C (AVX-512 + AMX). The next drew an AMD EPYC 7763 (Zen 3: neither) and the entire suite passed, including the test that had just crashed three times consecutively. - ATEN_CPU_CAPABILITY=default did not suppress it, which fits: it governs ATen's own kernels, while oneDNN JIT-generates its own from runtime detection. ONEDNN_MAX_CPU_ISA is the documented knob for that. - Independently reported elsewhere with the same signature -- a bf16 GEMM in the Windows CPU torch build faulting 0xC000001D on some runner CPUs, intermittently, at a comparable rate. The fix caps ONEDNN_MAX_CPU_ISA at AVX2 for this job. AVX2 is the ceiling the AMD runners already operate at, and those have never shown the crash, so it is the setting with evidence behind it rather than the highest one that might work. It changes which kernel runs, not what is tested. Because the cap also hides the fault, a canary runs the same bf16 GEMM uncapped in a throwaway process and reports whether the host would have faulted. That keeps the justification observable per run and per CPU without flaking the job, and will show plainly if the fleet changes. What is not established: precisely which instruction faults, and why AMX is unusable on a machine that advertises it -- most likely XSAVE tile state the hypervisor never enabled. Answering that needs a minidump from an Intel host, which the procdump postmortem hook and the dump parser are still in place to capture. It does not block the mitigation. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 74 ++++++++++++-------------------- 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 7863a6c6a69..49ed77a0365 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -83,10 +83,7 @@ jobs: if: needs.check-file-changes.outputs.any_changed == 'true' needs: [linux, check-file-changes] runs-on: windows-latest - # 15 while this job only ran the suite once. The 0xc000001d diagnosis reruns it at a capped - # oneDNN ISA when it crashes, which needs room for a second pass. Revert to 15 once the - # crash is understood and the diagnostic steps come out. - timeout-minutes: 30 + timeout-minutes: 15 permissions: contents: read steps: @@ -132,26 +129,27 @@ jobs: # the main thread's frames, which is what made the last dump unreadable. PYTHONUNBUFFERED: "1" PYTHONFAULTHANDLER: "1" + # Cap oneDNN's instruction set. bf16 linear/matmul on CPU dispatch through oneDNN, + # which by default picks the highest ISA the host advertises -- Intel AMX on the + # Emerald Rapids runners in the Actions fleet. On those hosts that path executes an + # instruction that faults with #UD, which surfaces as 0xc000001d and kills the whole + # pytest process. AMD EPYC runners have neither AMX nor AVX-512, take the AVX2 path, + # and have never shown the crash -- so AVX2 is the ceiling with evidence behind it. + # This is a CI mitigation for a torch/oneDNN Windows issue, not a ModelOpt bug: it + # changes which kernel runs, not what is being tested. The canary below keeps proving + # it is still load-bearing. See the commit that added this for the full evidence. + ONEDNN_MAX_CPU_ISA: "AVX2" + DNNL_MAX_CPU_ISA: "AVX2" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" - # --- 0xc000001d diagnosis ------------------------------------------------------------- - # Corrected picture, after sampling runs back to July. The crash is NOT deterministic and - # NOT specific to one test: it has landed on test_peft_save_restore (four times, Jul-Aug), - # test_unet_save_restore, and now test_fp8_export_rejects[mixed-format]. It appeared on - # five unrelated branches, so it is neither new nor anything a PR introduced. Within a - # single job it reproduces every time -- which is what made it look deterministic -- but - # that is because all attempts share one VM. + # --- 0xc000001d canary ---------------------------------------------------------------- + # The cap above makes the suite stable, which also means a regression in the cap would be + # invisible. This step runs the offending operation -- a bf16 GEMM -- with the cap removed, + # in a throwaway process, and reports whether this host faults. It is the evidence that the + # cap is doing something, gathered per run and per CPU without flaking the job. # - # What it tracks is the CPU. The run that crashed drew an Intel Xeon 8573C (Emerald - # Rapids: AVX-512 and AMX). The very next run drew an AMD EPYC 7763 (Zen 3: neither) and - # the whole suite passed, including the test that had just crashed three times in a row. - # So the suspect is native code dispatching to an AVX-512 or AMX path on Intel hosts, most - # likely AMX -- its tile instructions fault with #UD, which is exactly 0xc000001d, unless - # the hypervisor enabled XSAVE tile state, and that enablement varies across the fleet. - # - # ATEN_CPU_CAPABILITY was tried and did not help. It would not: it governs ATen's own - # kernels, while oneDNN JIT-generates its own from runtime detection. ONEDNN_MAX_CPU_ISA - # is the knob for that, and it is what the rerun below tests. - - name: What ISA did oneDNN actually select + # exit 3221225501 is 0xc000001d: this host would have crashed the suite uncapped. exit 0 + # means this host is unaffected (every AMD runner so far). Either way the job is unharmed. + - name: Canary -- does this host fault on an uncapped bf16 GEMM if: always() continue-on-error: true shell: pwsh @@ -159,32 +157,16 @@ jobs: $venv = Get-ChildItem -Path "D:/tmp/.nox","C:/tmp/.nox","/tmp/.nox","$env:GITHUB_WORKSPACE/.nox" ` -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "unit-3-12*" } | Select-Object -First 1 - if (-not $venv) { Write-Host "no nox venv -- nothing to report"; exit 0 } + if (-not $venv) { Write-Host "no nox venv -- nothing to probe"; exit 0 } $py = Join-Path $venv.FullName "Scripts\python.exe" + Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue + Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue $env:ONEDNN_VERBOSE = "1" - # oneDNN prints its selected ISA once, when the first primitive is created. A bf16 - # matmul is the cheapest way to force that and is the shape of op both crash sites run. - & $py -c "import torch; a=torch.randn(256,256,dtype=torch.bfloat16); b=torch.randn(256,256,dtype=torch.bfloat16); print('result', float((a@b).sum()))" 2>&1 | - Select-String -Pattern "onednn_verbose.*info|result" | ForEach-Object { $_.Line } - Remove-Item Env:\ONEDNN_VERBOSE -ErrorAction SilentlyContinue - - name: Does lowering the oneDNN ISA stop the crash - # Only worth the five minutes when the suite actually crashed -- and only the full suite - # is a proven reproducer. Running the single test in isolation passed on the AMD host, - # so a green single-test result would prove nothing either way. - if: always() && steps.unit.outcome == 'failure' - continue-on-error: true - shell: pwsh - env: - PYTHONUTF8: "1" - PYTHONUNBUFFERED: "1" - ONEDNN_MAX_CPU_ISA: "AVX2" - DNNL_MAX_CPU_ISA: "AVX2" - run: | - nox -s "unit-3.12(torch_214, tf_latest)" - # 3221225501 is 0xc000001d. 0 means capping the ISA fixed it, and the fix is this env - # var; 1 means ordinary test failures and the crash did not recur; 3221225501 means the - # ISA ceiling is not the mechanism and the dump below is the remaining lead. - Write-Host "full-suite-at-AVX2 exit=$LASTEXITCODE" + & $py -c "import torch; a=torch.randn(256,256,dtype=torch.bfloat16); b=torch.randn(256,256,dtype=torch.bfloat16); print('bf16 gemm ok', float((a@b).sum()))" 2>&1 | + Select-String -Pattern "onednn_verbose.*info|bf16 gemm ok" | ForEach-Object { $_.Line } + $code = $LASTEXITCODE + Write-Host "uncapped-bf16-gemm exit=$code" + if ($code -eq 3221225501) { Write-Host "=> this host faults uncapped; the ISA cap is load-bearing here" } - name: Name the faulting module from the dump if: always() continue-on-error: true From 1fc2a00f063b28837a3b59c7eeac3a1d32f7209d Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 01:41:30 +0000 Subject: [PATCH 14/20] Canary: run an nn.Linear forward, not a bare matmul The first canary printed its result but no onednn_verbose line, which means the bare `a @ b` never created a oneDNN primitive -- so it was not exercising the path that faults, and a clean exit from it would have proved nothing. This runs what the crash sites actually run: an nn.Linear forward in bf16 under eval/no_grad, at the 128-wide shape from the crashing test and at 512, because oneDNN selects its kernel by shape as well as by ISA and the small case may stay in a reference implementation. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 49ed77a0365..b1f00375dea 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -162,8 +162,17 @@ jobs: Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue $env:ONEDNN_VERBOSE = "1" - & $py -c "import torch; a=torch.randn(256,256,dtype=torch.bfloat16); b=torch.randn(256,256,dtype=torch.bfloat16); print('bf16 gemm ok', float((a@b).sum()))" 2>&1 | - Select-String -Pattern "onednn_verbose.*info|bf16 gemm ok" | ForEach-Object { $_.Line } + # A bare tensor @ tensor did not reach oneDNN on the first attempt -- no verbose line + # appeared -- so this runs what the crash sites actually run: an nn.Linear forward in + # bf16, under eval/no_grad, at the 128-wide shape from the crashing test and a larger + # one, since oneDNN picks its kernel by shape as well as by ISA. + & $py -c "import torch +torch.set_grad_enabled(False) +for n in (128, 512): + m = torch.nn.Linear(n, n, bias=False).eval().to(torch.bfloat16) + x = torch.ones(8, n, dtype=torch.bfloat16) + print('bf16 linear', n, 'ok', float(m(x).sum()))" 2>&1 | + Select-String -Pattern "onednn_verbose.*info|bf16 linear" | ForEach-Object { $_.Line } $code = $LASTEXITCODE Write-Host "uncapped-bf16-gemm exit=$code" if ($code -eq 3221225501) { Write-Host "=> this host faults uncapped; the ISA cap is load-bearing here" } From 5fd9fce8bbd314f8edbec77d0f2a627ca28f12d5 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 01:41:55 +0000 Subject: [PATCH 15/20] Fix the workflow YAML the previous commit broke; move the canary to a file The previous commit inlined a multi-line Python program into the pwsh `run:` block. Its continuation lines start at column 0, which terminates the YAML block scalar early -- the file no longer parsed, so the workflow would not have run at all. My verification ran `git commit` on a line separate from the parse check, so the failing parse did not stop the commit; both now live in one step. The program moves to .github/scripts/bf16_canary.py, next to the dump parser, which removes the indentation fight for good. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/scripts/bf16_canary.py | 23 +++++++++++++++++++++++ .github/workflows/unit_tests.yml | 14 ++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 .github/scripts/bf16_canary.py diff --git a/.github/scripts/bf16_canary.py b/.github/scripts/bf16_canary.py new file mode 100644 index 00000000000..942302be849 --- /dev/null +++ b/.github/scripts/bf16_canary.py @@ -0,0 +1,23 @@ +"""Run the bf16 forward that 0xc000001d faults on, so a host can be tested without the suite. + +Kept as a file rather than inlined into the workflow: a multi-line program inside a YAML `run:` +block has to fight the block scalar's indentation, and the first version of this canary silently +degraded to a bare `a @ b` that never reached oneDNN at all. +""" + +import torch + + +def main() -> int: + torch.set_grad_enabled(False) + # The crashing test builds a 128-wide bf16 Linear; 512 is included because oneDNN selects a + # kernel by shape as well as by ISA, and the small case may stay in a reference path. + for n in (128, 512): + layer = torch.nn.Linear(n, n, bias=False).eval().to(torch.bfloat16) + x = torch.ones(8, n, dtype=torch.bfloat16) + print(f"bf16 linear {n} ok {float(layer(x).sum())}", flush=True) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index b1f00375dea..49327c7dc62 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -162,16 +162,10 @@ jobs: Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue $env:ONEDNN_VERBOSE = "1" - # A bare tensor @ tensor did not reach oneDNN on the first attempt -- no verbose line - # appeared -- so this runs what the crash sites actually run: an nn.Linear forward in - # bf16, under eval/no_grad, at the 128-wide shape from the crashing test and a larger - # one, since oneDNN picks its kernel by shape as well as by ISA. - & $py -c "import torch -torch.set_grad_enabled(False) -for n in (128, 512): - m = torch.nn.Linear(n, n, bias=False).eval().to(torch.bfloat16) - x = torch.ones(8, n, dtype=torch.bfloat16) - print('bf16 linear', n, 'ok', float(m(x).sum()))" 2>&1 | + # The first version of this canary was a bare `a @ b` and printed no onednn_verbose + # line at all, meaning it never created a oneDNN primitive -- so a clean exit from it + # proved nothing. name_faulting_module.py's sibling runs the real operation instead. + & $py .github/scripts/bf16_canary.py 2>&1 | Select-String -Pattern "onednn_verbose.*info|bf16 linear" | ForEach-Object { $_.Line } $code = $LASTEXITCODE Write-Host "uncapped-bf16-gemm exit=$code" From 1c1672b9f98f3633cab0c35605767ca468e7bb5d Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 01:52:28 +0000 Subject: [PATCH 16/20] Add the SPDX headers pre-commit requires on the two new scripts code-quality failed on both files: the repo's license hook covers .github/scripts as well, which I did not check before pushing. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/scripts/bf16_canary.py | 15 +++++++++++++++ .github/scripts/name_faulting_module.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.github/scripts/bf16_canary.py b/.github/scripts/bf16_canary.py index 942302be849..3db0670ee26 100644 --- a/.github/scripts/bf16_canary.py +++ b/.github/scripts/bf16_canary.py @@ -1,3 +1,18 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 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. + """Run the bf16 forward that 0xc000001d faults on, so a host can be tested without the suite. Kept as a file rather than inlined into the workflow: a multi-line program inside a YAML `run:` diff --git a/.github/scripts/name_faulting_module.py b/.github/scripts/name_faulting_module.py index 93d78bdd0ec..912c03021a6 100644 --- a/.github/scripts/name_faulting_module.py +++ b/.github/scripts/name_faulting_module.py @@ -1,3 +1,18 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 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. + """Name the module that executed the illegal instruction, from a minidump. The Python-level faulthandler cannot do this: it prints interpreter frames, and the fault is in From b64d39c060cbcd60b86714618906da517602368c Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 02:03:55 +0000 Subject: [PATCH 17/20] Satisfy ruff on the two new scripts: docstrings and formatting D103 on both main() functions, an unused noqa ruff stripped by itself, and a print() ruff wants wrapped differently. Verified with the pinned ruff 0.15.20 across the whole repo rather than just the files I touched -- checking only my own file list is what let the previous code-quality failure through. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/scripts/bf16_canary.py | 1 + .github/scripts/name_faulting_module.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/scripts/bf16_canary.py b/.github/scripts/bf16_canary.py index 3db0670ee26..ac638a96ffb 100644 --- a/.github/scripts/bf16_canary.py +++ b/.github/scripts/bf16_canary.py @@ -24,6 +24,7 @@ def main() -> int: + """Run the bf16 Linear forwards; a #UD here kills the process rather than returning.""" torch.set_grad_enabled(False) # The crashing test builds a 128-wide bf16 Linear; 512 is included because oneDNN selects a # kernel by shape as well as by ISA, and the small case may stay in a reference path. diff --git a/.github/scripts/name_faulting_module.py b/.github/scripts/name_faulting_module.py index 912c03021a6..9bee89131ff 100644 --- a/.github/scripts/name_faulting_module.py +++ b/.github/scripts/name_faulting_module.py @@ -25,6 +25,7 @@ def main(folder: str) -> int: + """Print the exception and the module containing its address, for each dump found.""" dumps = sorted(Path(folder).glob("*.dmp")) if not dumps: print("no .dmp files -- the crash did not reach the postmortem debugger") @@ -39,7 +40,7 @@ def main(folder: str) -> int: print(f"=== {d.name} ({d.stat().st_size / 1e6:.1f} MB) ===") try: mf = MinidumpFile.parse(str(d)) - except Exception as exc: # noqa: BLE001 - diagnostic, never fatal + except Exception as exc: print(f" unreadable: {exc}") continue @@ -65,8 +66,10 @@ def main(folder: str) -> int: print(f" >>> FAULTING MODULE: {getattr(m, 'name', '?')} (+0x{addr - base:x})") break else: - print(f" >>> address 0x{addr:x} is in NO loaded module " - "-- a jump into non-code memory, i.e. corruption, not a missing opcode") + print( + f" >>> address 0x{addr:x} is in NO loaded module " + "-- a jump into non-code memory, i.e. corruption, not a missing opcode" + ) return 0 From f3563c9c175aff8736a384135bc8c0f270cee723 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 17:58:38 +0000 Subject: [PATCH 18/20] Drop the windows crash diagnostics; keep the fix The scaffolding cannot produce a signal any more and should not outlive the investigation. The canary in particular was worse than nothing. It has never returned non-zero: every run since it landed drew an AMD host, so there is no evidence it can detect anything. A detector that has never fired is indistinguishable from a broken one -- an exit=0 on an Intel host would not tell us whether the host was fine or the canary simply does not reproduce, and it plausibly does not, since all six real crashes happened deep inside full suite runs rather than in a bare Linear forward in a fresh process. Its green result would have read as reassurance regardless of the truth. The same holds for the rest: with ONEDNN_MAX_CPU_ISA capped the suite does not crash, so `procdump -i` never fires, the dump parser never receives a dump, and the upload step never has anything to upload. Four of the six steps in this job were unreachable by construction. Removed: both .github/scripts files, the postmortem-debugger install, the canary, the dump parser and the dump upload. Kept: the ISA cap with the evidence behind it, and the one-line CPU record, which is the first thing anyone would want if this recurs. Comments that referenced the removed steps are corrected rather than left pointing at nothing, and the `id: unit` that existed only so later steps could branch on the suite failing is gone with them. Nothing is lost: the diagnostics, what each one found, and the two that measured the wrong thing are all in this branch's history and in the PR description. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/scripts/bf16_canary.py | 39 ------------ .github/scripts/name_faulting_module.py | 77 ------------------------ .github/workflows/unit_tests.yml | 79 +++---------------------- 3 files changed, 8 insertions(+), 187 deletions(-) delete mode 100644 .github/scripts/bf16_canary.py delete mode 100644 .github/scripts/name_faulting_module.py diff --git a/.github/scripts/bf16_canary.py b/.github/scripts/bf16_canary.py deleted file mode 100644 index ac638a96ffb..00000000000 --- a/.github/scripts/bf16_canary.py +++ /dev/null @@ -1,39 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 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. - -"""Run the bf16 forward that 0xc000001d faults on, so a host can be tested without the suite. - -Kept as a file rather than inlined into the workflow: a multi-line program inside a YAML `run:` -block has to fight the block scalar's indentation, and the first version of this canary silently -degraded to a bare `a @ b` that never reached oneDNN at all. -""" - -import torch - - -def main() -> int: - """Run the bf16 Linear forwards; a #UD here kills the process rather than returning.""" - torch.set_grad_enabled(False) - # The crashing test builds a 128-wide bf16 Linear; 512 is included because oneDNN selects a - # kernel by shape as well as by ISA, and the small case may stay in a reference path. - for n in (128, 512): - layer = torch.nn.Linear(n, n, bias=False).eval().to(torch.bfloat16) - x = torch.ones(8, n, dtype=torch.bfloat16) - print(f"bf16 linear {n} ok {float(layer(x).sum())}", flush=True) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/.github/scripts/name_faulting_module.py b/.github/scripts/name_faulting_module.py deleted file mode 100644 index 9bee89131ff..00000000000 --- a/.github/scripts/name_faulting_module.py +++ /dev/null @@ -1,77 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 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. - -"""Name the module that executed the illegal instruction, from a minidump. - -The Python-level faulthandler cannot do this: it prints interpreter frames, and the fault is in -native code several frames below the interpreter. The minidump carries the exception record and -the loaded-module list, so the faulting address can be resolved to a DLL by containment. -""" - -import sys -from pathlib import Path - - -def main(folder: str) -> int: - """Print the exception and the module containing its address, for each dump found.""" - dumps = sorted(Path(folder).glob("*.dmp")) - if not dumps: - print("no .dmp files -- the crash did not reach the postmortem debugger") - return 0 - try: - from minidump.minidumpfile import MinidumpFile - except ImportError: - print("minidump package unavailable; dumps are uploaded as an artifact instead") - return 0 - - for d in dumps: - print(f"=== {d.name} ({d.stat().st_size / 1e6:.1f} MB) ===") - try: - mf = MinidumpFile.parse(str(d)) - except Exception as exc: - print(f" unreadable: {exc}") - continue - - addr = None - exc_rec = getattr(mf, "exception", None) - if exc_rec is not None: - for record in getattr(exc_rec, "exception_records", []) or [exc_rec]: - er = getattr(record, "ExceptionRecord", record) - code = getattr(er, "ExceptionCode", None) - addr = getattr(er, "ExceptionAddress", None) - code_s = f"0x{int(code):08x}" if isinstance(code, int) else str(code) - print(f" exception {code_s} at 0x{addr:x}" if addr else f" exception {code_s}") - break - - mods = getattr(getattr(mf, "modules", None), "modules", []) or [] - print(f" {len(mods)} modules loaded") - if addr is None: - continue - for m in mods: - base = getattr(m, "baseaddress", 0) - size = getattr(m, "size", 0) - if base <= addr < base + size: - print(f" >>> FAULTING MODULE: {getattr(m, 'name', '?')} (+0x{addr - base:x})") - break - else: - print( - f" >>> address 0x{addr:x} is in NO loaded module " - "-- a jump into non-code memory, i.e. corruption, not a missing opcode" - ) - return 0 - - -if __name__ == "__main__": - sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "crashdumps")) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 49327c7dc62..0769b844090 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -91,33 +91,16 @@ jobs: - uses: actions/setup-python@v6 with: python-version: "3.12" - # Diagnostics for the intermittent 0xc000001d (STATUS_ILLEGAL_INSTRUCTION) crash: a native - # module executes an opcode this host lacks. The runner fleet is heterogeneous, so the same - # wheel passes on one machine and dies on another. Nothing here changes what is tested; it - # records which CPU ran, and leaves a dump behind naming the faulting DLL if it happens. - name: Record which CPU this runner got - # CPU model only -- torch is not installed in this interpreter yet, it lives in the nox - # venv created by the test step below. An earlier version of this step claimed to report - # torch's dispatch capability and could not; the ISA probe after the tests does that. + # The 0xc000001d crash this job used to hit tracked the host CPU, not the test, so this + # is the first thing worth knowing if it ever recurs. See the ONEDNN_MAX_CPU_ISA comment + # on the test step below. continue-on-error: true shell: pwsh run: | Get-CimInstance Win32_Processor | Select-Object Name, Description, NumberOfCores | Format-List python -c "import platform; print('machine:', platform.machine(), platform.processor())" - - name: Install a postmortem debugger that catches any crashing process - continue-on-error: true - shell: pwsh - run: | - # WER LocalDumps was tried here first and produced nothing, and procdump launched as a - # parent produced nothing either -- it wrapped the nox process while the crash was in - # the pytest child. `procdump -i` installs procdump as the system postmortem debugger - # (AeDebug), so it catches whichever process actually dies, anywhere in the tree. - $d = "$env:GITHUB_WORKSPACE\crashdumps" - New-Item -ItemType Directory -Force -Path $d | Out-Null - choco install procdump -y --no-progress | Out-Null - procdump -accepteula -ma -i $d - name: Run unit tests (without coverage) - id: unit # PEP 540 UTF-8 mode. Windows defaults text I/O to the locale codepage (cp1252 on these # runners), so any read of a UTF-8 file without an explicit encoding= dies with # UnicodeDecodeError on the first non-Latin-1 byte -- a failure no other platform sees. @@ -125,8 +108,8 @@ jobs: # library code from passing encoding= explicitly: user processes will not have this set. env: PYTHONUTF8: "1" - # Unbuffered so a crash does not interleave two threads' faulthandler output and lose - # the main thread's frames, which is what made the last dump unreadable. + # Unbuffered so a crash does not interleave two threads' faulthandler output into + # each other, which is what made the 0xc000001d tracebacks unreadable. PYTHONUNBUFFERED: "1" PYTHONFAULTHANDLER: "1" # Cap oneDNN's instruction set. bf16 linear/matmul on CPU dispatch through oneDNN, @@ -136,58 +119,12 @@ jobs: # pytest process. AMD EPYC runners have neither AMX nor AVX-512, take the AVX2 path, # and have never shown the crash -- so AVX2 is the ceiling with evidence behind it. # This is a CI mitigation for a torch/oneDNN Windows issue, not a ModelOpt bug: it - # changes which kernel runs, not what is being tested. The canary below keeps proving - # it is still load-bearing. See the commit that added this for the full evidence. + # changes which kernel runs, not what is being tested. To check whether it is still + # needed, drop it and run the suite repeatedly until an Intel host comes up. + # See the PR that added this for the six crashes and the host correlation behind it. ONEDNN_MAX_CPU_ISA: "AVX2" DNNL_MAX_CPU_ISA: "AVX2" run: pip install nox uv && nox -s "unit-3.12(torch_214, tf_latest)" - # --- 0xc000001d canary ---------------------------------------------------------------- - # The cap above makes the suite stable, which also means a regression in the cap would be - # invisible. This step runs the offending operation -- a bf16 GEMM -- with the cap removed, - # in a throwaway process, and reports whether this host faults. It is the evidence that the - # cap is doing something, gathered per run and per CPU without flaking the job. - # - # exit 3221225501 is 0xc000001d: this host would have crashed the suite uncapped. exit 0 - # means this host is unaffected (every AMD runner so far). Either way the job is unharmed. - - name: Canary -- does this host fault on an uncapped bf16 GEMM - if: always() - continue-on-error: true - shell: pwsh - run: | - $venv = Get-ChildItem -Path "D:/tmp/.nox","C:/tmp/.nox","/tmp/.nox","$env:GITHUB_WORKSPACE/.nox" ` - -Directory -ErrorAction SilentlyContinue | - Where-Object { $_.Name -like "unit-3-12*" } | Select-Object -First 1 - if (-not $venv) { Write-Host "no nox venv -- nothing to probe"; exit 0 } - $py = Join-Path $venv.FullName "Scripts\python.exe" - Remove-Item Env:\ONEDNN_MAX_CPU_ISA -ErrorAction SilentlyContinue - Remove-Item Env:\DNNL_MAX_CPU_ISA -ErrorAction SilentlyContinue - $env:ONEDNN_VERBOSE = "1" - # The first version of this canary was a bare `a @ b` and printed no onednn_verbose - # line at all, meaning it never created a oneDNN primitive -- so a clean exit from it - # proved nothing. name_faulting_module.py's sibling runs the real operation instead. - & $py .github/scripts/bf16_canary.py 2>&1 | - Select-String -Pattern "onednn_verbose.*info|bf16 linear" | ForEach-Object { $_.Line } - $code = $LASTEXITCODE - Write-Host "uncapped-bf16-gemm exit=$code" - if ($code -eq 3221225501) { Write-Host "=> this host faults uncapped; the ISA cap is load-bearing here" } - - name: Name the faulting module from the dump - if: always() - continue-on-error: true - shell: pwsh - run: | - Get-ChildItem "$env:GITHUB_WORKSPACE\crashdumps" -ErrorAction SilentlyContinue | - Format-Table Name, Length - python -m pip install --quiet minidump - python .github/scripts/name_faulting_module.py "$env:GITHUB_WORKSPACE\crashdumps" - - name: Upload crash dumps - # always(): the dump is the point of the run even when a later diagnostic step passes. - if: always() - continue-on-error: true - uses: actions/upload-artifact@v4 - with: - name: windows-crashdumps - path: crashdumps/ - if-no-files-found: ignore multi-version: if: needs.check-file-changes.outputs.any_changed == 'true' needs: [linux, check-file-changes] From 1ad7e2d2d69fdb964c068c4b1095c60fb0f4d911 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 18:10:19 +0000 Subject: [PATCH 19/20] Drop PYTHONUTF8 from the windows job; it masks the bug it looks like it prevents PYTHONUTF8 was never fixing an observed failure. There is no UnicodeDecodeError, cp1252 or charmap error in any windows log I could find, including several failed runs, and main sets no such flag while passing 3478 tests. What it does do is hide the exact defect the explicit-encoding change in this PR exists to prevent. That change annotates the YAML config I/O in modelopt/ because a user's process will not be in UTF-8 mode; 139 encoding-less text reads remain in modelopt/ besides. Running CI in UTF-8 mode means CI can never fail on any of them, so the one place that could catch a missing encoding= in shipped code is blinded to it. A guard that suppresses the signal it is meant to raise is worse than no guard. Note this is not because the explicit-encoding work made it redundant: that work covers ten YAML sites, while ~450 encoding-less reads remain in tests/ and examples/. Those are the real cost of dropping the flag -- a future test fixture carrying a non-ASCII byte will now break the windows job. That is noise for files no user executes, and it is the price of CI being able to see the shipped-code case at all. PYTHONUNBUFFERED and PYTHONFAULTHANDLER go with it. Both were added only to stop two threads' faulthandler output interleaving so the 0xc000001d traceback could be read; that crash is fixed by the ISA cap, and pytest enables faulthandler itself. They are the same scaffolding as the steps removed in the previous commit. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- .github/workflows/unit_tests.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 0769b844090..d0aeb20875b 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -101,17 +101,7 @@ jobs: Get-CimInstance Win32_Processor | Select-Object Name, Description, NumberOfCores | Format-List python -c "import platform; print('machine:', platform.machine(), platform.processor())" - name: Run unit tests (without coverage) - # PEP 540 UTF-8 mode. Windows defaults text I/O to the locale codepage (cp1252 on these - # runners), so any read of a UTF-8 file without an explicit encoding= dies with - # UnicodeDecodeError on the first non-Latin-1 byte -- a failure no other platform sees. - # This makes the whole test process read UTF-8 regardless of locale. It does NOT excuse - # library code from passing encoding= explicitly: user processes will not have this set. env: - PYTHONUTF8: "1" - # Unbuffered so a crash does not interleave two threads' faulthandler output into - # each other, which is what made the 0xc000001d tracebacks unreadable. - PYTHONUNBUFFERED: "1" - PYTHONFAULTHANDLER: "1" # Cap oneDNN's instruction set. bf16 linear/matmul on CPU dispatch through oneDNN, # which by default picks the highest ISA the host advertises -- Intel AMX on the # Emerald Rapids runners in the Actions fleet. On those hosts that path executes an From 6c717a79d64ab95a57db21785e8bbb9bd97d5ee7 Mon Sep 17 00:00:00 2001 From: Shengliang Xu <shengliangx@nvidia.com> Date: Fri, 18 Sep 2026 19:03:10 +0000 Subject: [PATCH 20/20] Remove the last crash diagnostic, and changelog the encoding fix Two things the cleanup missed. `tests/unit/conftest.py` still carried `_report_cpu_dispatch`, a session-scoped autouse fixture that printed torch's CPU dispatch to help identify the 0xc000001d crash. That is the same scaffolding as the workflow steps and scripts removed earlier; the previous commit only looked at .github/, so this one survived. It is registered for every tests/unit process even though its body is Windows-only, and the crash it existed to investigate is fixed. The file is back to what it was before this PR. The UTF-8 config I/O change also had no changelog entry. It is the one user-visible fix here -- the other two are CI-only and correctly absent -- and it changes behaviour for anyone loading a config with non-ASCII content on a machine whose locale is not UTF-8. Added under Bug Fixes. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com> --- CHANGELOG.rst | 1 + tests/unit/conftest.py | 27 --------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c15cd53075d..15eb3b12cc7 100755 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -73,6 +73,7 @@ Changelog - Fix a DDP hang in DFlash training at scale where a rank whose batch contained no valid anchor skipped the draft forward, leaving its rotary buffer list shorter than other ranks' and causing ``broadcast_buffers`` to hang. The buffer is now created during ``modify()`` before training begins. - Fix ``megatron_generate`` dropping the VLM vision inputs (``pixel_values`` / ``image_grid_thw`` / ``image_sizes``) after the first generated token when KV-cache decoding is off, including the automatic fallback under sequence parallelism, which made generation silently ignore the image. No other ModelOpt feature is affected. - Fix two issues in the vLLM offline hidden-state dump (``examples/speculative_decoding/collect_hidden_states/compute_hidden_states_vllm.py``) that only surface on large runs. **Resume:** the filter that skips conversations whose ``.pt`` already exists now runs with ``load_from_cache_file=False``. It depends on on-disk state, which is not part of the fingerprint ``datasets`` computes from the function and the dataset, so with a persistent HF cache reused across a resumed or requeued run the cached "keep everything" result from an earlier run was replayed and the dump re-generated and overwrote conversations it had already finished (observed: tens of thousands of ``.pt`` rewritten while the output count stayed flat). **Staging:** generation is now chunked (``--save-chunk-size``, default 256), so each chunk is saved and its staged hidden states freed before the next chunk is generated. Previously the whole dataset was generated before anything was saved, which kept every conversation staged in the connector's ``shared_storage_path`` (``/dev/shm``, i.e. RAM, by default) at once and exhausted it partway through large dumps. Chunking also makes the dump incrementally durable, so an interrupted run keeps its finished conversations and resumes from them. The save path now also frees each conversation's staged hidden states in a ``finally``, so a conversation skipped mid-loop (e.g. a short ``loss_mask``) can no longer leak its staging file, and conversation ids are validated as plain filenames before being used to build output paths. +- Fix YAML config I/O decoding with the locale codepage instead of UTF-8, which made a config containing any non-ASCII byte fail to load on a machine whose locale is not UTF-8 (notably Windows, where the default is cp1252). ``modelopt/recipe/loader.py``, the two ONNX autotune state files, the two transformers config readers, the distill config and the puzzletron profile now pass ``encoding="utf-8"`` explicitly. Only the YAML config paths are covered: these are the files most likely to carry non-ASCII text in comments, model names or paths, and the only ones read inside a user's process. 0.47.0 (2026-09-xx) ^^^^^^^^^^^^^^^^^^^ diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 30a8e6063fc..f397205f022 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -15,9 +15,6 @@ import contextlib import os -import sys - -import pytest # Enforce no HuggingFace Hub network access for unit tests os.environ["HF_HUB_OFFLINE"] = "1" @@ -28,27 +25,3 @@ import huggingface_hub.constants as _hf_constants _hf_constants.HF_HUB_OFFLINE = True - - -@pytest.fixture(scope="session", autouse=True) -def _report_cpu_dispatch(): - """On Windows, record what torch decided the CPU can do. - - The job intermittently dies with 0xc000001d (STATUS_ILLEGAL_INSTRUCTION): a native module - executing an opcode the host lacks. torch selects a vectorized kernel set at runtime, so what - it chose -- compared against the CPU the workflow records before the run -- is the first thing - to check. Reported from inside the test process because that is where the torch under test - lives; the runner interpreter has only nox and uv. - - Windows-only and best-effort: elsewhere it is noise, and a diagnostic must never fail a run. - """ - if sys.platform != "win32": - return - with contextlib.suppress(Exception): - import torch - - print( - f"\n[diag] torch {torch.__version__} " - f"cpu_capability={torch.backends.cpu.get_cpu_capability()}", - flush=True, - )