Skip to content

Commit d7a1999

Browse files
authored
[recipe, model] fix: resolve 26.08 model workflow regressions (#4994)
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
1 parent 381d0b8 commit d7a1999

5 files changed

Lines changed: 90 additions & 3 deletions

File tree

examples/models/nemotron/nemotron_3/nano/slurm_sft.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ for CONFIG in "${PARALLELISM_CONFIGS[@]}"; do
155155
model.seq_length=$SEQ_LENGTH"
156156

157157
CMD="uv run --no-sync python scripts/training/run_recipe.py"
158-
CMD="$CMD --recipe ${MODEL_NAME}_finetune_config"
158+
CMD="$CMD --recipe ${MODEL_NAME}_sft_config"
159159
CMD="$CMD $CLI_OVERRIDES"
160160

161161
echo "Executing command..."

examples/models/qwen/qwen2_audio/sft.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ for par_config in "${PARALLELISM_CONFIGS[@]}"; do
7777
echo " run_recipe.py | TP=${TP}, PP=${PP}"
7878
echo "============================================================"
7979
uv run --no-sync python -m torch.distributed.run --nproc_per_node=${NPROC} scripts/training/run_recipe.py \
80-
--recipe qwen2_audio_7b_finetune_config \
80+
--recipe qwen2_audio_7b_sft_config \
8181
--step_func audio_lm_step \
8282
checkpoint.pretrained_checkpoint=$PRETRAINED_CHECKPOINT \
8383
checkpoint.save=${WORKSPACE}/exp/${MODEL_NAME}_sft_tp${TP}_pp${PP} \

src/megatron/bridge/models/hf_pretrained/base.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,26 @@ def save_artifacts(
211211
for name in self.OPTIONAL_ARTIFACTS:
212212
artifact = getattr(self, name, None)
213213
if artifact is not None and hasattr(artifact, "save_pretrained"):
214-
artifact.save_pretrained(save_path)
214+
try:
215+
artifact.save_pretrained(save_path)
216+
except ValueError:
217+
if name != "generation_config":
218+
raise
219+
220+
source_path = original_source_path or getattr(self, "model_name_or_path", None)
221+
copied_files = []
222+
if source_path is not None:
223+
copied_files = self._copy_custom_modeling_files(
224+
source_path=source_path,
225+
target_path=save_path,
226+
file_patterns=["generation_config.json"],
227+
)
228+
if "generation_config.json" not in copied_files:
229+
raise
230+
logger.warning(
231+
"GenerationConfig.save_pretrained() rejected the source artifact; "
232+
"preserved the original generation_config.json instead."
233+
)
215234

216235
# Download/copy additional files if specified
217236
if additional_files:

tests/unit_tests/doc_consistency/test_readme_consistency.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,37 @@ def test_model_examples_use_current_run_recipe_arguments():
211211
assert not offenders, f"Model examples use removed run_recipe.py arguments: {offenders}"
212212

213213

214+
def test_nemotron_and_qwen2_audio_finetune_launchers_use_exported_recipes():
215+
"""SFT and PEFT example recipes must match their launcher-visible aliases."""
216+
launcher_expectations = {
217+
MODEL_EXAMPLES / "nemotron" / "nemotron_3" / "nano" / "slurm_sft.sh": (
218+
"${MODEL_NAME}_sft_config",
219+
"nemotron_3_nano_finetune_config",
220+
),
221+
MODEL_EXAMPLES / "nemotron" / "nemotron_3" / "nano" / "slurm_peft.sh": (
222+
"${MODEL_NAME}_peft_config",
223+
"nemotron_3_nano_finetune_config",
224+
),
225+
MODEL_EXAMPLES / "qwen" / "qwen2_audio" / "sft.sh": (
226+
"qwen2_audio_7b_sft_config",
227+
"qwen2_audio_7b_finetune_config",
228+
),
229+
}
230+
for path, (expected_recipe, stale_recipe) in launcher_expectations.items():
231+
text = _read(path)
232+
assert expected_recipe in text
233+
assert stale_recipe not in text
234+
235+
defined_recipes = _defined_recipe_names()
236+
expected_recipes = {
237+
"nemotron_3_nano_sft_config",
238+
"nemotron_3_nano_peft_config",
239+
"qwen2_audio_7b_sft_config",
240+
"qwen2_audio_7b_peft_config",
241+
}
242+
assert expected_recipes <= defined_recipes
243+
244+
214245
def test_dclm_readme_megatron_lm_tool_path():
215246
"""The DCLM tutorial points at the bundled submodule tool path (bug 6)."""
216247
gitmodules = _read(REPO_ROOT / ".gitmodules")

tests/unit_tests/models/hf_pretrained/test_base.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pathlib import Path
2121
from unittest.mock import Mock, patch
2222

23+
import pytest
2324
from transformers.configuration_utils import PretrainedConfig
2425

2526

@@ -274,6 +275,42 @@ def test_save_artifacts_without_model_name_or_path():
274275
print("✅ test_save_artifacts_without_model_name_or_path passed")
275276

276277

278+
def test_save_artifacts_preserves_rejected_source_generation_config():
279+
"""Test invalid-but-loadable source generation metadata is preserved verbatim."""
280+
with tempfile.TemporaryDirectory() as tmp_dir:
281+
tmp_path = Path(tmp_dir)
282+
source_dir = tmp_path / "source"
283+
source_dir.mkdir()
284+
source_generation_config = '{"do_sample": false, "temperature": 0.000001}\n'
285+
(source_dir / "generation_config.json").write_text(source_generation_config)
286+
target_dir = tmp_path / "target"
287+
288+
base = MockPreTrainedBase(model_name_or_path=str(source_dir))
289+
base._config = Mock(save_pretrained=Mock())
290+
base._generation_config = Mock()
291+
base._generation_config.save_pretrained.side_effect = ValueError("invalid generation config")
292+
293+
base.save_artifacts(target_dir)
294+
295+
assert (target_dir / "generation_config.json").read_text() == source_generation_config
296+
297+
298+
def test_save_artifacts_reraises_generation_config_error_without_source_file():
299+
"""Test generation config save errors are not hidden without a source artifact."""
300+
with tempfile.TemporaryDirectory() as tmp_dir:
301+
tmp_path = Path(tmp_dir)
302+
source_dir = tmp_path / "source"
303+
source_dir.mkdir()
304+
305+
base = MockPreTrainedBase(model_name_or_path=str(source_dir))
306+
base._config = Mock(save_pretrained=Mock())
307+
base._generation_config = Mock()
308+
base._generation_config.save_pretrained.side_effect = ValueError("invalid generation config")
309+
310+
with pytest.raises(ValueError, match="invalid generation config"):
311+
base.save_artifacts(tmp_path / "target")
312+
313+
277314
def test_copy_handles_permission_errors():
278315
"""Test that copy failures are handled gracefully."""
279316
with tempfile.TemporaryDirectory() as tmp_dir:

0 commit comments

Comments
 (0)