Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
4 changes: 2 additions & 2 deletions components/src/dynamo/common/tests/test_video_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def _mock_iio_v2(self):
iio.get_writer = MagicMock(return_value=writer)
return iio, writer

def test_mp4_selects_libx264_codec(self):
def test_mp4_selects_h264_nvenc_codec(self):
from dynamo.common.utils.video_utils import encode_to_video_bytes

iio = self._mock_iio_v3()
Expand All @@ -56,7 +56,7 @@ def test_mp4_selects_libx264_codec(self):

iio.imwrite.assert_called_once()
_, kwargs = iio.imwrite.call_args
assert kwargs.get("codec") == "libx264"
assert kwargs.get("codec") == "h264_nvenc"
assert kwargs.get("fps") == 8

def test_webm_selects_libvpx_vp9_codec(self):
Expand Down
12 changes: 7 additions & 5 deletions components/src/dynamo/common/utils/video_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,15 @@ def encode_to_mp4(
logger.info(f"Encoding {len(frames)} frames to {output_path} at {fps} fps")

try:
# Use imageio to write MP4
# imageio.v3 API
# Use imageio to write MP4. We use h264_nvenc (NVIDIA HW encoder) instead
# of libx264 because the in-tree ffmpeg build is LGPL-only and libx264
# is GPL-licensed; see container/templates/wheel_builder.Dockerfile.
# Requires a CUDA-capable GPU at runtime.
if hasattr(iio, "imwrite"):
iio.imwrite(output_path, frames, fps=fps, codec="libx264")
iio.imwrite(output_path, frames, fps=fps, codec="h264_nvenc")
else:
# Fall back to v2 API
writer = iio.get_writer(output_path, fps=fps, codec="libx264") # type: ignore[attr-defined]
writer = iio.get_writer(output_path, fps=fps, codec="h264_nvenc") # type: ignore[attr-defined]
try:
for frame in frames:
writer.append_data(frame)
Expand Down Expand Up @@ -215,7 +217,7 @@ def encode_to_video_bytes(
if output_format == "webm":
kwargs["codec"] = "libvpx-vp9"
elif output_format == "mp4":
kwargs["codec"] = "libx264"
kwargs["codec"] = "h264_nvenc"
else:
raise ValueError(f"No codec specified for response format: {output_format}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ async def _generate_video(
return video_bytes

async def _frames_to_video(
self, frames: list, fps: int, codec: str = "libx264"
self, frames: list, fps: int, codec: str = "h264_nvenc"
) -> bytes:
"""Convert list of frames to video bytes.

Expand Down
9 changes: 8 additions & 1 deletion container/deps/requirements.common.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@
# Core runtime dependencies shared by ALL Dynamo containers.
# See README.md in this directory for version pinning strategy.

# Force a source install of imageio-ffmpeg (pure-Python wrapper). The PyPI wheel
# bundles a prebuilt, GPL-encumbered ffmpeg binary in <site-packages>/imageio_ffmpeg/binaries/
# that has CVE exposure; we point imageio at the in-tree LGPL ffmpeg CLI via
# IMAGEIO_FFMPEG_EXE instead. This directive is honored by pip and uv when this
# file is passed via --requirement, and applies to the whole install.
--no-binary imageio-ffmpeg

aiohttp>=3.9.0,<4.0
fastapi==0.120.1
grpcio-tools<=1.76.0 # May have platform-specific builds; pins grpcio ecosystem version
httpx==0.28.1

# Video generation: encode frames to MP4 (used by TRT-LLM, vLLM-Omni, SGLang diffusion)
imageio>=2.37.0
imageio-ffmpeg>=0.6.0
imageio-ffmpeg>=0.6.0 # binary skipped per --no-binary directive at top of file
# Shared plotting utility used by runtime diagnostics and benchmark tooling.
matplotlib==3.10.7
msgspec==0.19.0
Expand Down
7 changes: 6 additions & 1 deletion container/deps/requirements.trtllm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
# with --no-deps so upstream nvcr.io/nvidia/tensorrt-llm/release's solve
# stays intact. Sorted alphabetically per pre-commit requirements-txt-fixer.

# Force a source install of imageio-ffmpeg. The PyPI wheel bundles a prebuilt,
# GPL-encumbered ffmpeg binary that has CVE exposure; we point imageio at the
# in-tree LGPL ffmpeg CLI via IMAGEIO_FFMPEG_EXE instead.
--no-binary imageio-ffmpeg

# Used by the trtllm video_diffusion handler to encode generated frames to MP4.
# Upstream tensorrt-llm/release does not ship them.
imageio>=2.37.0
imageio-ffmpeg>=0.6.0
imageio-ffmpeg>=0.6.0 # binary skipped per --no-binary directive at top of file
# Required by ai_dynamo_runtime + gpu_memory_service. Upstream tensorrt-llm/release
# does not ship them; vllm/vllm-openai does (which is why DYN-2204's vllm path
# does not need this).
Expand Down
8 changes: 6 additions & 2 deletions container/templates/dynamo_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,17 @@ COPY --chown=dynamo: --from=wheel_builder ${NIXL_PREFIX}/ ${NIXL_PREFIX}/
COPY --chown=dynamo: --from=wheel_builder /opt/dynamo/dist/nixl/ /opt/dynamo/wheelhouse/nixl/
COPY --chown=dynamo: --from=wheel_builder /workspace/nixl/build/src/bindings/python/nixl-meta/nixl-*.whl /opt/dynamo/wheelhouse/nixl/

# Always copy FFmpeg so libs are available for Rust checks in CI
# Always copy FFmpeg so libs are available for Rust checks in CI.
# libvpx.so* is included because the in-tree ffmpeg is built with --enable-libvpx,
# so libavcodec.so has a runtime dependency on libvpx.so.9.
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 -r /tmp/usr/local/src/ffmpeg /usr/local/src/
cp -r /tmp/usr/local/src/ffmpeg /usr/local/src/ && \
ldconfig

{% if target not in ("dev", "local-dev") %}
# Copy built artifacts (not needed for dev/local-dev; users build from source)
Expand Down
20 changes: 18 additions & 2 deletions container/templates/sglang_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,19 @@ RUN userdel -r ubuntu > /dev/null 2>&1 || true \
&& mkdir -p /etc/profile.d && echo 'umask 002' > /etc/profile.d/00-umask.sh

{% if context.sglang.enable_media_ffmpeg == "true" %}
Comment thread
saturley-hall marked this conversation as resolved.
Outdated
# Copy ffmpeg
# Copy ffmpeg from wheel_builder: shared libs 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.
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 -r /tmp/usr/local/src/ffmpeg /usr/local/src/
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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{% endif %}

{% if target not in ("dev", "local-dev") %}
Expand Down Expand Up @@ -86,6 +92,16 @@ 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.
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
export PIP_CACHE_DIR=/root/.cache/pip && \
if pip show --break-system-packages imageio-ffmpeg >/dev/null 2>&1; then \
pip install --break-system-packages --force-reinstall --no-deps --no-binary imageio-ffmpeg "imageio-ffmpeg>=0.6.0"; \
Comment thread
saturley-hall marked this conversation as resolved.
Outdated
fi

# 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
17 changes: 17 additions & 0 deletions container/templates/trtllm_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \
\
# Third-party deps Dynamo wheels declare but upstream lacks, plus the
# huggingface-hub pin and KVBM-matching nixl-cu13. See the file for context.
# The requirements.trtllm.txt file itself carries a `--no-binary imageio-ffmpeg`
# directive that keeps the GPL-encumbered prebuilt ffmpeg off disk; IMAGEIO_FFMPEG_EXE
# below points imageio at the in-tree LGPL CLI.
uv pip install --no-deps --requirement /tmp/requirements.trtllm.txt && \
\
if [ "${ENABLE_KVBM}" = "true" ]; then \
Expand All @@ -143,6 +146,20 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \
fi
{% endif %}

# Copy the in-tree LGPL ffmpeg from wheel_builder. The TRT-LLM diffusion handler
# always encodes video (video_handler.py:263 → encode_to_video_bytes), so the
# CLI and its libav* / libvpx runtime libs need to be present in this image and
# imageio must be pointed at it via IMAGEIO_FFMPEG_EXE. Ungated by
# enable_media_ffmpeg because TRT-LLM unconditionally needs the encoder.
RUN --mount=type=bind,from=wheel_builder,source=/usr/local/,target=/tmp/usr/local/ \
cp -nL /tmp/usr/local/lib/libav*.so* /usr/local/lib/ 2>/dev/null || true && \
cp -nL /tmp/usr/local/lib/libsw*.so* /usr/local/lib/ 2>/dev/null || true && \
cp -nL /tmp/usr/local/lib/lib*vpx*.so* /usr/local/lib/ 2>/dev/null || true && \
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
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Pull /workspace_src (incl. ATTRIBUTION/LICENSE) from the transport stage and
# wire up the launch screen in a single RUN — saves the standalone workspace COPY layer.
RUN --mount=type=bind,from=workspace_files,source=/workspace_src,target=/tmp/workspace_src \
Expand Down
10 changes: 10 additions & 0 deletions container/templates/vllm_runtime.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,16 @@ RUN --mount=type=bind,from=wheel_builder,source=/usr/local/,target=/tmp/usr/loca
cp -r /tmp/usr/local/src/ffmpeg /usr/local/src/
{% endif %}

# Replace any imageio-ffmpeg from upstream (which ships a GPL-encumbered prebuilt
# ffmpeg binary) with a source build that leaves no binary on disk. vLLM-Omni
# uses diffusers.export_to_video and doesn't invoke imageio-ffmpeg, so no
# IMAGEIO_FFMPEG_EXE is needed; this is purely to clear the GPL binary.
RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \
export UV_CACHE_DIR=/root/.cache/uv && \
if uv pip show {{ pip_target }} imageio-ffmpeg >/dev/null 2>&1; then \
uv pip install {{ pip_target }} --reinstall-package imageio-ffmpeg --no-deps --no-binary imageio-ffmpeg "imageio-ffmpeg>=0.6.0"; \
Comment thread
saturley-hall marked this conversation as resolved.
Outdated
fi

# Remove the vLLM source tree shipped in the base image to avoid pytest
# collection conflicts (duplicate conftest plugin registration) and stale
# tool scripts referencing files not present in Dynamo's build context.
Expand Down
42 changes: 34 additions & 8 deletions container/templates/wheel_builder.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,16 @@ RUN if [ "$USE_SCCACHE" = "true" ]; then \
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
# Do not delete the source tarball for legal reasons
# Always 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).
# Do not delete the source tarball for legal reasons.
ARG FFMPEG_VERSION
ARG NV_CODEC_HEADERS_REF=n13.0.19.0
ARG LIBVPX_REF=v1.14.1
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 \
export AWS_WEB_IDENTITY_TOKEN_FILE=/run/secrets/aws-token && \
Expand All @@ -266,11 +273,26 @@ RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token
eval $(/tmp/use-sccache.sh setup-env); \
fi && \
if [ "$DEVICE" = "xpu" ] || [ "$DEVICE" = "cpu" ]; then \
apt-get update -y && apt-get install -y build-essential pkg-config xz-utils; \
apt-get update -y && apt-get install -y build-essential pkg-config xz-utils git yasm; \
apt-get clean && rm -rf /var/lib/apt/lists/*; \
elif [ "$DEVICE" = "cuda" ]; then \
dnf install -y --setopt=tsflags=nocontexts pkg-config xz; \
dnf install -y --setopt=tsflags=nocontexts pkg-config xz git yasm; \
fi && \
# nv-codec-headers: provides the NVENC/NVDEC API headers ffmpeg compiles against.
# Header-only, no runtime dep here; libcuda/libnvidia-encode are loaded at runtime
# in the consuming container.
cd /tmp && \
git clone --depth 1 --branch ${NV_CODEC_HEADERS_REF} https://github.com/FFmpeg/nv-codec-headers.git && \
make -C nv-codec-headers PREFIX=/usr/local install && \
# libvpx: BSD-licensed VP9 encoder needed for the WebM output path. Built from
# source so we don't need to track distro package names (libvpx-dev on Debian
# vs libvpx-devel via EPEL on RHEL/manylinux).
git clone --depth 1 --branch ${LIBVPX_REF} https://chromium.googlesource.com/webm/libvpx.git && \
cd libvpx && \
./configure --prefix=/usr/local --enable-shared --disable-static --disable-examples --disable-unit-tests --disable-tools --disable-docs && \
make -j$(nproc) && \
make install && \
ldconfig && \
cd /tmp && \
curl --retry 5 --retry-delay 3 -LO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz && \
tar xf ffmpeg-${FFMPEG_VERSION}.tar.xz && \
Expand All @@ -279,17 +301,21 @@ RUN --mount=type=secret,id=aws-web-identity-token,target=/run/secrets/aws-token
--prefix=/usr/local \
--disable-gpl \
--disable-nonfree \
--disable-programs \
--disable-doc \
--disable-static \
--disable-x86asm \
--disable-network \
--disable-encoders \
--disable-muxers \
--disable-bsfs \
--disable-devices \
--disable-libdrm \
--enable-shared && \
--enable-shared \
--enable-nvenc \
--enable-libvpx \
--disable-encoders \
--enable-encoder=h264_nvenc,libvpx_vp9 \
--disable-muxers \
--enable-muxer=mov,mp4,matroska,webm \
--enable-protocol=file,pipe && \
make -j$(nproc) && \
make install && \
/tmp/use-sccache.sh show-stats "FFMPEG" && \
Expand Down
6 changes: 4 additions & 2 deletions docs/backends/trtllm/trtllm-diffusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ image generation through `--modality image_diffusion` flag.

- **TensorRT-LLM with visual_gen**: The `visual_gen` module is part of TensorRT-LLM (`tensorrt_llm._torch.visual_gen`). Install TensorRT-LLM following the [official instructions](https://github.com/NVIDIA/TensorRT-LLM#installation).
- **dynamo-runtime with multimodal API**: The Dynamo runtime must include `ModelType.Videos` or `ModelType.Images` support. Ensure you're using a compatible version.
- **VIDEO diffusion: imageio with ffmpeg**: Required for encoding generated frames to MP4 video:
- **VIDEO diffusion: imageio with ffmpeg**: Required for encoding generated frames to MP4 video. The Dynamo TRT-LLM runtime container ships an LGPL-only ffmpeg CLI built with the NVIDIA NVENC H.264 encoder (`h264_nvenc`) and `libvpx_vp9` for WebM, and points `imageio` at it via `IMAGEIO_FFMPEG_EXE=/usr/local/bin/ffmpeg` — the GPL-encumbered ffmpeg binary normally shipped inside the `imageio-ffmpeg` PyPI wheel is **not** installed. If you're running outside the container, install the Python wrapper without the bundled binary and point it at your own ffmpeg:
```bash
pip install imageio[ffmpeg]
pip install --no-binary imageio-ffmpeg "imageio[ffmpeg]"
export IMAGEIO_FFMPEG_EXE=/path/to/your/ffmpeg
```
MP4 output requires an NVIDIA GPU at runtime (NVENC is a hardware encoder).

## Supported Models

Expand Down
Loading