sgl-project/sglang · error · ValueError

update_mask length mismatch: {update_mask.shape[0]} != {vide

Error message

update_mask length mismatch: {update_mask.shape[0]} != {video_logits.shape[0]}

What it means

Before zeroing out condition rows in output logits, forward validates that update_mask (flattened) length equals the gathered video_logits row count. A mismatch means the mask was built for a different token set than the logits.

Source

Thrown at python/sglang/multimodal_gen/runtime/models/dits/minimax_h3.py:2638

                torch.cat((video_logits, audio_logits), dim=-1), dim=0
            )
            video_logits, audio_logits = logits.split(
                (video_width, logits.shape[-1] - video_width), dim=-1
            )

        # Preserve the full-row output GEMM (and therefore its numerical
        # contract), but defer TP column gathers until after dead text/padding
        # rows have been removed. For hybrid TP+Ulysses, the preceding SP row
        # gather also carries only the TP-local output width.
        video_logits = video_logits.index_select(0, infer_out_pos.to(device))
        audio_logits = audio_logits.index_select(0, audio_pos.to(device))
        if get_tp_world_size() > 1:
            video_logits = tensor_model_parallel_all_gather(video_logits)
            audio_logits = tensor_model_parallel_all_gather(audio_logits)
        if not skip_mask_out_condition:
            update_mask = update_mask.view(-1).to(device)
            if update_mask.shape[0] != video_logits.shape[0]:
                raise ValueError(
                    "update_mask length mismatch: "
                    f"{update_mask.shape[0]} != {video_logits.shape[0]}"
                )
            video_logits = video_logits * update_mask.unsqueeze(-1)
            # Audio has no condition rows in the supported tasks, so its
            # derived update mask is all ones. Honor an explicit mask when
            # provided.
            update_audio_mask = kwargs.get("update_audio_mask")
            if update_audio_mask is not None:
                audio_logits = audio_logits * update_audio_mask.view(-1).unsqueeze(-1)
        return video_logits, audio_logits


EntryClass = MiniMaxH3DiTModel

__all__ = [
    "MINIMAX_H3_FP32_BUFFER_NAMES",
    "MINIMAX_H3_FP32_PARAM_NAMES",

View on GitHub (pinned to 0132848349)

Solutions

  1. Build update_mask over exactly the video token rows of the packed sequence (length must equal video_logits.shape[0] after all-gather)
  2. If you pass token_tags, prefer deriving the mask from tags so lengths stay consistent
  3. Verify skip_mask_out_condition when condition zeroing is not needed

Example fix

// before
model(..., update_mask=local_mask)  # length != gathered video rows
// after
video_mask = torch.ones(num_video_rows, device=x.device)
video_mask[cond_video_rows] = 0
model(..., update_mask=video_mask)
Defensive patterns

Strategy: validation

Validate before calling

update_mask = update_mask.view(-1)
assert update_mask.shape[0] == num_gathered_video_rows, (update_mask.shape[0], num_gathered_video_rows)

Prevention

When it happens

Trigger: Passing an update_mask sized to the pre-TP-gather local rows, or to text+video tokens, while video_logits has the full gathered video-row count.

Common situations: TP all-gather changing row counts vs. locally-built masks; update masks built before packing/padding changes; masks computed for audio rows instead of video rows.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/f7da28beec300f37. Report an issue: GitHub.