Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(masking): preserve dict return type in create_masks_for_generate single-pattern path#46988

Closed
fyhon wants to merge 1 commit into
huggingface:mainfrom
fyhon:fix/issue-46962
Closed

fix(masking): preserve dict return type in create_masks_for_generate single-pattern path#46988
fyhon wants to merge 1 commit into
huggingface:mainfrom
fyhon:fix/issue-46962

Conversation

@fyhon

@fyhon fyhon commented Jul 1, 2026

Copy link
Copy Markdown

CI

Summary

Fixes #46962. Restores the dict return-type contract of create_masks_for_generate for the single-pattern early-return path introduced by #41251, so that downstream models like Qwen3Model.forward do not silently rebuild the mask that generate just precomputed.

Changes

  • src/transformers/masking_utils.py (2 insertions, 1 deletion): wrap the single-pattern early-return in a dict keyed by the layer pattern name.

Before:

if len(layer_patterns) == 1:
    return LAYER_PATTERN_TO_MASK_FUNCTION_MAPPING[next(iter(layer_patterns))](**mask_kwargs)

After:

if len(layer_patterns) == 1:
    single_pattern = next(iter(layer_patterns))
    return {single_pattern: LAYER_PATTERN_TO_MASK_FUNCTION_MAPPING[single_pattern](**mask_kwargs)}

The unknown-types path (L1560-1561, return attention_mask) is intentionally left untouched: returning the raw attention_mask there is a deliberate 'defer to model' fallback whose semantics differ from the dict path — wrapping it in a synthetic key would over-reach.

Root Cause

PR #41251 added two early-return paths in create_masks_for_generate that return bare values instead of the dict wrapper the function had always produced when layer_types was set. Downstream forward methods (e.g. Qwen3Model.forward, L403) detect precomputed masks with isinstance(attention_mask, dict); a bare mask value fails the check and triggers a second create_causal_mask(...) call. In XPU/CPU paths this second call falls into _can_skip_causal_mask and cannot prove the mask is purely causal, so it builds a full 4D mask with is_causal=False, silently degrading generate() throughput.

Duplicate Check

  • gh pr list --repo huggingface/transformers --state all --search "46962 in:body" → 0 results
  • gh pr list --repo huggingface/transformers --state open --search "create_masks_for_generate return type" → 0 results

Testing

Environment: CPU-only container based on hub.bilibili.co/nyx-base/vllm:0.18.0 (python 3.10, torch 2.10+cu128 with CUDA unavailable), transformers 5.8.0.dev0 (upstream/main@181beb3ba4 + this patch).

pytest tests/utils/test_masking_utils.py -q
# → 10 passed in 18.33s (up from 9 passed before PR #46738 landed)

pytest tests/models/qwen3/test_modeling_qwen3.py -q -k "mask or generate or forward or attention"
# → 58 passed, 22 skipped, 185 deselected in 28.40s

python3 repro_46962.py
# → single_pattern → dict (fixed), multi_pattern → dict (unchanged), unknown_types → bare (preserved by design)

Reproduction script (based on the repro_contract.py attached to the issue):

  • Case A (layer_types=["full_attention"]*4) — used to return Tensor, now returns {"full_attention": Tensor}
  • Case B (layer_types=["unknown_attn_type"]*2) — still returns bare None, deliberate 'defer to model'
  • Case C (layer_types=["full_attention", "sliding_attention", "full_attention"]) — always returned dict, unchanged

Downstream Qwen3Model.forward now uses the precomputed mask on Case A instead of rebuilding it.

AI Disclosure

This PR was drafted with AI assistance (Claude Opus 4.6). The human submitter (@fyhon) has reviewed each changed line, ran the tests reported above, and takes responsibility for the change. The AI's role was scoped to:

  1. Locating the two early-return paths and identifying which downstream models rely on the dict contract.
  2. Composing the reproduction script and the patch.
  3. Drafting this PR description.

No maintainer coordination was needed beyond the routing already present in the issue thread (Rocketknight1 cc'd @ArthurZucker), because the fix is a direct restoration of the pre-#41251 return-type contract on the specific path that regresses Qwen3.

…single-pattern path

PR huggingface#41251 introduced an early-return path in create_masks_for_generate that
returns a bare mask value (instead of a dict) when effective_config.layer_types
contains a single unique pattern. Downstream forward methods like
Qwen3Model.forward use isinstance(attention_mask, dict) to detect whether the
mask was already prepared by generate(), so a bare return causes the model to
silently rebuild the mask, defeating the purpose of precomputation.

Wrap the single-pattern early-return in {layer_pattern: mask} to keep the
dict contract. The unknown-types path (L1561) is left untouched: returning the
raw attention_mask there is a deliberate 'defer to model' fallback whose
semantics differ from the dict path.

Fixes huggingface#46962

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 28489555808:2
Result: failure | Jobs: 14 | Tests: 70,150 | Failures: 4 | Duration: 17h 55m

@Rocketknight1

Copy link
Copy Markdown
Member

Would prefer to wait for Arthur to reply in the thread rather than just asking Claude to make a change immediately!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Qwen3] create_masks_for_generate return type change after #41251

2 participants