{"record":{"id":"fa8314a96d9def13","repo":"sgl-project/sglang","slug":"predict-num-frames-supports-a-single-prediction-on","errorCode":null,"errorMessage":"predict_num_frames supports a single prediction only, got shape {tuple(predicted_seconds.shape)}. One frame count cannot serve prompts with different natural durations.","messagePattern":"predict_num_frames supports a single prediction only, got shape (.+?)\\. One frame count cannot serve prompts with different natural durations\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/models/adapter/ltx_2_duration_head.py","lineNumber":136,"sourceCode":"\n    def predict_num_frames(\n        self,\n        video_tokens: torch.Tensor | None = None,\n        audio_tokens: torch.Tensor | None = None,\n        *,\n        frame_rate: float,\n        temporal_compression_ratio: int,\n        min_seconds: float = 1.0,\n        max_seconds: float = 20.0,\n    ) -> int:\n        \"\"\"Predict a frame count on the VAE's causal temporal grid.\n\n        Clamp first, then snap: a clamped count is not necessarily grid-aligned,\n        so snapping first would give a different answer.\n        \"\"\"\n        predicted_seconds = self(video_tokens, audio_tokens)\n        if predicted_seconds.numel() != 1:\n            raise ValueError(\n                \"predict_num_frames supports a single prediction only, got shape \"\n                f\"{tuple(predicted_seconds.shape)}. One frame count cannot serve \"\n                \"prompts with different natural durations.\"\n            )\n        seconds = predicted_seconds.item()\n\n        # Floor at 1 so the grid arithmetic cannot go negative.\n        min_frames = max(1, round(min_seconds * frame_rate))\n        max_frames = round(max_seconds * frame_rate)\n        clamped_frames = max(min_frames, min(round(seconds * frame_rate), max_frames))\n\n        num_frames = (\n            (clamped_frames - 1) // temporal_compression_ratio\n        ) * temporal_compression_ratio + 1\n\n        if num_frames < min_frames:\n            # Flooring undershot the lower bound; take the next grid point up.\n            snapped_up = num_frames + temporal_compression_ratio","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/models/adapter/ltx_2_duration_head.py#L118-L154","documentation":"predict_num_frames converts a predicted duration (seconds) into a single frame count, which only makes sense for exactly one sample. If the model was given a batch (or a multi-prompt tensor), one scalar frame count cannot represent different natural durations, so it errors when predicted_seconds.numel() != 1.","triggerScenarios":"Calling `head.predict_num_frames(video_tokens=..., audio_tokens=...)` with a batched tensor of shape (B, ...) where B > 1, since forward returns shape (B,) and numel() > 1.","commonSituations":"Scaling a single-sample generation script to batch inference; mixing per-prompt loops with vectorized batching; tests that accidentally pass a batch dimension of 2.","solutions":["Call predict_num_frames per sample: index/slice the token tensors to batch size 1 before calling","Restructure the loop to iterate over batch items and collect per-item frame counts","If you truly want one count for all prompts, pick per-sample prediction first and then aggregate deliberately"],"exampleFix":"# before\nnum_frames = head.predict_num_frames(video_tokens, audio_tokens)  # batched\n# after\nnum_frames = [\n    head.predict_num_frames(video_tokens[i:i+1], audio_tokens[i:i+1] if audio_tokens is not None else None)\n    for i in range(video_tokens.shape[0])\n]","handlingStrategy":"validation","validationCode":"if video_tokens.shape[0] > 1:\n    raise ValueError(\"call predict_num_frames per sample, batched input unsupported\")\nnum_frames = head.predict_num_frames(video_tokens, audio_tokens)","typeGuard":"def is_single_sample(t: torch.Tensor) -> bool:\n    return t.dim() >= 1 and t.shape[0] == 1","tryCatchPattern":"try:\n    n = head.predict_num_frames(v, a)\nexcept ValueError as e:\n    if \"single prediction only\" in str(e):\n        n = [head.predict_num_frames(v[i:i+1], None if a is None else a[i:i+1]) for i in range(v.shape[0])]\n    else:\n        raise","preventionTips":["Treat predict_num_frames as a per-sample API in batched code paths","Add a batch-size-1 assertion in shared helpers that wrap this call"],"tags":["batching","duration-head","shape-validation","ltx-2"],"backgroundTag":"batch-size-mismatch","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}