Skip to content

Commit 1b674d8

Browse files
authored
fix: apply --draft-attn-impl when loading with --from-pretrained (vllm-project#755)
### Problem `--draft-attn-impl` is silently dropped on the `--from-pretrained` load path: HF configs never serialize `_attn_implementation`, and the load call doesn't re-apply the CLI choice, so the loaded draft always resolves the model default (`simple_flex_attention`). On backends without FlexAttention this crashes during offline eval of trained checkpoints. Details and reproduction in vllm-project#754. ### Fix Re-apply the CLI selection on the loaded config before model construction, mirroring the `from_training_args` mechanism (`transformer_layer_config._attn_implementation`), on both the weights-loading path and the config-only path. Applying the default value is behavior-preserving, so this is a no-op for anyone not using the flag. ### Validation - Loading a DSpark checkpoint with `--from-pretrained --val-only --draft-attn-impl <non-default>` now routes to the selected backend (previously: flex dispatch error). - Offline eval of a GLM-5.2 draft over six public benchmarks ran end-to-end on this path; scoring reproducibility across independent runs Δ=0.14%. Fixes vllm-project#754 --------- Signed-off-by: Frozen7771 <133861939+Frozen7771@users.noreply.github.com>
1 parent 548f9f7 commit 1b674d8

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

scripts/train.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ def _build_from_config_only(
366366
t2d: torch.Tensor | None,
367367
d2t: torch.Tensor | None,
368368
verifier_name_or_path: str | None = None,
369+
draft_attn_impl: str | None = None,
369370
) -> SpeculatorModel:
370371
"""Initialize a fresh draft from a saved speculator *config* (no weights).
371372
@@ -374,6 +375,8 @@ def _build_from_config_only(
374375
no trained draft weights to restore (decoder weights are randomly initialized).
375376
"""
376377
config = model_class.config_class.from_pretrained(path)
378+
if draft_attn_impl is not None:
379+
config.transformer_layer_config._attn_implementation = draft_attn_impl
377380
speculators_config = getattr(config, "speculators_config", None)
378381
# Fall back to the CLI --verifier-name-or-path only when the saved config has
379382
# no verifier path -- either null or blanked to "". A real path in the config
@@ -426,6 +429,23 @@ def build_draft_model(
426429
t2d=t2d,
427430
d2t=d2t,
428431
verifier_name_or_path=args.verifier_name_or_path,
432+
draft_attn_impl=(
433+
args.draft_attn_impl if args.speculator_type != "mtp" else None
434+
),
435+
)
436+
if args.speculator_type != "mtp":
437+
# _attn_implementation is never serialized by HF configs, so re-apply
438+
# the CLI selection before construction -- mirroring from_training_args.
439+
# MTP is skipped: its from_training_args never sets the field and its
440+
# __init__ resolves its own default ("eager") when it is absent.
441+
config = model_class.config_class.from_pretrained(args.from_pretrained)
442+
config.transformer_layer_config._attn_implementation = args.draft_attn_impl
443+
return model_class.from_pretrained(
444+
args.from_pretrained,
445+
config=config,
446+
t2d=t2d,
447+
d2t=d2t,
448+
verifier=args.verifier_name_or_path,
429449
)
430450
return model_class.from_pretrained(
431451
args.from_pretrained,

tests/unit/train/test_draft_config_init.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,35 @@ def test_build_from_config_only_preserves_existing_verifier_name(tmp_path):
434434
assert built.config.speculators_config.verifier.name_or_path == "real-verifier"
435435

436436

437+
def test_config_roundtrip_drops_attn_implementation(tmp_path):
438+
# Precondition of the --from-pretrained bug: HF configs never serialize
439+
# _attn_implementation, so the field does not survive a save/load round-trip
440+
# and must be re-applied from the CLI selection.
441+
config = _make_eagle3_config()
442+
config.transformer_layer_config._attn_implementation = "sdpa"
443+
save_dir = tmp_path / "roundtrip"
444+
config.save_pretrained(str(save_dir))
445+
446+
reloaded = Eagle3SpeculatorConfig.from_pretrained(str(save_dir))
447+
448+
assert reloaded.transformer_layer_config._attn_implementation != "sdpa"
449+
450+
451+
def test_build_from_config_only_reapplies_draft_attn_impl(tmp_path):
452+
model_dir = _save_config_only_dir(tmp_path)
453+
454+
with patch.object(Eagle3DraftModel, "load_verifier_weights"):
455+
built = _build_from_config_only(
456+
Eagle3DraftModel,
457+
str(model_dir),
458+
None,
459+
None,
460+
draft_attn_impl="sdpa",
461+
)
462+
463+
assert built.config.transformer_layer_config._attn_implementation == "sdpa"
464+
465+
437466
# ---------------------------------------------------------------------------
438467
# build_draft_model: MTP-from-scratch routing
439468
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)