Root cause
Authentication & access control introduced
The Claude-marked feature adds the selectable DeepStream codec and its cached pool without the subsequently required controls.
How AI contributed: Direct introductionPublished Aug 11, 2026 in
vllm-project/vllm
The AI-assisted codec feature is absent from the GPU registry and accepts first-decode pool sizing from requests. Source releases 0.26.0, 0.27.0 and 0.27.1 retain these omissions; 0.28.0 contains classification, startup-only sizing and pre-decode metadata checks.
Root cause
The Claude-marked feature adds the selectable DeepStream codec and its cached pool without the subsequently required controls.
Fix
The Cursor-marked repair member implements the controls; a second member restores software-codec compatibility before the default-branch closure.
Case facts
@@ -752,19 +752,128 @@ class PyNvVideoCodecVideoBackendMixin: return frames, source, frame_idx, valid_frame_indices +class DeepStreamVideoBackendMixin:+ """NVIDIA DeepStream (NVDEC) GPU-decode codec utilities.++ Decoding runs on a shared pool of daemon threads inside one CUDA+ context (see the ``nvidia-deepstream-videodecode-cu13`` package). The+ container bytes are pushed into an ``appsrc`` GStreamer pipeline, so no+ local file path is required — HTTP and base64 sources decode identically+ to local files.++ Like the OpenCV/PyAV mixins, this provides only the codec layer.+ Frame *selection* lives in the loader's+ ``compute_frames_index_to_sample`` and arrives here as an explicit+ list of frame indices.+ """++ # Process-wide lazy decode pool, shared across all DeepStream backends.+ _pool: ClassVar[Any] = None+ _pool_lock: ClassVar[Any] = None++ @classmethod+ def _get_pool(cls, pool_size: int | None = None):+ """Lazy-initialize the shared decode pool on first use.++ ``pool_size`` (number of decode worker threads) comes from+ ``--media-io-kwargs`` (``{"video": {"pool_size": N}}``); when unset it+ defaults to the existing ``VLLM_MEDIA_LOADING_THREAD_COUNT`` so no+ DeepStream-specific env var is needed. The pool is a process-wide+ singleton, so the first decode's value wins.+ """+ if cls._pool is not None:+ return cls._pool+ if cls._pool_lock is None:+ cls._pool_lock = threading.Lock()+ with cls._pool_lock:+ if cls._pool is not None:+ return cls._pool+ import os++ from nvidia.deepstream_videodecode import DecodePool++ if pool_size is None:+ pool_size = os.environ.get("VLLM_MEDIA_LOADING_THREAD_COUNT", 8)+ pool_size = max(1, min(int(pool_size), 16))+ logger.info(+ "[DeepStream] initializing decode pool with %d workers",+ pool_size,+ )+ cls._pool = DecodePool(num_workers=pool_size)+ return cls._pool++ @classmethod+ def decode_indices(+ cls,+ data: bytes,+ frame_indices: list[int],+ source: VideoSourceMetadata,+ codec: str = "",+ pool_size: int | None = None,+ timeout_sec: float = 120.0,+ ) -> tuple[npt.NDArray, list[int]]:+ """Decode the requested frame indices from raw container bytes.++ The whole stream is decoded; the pool keeps exactly the frames whose+ decode-order index is in ``frame_indices`` (1:1, frame-exact) and+ sends EOS once the last one is matched.++ ``codec`` (e.g. ``"h264"``/``"hevc"``) lets the pool keep its NVDEC+ session warm across same-codec streams and rebuild only on a codec+ change. Frames are returned as a CPU NHWC uint8 array so the+ upstream multimodal parser sees the same shape as the other+ backends.+ """+ if not frame_indices:+ raise ValueError("DeepStream backend received no frame indices")++ result = cls._get_pool(pool_size).decode(+ data,+ target_indices=frame_indices,+ codec=codec,+ max_frames=len(frame_indices),+ timeout_sec=timeout_sec,+ )+ if result.error:+ raise ValueError(f"DeepStream decode failed: {result.error}")+ if result.frames is None or result.n_kept == 0:+ raise ValueError("DeepStream decode produced no frames")++ valid = frame_indices[: result.n_kept]+ # GPU -> CPU NHWC uint8 at the codec boundary (one PCIe copy); keeps+ # the array shape identical to the OpenCV/PyAV backends. Copy into+ # PINNED host memory (reused across calls by PyTorch's pinned caching+ # allocator) so the D2H runs at full PCIe bandwidth (~13 GB/s) rather+ # than the ~1 GB/s pageable path that plain ``.cpu()`` takes — ~12x+ # faster for a 1080p x8 frame batch (~46ms -> ~4ms). ``numpy()`` keeps+ # the pinned tensor alive via the array's base.+ import torch++ gpu = result.frames+ if gpu.is_cuda:+ host = torch.empty(gpu.shape, dtype=gpu.dtype, pin_memory=True)+ host.copy_(gpu, non_blocking=True)+ torch.cuda.current_stream().synchronize()+ arr = host.numpy()+ else:+ arr = gpu.numpy()+ return arr, valid++ @VIDEO_LOADER_REGISTRY.register("opencv") class VideoBackend( VideoLoader, OpenCVVideoBackendMixin, PyAVVideoBackendMixin, PyNvVideoCodecVideoBackendMixin,+ DeepStreamVideoBackendMixin, ): """Uniform-sampling video backend. Samples ``num_frames`` uniformly across the video (or one frame every ``1/fps`` seconds, whichever produces fewer frames). The decoding codec- is selected via the ``backend`` kwarg (``"opencv"``, ``"pyav"``, or- ``"pynvvideocodec"``), which can be passed through+ is selected via the ``backend`` kwarg (``"opencv"``, ``"pyav"``,+ ``"pynvvideocodec"``, or ``"deepstream"``), which can be passed through ``--media-io-kwargs``. Defaults to ``"opencv"``. """ @@ -868,10 +977,38 @@ class VideoBackend( target, **kwargs, )+ elif backend == "deepstream":+ assert not frame_recovery, (+ "frame_recovery is only available for `opencv` backend"+ )+ # Decode-pool size comes from media-io-kwargs (no env var); the+ # pool is a process-wide singleton so the first decode's value+ # wins. Pop it so it isn't forwarded to the frame sampler.+ pool_size = kwargs.pop("pool_size", None)+ # Probe container metadata from the bytes via GStreamer (in+ # the deepstream video-decode wheel) — no PyAV/pymediainfo, no path.+ from nvidia.deepstream_videodecode import probe_metadata++ total_frames, original_fps, duration, _w, _h, codec = (+ probe_metadata(data)+ )+ source = cls._prepare_source(+ VideoSourceMetadata(+ total_frames_num=total_frames,+ original_fps=original_fps,+ duration=duration,+ )+ )+ frame_idx = cls.compute_frames_index_to_sample(+ source=source, target=target, **kwargs+ )+ frames, valid = cls.decode_indices(+ data, frame_idx, source, codec=codec, pool_size=pool_size+ ) else: raise ValueError( f"Unknown video codec backend {backend!r}; "- "valid options: 'opencv', 'pyav', 'pynvvideocodec'."+ "valid options: 'opencv', 'pyav', 'pynvvideocodec', 'deepstream'." ) if len(valid) < len(frame_idx):@@ -35,6 +35,7 @@ class VideoMediaIO(MediaIO[MediaWithBytes[tuple[npt.NDArray, dict[str, Any]]]]): # Decoder GPU memory is reserved from the startup value. runtime_kwargs = dict(runtime_kwargs) runtime_kwargs.pop("hw_decoders", None)+ runtime_kwargs.pop("pool_size", None) # Block request-level selection of GPU video backends that # were not configured (and VRAM-reserved) at startup.@@ -92,8 +92,12 @@ class VideoLoaderRegistry(ExtensionManager): return self.processor2backend.get(video_processor) + def register_gpu_codec(self, name: str) -> None:+ """Mark a codec name as requiring GPU without registering a loader."""+ self._requires_gpu[name] = True+ def backend_requires_gpu(self, name: str) -> bool:- return self._requires_gpu.get(name, False)+ return self._requires_gpu.get(name, True) def get_video_loader_backend_for_processor(@@ -208,6 +212,7 @@ class VideoLoader: VIDEO_LOADER_REGISTRY = VideoLoaderRegistry()+VIDEO_LOADER_REGISTRY.register_gpu_codec("deepstream") PYNVVIDEOCODEC_VIDEO_BACKEND: Literal["pynvvideocodec"] = "pynvvideocodec" # Per-decoder upper bound reserved for persistent PyNvVideoCodec surfaces.@@ -1136,6 +1141,7 @@ class VideoBackend( from nvidia.deepstream_videodecode import probe_metadata total_frames, original_fps, duration, _w, _h, codec = probe_metadata(data)+ _check_frame_pixel_limit(_w, _h) source = cls._prepare_source( VideoSourceMetadata( total_frames_num=total_frames,AI-assisted change e2b457d3ee3eb526b85104a42aceafdaf2efd3bbaf0c3e48a5bd65d8c782bb13 · Fix 34206ddafd272502118c53c9419fff9e21cfc9cc1b92257e77f5169c3dde533a