PaddlePaddle/PaddleOCR · error · ValueError

Head mask for a single layer should be of shape {(self.num_h

Error message

Head mask for a single layer should be of shape {(self.num_heads,)}, but is {layer_head_mask.shape}

What it means

In the eager attention path of the UniMERNet decoder layer, an optional per-layer head mask must have exactly num_heads elements so it can scale each attention head's weights. The code validates tuple(layer_head_mask.shape) == (num_heads,) before applying the mask, raising ValueError otherwise because a wrong-size mask would broadcast incorrectly over the [bsz, num_heads, tgt, src] weights.

Source

Thrown at ppocr/modeling/heads/rec_unimernet_head.py:628

        key_states = key_states.reshape(proj_shape)
        value_states = value_states.reshape(proj_shape)

        src_len = key_states.shape[1]
        attn_weights = paddle.bmm(query_states, key_states.transpose([0, 2, 1]))

        if attention_mask is not None:
            attn_weights = (
                attn_weights.reshape([bsz, self.num_heads, tgt_len, src_len])
                + attention_mask
            )
            attn_weights = attn_weights.reshape(
                [bsz * self.num_heads, tgt_len, src_len]
            )

        attn_weights = nn.functional.softmax(attn_weights, axis=-1)
        if layer_head_mask is not None:
            if tuple(layer_head_mask.shape) != (self.num_heads,):
                raise ValueError(
                    f"Head mask for a single layer should be of shape {(self.num_heads,)}, but is"
                    f" {layer_head_mask.shape}"
                )
            attn_weights = layer_head_mask.reshape(
                [1, -1, 1, 1]
            ) * attn_weights.reshape([bsz, self.num_heads, tgt_len, src_len])
            attn_weights = attn_weights.reshape(
                [bsz * self.num_heads, tgt_len, src_len]
            )

        if output_attentions:
            attn_weights_reshaped = attn_weights.reshape(
                [bsz, self.num_heads, tgt_len, src_len]
            )
            attn_weights = attn_weights_reshaped.reshape(
                [bsz * self.num_heads, tgt_len, src_len]
            )
        else:

View on GitHub (pinned to 2661c7c0ef)

Solutions

  1. Pass head_mask=None (the normal path for this head; head pruning is rarely needed)
  2. If masking heads, supply shape [num_layers, num_heads] so each layer slice is (num_heads,)

Example fix

# before
out = layer(hidden, head_mask=head_mask_scalar)  # wrong shape
# after
out = layer(hidden, head_mask=None)
Defensive patterns

Strategy: validation

Validate before calling

if head_mask is not None:
    assert head_mask.shape[1:] == (num_heads,), f'per-layer head mask must be ({num_heads},)'

Type guard

def valid_head_mask(mask, num_layers: int, num_heads: int) -> bool:
    return mask is None or (tuple(mask.shape) == (num_layers, num_heads))

Prevention

When it happens

Trigger: Passing head_mask (or cross_attn_head_mask) to the decoder forward with a per-layer slice whose shape is not (num_heads,), e.g. shape [num_layers] used directly, or a scalar broadcast mask.

Common situations: Porting generation code from transformers where head_mask semantics differ; debugging attention with a custom mask tensor whose leading layer dimension was indexed incorrectly.

Related errors


AI-assisted analysis of PaddlePaddle/PaddleOCR@2661c7c0ef (2026-08-14). Data as JSON: /api/errors/3b3ff83ec328c4ba. Report an issue: GitHub.