Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build Mac wheels in CI #230

Merged
merged 17 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 6 additions & 4 deletions .github/workflows/linux_wheel.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and test Linux wheels
name: Build and test Linux wheel

on:
pull_request:
Expand All @@ -24,6 +24,7 @@ defaults:
shell: bash -l -eo pipefail {0}

jobs:

generate-matrix:
uses: pytorch/test-infra/.github/workflows/generate_binary_build_matrix.yml@main
with:
Expand All @@ -34,11 +35,13 @@ jobs:
with-xpu: disable
with-rocm: disable
with-cuda: disable
build-python-only: "disable"

build:
needs: generate-matrix
strategy:
fail-fast: false
name: Build and Upload wheel
name: Build and Upload Linux wheel
uses: pytorch/test-infra/.github/workflows/build_wheels_linux.yml@main
with:
repository: pytorch/torchcodec
Expand All @@ -60,7 +63,6 @@ jobs:
matrix:
python-version: ['3.9']
ffmpeg-version-for-tests: ['4.4.2', '5.1.2', '6.1.1', '7.0.1']
if: ${{ always() }}
needs: build
steps:
- uses: actions/download-artifact@v3
Expand Down Expand Up @@ -121,7 +123,7 @@ jobs:
ls
- name: Smoke test
run: |
python test/decoders/manual_smoke_test.py
python -X faulthandler test/decoders/manual_smoke_test.py
- name: Run Python tests
run: |
pytest test -vvv
92 changes: 72 additions & 20 deletions .github/workflows/macos_wheel.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build and test MacOS
name: Build and test MacOS wheel

on:
pull_request:
Expand All @@ -24,15 +24,54 @@ defaults:
shell: bash -l -eo pipefail {0}

jobs:

generate-matrix:
uses: pytorch/test-infra/.github/workflows/generate_binary_build_matrix.yml@main
with:
package-type: wheel
os: macos-arm64
test-infra-repository: pytorch/test-infra
test-infra-ref: main
with-xpu: disable
with-rocm: disable
with-cuda: disable
build-python-only: "disable"

build:
needs: generate-matrix
strategy:
fail-fast: false
name: Build and Upload Mac wheel
uses: pytorch/test-infra/.github/workflows/build_wheels_macos.yml@main
with:
repository: pytorch/torchcodec
ref: ""
test-infra-repository: pytorch/test-infra
test-infra-ref: main
build-matrix: ${{ needs.generate-matrix.outputs.matrix }}
post-script: packaging/post_build_script.sh
smoke-test-script: packaging/fake_smoke_test.py
runner-type: macos-m1-stable
package-name: torchcodec
trigger-event: ${{ github.event_name }}
build-platform: "python-build-package"
build-command: "BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 python -m build --wheel -vvv --no-isolation"

install-and-test:
runs-on: macos-m1-stable
runs-on: macos-14-xlarge
strategy:
fail-fast: false
matrix:
python-version: ['3.9']
ffmpeg-version-for-tests: ['4.4.2', '5.1.2', '6.1.1', '7.0.1']
if: ${{ always() }}
needs: build
steps:
- name: Download wheel
uses: actions/download-artifact@v3
with:
name: pytorch_torchcodec__${{ matrix.python-version }}_cpu_
path: pytorch/torchcodec/dist/

- name: Setup conda env
uses: conda-incubator/setup-miniconda@v3
with:
Expand All @@ -42,36 +81,49 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Update pip
run: python -m pip install --upgrade pip

- name: Install PyTorch
run: |
python -m pip install --pre torch --index-url https://download.pytorch.org/whl/nightly/cpu
- name: Check out repo

- name: Install torchcodec from the wheel
run: |
wheel_path=`find pytorch/torchcodec/dist -type f -name "*.whl"`
echo Installing $wheel_path
python -m pip install $wheel_path -vvv

- name: Check out torchcodec repo
uses: actions/checkout@v3
- name: Install compile from source dependencies

- name: Install ffmpeg
run: |
conda install cmake pkg-config -c conda-forge
conda install "ffmpeg=${{ matrix.ffmpeg-version-for-tests }}" -c conda-forge
ffmpeg -version

- name: Install test dependencies
run: |
python -m pip install --pre torchvision --index-url https://download.pytorch.org/whl/nightly/cpu
# Ideally we would find a way to get those dependencies from pyproject.toml
python -m pip install numpy pytest pillow
- name: Install torchcodec from source, building against non-GPL FFmpeg
run: |
BUILD_AGAINST_ALL_FFMPEG_FROM_S3=1 pip install -e ".[dev]" --no-build-isolation
- name: Install ffmpeg, post build

- name: Delete the src/ folder just for fun
run: |
# Ideally we would have checked for that before installing the wheel,
# but we need to checkout the repo to access this file, and we don't
# want to checkout the repo before installing the wheel to avoid any
# side-effect. It's OK.
source packaging/helpers.sh
assert_ffmpeg_not_installed
# The only reason we checked-out the repo is to get access to the
# tests. We don't care about the rest. Out of precaution, we delete
# the src/ folder to be extra sure that we're running the code from
# the installed wheel rather than from the source.
# This is just to be extra cautious and very overkill because a)
# there's no way the `torchcodec` package from src/ can be found from
# the PythonPath: the main point of `src/` is precisely to protect
# against that and b) if we ever were to execute code from
# `src/torchcodec`, it would fail loudly because the built .so files
# aren't present there.
rm -r src/
ls -lh

conda install "ffmpeg=${{ matrix.ffmpeg-version-for-tests }}" -c conda-forge
ffmpeg -version
- name: Smoke test
run: |
python test/decoders/manual_smoke_test.py
python -X faulthandler test/decoders/manual_smoke_test.py

- name: Run Python tests
run: |
pytest test -vvv
5 changes: 3 additions & 2 deletions packaging/check_glibcxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@

MAX_ALLOWED = (3, 4, 19)

symbol_matches = sys.argv[1].split("\n")
all_symbols = set()
for line in sys.argv[1].split("\n"):
for line in symbol_matches:
# We search for GLIBCXX_major.minor.micro
if match := re.search(r"GLIBCXX_\d+\.\d+\.\d+", line):
all_symbols.add(match.group(0))

if not all_symbols:
raise ValueError("No GLIBCXX symbols found. Something is wrong.")
raise ValueError(f"No GLIBCXX symbols found in {symbol_matches}. Something is wrong.")

all_versions = (symbol.split("_")[1].split(".") for symbol in all_symbols)
all_versions = (tuple(int(v) for v in version) for version in all_versions)
Expand Down
50 changes: 31 additions & 19 deletions packaging/post_build_script.sh
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
#!/bin/bash

set -ex

source packaging/helpers.sh

wheel_path=$(pwd)/$(find dist -type f -name "*.whl")
echo "Wheel content:"
unzip -l $wheel_path

ffmpeg_versions=(4 5 6 7)
unamestr=$(uname)
if [[ "$unamestr" == 'Linux' ]]; then
ext="so"
elif [[ "$unamestr" == 'Darwin' ]]; then
ext="dylib"
else
echo "Unknown operating system: $unamestr"
exit 1
fi

# TODO: Make ffmpeg4 work with nvcc.
if [ "$ENABLE_CUDA" -eq 1 ]; then
ffmpeg_versions=(5 6 7)
if [[ "$ENABLE_CUDA" -eq 1 ]]; then
ffmpeg_versions=(5 6 7)
fi

for ffmpeg_major_version in ${ffmepg_versions[@]}; do
assert_in_wheel $wheel_path torchcodec/libtorchcodec${ffmpeg_major_version}.so
for ffmpeg_major_version in ${ffmpeg_versions[@]}; do
assert_in_wheel $wheel_path torchcodec/libtorchcodec${ffmpeg_major_version}.${ext}
done
assert_not_in_wheel $wheel_path libtorchcodec.so
assert_not_in_wheel $wheel_path libtorchcodec.${ext}

for ffmpeg_so in libavcodec.so libavfilter.so libavformat.so libavutil.so libavdevice.so ; do
assert_not_in_wheel $wheel_path $ffmpeg_so
for ffmpeg_ext in libavcodec.${ext} libavfilter.${ext} libavformat.${ext} libavutil.${ext} libavdevice.${ext} ; do
assert_not_in_wheel $wheel_path $ffmpeg_ext
done

assert_not_in_wheel $wheel_path "^test"
assert_not_in_wheel $wheel_path "^doc"
assert_not_in_wheel $wheel_path "^benchmarks"
assert_not_in_wheel $wheel_path "^packaging"

# See invoked python script below for details about this check.
extracted_wheel_dir=$(mktemp -d)
unzip -q $wheel_path -d $extracted_wheel_dir
symbols_matches=$(find $extracted_wheel_dir | grep ".so$" | xargs objdump --syms | grep GLIBCXX_3.4.)
python packaging/check_glibcxx.py "$symbols_matches"
if [[ "$unamestr" == 'Linux' ]]; then
# See invoked python script below for details about this check.
extracted_wheel_dir=$(mktemp -d)
unzip -q $wheel_path -d $extracted_wheel_dir
symbols_matches=$(find $extracted_wheel_dir | grep ".so$" | xargs objdump --syms | grep GLIBCXX_3.4.)
python packaging/check_glibcxx.py "$symbols_matches"

echo "ls dist"
ls dist
echo "ls dist"
ls dist

old="linux_x86_64"
new="manylinux_2_17_x86_64.manylinux2014_x86_64"
echo "Replacing ${old} with ${new} in wheel name"
mv dist/*${old}*.whl $(echo dist/*${old}*.whl | sed "s/${old}/${new}/")
old="linux_x86_64"
new="manylinux_2_17_x86_64.manylinux2014_x86_64"
echo "Replacing ${old} with ${new} in wheel name"
mv dist/*${old}*.whl $(echo dist/*${old}*.whl | sed "s/${old}/${new}/")
fi

echo "ls dist"
ls dist
17 changes: 0 additions & 17 deletions src/torchcodec/decoders/_core/VideoDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,9 +676,6 @@ void VideoDecoder::maybeSeekToBeforeDesiredPts() {
int64_t desiredPtsForStream = *maybeDesiredPts_ * streamInfo.timeBase.den;
if (!canWeAvoidSeekingForStream(
streamInfo, streamInfo.currentPts, desiredPtsForStream)) {
VLOG(5) << "Seeking is needed for streamIndex=" << streamIndex
<< " desiredPts=" << desiredPtsForStream
<< " currentPts=" << streamInfo.currentPts;
mustSeek = true;
break;
}
Expand Down Expand Up @@ -727,13 +724,10 @@ VideoDecoder::RawDecodedOutput VideoDecoder::getDecodedOutputWithFilter(
if (activeStreamIndices_.size() == 0) {
throw std::runtime_error("No active streams configured.");
}
VLOG(9) << "Starting getDecodedOutputWithFilter()";
resetDecodeStats();
if (maybeDesiredPts_.has_value()) {
VLOG(9) << "maybeDesiredPts_=" << *maybeDesiredPts_;
maybeSeekToBeforeDesiredPts();
maybeDesiredPts_ = std::nullopt;
VLOG(9) << "seeking done";
}
auto seekDone = std::chrono::high_resolution_clock::now();
// Need to get the next frame or error from PopFrame.
Expand All @@ -748,13 +742,9 @@ VideoDecoder::RawDecodedOutput VideoDecoder::getDecodedOutputWithFilter(
StreamInfo& streamInfo = streams_[streamIndex];
ffmpegStatus =
avcodec_receive_frame(streamInfo.codecContext.get(), frame.get());
VLOG(9) << "received frame" << " status=" << ffmpegStatus
<< " streamIndex=" << streamInfo.stream->index;
bool gotNonRetriableError =
ffmpegStatus != AVSUCCESS && ffmpegStatus != AVERROR(EAGAIN);
if (gotNonRetriableError) {
VLOG(9) << "Got non-retriable error from decoder: "
<< getFFMPEGErrorStringFromErrorCode(ffmpegStatus);
gotPermanentErrorOnAnyActiveStream = true;
break;
}
Expand Down Expand Up @@ -784,7 +774,6 @@ VideoDecoder::RawDecodedOutput VideoDecoder::getDecodedOutputWithFilter(
UniqueAVPacket packet(av_packet_alloc());
ffmpegStatus = av_read_frame(formatContext_.get(), packet.get());
decodeStats_.numPacketsRead++;
VLOG(9) << "av_read_frame returned status: " << ffmpegStatus;
if (ffmpegStatus == AVERROR_EOF) {
// End of file reached. We must drain all codecs by sending a nullptr
// packet.
Expand All @@ -807,8 +796,6 @@ VideoDecoder::RawDecodedOutput VideoDecoder::getDecodedOutputWithFilter(
"Could not read frame from input file: " +
getFFMPEGErrorStringFromErrorCode(ffmpegStatus));
}
VLOG(9) << "Got packet: stream_index=" << packet->stream_index
<< " pts=" << packet->pts << " size=" << packet->size;
if (activeStreamIndices_.count(packet->stream_index) == 0) {
// This packet is not for any of the active streams.
continue;
Expand Down Expand Up @@ -846,10 +833,6 @@ VideoDecoder::RawDecodedOutput VideoDecoder::getDecodedOutputWithFilter(
std::chrono::duration_cast<std::chrono::milliseconds>(seekDone - start);
auto seekToDecodeDone = std::chrono::duration_cast<std::chrono::milliseconds>(
decodeDone - seekDone);
VLOG(3) << "Got frame: stream_index=" << activeStream.stream->index
<< " pts=" << frame->pts << " stats=" << decodeStats_
<< " startToSeekDone=" << startToSeekDone.count() << "ms"
<< " seekToDecodeDone=" << seekToDecodeDone.count() << "ms";
RawDecodedOutput rawOutput;
rawOutput.streamIndex = frameStreamIndex;
rawOutput.frame = std::move(frame);
Expand Down
6 changes: 0 additions & 6 deletions src/torchcodec/decoders/_core/VideoDecoderOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,6 @@ bool _test_frame_pts_equality(
int64_t frame_index,
double pts_seconds_to_test) {
auto videoDecoder = unwrapTensorToGetDecoder(decoder);
LOG(INFO) << "pts_seconds_to_test: " << std::setprecision(15)
<< pts_seconds_to_test << std::endl;
LOG(INFO) << "frame pts : " << std::setprecision(15)
<< videoDecoder->getPtsSecondsForFrame(stream_index, frame_index)
<< std::endl
<< std::endl;
return pts_seconds_to_test ==
videoDecoder->getPtsSecondsForFrame(stream_index, frame_index);
}
Expand Down
Loading