torchcodec: Add version 0.16.0 - #2105
Merged
Merged
Conversation
torchcodec is PyTorch's video decoder: a scikit-build-core/CMake project that compiles a C++20 core against libtorch and FFmpeg, plus a separate, FFmpeg-free image-decoder library. Upstream links its wheels against prebuilt non-GPL FFmpeg tarballs it hosts on S3, which carry no riscv64 build, so the workflow takes the project's other supported path -- pkg-config against an installed FFmpeg -- and builds that FFmpeg from source in the container, LGPL and never shipped. FFmpeg 6.1 because the major version is baked into the libtorchcodec_core<N>.so name the Python loader dlopen()s, and 6.1 is what ubuntu-24.04-riscv ships. AVIF and HEIC image decoding are off: libavif comes from the same S3 bucket and libheif is packaged in neither Rocky 10 nor a riscv64 EPEL. JPEG, PNG, WebP and the vendored giflib are built as usual. Also records two reusable gotchas found along the way (383, 384).
luhenry
added a commit
that referenced
this pull request
Sep 19, 2026
RPM installs %license files regardless of tsflags=nodocs (only %doc is dropped), and all four packages whose licences the wheel needs mark them %license -- confirmed against the CentOS Stream 10 spec files, which are also where `%files devel` confirms libwebp-devel ships the CMake package config find_package(WebP) needs. The reinstall stays as belt-and-braces but must not fail the build when a package is no longer available from a repo.
Restores skills/python-project-porting/references/{gotchas-index.md,
gotchas/local-validation-and-rehearsal.md, gotchas/native-deps-and-linking.md}
to main's current content. The two gotchas this PR had added (383/384,
torch+CUDA rehearsal and versioned-dlopen ABI contract) also collided with
gotcha numbers other agents had already taken on main in the meantime -
they'll be re-added on main directly with fresh numbers instead.
luhenry
added a commit
that referenced
this pull request
Sep 19, 2026
These were originally added inside PR #2105 (torchcodec), which is not allowed to touch skills/ - removed from that branch and landed here instead, renumbered from their original 383/384 (already taken by other concurrent work by the time this was caught).
…is branch no longer touches # Conflicts: # skills/python-project-porting/references/gotchas-index.md # skills/python-project-porting/references/gotchas/local-validation-and-rehearsal.md
Contributor
|
…y loads
All four riscv64 jobs of the first CI run built, repaired and installed a
wheel and then died at the test step with
OSError: Could not load this library:
.../site-packages/torchcodec/libtorchcodec_image.so
pytest's conftest import report drops the exception chain, so the cause
never reached the log. Reproducing the run under docker+QEMU against
quay.io/pypa/manylinux_2_39_riscv64 gives it:
OSError: .../libtorchcodec_image.so: undefined symbol:
_ZN8facebook10torchcodec11decode_avifERKN5torch6stable6TensorElll
that is facebook::torchcodec::decode_avif(torch::stable::Tensor const&,
long, long, long). DecodeAvif.h declares decode_avif() with four
parameters, the real implementation defines four, and
image_custom_ops.cpp registers it with TORCH_BOX(&decode_avif) - but the
!TORCHCODEC_ENABLE_AVIF stub this build selects still has the
three-parameter signature from before num_threads was added, so it
defines a different overload and the four-parameter one exists nowhere.
An ELF shared object may carry undefined symbols, so the link, the wheel
build and auditwheel all stay green and the first dlopen is where it
fails - and since load_image_library() runs at `import torchcodec`, that
takes the whole package down, not just decode_avif.
Upstream never builds this path: their wheels always link the libavif
they fetch from S3, and no CI job sets TORCHCODEC_BUILD_AVIF=0. We have
to, because that bucket has no riscv64 build and libavif is packaged in
neither Rocky 10 nor a riscv64 EPEL.
Validated under docker+QEMU on quay.io/pypa/manylinux_2_39_riscv64,
cp312: the unpatched wheel reproduces the CI traceback exactly, the
patched one imports (`IMPORT_OK 0.16.0 6`), `ldd -r` reports no
unresolved symbol in any of the five shipped libraries, and the
workflow's own wheel-contents check passes on the repaired wheel.
luhenry
added a commit
that referenced
this pull request
Sep 19, 2026
… flight All four riscv64 jobs of PR #2105's first run failed at the test step on an unloadable libtorchcodec_image.so. Reproduced the job under docker+QEMU on the real manylinux riscv64 image: torchcodec's no-libavif decode_avif() stub has a stale three-parameter signature, so with TORCHCODEC_BUILD_AVIF=0 the four-parameter overload the op registration takes the address of is defined nowhere and the library carries an undefined symbol that only surfaces at dlopen. Patch 0002 fixes it; the patched wheel imports and ldd -r is clean.
The second CI run built and imported the wheel on all four interpreters and then failed the same 242 tests on each of them. Every one of those tests needs an encoder FFmpeg does not have: upstream tests against conda-forge's GPL FFmpeg, while the build here configures FFmpeg with no external libraries, so it has none of the H.264, HEVC, VP9, AV1 or MP3 encoders (every one of them lives in an external library, and libx264/libx265 need --enable-gpl) and its AV1 decoder only works through a hardware accelerator. The symptoms all follow from that: FFmpeg falls back to the container format's default codec, so the failures read "yuv444p is not supported by the mpeg4 encoder", "avcodec_open2 failed: Invalid argument", "Video codec av1 not found" or "Codec not found", and the ffmpeg CLI the encoder tests compare themselves against cannot write an MP3 either. Drop the tests that mux a real video, shell out to that CLI or ask for MP3, and smoke_test.py with them - it encodes H.264 yuv444p to build every fixture it then decodes. test_logging.py goes too: it asserts an empty subprocess stderr, which torch's libcpuinfo pollutes on this runner by reading core_id -1 from /sys. The selection is deliberately narrow - the MP3 clauses keep the WAV and FLAC parametrisations of the same tests, so the audio encoder, the image encoder and every decoder stay covered. Checked against the node ids of the failed run: the expression deselects exactly those 232 tests and nothing that passed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
torchcodec0.16.0Compiles PyTorch's video decoder: a C++20 core linked against libtorch and FFmpeg, plus a separate FFmpeg-free image-decoder library. Upstream publishes no riscv64 wheel.
Mirrors upstream's
linux_wheel.yaml.Differs from upstream
libtorchcodec_core6.somatches ubuntu-24.04-riscv's system FFmpeg.TORCHCODEC_BUILD_AVIF=0,TORCHCODEC_BUILD_HEIC=0- libavif comes from the same bucket, libheif is in neither Rocky 10 nor a riscv64 EPEL.Testing
torchvisionnot on the registry, sotest_transform_ops.pyis deselected.License: Wheel bundles libjpeg-turbo, libpng, zlib and libwebp (all permissive); upstream ships their licence texts from its own repair script, which this build does not use, so the build adds them.
Patches
0001-pyproject-glob-the-licence-texts-of-the-bundled-image.patch-Upstream-Status: Inappropriate. Without it PEP 639's explicitlicense-fileslist silently drops the vendored-library licence texts. Not riscv64-specific, but only meaningful for a plain-auditwheel repair.0002-DecodeAvif-give-the-no-libavif-stub-the-declared-sign.patch-Upstream-Status: To upstream.DecodeAvif.cpp's!TORCHCODEC_ENABLE_AVIFstub still has the three-parameter signature from beforenum_threadswas added, so withTORCHCODEC_BUILD_AVIF=0the four-parameterdecode_avif()thatDecodeAvif.hdeclares andimage_custom_ops.cppregisters viaTORCH_BOX(&decode_avif)is defined nowhere. An ELF shared object may carry undefined symbols, so everything stays green until the firstdlopen- whichload_image_library()performs atimport torchcodec, taking the whole package down. Not riscv64-specific; upstream never builds this path because their wheels always link the libavif they fetch from S3.First CI run (all four jobs failed at the test step)
pytest's conftest import report drops the exception chain, so the cause was not in the log. Reproduced under docker+QEMU against
quay.io/pypa/manylinux_2_39_riscv64, which gives it:Patch
0002is the fix.Second CI run: the wheel builds and imports everywhere, and 242 tests need an encoder that does not exist here
All four interpreters got past patch
0002- the wheel builds, repairs and imports - and then failed the same 242 tests each (242 failed, ~1515 passed, ~863 skipped; cp312/cp313/cp314/cp314t agree test-for-test). One cause, not three:Every modern video encoder, and the MP3 one, lives in an external FFmpeg library, and this build takes none of them:
./configure --disable-static --enable-shared --enable-pic --disable-docreportsExternal libraries: iconv libxcb* zliband defines noCONFIG_*_ENCODERfor H.264, HEVC, VP9, AV1 or MP3 (verified by running that exact configure on FFmpeg 6.1.6 - the codec set is architecture-independent). FFmpeg then falls back to the container format's default codec, which is why the failures accusempeg4of not takingyuv444pandavcodec_open2of rejectingcrf/preset/profile, rather than saying "no H.264 encoder".test_get_frame_at_av1is the decode-side face of the same thing: FFmpeg 6.1's native AV1 decoder only runs through a hardware accelerator (libavcodec/av1dec.c: "Your platform doesn't suppport hardware accelerated AV1 decoding"), software AV1 decoding means libdav1d or libaom.Upstream never sees this because its test job does
conda install ffmpeg -c conda-forge(packaging/install_ffmpeg.sh), whose default build is the GPL one - hence test ids liketest_codec_spec_vs_impl_equivalence[h264-libx264-to_file]. Two of the missing libraries, libx264 and libx265, can only be enabled with--enable-gpl, which is exactly what a wheel that links FFmpeg must not be built against, so this surface is not fixable here: it is deselected instead.What is dropped, and what deliberately is not:
ffmpegCLI, or ask for MP3.smoke_test.pyentirely - every fixture it decodes is built by encoding H.264 yuv444p first, which is why itsTestVideoDecodercases are in the failure list at all.test_logging.pyentirely - it asserts an empty subprocess stderr, which torch's libcpuinfo pollutes on this runner by readingcore_id -1from/sys(Error in cpuinfo: failed to parse file /sys/devices/system/cpu/cpu0/topology/core_id). Unrelated to FFmpeg, and not something the wheel can fix.not ((test_audio_against_cli or ...) and mp3)), so the WAV and FLAC parametrisations of those same tests still run - the audio encoder, the image encoder (TestImageEncoders, PNG round-trip and the PIL comparison), the multi-stream encoder's error paths and every decoder module stay covered.The shipped wheel bundles no FFmpeg, so none of this reaches users: on ubuntu-24.04-riscv the system FFmpeg supplies libx264, libvpx, libaom and dav1d, and encoding works there. The limitation is the container's, not the wheel's.
Local validation
quay.io/pypa/manylinux_2_39_riscv64, cp312): the unpatched build reproduces the CI traceback exactly; with0002the repaired wheel imports (IMPORT_OK 0.16.0 6),ldd -rreports no unresolved symbol in any of the five shipped libraries (only thePy*ones the interpreter provides), and the workflow's own wheel-contents check passes -libtorchcodec_{core6,custom_ops6,heic,image,pybind_ops}.soplus graftedlibjpeg/libpng16/libwebp/libwebpdemux/libsharpyuv, no FFmpeg and no libtorch, and the three third-party licence texts in.dist-info/licenses/.CIBW_TEST_COMMANDwas read back out of the YAML and executed throughsh -cthe way cibuildwheel invokes it. It deselects exactly the 232 in-scope failures (the other 10 are the two ignored modules) and no test that passed.