diff --git a/src/llamafactory/hparams/parser.py b/src/llamafactory/hparams/parser.py index c22c1e375..5602bd26a 100644 --- a/src/llamafactory/hparams/parser.py +++ b/src/llamafactory/hparams/parser.py @@ -180,6 +180,13 @@ def _verify_trackio_args(training_args: "TrainingArguments") -> None: logger.warning("Consider setting --run_name for better experiment tracking clarity.") +def _normalize_swanlab_args(training_args: "TrainingArguments", finetuning_args: "FinetuningArguments") -> None: + r"""Route SwanLab reporting through the native SwanLab callback.""" + if "swanlab" in training_args.report_to: + training_args.report_to = [reporter for reporter in training_args.report_to if reporter != "swanlab"] + finetuning_args.use_swanlab = True + + def _set_transformers_logging() -> None: if os.getenv("LLAMAFACTORY_VERBOSITY", "INFO") in ["DEBUG", "INFO"]: transformers.utils.logging.set_verbosity_info() @@ -373,6 +380,8 @@ def get_train_args(args: dict[str, Any] | list[str] | None = None) -> _TRAIN_CLS finetuning_args.use_mca = False finetuning_args.use_megatron_bridge = False + _normalize_swanlab_args(training_args, finetuning_args) + # Setup logging if training_args.should_log: _set_transformers_logging() @@ -553,9 +562,6 @@ def get_train_args(args: dict[str, Any] | list[str] | None = None) -> _TRAIN_CLS # https://github.com/huggingface/transformers/blob/v4.50.0/src/transformers/trainer.py#L782 training_args.label_names = training_args.label_names or ["labels"] - if "swanlab" in training_args.report_to and finetuning_args.use_swanlab: - training_args.report_to.remove("swanlab") - if ( training_args.parallel_mode == ParallelMode.DISTRIBUTED and training_args.ddp_find_unused_parameters is None diff --git a/tests/hparams/test_parser.py b/tests/hparams/test_parser.py new file mode 100644 index 000000000..5397166c7 --- /dev/null +++ b/tests/hparams/test_parser.py @@ -0,0 +1,56 @@ +# Copyright 2026 the LlamaFactory team. +# +# 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. + +from types import SimpleNamespace + +import pytest +from transformers.training_args import ParallelMode + +from llamafactory.hparams import parser +from llamafactory.hparams.parser import _normalize_swanlab_args + + +@pytest.mark.parametrize( + ("report_to", "use_swanlab", "expected_report_to", "expected_use_swanlab"), + [ + (["swanlab"], False, [], True), + (["tensorboard", "swanlab", "wandb"], False, ["tensorboard", "wandb"], True), + (["swanlab"], True, [], True), + (["tensorboard"], False, ["tensorboard"], False), + ], +) +def test_normalize_swanlab_args(report_to, use_swanlab, expected_report_to, expected_use_swanlab): + training_args = SimpleNamespace(report_to=report_to) + finetuning_args = SimpleNamespace(use_swanlab=use_swanlab) + + _normalize_swanlab_args(training_args, finetuning_args) + + assert training_args.report_to == expected_report_to + assert finetuning_args.use_swanlab is expected_use_swanlab + + +def test_report_to_swanlab_uses_native_callback(monkeypatch, tmp_path): + monkeypatch.setattr(parser.TrainingArguments, "parallel_mode", property(lambda self: ParallelMode.DISTRIBUTED)) + monkeypatch.setattr(parser, "check_version", lambda *args, **kwargs: None) + + _, _, training_args, finetuning_args, _ = parser.get_train_args( + { + "model_name_or_path": "dummy", + "output_dir": str(tmp_path), + "report_to": "swanlab", + } + ) + + assert training_args.report_to == [] + assert finetuning_args.use_swanlab is True