{"record":{"id":"d5363d96557600ab","repo":"sgl-project/sglang","slug":"qwenimage-text-conditioning-mask-has-shape-tuple","errorCode":null,"errorMessage":"QwenImage text conditioning mask has shape {tuple(mask.shape)}, expected {(batch_size, text_seq_len)}.","messagePattern":"QwenImage text conditioning mask has shape (.+?), expected (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/configs/pipeline_configs/qwen_image.py","lineNumber":438,"sourceCode":"        dim], so we pass a [batch, text_seq_len] boolean mask to keep attention\n        on real text tokens and ignore padding.\n\n        If every request uses the full padded length, no mask is needed and this\n        returns None. Otherwise, prefer the embedding-aligned mask stored by the\n        text encoding stage. If that is unavailable, rebuild the same mask from\n        `txt_seq_lens`: position j is valid for row i when\n        `j < txt_seq_lens[i]`.\n        \"\"\"\n        if all(seq_len == text_seq_len for seq_len in txt_seq_lens):\n            return None\n\n        masks_by_encoder = (\n            batch.negative_prompt_embeds_mask if negative else batch.prompt_embeds_mask\n        )\n        if masks_by_encoder is not None and encoder_index < len(masks_by_encoder):\n            mask = masks_by_encoder[encoder_index]\n            if mask.shape != (batch_size, text_seq_len):\n                raise ValueError(\n                    \"QwenImage text conditioning mask has shape \"\n                    f\"{tuple(mask.shape)}, expected {(batch_size, text_seq_len)}.\"\n                )\n            return mask\n\n        # TODO: cache positions by (device, text_seq_len) if this allocation shows up hot.\n        positions = torch.arange(text_seq_len, device=batch.prompt_embeds[0].device)\n        seq_lens = torch.tensor(\n            txt_seq_lens,\n            device=batch.prompt_embeds[0].device,\n            dtype=torch.long,\n        )\n        return positions.unsqueeze(0) < seq_lens.unsqueeze(1)\n\n    def prepare_pos_cond_kwargs(self, batch, device, rotary_emb, dtype):\n        return self._prepare_cond_kwargs(\n            batch, batch.prompt_embeds, rotary_emb, device, dtype, negative=False\n        )","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/configs/pipeline_configs/qwen_image.py#L420-L456","documentation":"For text conditioning, QwenImage expects a per-encoder boolean mask of shape (batch_size, text_seq_len) matching the current batch. If the stored prompt (or negative prompt) embeds mask has a different shape — stale cache, mismatched encoder index, or reshaped batch — this ValueError is thrown in _prepare_encoder_hidden_states_mask.","triggerScenarios":"Batch size or text sequence length changed between embedding computation and denoising (e.g. CFG negative branch with different prompt lengths); reusing cached prompt_embeds_mask from a previous request with a different batch/seq len; encoder_index pointing at a mask computed with different padding.","commonSituations":"Mixing cached text embeddings across requests with different batch sizes or prompt token counts; padding/tokenization inconsistencies between prompt and negative prompt paths; dynamic batching that changes batch_size after masks were computed.","solutions":["Recompute text embeddings/masks for the current batch instead of reusing cached ones","Ensure prompt and negative prompt go through the same tokenizer/padding so masks share (batch_size, text_seq_len)","If caching, key the cache by (batch_size, text_seq_len) and invalidate on mismatch"],"exampleFix":"# before\nmask = cached_masks[encoder_index]  # from earlier request\n\n# after\nmask = encode_text(batch)  # recompute for current batch_size / text_seq_len\nassert mask.prompt_embeds_mask[0].shape == (batch_size, text_seq_len)","handlingStrategy":"validation","validationCode":"expected = (batch_size, text_seq_len)\nfor enc_idx in range(num_encoders):\n    m = masks[enc_idx]\n    assert m is None or tuple(m.shape) == expected, f\"mask {enc_idx}: {tuple(m.shape)} != {expected}\"","typeGuard":null,"tryCatchPattern":"try:\n    mask = pipe._prepare_encoder_hidden_states_mask(batch, encoder_index, negative, batch_size, text_seq_len)\nexcept ValueError:\n    batch.prompt_embeds_mask = None  # drop stale cache\n    batch = pipe.encode_text(batch)  # recompute\n    mask = pipe._prepare_encoder_hidden_states_mask(batch, encoder_index, negative, batch_size, text_seq_len)","preventionTips":["Never mix cached embed masks across batches with different sizes/lengths","Key mask caches by (batch_size, text_seq_len)","Apply identical padding to positive and negative prompt encodes"],"tags":["qwen-image","shape-mismatch","mask-validation","text-embedding"],"backgroundTag":"tensor-shape-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}