Skip to navigation

DeepStream Reference Application - deepstream-vllm-plugin

View as Markdown

The nvvllmvlm plugin is a GStreamer plugin for NVIDIA DeepStream that integrates Vision-Language Models (VLM) using vLLM for real-time video understanding and analysis. It enables the integration of vision-language models (Cosmos Reason 2, etc.) into DeepStream pipelines, processing video frames in configurable time segments and performing batch VLM inference asynchronously.

The plugin source and reference application are available in the DeepStream mono-repo under src/apps/reference_apps/deepstream-vllm-plugin.

Key Features

  • Segment-based processing: Collects frames into time windows for batch inference.
  • Flexible frame sampling: FPS-based or interval-based subsampling.
  • Async inference: Background worker thread for non-blocking processing.
  • Multi-stream support: One plugin instance handles multiple streams efficiently.
  • Per-stream prompts: Different prompts and settings for each stream.
  • Configurable models: Supports video-native and image-only VLM models.
  • GPU-optimized: Zero-copy GPU operations, with the model shared across streams.
  • Flexible input formats: PyTorch tensors, PIL Images, or numpy arrays.

Requirements

  • NVIDIA GPU with CUDA support
  • NVIDIA DeepStream SDK 9.0.0 or later
  • Docker with NVIDIA Container Runtime
  • Python 3.12 or later
  • vLLM 0.21.0 with PyTorch 2.11.0 (installed by install.sh)
  • 40 GB or more of GPU memory
  • Currently supported on x86-based GPU platforms

Quick Start

Installation

Launch the DeepStream Triton container and install the plugin dependencies:

# Launch the DeepStream container
sudo docker run -it --rm --runtime=nvidia --gpus all --network=host \
-v $(pwd):/home/vllm_ds_plugin \
ghcr.io/nvidia/deepstream:9.2-triton-multiarch
# Inside the DeepStream container, install the dependencies
cd /home/vllm_ds_plugin/deepstream-vllm-plugin
chmod +x install.sh
./install.sh

Obtain a Hugging Face token

  1. Log in at https://huggingface.co.
  2. Go to Profile → Access Tokens.
  3. Create and save the generated token.
  4. To use the Cosmos Reason 2 model, go to https://huggingface.co/nvidia/Cosmos-Reason2-8B, review and accept the NVIDIA Open Model License Agreement.

Single-stream and multi-stream processing

Run the following inside the DeepStream container:

# Select the GPU (optional)
export CUDA_VISIBLE_DEVICES=0
# Export the Hugging Face token to download models from HF
export HF_TOKEN=<Your HF Token>
# Single stream (dry-run: results printed to the console)
python3 vllm_ds_app_kafka_publish.py <video1.mp4 or RTSP stream url> --dry-run
# For example:
python3 vllm_ds_app_kafka_publish.py /opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4 --dry-run
# Multi-stream with a shared model (dry-run)
python3 vllm_ds_app_kafka_publish.py <video1 url> <video2 url> --dry-run

Kafka integration

To stream results to Kafka in real time, bring up the Kafka containers on the host (outside the DeepStream container):

docker compose -f docker-compose-kafka.yml up -d

Then run the publishing application and consumer inside the DeepStream container:

# Run with Kafka publishing (single- or multi-stream)
python3 vllm_ds_app_kafka_publish.py <video1.mp4> <video2.mp4> \
--kafka-bootstrap localhost:9092 --topic vlm-results
# On another terminal, start the consumer test script
python3 test_consumer.py --topic vlm-results

Configuration

Place config.yaml in the plugin directory or the current working directory. A complete example follows:

# Model Configuration
model:
path: "nvidia/Cosmos-Reason2-8B"
max_model_len: 20480 # Max context length
gpu_memory_utilization: 0.7 # GPU memory fraction to use; update per platform/available GPU memory
trust_remote_code: true
gpu_id: 0 # GPU device ID (-1 for auto)
video_mode: 1 # 1=video metadata (native video support), 0=multi-image mode
tensor_format: "pytorch" # pytorch/pil/numpy (image inputs)
# Segment Processing
segment:
length_sec: 30 # Segment length in seconds
overlap_sec: 0 # Segment overlap in seconds
subsample_interval: 1 # Keep every Nth frame
selection_fps: 30 # Target FPS (0 to disable FPS-based sampling)
# Inference Configuration
inference:
# User prompt with optional placeholders: {num_frames}, {stream_id}, {timestamps}
user_prompt: "These are {num_frames} images from stream {stream_id} sampled at timestamps {timestamps}. Describe the scene in detail."
system_prompt: |
Provide captions with timestamps using format:
<start time> <end time> caption of event.
max_tokens: 2048 # Max tokens to generate
temperature: 0.7 # Sampling temperature (0-1)
# top_p: 0.9 # Top-p nucleus sampling
# top_k: 100 # Top-k sampling
# repetition_penalty: 1.1 # Repetition penalty
# Per-stream prompt overrides (multi-stream mode)
stream_prompts:
0:
user_prompt: "Stream {stream_id} at {timestamps}: Monitor for security threats."
system_prompt: "You are a security analyst."
1:
user_prompt: "Analyze traffic flow."
# Pipeline Configuration
pipeline:
queue_maxsize: 20 # Max inference queue size
max_wait_timeout: 300 # Max wait time for segment completion (seconds)
# Video Configuration
video:
default_fps_numerator: 30 # Default FPS numerator (30/1 = 30 fps)
default_fps_denominator: 1 # Default FPS denominator (used if the stream lacks FPS)

Key settings:

  • Video mode: video_mode: 1 uses video metadata (for models with native video support); video_mode: 0 uses multi-image mode (image-only models).
  • Tensor format (image modes only): pytorch (default), pil, or numpy.
  • System prompt: specify a value to use it; omit it for no system prompt; use an empty string to send an empty system prompt.
  • Prompt placeholders: {num_frames} (number of frames in the segment), {stream_id} (stream identifier), and {timestamps} (timestamp string). Include {timestamps} to show timestamps, or omit it to exclude them.
  • Per-stream prompts: override any inference setting for specific streams; streams without overrides use the global defaults.

Plugin properties

Properties can override the config values at runtime:

PropertyTypeDefaultDescription
modelstringfrom configHuggingFace model ID
user-promptstringfrom configUser prompt with placeholders
system-promptstringNoneSystem prompt (optional)
segment-length-secint10Segment length in seconds
overlap-secint0Segment overlap
selection-fpsint1Target FPS (0=disabled)
subsample-intervalint1Keep every Nth frame
max-tokensint2048Max tokens to generate
temperaturefloat0.7Sampling temperature
top-pfloat0.9Top-p nucleus sampling
top-kint100Top-k sampling
repetition-penaltyfloat1.1Repetition penalty
max-model-lenint20480Max model context length
trust-remote-codebooltrueTrust remote code
gpu-memory-utilizationfloat0.7GPU memory fraction (0.0-1.0)
gpu-idint0GPU device ID (-1=auto)
video-modeint11=video, 0=multi-image
tensor-formatstringpytorchpytorch/pil/numpy
queue-maxsizeint20Inference queue size
max-wait-timeoutint300Shutdown timeout (seconds)
default-fps-numeratorint30Default FPS numerator
default-fps-denominatorint1Default FPS denominator

Model Support

Video-native models (video_mode: 1): Cosmos-Reason2-8B (default) and other models with native video-metadata support.

Image-only models (video_mode: 0): models that support image inputs only.

Signal-based results

Access results via GObject signals:

def on_vlm_result(element, stream_id, start_time, end_time, text, user_data):
print(f"Stream {stream_id} [{start_time:.2f}s-{end_time:.2f}s]: {text}")
vlm.connect("vlm-result", on_vlm_result, None)