Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions container/compliance/native_packages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,14 @@ packages:
# generator), so it is intentionally NOT duplicated here.

# LGPL media stack — built --disable-gpl in wheel_builder (h264_nvenc + libvpx)
# and copied into vllm/sglang runtime (container/templates/vllm_runtime.Dockerfile
# + sglang_runtime.Dockerfile). Replaces the purged GPL apt ffmpeg.
# and copied into the vLLM runtime. SGLang intentionally contains no FFmpeg.
- name: ffmpeg
version: "8.1"
version: "8.1.2"
license: LGPL-2.1-or-later
source: https://ffmpeg.org/
images:
- vllm-runtime
- vllm-runtime-efa
- sglang-runtime
- sglang-runtime-efa

- name: libvpx
# VP8/VP9 codec library, ffmpeg's only non-NVIDIA encoder dependency.
Expand All @@ -98,8 +95,6 @@ packages:
images:
- vllm-runtime
- vllm-runtime-efa
- sglang-runtime
- sglang-runtime-efa

# UCX — Unified Communication X, built from source in wheel_builder and used by
# the NIXL transport. Installed to /usr/local (no dpkg metadata).
Expand Down
13 changes: 8 additions & 5 deletions container/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ dynamo:
enable_kvbm: "true"
enable_media_ffmpeg: "false"
enable_gpu_memory_service: "true"
ffmpeg_version: "8.1"
# 8.1.2 is an upstream maintenance release that picks up security fixes over
# 8.1; combined with the narrowed decoder set in wheel_builder it trims the
# media decode surface. Keep in sync with native_packages.yaml's ffmpeg entry.
ffmpeg_version: "8.1.2"
# ffmpeg build inputs (only consumed when ENABLE_MEDIA_FFMPEG=true).
nv_codec_headers_ref: "n13.0.19.0"
libvpx_ref: "v1.14.1"
Expand Down Expand Up @@ -103,9 +106,9 @@ sglang:
# lmsysorg/sglang is built FROM this nvidia/cuda cudnn-devel base, so this
# attributes everything sglang adds over the clean NVIDIA CUDA+cuDNN floor
# (framework, python stack, system libs) rather than hiding it. Per-arch stem;
# the licenses stage appends -${TARGETARCH}.cdx.json. (sglang ships no system
# GPL ffmpeg/codec dpkgs; the GPL imageio-ffmpeg pip binary is replaced by an
# in-tree LGPL ffmpeg in sglang_runtime.Dockerfile — no dpkg purge needed.)
# the licenses stage appends -${TARGETARCH}.cdx.json. SGLang's codec-bearing
# Python wheels are purged in sglang_runtime.Dockerfile; FFmpeg is not copied
# into the image and the Dynamo Rust wheel is built without media-ffmpeg.
baseline_sbom: cuda@8b2705ea
xpu:
base_image: intel/deep-learning-essentials
Expand All @@ -124,7 +127,7 @@ sglang:
# NIXL Python stack — its wheel COPY is narrowed to ai_dynamo*.whl so the SDK
# build doesn't leak into the runtime image.
nixl_ref: v1.3.0
enable_media_ffmpeg: "true"
enable_media_ffmpeg: "false"
enable_gpu_memory_service: "true"
enable_kvbm: "false"
enable_modelexpress: "true"
Expand Down
12 changes: 4 additions & 8 deletions container/deps/requirements.sglang.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Third-party Python dependencies for the sglang runtime image. Installed
# with --force-reinstall --no-deps to replace the upstream lmsysorg/sglang
# base image's imageio-ffmpeg wheel (which ships a GPL-encumbered prebuilt
# ffmpeg binary) with a source build that leaves no binary on disk.
# IMAGEIO_FFMPEG_EXE points imageio at the in-tree LGPL ffmpeg CLI.

--no-binary imageio-ffmpeg
# Third-party Python dependencies for the SGLang runtime image. Installed with
# --force-reinstall --no-deps so the upstream SGLang dependency stack remains
# otherwise unchanged. FFmpeg and codec-bearing wheels are removed separately
# in sglang_runtime.Dockerfile.

blake3>=1.0.0,<2.0.0 # Dynamo SGLang multimodal request handlers import blake3 at startup
imageio-ffmpeg>=0.6.0 # binary skipped per --no-binary directive at top of file
zstandard==0.23.0
99 changes: 75 additions & 24 deletions container/templates/sglang_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -77,25 +77,6 @@ $NIXL_PLUGIN_DIR:\
${LD_LIBRARY_PATH:-}
{% endif %}

# Copy ffmpeg from wheel_builder: versioned shared libs (libav*.so*,
# libsw*.so*) for the Rust media-ffmpeg decoder, plus the LGPL CLI binary
# (built with h264_nvenc + libvpx_vp9 encoders) that imageio targets via
# IMAGEIO_FFMPEG_EXE for video encoding. Ungated by enable_media_ffmpeg
# because the upstream lmsysorg/sglang base image always ships
# imageio-ffmpeg with a GPL-encumbered prebuilt binary that we replace
# unconditionally below; the LGPL CLI must be present so imageio has
# something to target.
RUN --mount=type=bind,from=wheel_builder,source=/usr/local/,target=/tmp/usr/local/ \
mkdir -p /usr/local/lib/pkgconfig && \
cp -rnL /tmp/usr/local/include/libav* /tmp/usr/local/include/libsw* /usr/local/include/ && \
cp -nL /tmp/usr/local/lib/libav*.so* /tmp/usr/local/lib/libsw*.so* /usr/local/lib/ && \
cp -nL /tmp/usr/local/lib/lib*vpx*.so* /usr/local/lib/ 2>/dev/null || true && \
cp -nL /tmp/usr/local/lib/pkgconfig/libav*.pc /tmp/usr/local/lib/pkgconfig/libsw*.pc /usr/local/lib/pkgconfig/ && \
cp -nL /tmp/usr/local/bin/ffmpeg /usr/local/bin/ffmpeg && \
cp -r /tmp/usr/local/src/ffmpeg /usr/local/src/ && \
ldconfig
ENV IMAGEIO_FFMPEG_EXE=/usr/local/bin/ffmpeg

{% if target not in ("dev", "local-dev") %}
# Runtime target installs only the prebuilt Dynamo wheels. SGLang and its NIXL
# packages come from the upstream lmsysorg/sglang runtime image; --no-deps keeps
Expand Down Expand Up @@ -161,17 +142,64 @@ RUN --mount=type=bind,source=./container/deps/requirements.common.txt,target=/tm
export PIP_CACHE_DIR=/root/.cache/pip && \
pip install --break-system-packages --no-deps $(grep -E '^nvtx==' /tmp/requirements.common.txt)

# Replace the upstream lmsysorg/sglang image's imageio-ffmpeg (which ships a
# GPL-encumbered prebuilt ffmpeg binary in <site-packages>/imageio_ffmpeg/binaries/)
# with a source install that leaves no binary on disk. IMAGEIO_FFMPEG_EXE points
# imageio at the LGPL CLI we copied from wheel_builder above. The --no-binary
# directive lives in the requirements file itself.
# Install SGLang-specific runtime dependencies without changing the upstream
# dependency solution. imageio-ffmpeg is intentionally absent.
RUN --mount=type=bind,source=./container/deps/requirements.sglang.txt,target=/tmp/requirements.sglang.txt \
--mount=type=cache,target=/root/.cache/pip,sharing=locked \
export PIP_CACHE_DIR=/root/.cache/pip && \
pip install --break-system-packages --force-reinstall --no-deps \
--requirement /tmp/requirements.sglang.txt

# Remove every codec-bearing component found in the upstream SGLang image and
# fail the build if an executable or shared library for FFmpeg, H.264, H.265, or
# AAC remains in the merged runtime filesystem.
#
# Inkling image preprocessing uses Pillow. Its audio feature extractor imports
# soundfile and uses torchaudio only for resampling, so those paths remain
# available for formats supported by libsndfile (for example WAV and FLAC).
# AAC-backed M4A and all video encode/decode support are intentionally removed.
RUN set -eux; \
python3 -m pip uninstall --yes \
av \
decord \
decord2 \
imageio-ffmpeg \
opencv-python \
opencv-python-headless \
torchcodec; \
SITE_PACKAGES="$(python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"; \
rm -rf \
"${SITE_PACKAGES}"/av \
"${SITE_PACKAGES}"/av-*.dist-info \
"${SITE_PACKAGES}"/av.libs \
"${SITE_PACKAGES}"/cv2 \
"${SITE_PACKAGES}"/decord \
"${SITE_PACKAGES}"/decord-*.dist-info \
"${SITE_PACKAGES}"/decord.libs \
"${SITE_PACKAGES}"/decord2 \
"${SITE_PACKAGES}"/decord2-*.dist-info \
"${SITE_PACKAGES}"/decord2.libs \
"${SITE_PACKAGES}"/imageio_ffmpeg \
"${SITE_PACKAGES}"/imageio_ffmpeg-*.dist-info \
"${SITE_PACKAGES}"/opencv_python*.dist-info \
"${SITE_PACKAGES}"/opencv_python*.libs \
"${SITE_PACKAGES}"/torchcodec \
"${SITE_PACKAGES}"/torchcodec-*.dist-info \
/usr/local/bin/ffmpeg \
/usr/local/bin/ffprobe \
/usr/local/include/libav* \
/usr/local/include/libsw* \
/usr/local/lib/libav* \
/usr/local/lib/libpostproc* \
/usr/local/lib/libsw* \
/usr/local/lib/pkgconfig/libav*.pc \
/usr/local/lib/pkgconfig/libpostproc*.pc \
/usr/local/lib/pkgconfig/libsw*.pc \
/usr/local/src/ffmpeg \
/root/.cache/pip; \
ldconfig
ENV IMAGEIO_FFMPEG_EXE=

# Copy tests, deploy and components for CI with correct ownership
COPY --chmod=775 --chown=dynamo:0 tests /workspace/tests
COPY --chmod=775 --chown=dynamo:0 examples /workspace/examples
Expand Down Expand Up @@ -217,6 +245,29 @@ RUN SITE_PACKAGES="$(python3 -c 'import site; print(site.getsitepackages()[0])')
(python3 -m compileall -q -j0 /sgl-workspace/sglang/python || true)
{%- endif %}

# Keep this guard at the end of the populated runtime stage so later COPY/RUN
# steps cannot silently reintroduce a codec. The extra AAC library names cover
# common non-FFmpeg implementations even though the current Syft baseline did
# not find them.
RUN set -eux; \
remaining="$(find /usr /opt /workspace /sgl-workspace -xdev \
\( -type f -o -type l \) \
\( -name ffmpeg -o -name ffprobe \
-o -name 'libavcodec*.so*' -o -name 'libavdevice*.so*' \
-o -name 'libavfilter*.so*' -o -name 'libavformat*.so*' \
-o -name 'libavutil*.so*' -o -name 'libpostproc*.so*' \
-o -name 'libswresample*.so*' -o -name 'libswscale*.so*' \
-o -name 'libx264*.so*' -o -name 'libx265*.so*' \
-o -name 'libopenh264*.so*' -o -name 'libfdk-aac*.so*' \
-o -name 'libfaac*.so*' -o -name 'libvo-aacenc*.so*' \
-o -name 'libaacplus*.so*' \) -print)"; \
if [ -n "${remaining}" ]; then \
echo "ERROR: codec-bearing files remain in the SGLang image:" >&2; \
echo "${remaining}" >&2; \
exit 1; \
fi; \
python3 -c 'import soundfile, torchaudio; from PIL import Image'

USER dynamo
ARG DYNAMO_COMMIT_SHA
ENV DYNAMO_COMMIT_SHA=${DYNAMO_COMMIT_SHA}
Expand Down
24 changes: 24 additions & 0 deletions container/templates/vllm_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,30 @@ RUN --mount=type=bind,source=./container/deps/requirements.vllm.txt,target=/tmp/
# tool scripts referencing files not present in Dynamo's build context.
RUN rm -rf /workspace/vllm

# Remove the codec-bearing video-DECODE wheels inherited from the vllm-openai
# base. Each bundles its own full ffmpeg carrying software H.264/H.265/AAC;
# PyAV and decord additionally ship GPL libx264/libx265. Dynamo's vLLM component
# imports none of them, so they are unused decode-side dead weight. The in-tree
# LGPL ffmpeg + imageio-ffmpeg installed above are intentionally KEPT for the
# omni HW video-encode path (h264_nvenc — the sanctioned path). Direct rm makes
# the removal robust regardless of how the base image's pip is configured; the
# guards fail the build if any of them survive.
RUN set -eux; \
python3 -m pip uninstall --yes \
av decord decord2 opencv-python opencv-python-headless torchcodec PyNvVideoCodec \
|| true; \
SITE_PACKAGES="$(python3 -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"; \
rm -rf \
"${SITE_PACKAGES}"/av "${SITE_PACKAGES}"/av-*.dist-info "${SITE_PACKAGES}"/av.libs \
"${SITE_PACKAGES}"/cv2 "${SITE_PACKAGES}"/opencv_python*.dist-info "${SITE_PACKAGES}"/opencv_python*.libs \
"${SITE_PACKAGES}"/decord "${SITE_PACKAGES}"/decord-*.dist-info "${SITE_PACKAGES}"/decord.libs \
"${SITE_PACKAGES}"/decord2 "${SITE_PACKAGES}"/decord2-*.dist-info "${SITE_PACKAGES}"/decord2.libs \
"${SITE_PACKAGES}"/torchcodec "${SITE_PACKAGES}"/torchcodec-*.dist-info \
"${SITE_PACKAGES}"/PyNvVideoCodec "${SITE_PACKAGES}"/PyNvVideoCodec-*.dist-info "${SITE_PACKAGES}"/PyNvVideoCodec.libs \
/root/.cache/pip; \
! python3 -c "import cv2" 2>/dev/null; \
! python3 -c "import av" 2>/dev/null

USER dynamo

# Copy the workspace surface needed by the current vLLM pre-merge test image.
Expand Down
45 changes: 41 additions & 4 deletions container/templates/wheel_builder.Dockerfile

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Pre-existing --disable-bsfs may affect VP9 decode in MP4 containers

The --disable-bsfs flag at container/templates/wheel_builder.Dockerfile:357 was already present before this PR and disables all bitstream filters. Some demuxers/decoders use bitstream filters internally (e.g., vp9_superframe for VP9 in MP4). If VP9-in-MP4 decode fails at runtime due to missing BSFs, this pre-existing flag would be the cause. The test fixtures were regenerated with -g 1 (all keyframes), which may mask the issue since superframe handling is primarily needed for non-keyframe packets. Worth monitoring in integration tests with real-world VP9-in-MP4 content that has B-frames or superframes.

(Refers to line 357)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,38 @@ RUN mkdir -p /tmp/native-sources
ENV SCCACHE_BUCKET=${USE_SCCACHE:+${SCCACHE_BUCKET}} \
SCCACHE_REGION=${USE_SCCACHE:+${SCCACHE_REGION}}

# Always build FFmpeg so libs are available for Rust checks in CI.
# Build FFmpeg for frameworks that retain media encode/decode support. SGLang
# deliberately omits it: its Inkling image/audio path uses Pillow and
# soundfile/torchaudio, and the SGLang runtime must not contain H.264, H.265, or
# AAC implementations.
{% if framework != "sglang" %}
# Build FFmpeg so libs are available for Rust checks in CI.
# We also build the ffmpeg CLI with h264_nvenc + libvpx_vp9 encoders so Python
# code can encode video without the GPL-licensed binary shipped by imageio-ffmpeg.
# Stays LGPL-only: --disable-gpl --disable-nonfree are preserved; H.264 comes from
# NVIDIA's NVENC (proprietary HW encoder, already a runtime dependency of these
# GPU images) and VP9 from libvpx (BSD).
#
# MEDIA CODEC ALLOWLIST: the in-tree libavcodec should carry only
# the media formats we actually build and use, not ffmpeg's full default decoder
# set. A blanket --disable-decoders/--disable-demuxers/--disable-parsers plus a
# narrow allowlist keeps the shipped libav*.so limited to that set (HW NVDEC can
# be re-added explicitly if a decode feature ever needs H.264/H.265). The
# allowlist covers exactly two paths: (1) the encode CLI ingesting rawvideo
# frames from imageio over a pipe and encoding with h264_nvenc (NVIDIA's HW
# encoder — the sanctioned path) or libvpx_vp9, and (2) the Rust media-ffmpeg
# VideoDecoder decoding VP8/VP9 in mp4/webm/mkv (test fixtures are VP9-in-mp4).
# The h264 *parser* is enabled — not the H.264 decoder — because the mp4 muxer
# needs it to package the h264_nvenc bitstream (extract SPS/PPS); a parser
# carries no codec implementation. Image decode does not use ffmpeg (it goes
# through the Rust `image` crate), so no still-image decoders are enabled here.
# The `fd` protocol is enabled alongside `pipe`: `ffmpeg -i -` reads stdin via
# the `fd:` protocol on ffmpeg 8.x (not `pipe:`), so omitting it breaks the
# imageio encode path with "Protocol not found. Did you mean file:fd:?". Both
# are pure fd/stream I/O and carry no codec implementation.
#
# Combined with the 8.1 -> 8.1.2 bump below (an upstream maintenance release),
# this also trims the decoder surface to what we ship.
# Do not delete the source tarball for legal reasons.
ARG FFMPEG_VERSION
ARG NV_CODEC_HEADERS_REF
Expand Down Expand Up @@ -346,16 +372,24 @@ RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token
--enable-libvpx \
--disable-encoders \
--enable-encoder=h264_nvenc,libvpx_vp9 \
--disable-decoders \
--enable-decoder=vp8,vp9,rawvideo \
--disable-muxers \
--enable-muxer=mov,mp4,matroska,webm \
--enable-protocol=file,pipe && \
--disable-demuxers \
--enable-demuxer=mov,matroska,rawvideo \
--disable-parsers \
--enable-parser=vp8,vp9,h264 \
--disable-protocols \
--enable-protocol=file,pipe,fd && \
make -j$(nproc) && \
make install && \
/tmp/use-sccache.sh show-stats "FFMPEG" && \
ldconfig && \
mkdir -p /usr/local/src/ffmpeg && \
find /tmp/ffmpeg-${FFMPEG_VERSION} \( -name config.log -o -name config.status \) -delete && \
mv /tmp/ffmpeg-${FFMPEG_VERSION}* /usr/local/src/ffmpeg/
{% endif %}

# Build and install UCX
RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token \
Expand Down Expand Up @@ -503,7 +537,9 @@ COPY components/ /opt/dynamo/components/

# Build ai-dynamo (pure Python) and ai-dynamo-runtime (maturin) wheels
ARG USE_SCCACHE
{% if framework != "sglang" %}
ARG ENABLE_MEDIA_FFMPEG
{% endif %}
RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token \
--mount=type=secret,id=aws-role-arn,env=AWS_ROLE_ARN \
--mount=type=cache,target=/root/.cargo/registry,sharing=shared \
Expand All @@ -520,12 +556,13 @@ RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token
cd /opt/dynamo && \
uv build --wheel --out-dir /opt/dynamo/dist && \
cd /opt/dynamo/lib/bindings/python && \
if [ "$ENABLE_MEDIA_FFMPEG" = "true" ]; then \
{% if framework == "sglang" %} maturin build --release --features "kv-indexer,slot-tracker,select-service,mm-routing,aic-forward-pass" --out /opt/dynamo/dist && \
{% else %} if [ "$ENABLE_MEDIA_FFMPEG" = "true" ]; then \
maturin build --release --features "media-ffmpeg,kv-indexer,slot-tracker,select-service,mm-routing,aic-forward-pass" --out /opt/dynamo/dist; \
else \
maturin build --release --features "kv-indexer,slot-tracker,select-service,mm-routing,aic-forward-pass" --out /opt/dynamo/dist; \
fi && \
/tmp/use-sccache.sh show-stats "Dynamo Runtime"
{% endif %} /tmp/use-sccache.sh show-stats "Dynamo Runtime"

# Compliance: harvest each crate's real LICENSE files from the cargo registry
# source cache so the rust NOTICES generator can inline upstream license text
Expand Down
3 changes: 3 additions & 0 deletions lib/llm/src/preprocessor/media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ register_model(
> [!WARNING]
> **Video decoding**: Video decoding needs to be enabled via the `dynamo-llm/media-ffmpeg` rust feature. The following ffmpeg dynamic libraries must be available on the system: `libavcodec`, `libavdevice`, `libavfilter`, `libavformat`, `libswresample`, `libswscale`. These are available in dynamo dockerfiles rendered with `enable_media_ffmpeg` set to true in `container/context.yaml`.

> [!WARNING]
> **Supported input codecs**: The in-tree ffmpeg is built with a narrow decoder allowlist (VP8/VP9 video in mp4/webm/mkv) — it carries only the media formats we build and use, not ffmpeg's full default set (see `container/templates/wheel_builder.Dockerfile`). Other codecs, including H.264 and H.265, are intentionally **not** decodable in software; decoding them would require enabling the NVDEC hardware decoders (`h264_cuvid`/`hevc_cuvid`), which is not wired up today.

## Image decoding options

### Limits (not overridable at runtime via `media_io_kwargs`)
Expand Down
3 changes: 3 additions & 0 deletions lib/llm/src/preprocessor/media/decoders/video.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ mod tests {

/// Load test video and parse expected dimensions from filename.
/// Filename format: "{resolution}_{frames}.mp4" (e.g., "240p_10.mp4" -> 320x240, 10 frames)
/// Fixtures are VP9-in-mp4: the in-tree ffmpeg only decodes a narrow
/// allowlist (VP8/VP9), so H.264 fixtures would not decode. Regenerate with
/// `ffmpeg -f lavfi -i testsrc2=size=WxH:rate=1 -frames:v N -c:v libvpx-vp9 -g 1 -strict -2 {resolution}_{frames}.mp4`.
fn load_test_video(filename: &str) -> (EncodedMediaData, u32, u32, u32) {
let path = format!(
"{}/tests/data/media/{}",
Expand Down
4 changes: 2 additions & 2 deletions lib/llm/tests/data/media/2160p_10.mp4
Git LFS file not shown
4 changes: 2 additions & 2 deletions lib/llm/tests/data/media/240p_1.mp4
Git LFS file not shown
4 changes: 2 additions & 2 deletions lib/llm/tests/data/media/240p_10.mp4
Git LFS file not shown
4 changes: 2 additions & 2 deletions lib/llm/tests/data/media/240p_100.mp4
Git LFS file not shown
4 changes: 2 additions & 2 deletions lib/llm/tests/data/media/2p_10.mp4
Git LFS file not shown
Loading
Loading