sgl-project/sglang · error · ValueError

video token dim {int(rows.shape[-1])} != patch volume * chan

Error message

video token dim {int(rows.shape[-1])} != patch volume * channel {expected_dim} for latent_shape={list(latent_shape)}, patch_size={[pt, ph, pw]}

What it means

minimax_h3_unpatchify_video_tokens reconstructs a latent from token rows and checks that the per-token feature dim equals pt*ph*pw*channel as implied by latent_shape and patch_size. A mismatch means the rows tensor was produced with a different patch size, channel count, or latent shape than declared, so the inverse einsum would silently corrupt data if allowed.

Source

Thrown at python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/packed_tokens.py:57

    packed = latent.reshape(batch, channel, t, pt, h, ph, w, pw)
    packed = torch.einsum("nctrhpwq->nthwcrpq", packed)
    return packed.reshape(batch * t * h * w, channel * pt * ph * pw).contiguous()


def minimax_h3_unpatchify_video_tokens(
    rows: torch.Tensor,
    *,
    latent_shape: Sequence[int],
    patch_size: Sequence[int],
) -> torch.Tensor:
    """Unpack DiT video token rows into SGLang latent [B,C,T,H,W]."""

    _rank(rows, "video token rows", 2)
    t, h, w, channel = _int_tuple(latent_shape, "latent_shape", 4)
    pt, ph, pw = _int_tuple(patch_size, "patch_size", 3)
    expected_dim = pt * ph * pw * channel
    if int(rows.shape[-1]) != expected_dim:
        raise ValueError(
            f"video token dim {int(rows.shape[-1])} != patch volume * channel "
            f"{expected_dim} for latent_shape={list(latent_shape)}, "
            f"patch_size={[pt, ph, pw]}"
        )
    rows_per_sample = t * h * w
    if int(rows.shape[0]) % rows_per_sample:
        raise ValueError(
            f"video rows {int(rows.shape[0])} must be divisible by t*h*w "
            f"{rows_per_sample} for latent_shape={list(latent_shape)}"
        )
    packed = rows.reshape(-1, t, h, w, channel, pt, ph, pw)
    latent = torch.einsum("nthwcrpq->nctrhpwq", packed)
    return latent.reshape(-1, channel, t * pt, h * ph, w * pw).contiguous()


def minimax_h3_unpack_audio_tokens(
    rows: torch.Tensor,
    *,

View on GitHub (pinned to 0132848349)

Solutions

  1. Pass the same patch_size and channel count that were used when the rows were patchified.
  2. If rows carry extra features, project/select down to pt*ph*pw*channel dims before unpatchify.
  3. Persist patch_size/latent_shape alongside the token rows (e.g. in the plan/state dict) so round-trips use matching params.

Example fix

# before
latent = minimax_h3_unpatchify_video_tokens(rows, latent_shape=shape, patch_size=(1, 8, 8))

# after
latent = minimax_h3_unpatchify_video_tokens(rows, latent_shape=shape, patch_size=(1, 16, 16))  # match patchify
Defensive patterns

Strategy: validation

Validate before calling

pt, ph, pw = patch_size
expected = pt * ph * pw * latent_shape[3]
assert rows.shape[-1] == expected, f"token dim {rows.shape[-1]} != expected {expected}"

Type guard

def rows_match_layout(rows, latent_shape, patch_size) -> bool:
    pt, ph, pw = patch_size
    return rows.ndim == 2 and rows.shape[-1] == pt * ph * pw * latent_shape[-1]

Prevention

When it happens

Trigger: Calling unpatchify with latent_shape=(2,8,8,16) and patch_size=(1,16,16) on rows whose last dim is 1*8*8*16 (produced with patch_size (1,8,8)); or rows carrying an extra projected feature dimension from a DiT output head.

Common situations: Changing patch_size or channel config between patchify and unpatchify calls; feeding raw DiT hidden states instead of the projected token rows; a channel mismatch after switching VAE versions.

Related errors


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