Skip to content

Windows + CUDA 13: NPP load fails because NPPRuntimeLoader.cpp hard-codes nppicc64_12.dllΒ #1422

Description

@sh202603

πŸ› Describe the bug

Hi torchcodec maintainers,

Thanks for the project β€” the lazy NPP loader from #1371 is a nice improvement for CPU-only installs. While testing the resulting CUDA path on Windows we hit a regression for CUDA 13 environments, and I would like to report it (with a suggested fix) before sending a PR.

Summary

After PR #1371 ("Lazily load NPP libraries") landed, the Windows branch of NPPRuntimeLoader.cpp::_loadLibrary() hard-codes nppicc64_12.dll. On a CUDA 13 host (where nvidia-npp 13.x ships nppicc64_13.dll), loadNPPLibrary() always returns false and any CUDA device interface β€” both CudaDeviceInterface and BetaCudaDeviceInterface β€” fails with:

Failed to load NPP library. NPP is required for CUDA color conversion.

The Linux branch in the same function has a libnppicc.so.12 / libnppicc.so.13 fallback, but the Windows branch does not. This looks like an oversight rather than an intentional restriction.

Reproduction

Tested on:

  • Windows 11 (x64)
  • Visual Studio 2022 Build Tools 17.x (VCTools workload)
  • CUDA Toolkit 13.2
  • Python 3.13 in a uv-managed venv with nvidia-npp == 13.1.0.48 installed
  • torchcodec built from source on main (latest tested commit: 11a4d98a) with ENABLE_CUDA=1
import torch
import torchcodec
from torchcodec.decoders import VideoDecoder

dec = VideoDecoder("test.mp4", device="cuda")
# RuntimeError: BetaCudaDeviceInterface, .../BetaCudaDeviceInterface.cpp:282,
# Failed to load NPP library. NPP is required for CUDA color conversion.

The Windows-side nvidia-npp PyPI wheel places its DLLs at
<venv>/Lib/site-packages/nvidia/cu13/bin/x86_64/, including:

nppc64_13.dll
nppicc64_13.dll      <-- this is what NPPRuntimeLoader looks for, but with "_12" suffix
nppial64_13.dll
nppidei64_13.dll
... (no nppicc64_12.dll)

Adding the directory to os.add_dll_directory() and to PATH does not help: LoadLibrary("nppicc64_12.dll") simply has no matching file.

Root cause

src/torchcodec/_core/NPPRuntimeLoader.cpp, function _loadLibrary():

static bool _loadLibrary() {
#if defined(WIN64) || defined(_WIN64)
#ifdef UNICODE
  static LPCWSTR nppiccDll = L"nppicc64_12.dll";   // <-- CUDA 12 hard-coded
#else
  static LPCSTR nppiccDll = "nppicc64_12.dll";
#endif
  g_nppicc_handle = LoadLibrary(nppiccDll);
  if (g_nppicc_handle == nullptr) {
    return false;
  }
#else
  // Linux already has the 12 / 13 fallback:
  g_nppicc_handle = dlopen("libnppicc.so", RTLD_NOW);
  if (g_nppicc_handle == nullptr) {
    g_nppicc_handle = dlopen("libnppicc.so.12", RTLD_NOW);
  }
  if (g_nppicc_handle == nullptr) {
    g_nppicc_handle = dlopen("libnppicc.so.13", RTLD_NOW);
  }
  if (g_nppicc_handle == nullptr) {
    return false;
  }
#endif
  return true;
}

Before PR #1371 the NPP library was statically linked via CMake (${CUDA_nppicc_LIBRARY}), so the NPP version matched the CUDA toolkit used at build time. After switching to runtime LoadLibrary, the Windows branch lost that flexibility because the filename now needs the matching version suffix.

Suggested fix

Mirror the Linux fallback on Windows: try nppicc64_13.dll first, fall back to nppicc64_12.dll. Trying 13 first is reasonable since older 12 setups can still match via the fallback path.

 static bool _loadLibrary() {
 #if defined(WIN64) || defined(_WIN64)
 #ifdef UNICODE
-  static LPCWSTR nppiccDll = L"nppicc64_12.dll";
+  static const LPCWSTR nppiccDlls[] = { L"nppicc64_13.dll", L"nppicc64_12.dll" };
 #else
-  static LPCSTR nppiccDll = "nppicc64_12.dll";
+  static const LPCSTR nppiccDlls[] = { "nppicc64_13.dll", "nppicc64_12.dll" };
 #endif
-  g_nppicc_handle = LoadLibrary(nppiccDll);
+  for (auto dll : nppiccDlls) {
+    g_nppicc_handle = LoadLibrary(dll);
+    if (g_nppicc_handle != nullptr) {
+      break;
+    }
+  }
   if (g_nppicc_handle == nullptr) {
     return false;
   }
 #else

Verified on the reproduction environment above: with this patch applied and torchcodec rebuilt, VideoDecoder(..., device="cuda") initializes successfully and decoded frames flow through the CUDA color-conversion path.

I am happy to send a PR. Let me know whether you would like the fallback list extended (e.g. a future nppicc64_14.dll) or kept minimal to 13 / 12.

Why this matters

This is the only code path for CUDA color conversion on Windows + CUDA 13, since:

  • The torchcodec 0.12.0 Windows wheel on PyPI is CPU-only (0.12.0+cpu, built without ENABLE_CUDA).
  • The PyTorch wheel index (cu128 / cu130 / nightly) currently does not publish Windows CUDA wheels for torchcodec.
  • conda-forge's Windows + CUDA torchcodec is at 0.10.0, which predates PR Lazily load NPP librariesΒ #1371 and the 0.12-series streaming encoder.

So a Windows + CUDA 13 user must build torchcodec from source today, and this hard-coded DLL name blocks them at the first VideoDecoder(..., device="cuda") call.

Notes

  • nvidia-nvjpeg 13.x ships nvjpeg64_13.dll (same naming pattern). If torchcodec adds an equivalent runtime loader for nvJPEG in the future, the same multi-version logic would be worth keeping in mind.

Thanks for taking the time to read through this. Happy to provide additional logs, run experiments, or open a PR β€” whichever is most useful. Looking forward to your guidance.

Versions

Collecting environment information...
PyTorch version: 2.11.0+cu130
Is debug build: False
CUDA used to build PyTorch: 13.0
ROCM used to build PyTorch: N/A

OS: Microsoft Windows 11 Enterprise (10.0.26200 64 γƒ“γƒƒγƒˆ)
GCC version: Could not collect
Clang version: Could not collect
CMake version: version 4.3.2
Libc version: N/A

Python version: 3.13.9 (tags/v3.13.9:8183fa5, Oct 14 2025, 14:09:13) [MSC v.1944 64 bit (AMD64)] (64-bit runtime)
Python platform: Windows-11-10.0.26200-SP0
Is CUDA available: True
CUDA runtime version: 13.2.78
CUDA_MODULE_LOADING set to:
GPU models and configuration: GPU 0: NVIDIA GeForce RTX 5060 Ti
Nvidia driver version: 596.36
cuDNN version: Could not collect
Is XPU available: False
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: True
Caching allocator config: N/A

CPU:
Name: AMD Ryzen 7 9700X 8-Core Processor
Manufacturer: AuthenticAMD
Family: 107
Architecture: 9
ProcessorType: 3
DeviceID: CPU0
CurrentClockSpeed: 3800
MaxClockSpeed: 3800
L2CacheSize: 8192
L2CacheSpeed: None
Revision: 17408

Versions of relevant libraries:
[pip3] Could not collect
[conda] Could not collect

----- relevant packages (from uv pip list) -----
uv pip list | Select-String -Pattern "^(torch|nvidia|tensorrt|pytorch)"

nvidia-npp 13.1.0.48
nvidia-nvjpeg 13.1.0.48
nvidia-vfx 0.1.0.1
tensorrt 10.15.1.29
tensorrt-cu13 10.15.1.29
tensorrt-cu13-bindings 10.15.1.29
tensorrt-cu13-libs 10.15.1.29
torch 2.11.0+cu130
torch-tensorrt 2.11.0+cu130
torchcodec 0.13.0a0 F:\test\torchcodec
torchvision 0.26.0+cu130

----- torchcodec version -----
..venv\Scripts\python.exe -c "import torchcodec; print(torchcodec.version)"
0.13.0a0+11a4d98

----- FFmpeg version -----
& "$FFmpegRoot\bin\ffmpeg.exe" -version | Select-Object -First 1
ffmpeg version 8.1-full_build-www.gyan.dev Copyright (c) 2000-2026 the FFmpeg developers

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions