{"record":{"id":"025098ede28599b5","repo":"sgl-project/sglang","slug":"cached-keyframe-preparation-disagrees-with-the-res","errorCode":null,"errorMessage":"cached keyframe preparation disagrees with the resolved plan","messagePattern":"cached keyframe preparation disagrees with the resolved plan","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/canvas.py","lineNumber":158,"sourceCode":"\n\ndef minimax_h3_prepared_keyframes(batch: Any, plan: Any) -> dict[str, Any]:\n    \"\"\"Resolve + prepare one or two first/last keyframes once per request.\n\n    The target canvas is shared across keyframes and must already be frozen by\n    the pre-queue probe/resolve hook.\n    Top-level ``image`` / ``canvas_width`` / ``canvas_height`` keys mirror the\n    first-keyframe payload for compatibility; per-keyframe entries live under\n    ``images``.\n    \"\"\"\n    keyframes = _keyframe_materials(plan)\n    semantic_indices = _validate_keyframe_materials(plan, keyframes)\n    cached = batch.extra.get(MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY)\n    if cached is not None:\n        cached_indices = tuple(cached.get(\"semantic_frame_indices\") or ())\n        cached_images = cached.get(\"images\") or ()\n        if cached_indices != semantic_indices or len(cached_images) != len(keyframes):\n            raise ValueError(\n                \"cached keyframe preparation disagrees with the resolved plan\"\n            )\n        return cached\n\n    canvas_w, canvas_h = _keyframe_canvas_size(plan.shape)\n\n    from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.minimax_h3.prequeue import (\n        MINIMAX_H3_PROBE_FACTS_EXTRA_KEY,\n        MINIMAX_H3_RESOLVED_MATERIAL_SHAPES_EXTRA_KEY,\n    )\n\n    probe_facts = batch.extra.get(MINIMAX_H3_PROBE_FACTS_EXTRA_KEY)\n    material_shapes = batch.extra.get(MINIMAX_H3_RESOLVED_MATERIAL_SHAPES_EXTRA_KEY)\n    for material in keyframes:\n        condition_index = int(material.condition_index)\n        facts = (\n            probe_facts.get(condition_index) if isinstance(probe_facts, dict) else None\n        )","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/sgl-project/sglang/blob/0132848349585cfe6aae51c4941cbae872505f8a/python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/minimax_h3/canvas.py#L140-L176","documentation":"The stage found a previously computed keyframe preparation cached under MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY in batch.extra, but its semantic_frame_indices or image count does not match the freshly validated plan. This protects against serving a stale cache entry that would encode keyframes inconsistent with the current request plan.","triggerScenarios":"batch.extra carries a MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY entry from an earlier run while the plan's semantic_indices or keyframes list length changed between runs within the same batch object.","commonSituations":"Retrying or re-running a pipeline stage on the same batch after modifying the plan, sharing batch objects across pipeline variants, or a caching layer that keys only on batch identity rather than plan content.","solutions":["Clear/invalidate the MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY entry in batch.extra whenever the plan or keyframe list changes","Make the cache key content-addressed (hash of semantic_indices + image count) instead of reusing the extra slot blindly","If regenerating materials, remove the cached dict before calling minimax_h3_prepared_keyframes"],"exampleFix":"// before\nresult = minimax_h3_prepared_keyframes(plan, keyframes, batch)  # batch.extra holds stale cache\n// after\nbatch.extra.pop(MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY, None)\nresult = minimax_h3_prepared_keyframes(plan, keyframes, batch)","handlingStrategy":"validation","validationCode":"cached = batch.extra.get(MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY)\nif cached is not None:\n    if tuple(cached.get('semantic_frame_indices') or ()) != semantic_indices or len(cached.get('images') or ()) != len(keyframes):\n        del batch.extra[MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY]","typeGuard":"def cache_is_fresh(cached, semantic_indices, n) -> bool:\n    return (tuple(cached.get('semantic_frame_indices') or ()) == tuple(semantic_indices)\n            and len(cached.get('images') or ()) == n)","tryCatchPattern":"try:\n    result = minimax_h3_prepared_keyframes(plan, keyframes, batch)\nexcept ValueError as e:\n    if 'cached keyframe preparation' in str(e):\n        batch.extra.pop(MINIMAX_H3_PREPARED_KEYFRAMES_EXTRA_KEY, None)\n        result = minimax_h3_prepared_keyframes(plan, keyframes, batch)\n    else:\n        raise","preventionTips":["Invalidate the extra-key cache on any plan mutation","Key caches by plan content, not batch identity"],"tags":["minimax-h3","cache-coherence","pipeline","keyframes"],"backgroundTag":"stale-cache-entry","analyzedSha":"0132848349585cfe6aae51c4941cbae872505f8a","analyzedAt":"2026-08-28T05:10:05.995Z","schemaVersion":2},"datasetVersion":"2026-08-28T06:17:29.519Z"}