Skip to content

Commit 15700a9

Browse files
kajalj22claude
authored andcommitted
chore: replace decord2 with torchvision/torchaudio for media loading (#3107)
Signed-off-by: Kajal Jain <kajalj@nvidia.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Charlie Truong <chtruong@nvidia.com>
1 parent b5f7a28 commit 15700a9

12 files changed

Lines changed: 90 additions & 60 deletions

File tree

docs/guides/grpo-audio.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Audio Post-training with NeMo RL
22

3+
> **Audio dependencies are not pre-installed in the NeMo-RL container.**
4+
> Run the following script once before training or evaluation:
5+
>
6+
> ```bash
7+
> bash tools/install_audio_deps.sh
8+
> ```
9+
>
10+
> The script is a no-op if they are already installed. Audio tests run it automatically.
11+
312
This guide explains how to use NeMo RL to train [Qwen2.5-Omni](https://huggingface.co/Qwen) (3B or 7B) and [Qwen3-Omni-30B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Omni-30B-A3B-Instruct) with GRPO on audio question-answering data, convert the resulting Megatron checkpoint to Hugging Face format, and evaluate it on the [MMAU benchmark](https://huggingface.co/datasets/TwinkStart/MMAU).
413
514
NeMo RL ships three recipes out of the box, but the pieces are independent and can be mixed:

nemo_rl/data/multimodal_utils.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
from io import BytesIO
2121
from typing import Any, Optional, Union
2222

23-
import decord
2423
import requests
2524
import torch
2625
from PIL import Image
@@ -357,17 +356,19 @@ def load_media_from_message(
357356
load_audio(aud, **multimodal_load_kwargs["audio"])
358357
)
359358
except (RuntimeError, FileNotFoundError, OSError) as e:
360-
logger.warning("Audio loading failed. Fall back to decord.")
361-
# use decord
362-
loaded_audio = decord.AudioReader(
363-
aud,
364-
sample_rate=multimodal_load_kwargs["audio"]["sampling_rate"],
365-
mono=True,
366-
)
359+
logger.warning("Audio loading failed. Falling back to torchaudio.")
360+
import torchaudio
361+
362+
waveform, sr = torchaudio.load(aud)
363+
target_sr = multimodal_load_kwargs["audio"]["sampling_rate"]
364+
if sr != target_sr:
365+
waveform = torchaudio.functional.resample(
366+
waveform, sr, target_sr
367+
)
368+
if waveform.shape[0] > 1:
369+
waveform = waveform.mean(0, keepdim=True)
367370
loaded_media["audio"].append(
368-
loaded_audio[:].asnumpy()[
369-
get_dim_to_pack_along(processor, "audio")
370-
]
371+
waveform.numpy()[get_dim_to_pack_along(processor, "audio")]
371372
)
372373
else:
373374
loaded_media["audio"].append(aud)
@@ -379,9 +380,8 @@ def load_media_from_message(
379380
if "video" in multimodal_load_kwargs
380381
else {}
381382
)
382-
# seems decord backend loads video faster with multithread ffmpeg and it is easier to install
383383
loaded_media["video"].append(
384-
load_video(vid, backend="decord", **load_video_kwargs)[0]
384+
load_video(vid, backend="torchcodec", **load_video_kwargs)[0]
385385
)
386386
else:
387387
loaded_media["video"].append(vid)

nemo_rl/data_plane/codec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def materialize(
261261
The lazy ``BatchedDataDict`` import keeps
262262
``import nemo_rl.data_plane`` cheap for unit tests that don't
263263
actually call this function (``BatchedDataDict`` transitively
264-
pulls multimodal deps like decord / torchvision).
264+
pulls multimodal deps like torchvision / torchaudio).
265265
266266
Args:
267267
td: Wire TensorDict to materialize.

pyproject.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ dependencies = [
5050
"sympy>=1.14.0",
5151
"pillow>=12.1.1",
5252
"torchvision==0.26.0",
53+
# torchaudio and torchcodec are NOT installed in the container image.
54+
# Run tools/install_audio_deps.sh before using audio features or audio tests.
5355
# Keep the base range broad enough while allowing backend extras to tighten as needed.
5456
# Floor raised to 5.5.0 for Gemma 4 support.
5557
"transformers>=5.5.0,<5.9.0",
@@ -60,7 +62,6 @@ dependencies = [
6062
"nvidia-nvshmem-cu13>=3.6.5; sys_platform == 'linux' and (platform_machine == 'x86_64' or platform_machine == 'aarch64')", # for deep_ep build
6163
"swanlab",
6264
"pyzmq",
63-
"decord2",
6465
"soundfile>=0.13.1",
6566
"nccl4py; sys_platform != 'darwin'", # for non-colocated refit
6667
"cuda-bindings; sys_platform != 'darwin'", # for non-colocated refit
@@ -255,7 +256,7 @@ nemo-automodel = { path = "3rdparty/Automodel-workspace/Automodel", editable = t
255256
megatron-bridge = { path = "3rdparty/Megatron-Bridge-workspace", editable = true }
256257
nemo_gym = { workspace = true }
257258
nemo_run = { git = "https://github.com/NVIDIA-NeMo/Run", rev = "414f0077c648fde2c71bb1186e97ccbf96d6844c" }
258-
# torch/torchaudio/torchvision/triton all come from the torch index in order to pick up aarch64 wheels
259+
# torch/torchvision/triton all come from the torch index in order to pick up aarch64 wheels
259260
torch = [
260261
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
261262
{ index = "pypi", marker = "sys_platform == 'darwin'" },
@@ -264,10 +265,6 @@ torchvision = [
264265
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
265266
{ index = "pypi", marker = "sys_platform == 'darwin'" },
266267
]
267-
torchaudio = [
268-
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
269-
{ index = "pypi", marker = "sys_platform == 'darwin'" },
270-
]
271268
triton = [
272269
{ index = "pytorch-cu130", marker = "sys_platform != 'darwin'" },
273270
{ index = "pypi", marker = "sys_platform == 'darwin'" },
@@ -349,7 +346,6 @@ override-dependencies = [
349346
"timm<=1.0.22",
350347
"nvidia-modelopt[torch]>=0.44.0a0",
351348
"torch==2.11.0",
352-
"torchaudio==2.11.0",
353349
# sglang has conflicting llguidance versions than vllm, so enforcing vllm's version since it's newer
354350
"llguidance>=1.3.0,<1.4.0",
355351
# Override setuptools range in other dependencies to address CVE GHSA-58pv-8j8x-9vj2
@@ -378,6 +374,13 @@ override-dependencies = [
378374
# extra composes with mcore/automodel without version-mirroring TQ's
379375
# requirements.txt. Forward-compatible across TQ minor bumps.
380376
"numpy>=2.1.0",
377+
# av (PyAV) carries CVE-bundled codec libs (libx264, libx265, libopenh264, libmp3lame).
378+
# It is only needed by megatron-bridge's optional WAN diffusion path, which installs it
379+
# at test time via scripts/install_diffusion_deps.sh. Exclude from the shipped container.
380+
"av; sys_platform == 'never'",
381+
# decord2 is pulled transitively by sglang → qwen-vl-utils[decord] on aarch64 Linux.
382+
# NeMo-RL no longer calls any decord API (replaced by torchvision/torchaudio).
383+
"decord2; sys_platform == 'never'",
381384
]
382385

383386
# CVE fixes

pyrefly.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ replace-imports-with-any = [
1010
"math_verify.*",
1111
"sympy.*",
1212
"torchdata.*",
13+
"torchaudio.*",
14+
"torchcodec.*",
1315
"nemo.*",
1416
"megatron.*",
1517
"ray.*",

tests/functional/audio_grpo_megatron.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ git config --global --add safe.directory $PROJECT_ROOT
77

88
set -eou pipefail
99

10+
# Audio deps (torchaudio/torchcodec) are not in the shipped container; install at test time.
11+
bash "$PROJECT_ROOT/tools/install_audio_deps.sh"
12+
1013
EXP_NAME=$(basename $0 .sh)
1114
EXP_DIR=$SCRIPT_DIR/$EXP_NAME
1215
LOG_DIR=$EXP_DIR/logs

tests/functional/eval_audio.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ git config --global --add safe.directory $PROJECT_ROOT
77

88
set -eou pipefail
99

10+
# Audio deps (torchaudio/torchcodec) are not in the shipped container; install at test time.
11+
bash "$PROJECT_ROOT/tools/install_audio_deps.sh"
12+
1013
EXP_NAME=$(basename $0 .sh)
1114
EXP_DIR=$SCRIPT_DIR/$EXP_NAME
1215
LOG_DIR=$EXP_DIR/logs

tests/functional/sft_avlm.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ git config --global --add safe.directory $PROJECT_ROOT
1010

1111
set -eou pipefail
1212

13+
# Audio/video deps (torchaudio/torchcodec/ffmpeg) are not in the shipped container.
14+
bash "$PROJECT_ROOT/tools/install_audio_deps.sh"
15+
1316
EXP_NAME=$(basename $0 .sh)
1417
EXP_DIR=$SCRIPT_DIR/$EXP_NAME
1518
LOG_DIR=$EXP_DIR/logs

tests/test_suites/llm/common.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
99
# catch-22 to get the project root and mark it safe if you don't know the project root
1010
PROJECT_ROOT=$(realpath $SCRIPT_DIR/../../..)
1111

12+
# Auto-install av deps for all VLM scripts and audio/omni/avqa scripts (no-op for LLM tests).
13+
if [[ "${BASH_SOURCE[1]:-}" =~ (omni|audio|avqa|vlm) ]]; then
14+
bash "$PROJECT_ROOT/tools/install_audio_deps.sh"
15+
fi
16+
1217
exit_if_max_steps_reached() {
1318
# Early stopping to save compute if max step has been reached
1419
STEPS_SO_FAR=$(jq 'to_entries | .[] | select(.key == "train/loss") | .value | keys | map(tonumber) | max' $JSON_METRICS || echo 0)

tests/unit/L0_Unit_Tests_Data.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@
1717

1818
source "$(dirname "${BASH_SOURCE[0]}")/run_unit_shard_common.sh"
1919

20+
# Audio/video deps (torchaudio/torchcodec/ffmpeg) are not in the shipped container.
21+
# Data unit tests (e.g. dailyomni) decode real audio/video via these deps.
22+
bash "$PROJECT_ROOT/tools/install_audio_deps.sh"
23+
2024
uv run --no-sync bash -x ./tests/run_unit.sh "unit/data/" "${EXCLUDED_UNIT_TESTS[@]}" --cov=nemo_rl --cov-report=term-missing --cov-report=json --hf-gated

0 commit comments

Comments
 (0)