From 3c37ac6372b4821057d9256a7e2aa592a7dfeeb3 Mon Sep 17 00:00:00 2001 From: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> Date: Sun, 18 Oct 2026 21:21:07 -0700 Subject: [PATCH 1/3] [None][fix] Encode VisualGen video without a GIL-held frame copy The ffmpeg encoder built its stdin payload with video_np.tobytes(). The videos the pipelines return are non-contiguous, so that call is a strided copy of the whole video made while holding the GIL (about 0.6 s for a 124x544x960 clip). In trtllm-serve the encode runs in an executor thread, so the event loop answers no request, status polls included, until the copy returns. Feed ffmpeg memoryview(video_np.reshape(-1)) instead: the same C-order bytes, produced without holding the GIL. The encoded MP4 is byte-identical. Signed-off-by: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> --- tensorrt_llm/media/encoding.py | 4 +- .../visual_gen/test_media_encoding.py | 51 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/tensorrt_llm/media/encoding.py b/tensorrt_llm/media/encoding.py index 0e2e32490909..d756cf821ad9 100644 --- a/tensorrt_llm/media/encoding.py +++ b/tensorrt_llm/media/encoding.py @@ -181,7 +181,9 @@ def encode_video( stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - raw_frames = video_np.tobytes() + # tobytes() copies the video while holding the GIL and stalls the serving event + # loop; reshape(-1) yields the same C-order bytes without holding it. + raw_frames = memoryview(video_np.reshape(-1)) _, stderr = process.communicate(input=raw_frames) if process.returncode != 0: raise RuntimeError(f"ffmpeg encoding failed: {stderr.decode()}") diff --git a/tests/unittest/visual_gen/test_media_encoding.py b/tests/unittest/visual_gen/test_media_encoding.py index ab2c65603ff4..4928af3b314f 100644 --- a/tests/unittest/visual_gen/test_media_encoding.py +++ b/tests/unittest/visual_gen/test_media_encoding.py @@ -6,6 +6,8 @@ ``resolve_video_format`` contract. """ +import threading +import time from io import BytesIO from pathlib import Path from unittest import mock @@ -14,6 +16,7 @@ import torch from PIL import Image +from tensorrt_llm.media import encoding as media_encoding from tensorrt_llm.media.encoding import ( image_to_bytes, resolve_video_format, @@ -131,6 +134,54 @@ def test_save_video_avi_writes_riff_container(tmp_path): assert head[8:12] == b"AVI " +def test_ffmpeg_encoder_feeds_frames_without_holding_the_gil(tmp_path): + """The ffmpeg encoder pipes the frames' C-order bytes without a GIL-held copy. + + Pipelines hand over non-contiguous videos; copying one while holding the GIL stalls + the serving event loop that runs alongside the encode thread. + """ + video = torch.randint(0, 256, (3, 48, 544, 960), dtype=torch.uint8).permute(1, 2, 3, 0) + fed = [] + + class _FakeFfmpeg: + returncode = 0 + + def __init__(self, *args, **kwargs): + pass + + def communicate(self, input): + fed.append(input) + return b"", b"" + + stop = threading.Event() + worst_gap = [0.0] + + def heartbeat(): + last = time.perf_counter() + while not stop.is_set(): + time.sleep(0.001) + now = time.perf_counter() + worst_gap[0] = max(worst_gap[0], now - last) + last = now + + thread = threading.Thread(target=heartbeat) + thread.start() + try: + with ( + mock.patch.object(media_encoding, "_get_ffmpeg_path", return_value="ffmpeg"), + mock.patch.object(media_encoding.subprocess, "Popen", _FakeFfmpeg), + ): + media_encoding._FfmpegCliEncoder().encode_video( + video, str(tmp_path / "out.mp4"), frame_rate=24.0 + ) + finally: + stop.set() + thread.join() + + assert len(fed) == 1 and bytes(fed[0]) == video.numpy().tobytes() + assert worst_gap[0] < 0.1 + + # --------------------------------------------------------------------------- # resolve_video_format # --------------------------------------------------------------------------- From ed37c29896e6da79d39b5e6cfb6e4e4af1ea1550 Mon Sep 17 00:00:00 2001 From: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:28:51 -0700 Subject: [PATCH 2/3] [None][fix] Start GIL heartbeat sampling before the encode in the test Wait until the heartbeat thread has recorded its first interval before calling encode_video, so a GIL-held copy at the start of the encode cannot run before sampling begins. Signed-off-by: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> --- tests/unittest/visual_gen/test_media_encoding.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unittest/visual_gen/test_media_encoding.py b/tests/unittest/visual_gen/test_media_encoding.py index 4928af3b314f..46c887aaea48 100644 --- a/tests/unittest/visual_gen/test_media_encoding.py +++ b/tests/unittest/visual_gen/test_media_encoding.py @@ -154,6 +154,7 @@ def communicate(self, input): return b"", b"" stop = threading.Event() + sampling = threading.Event() worst_gap = [0.0] def heartbeat(): @@ -163,9 +164,11 @@ def heartbeat(): now = time.perf_counter() worst_gap[0] = max(worst_gap[0], now - last) last = now + sampling.set() thread = threading.Thread(target=heartbeat) thread.start() + assert sampling.wait(timeout=10) try: with ( mock.patch.object(media_encoding, "_get_ffmpeg_path", return_value="ffmpeg"), From 57b65f51a99f48db02f7c544f0a7c4beac98f379 Mon Sep 17 00:00:00 2001 From: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:15:27 +0000 Subject: [PATCH 3/3] [None][fix] Drop the encoding comment and the GIL heartbeat test Per review: the comment described the removed tobytes() call, and the encoder's frame handoff is internal behavior that does not need its own unit test. Signed-off-by: Yiyun Lu <55233584+luyiyun1021@users.noreply.github.com> --- tensorrt_llm/media/encoding.py | 2 - .../visual_gen/test_media_encoding.py | 54 ------------------- 2 files changed, 56 deletions(-) diff --git a/tensorrt_llm/media/encoding.py b/tensorrt_llm/media/encoding.py index d756cf821ad9..5a5484f79e1f 100644 --- a/tensorrt_llm/media/encoding.py +++ b/tensorrt_llm/media/encoding.py @@ -181,8 +181,6 @@ def encode_video( stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - # tobytes() copies the video while holding the GIL and stalls the serving event - # loop; reshape(-1) yields the same C-order bytes without holding it. raw_frames = memoryview(video_np.reshape(-1)) _, stderr = process.communicate(input=raw_frames) if process.returncode != 0: diff --git a/tests/unittest/visual_gen/test_media_encoding.py b/tests/unittest/visual_gen/test_media_encoding.py index 46c887aaea48..ab2c65603ff4 100644 --- a/tests/unittest/visual_gen/test_media_encoding.py +++ b/tests/unittest/visual_gen/test_media_encoding.py @@ -6,8 +6,6 @@ ``resolve_video_format`` contract. """ -import threading -import time from io import BytesIO from pathlib import Path from unittest import mock @@ -16,7 +14,6 @@ import torch from PIL import Image -from tensorrt_llm.media import encoding as media_encoding from tensorrt_llm.media.encoding import ( image_to_bytes, resolve_video_format, @@ -134,57 +131,6 @@ def test_save_video_avi_writes_riff_container(tmp_path): assert head[8:12] == b"AVI " -def test_ffmpeg_encoder_feeds_frames_without_holding_the_gil(tmp_path): - """The ffmpeg encoder pipes the frames' C-order bytes without a GIL-held copy. - - Pipelines hand over non-contiguous videos; copying one while holding the GIL stalls - the serving event loop that runs alongside the encode thread. - """ - video = torch.randint(0, 256, (3, 48, 544, 960), dtype=torch.uint8).permute(1, 2, 3, 0) - fed = [] - - class _FakeFfmpeg: - returncode = 0 - - def __init__(self, *args, **kwargs): - pass - - def communicate(self, input): - fed.append(input) - return b"", b"" - - stop = threading.Event() - sampling = threading.Event() - worst_gap = [0.0] - - def heartbeat(): - last = time.perf_counter() - while not stop.is_set(): - time.sleep(0.001) - now = time.perf_counter() - worst_gap[0] = max(worst_gap[0], now - last) - last = now - sampling.set() - - thread = threading.Thread(target=heartbeat) - thread.start() - assert sampling.wait(timeout=10) - try: - with ( - mock.patch.object(media_encoding, "_get_ffmpeg_path", return_value="ffmpeg"), - mock.patch.object(media_encoding.subprocess, "Popen", _FakeFfmpeg), - ): - media_encoding._FfmpegCliEncoder().encode_video( - video, str(tmp_path / "out.mp4"), frame_rate=24.0 - ) - finally: - stop.set() - thread.join() - - assert len(fed) == 1 and bytes(fed[0]) == video.numpy().tobytes() - assert worst_gap[0] < 0.1 - - # --------------------------------------------------------------------------- # resolve_video_format # ---------------------------------------------------------------------------