Skip to content

MTP: test_generate_with_mtp skips for every checkpoint but Inkling, and MtpModel.forward raises with the default logits_processor=None #48003

Description

@pjordanandrsn

System info

  • transformers on main (c49429ed). Both are static—neither needs a run to see.

Who can help?

@Cyrilvallez@zucchini-nlp asked in
#47912 that
MTP-model-specific things go in their own issue tagged to you.

Two things noticed while working on #47912, the EOS-past-stop bug in the shared assisted-decoding path.
Neither is that bug; both are MTP-side.

1. test_generate_with_mtp skips for every checkpoint except Inkling

tests/generation/test_utils.py:2483 gates on the model's _keys_to_ignore_on_load_unexpected:

keys_to_ignore_unexpected = model_class._keys_to_ignore_on_load_unexpected or []
# If we don't have any mtp patterns, skip
if not hasattr(config.get_text_config(), "num_mtp_layers") or not any(
    "mtp" in x or re.search(r"layers\.\d+", x) is not None for x in keys_to_ignore_unexpected
):
    self.skipTest("No MTP keys registered")

Those entries are regexes, so their dots are escaped in the source, and re.search(r"layers\.\d+", x) wants
a literal . where the string has \.:

model _keys_to_ignore_on_load_unexpected gate
deepseek_v3 [r"model\.layers\.61.*"] skips
glm4_moe [r"model\.layers\.92.*", r"model\.layers\.46.*"] skips
solar_open None skips
inkling [r"model\.mtp\..*"] runs, on "mtp" in x

So the model.layers.{num_hidden_layers+1} half of what the docstring says it covers is never exercised—the
only model reaching the test is the one registering an mtp. pattern. re.search(r"layers\\?\.\d+", x)
matches both spellings.

Worth saying it would not have caught #47912 either way: it asserts only that generation raises nothing.

2. MtpModel.forward raises UnboundLocalError when logits_processor is None

src/transformers/modeling_layers.py:540:

if logits_processor is not None and full_input_ids is not None:
    next_token_scores = logits_processor(full_input_ids, next_token_logits.to(torch.float32))
if do_sample:
    probs = nn.functional.softmax(next_token_scores, dim=-1, dtype=torch.float32)
    next_mtp_token = torch.multinomial(probs, num_samples=1)
else:
    next_mtp_token = torch.argmax(next_token_scores, dim=-1, keepdim=True)

next_token_scores is bound only inside the guard and read unconditionally two lines below, so the call
raises UnboundLocalError whenever either argument is None. MTPCandidateGenerator.__init__ declares
logits_processor: Optional["LogitsProcessorList"] = None, so constructing one directly and drafting hits
it.

Not reachable through generate, which always passes a LogitsProcessorList and supplies full_input_ids
at candidate_generator.py:1529—so it is a trap for direct construction only. The tests in #48002 pass an
explicit LogitsProcessorList() for this reason. Falling back to next_token_logits, or making the
argument required, would both do.


AI-assisted: found and written up with Claude Code; both claims re-checked against main before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions