Skip to content

Use only aomenc and dav1d AVIF codecs to decrease wheel size #8858

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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from

Conversation

fdintino
Copy link
Contributor

@fdintino fdintino commented Apr 1, 2025

Fixes #8856

This removes all libavif codecs except for AOM for encoding and dav1d for decoding. It also builds libavif as a shared library on linux and macOS, which modestly decreases the file size because it allows the linker to omit unused objects from the codec libraries. Building with -Os and -flto reduces the size by another further 0.7-1.5MB.

Below is a comparison of the average CPython wheel sizes by platform for the wheels (MiB) prior to the merge of the avif pull request, the current 11.2.0 wheels, and the wheels from this pull request. An additional few-hundred K could be saved by using aom's decoder instead of dav1d, but I think the benefits of dav1d outweigh those cost savings.

Since Pillow does not yet support multi-channel high bit depth images, I've compiled libaom without high-bit-depth support. This further reduces wheel sizes by ~200KiB.

In absolute terms, the uncompressed libavif shared library file size is somewhere between 2.8MiB (macOS ARM) and 5.0MiB (musllinux x86_64), with the exception of manylinux2014 x86_64 which, because of a gcc bug, is missing some optimizations and so is 7.4MiB.

platform pre-AVIF 11.2.0 aomenc + dav1d -flto -Os further optimizations
macosx_86_64 3.0 10.9 6.9 5.8 5.1
macosx_arm64 2.9 8.5 5.7 4.9 4.5
win_amd64 2.5 13.1 7.4 6.9 6.7
manylinux_2_17_aarch64 4.2 16.3 7.7 6.3 5.6
manylinux_2_17_x86_64 4.3 18.0 8.2 8.2 7.3
manylinux_2_28_aarch64 4.3 16.8 8.0 6.5 5.7
manylinux_2_28_x86_64 4.4 18.7 8.6 7.2 6.3
musllinux_1_2_aarch64 4.3 17.2 8.3 6.6 5.8
musllinux_1_2_x86_64 4.4 18.8 8.7 7.3 6.4

@fdintino
Copy link
Contributor Author

fdintino commented Apr 2, 2025

I'm going to see what effect @wantehchang's suggestion has on the wheel sizes.

@fdintino fdintino force-pushed the chore/remove-rav1e branch from 50c6633 to 86464ca Compare April 2, 2025 15:54
@fdintino fdintino changed the title fix: remove rav1e to decrease wheel size fix: use only aomenc and dav1d AVIF codecs to decrease wheel size Apr 2, 2025
Also build libavif as a shared library. When it is built as a static
library, the dependency library files are combined into a single
archive. But when they are linked as a shared library, the linker is
able to remove unused objects. This yields a modest but not
insignificant file size reduction.
@fdintino fdintino force-pushed the chore/remove-rav1e branch from 86464ca to 8b4e66e Compare April 2, 2025 16:23
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_SHARED_LIBS=ON \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to change BUILD_SHARED_LIBS to ON? It is still OFF in winbuild/build_prepare.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did. Using the linker to generate the shared library file allows it to remove unused objects. There is a mechanism for including shared library dependencies of a python extension inside a wheel file on macOS and linux (delocate and auditwheel, respectively) but there is no such standard mechanism for doing the same on windows. So the windows library has to be built statically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The savings are modest, about 0.5 MB, but there isn't any cost to switching to a shared library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you say 0.5mb, I presume that's per wheel. Which wheel(s) were you using to check that?

fdintino added 2 commits April 2, 2025 15:55
ENABLE_NASM=ON is unnecessary...it forces the use of nasm over yasm if
both are available, but only nasm is installed in the wheel build
environments. And CMAKE_MODULE_PATH was only necessary when we used
pre-built rav1e binaries.
@wantehchang
Copy link

Frankie: Here are some other ideas to further reduce the binary size.

  1. On Linux, use the compiler flags -ffunction-sections -fdata-sections and the linker flag -Wl,--gc-sections. See, for example, https://www.sandordargo.com/blog/2023/07/19/binary-sizes-and-compiler-flags and https://www.sandordargo.com/blog/2023/07/19/binary-sizes-and-compiler-flags.
  2. Look at the binary sizes of libsharpyuv and libyuv.
    • If libsharpyuv is big, consider changing -DAVIF_LIBSHARPYUV=LOCAL to -DAVIF_LIBSHARPYUV=OFF.
    • libyuv is critical to performance. If libyuv is big, I will look into how to make it smaller.
  3. In libaom, it may be possible to exclude the fallback SIMD optimizations and only include the "highest" SIMD optimizations. For example, if a function has an AVX2 optimization, then exclude its SSE4 and SSE2 optimizations. I will look into this.

@fdintino fdintino force-pushed the chore/remove-rav1e branch from 8328cd4 to 7696f13 Compare April 4, 2025 21:47
@@ -146,7 +146,7 @@ def test_write_rgb(self, tmp_path: Path) -> None:

# avifdec hopper.avif avif/hopper_avif_write.png
assert_image_similar_tofile(
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.02
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to relax the average pixel difference threshold?

@@ -146,7 +146,7 @@ def test_write_rgb(self, tmp_path: Path) -> None:

# avifdec hopper.avif avif/hopper_avif_write.png
assert_image_similar_tofile(
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.02
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to relax the average pixel difference threshold?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this temporarily; it has since been changed back. It seems that when aom is built with -Os for manylinux2014 wheels (i.e. CentOS 7 with gcc 10) the encoder behavior changes. I think this must be a bug with gcc? I can't see why compiler optimization flags should affect the actual behavior of an application. I've opted to use -DCMAKE_BUILD_TYPE=Release for these el7 builds.

@fdintino fdintino force-pushed the chore/remove-rav1e branch from 7696f13 to f5d13cd Compare April 5, 2025 00:42
@fdintino fdintino force-pushed the chore/remove-rav1e branch from f5d13cd to deb1c9d Compare April 5, 2025 02:40
@hugovk hugovk changed the title fix: use only aomenc and dav1d AVIF codecs to decrease wheel size Use only aomenc and dav1d AVIF codecs to decrease wheel size Apr 5, 2025
@hugovk
Copy link
Member

hugovk commented Apr 5, 2025

The alternative #8869 removed installation of things like rav1e and svt-av1, are they needed here?

@fdintino
Copy link
Contributor Author

fdintino commented Apr 5, 2025

They have been removed here as well. The only difference between this PR and that one is that this one uses the dav1d decoder instead of AOM's. And it leaves open the possibility of users compiling for more codecs

@wantehchang
Copy link

@hugovk Another difference between this PR and the alternative #8869 is that this PR has additional changes to the build system to reduce the binary size, specifically:

  • Change -DBUILD_SHARED_LIBS=OFF to -DBUILD_SHARED_LIBS=ON (except on Windows)
  • Change -DCMAKE_BUILD_TYPE=Release to -DCMAKE_BUILD_TYPE=MinSizeRel under some condition (I haven't figured out what the condition is)
  • Add -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON

For a fair comparison, these changes should be applied to the alternative #8869.

@fdintino
Copy link
Contributor Author

fdintino commented Apr 8, 2025

@radarhere After I merged your change, I noticed that the windows wheels had increased to 7.1 MiB from 6.9 MiB, so I re-added the flag in build_prepare.py.

@fdintino
Copy link
Contributor Author

fdintino commented Apr 8, 2025

It actually looks like macOS is the only platform where CMAKE_INTERPROCEDURAL_OPTIMIZATION=ON results in larger binaries. They are only modestly smaller on some platforms (less than 40KiB on manylinux_2_28 and musllinux), but the LTO binaries are 1.3 MiB smaller on manylinux_2_17 aarch64.

radarhere
radarhere previously approved these changes Apr 8, 2025
@radarhere
Copy link
Member

Another difference between this PR and the alternative #8869 is that this PR has additional changes to the build system to reduce the binary size, specifically:

  • Change -DBUILD_SHARED_LIBS=OFF to -DBUILD_SHARED_LIBS=ON (except on Windows)
  • Change -DCMAKE_BUILD_TYPE=Release to -DCMAKE_BUILD_TYPE=MinSizeRel under some condition (I haven't figured out what the condition is)
  • Add -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON

For a fair comparison, these changes should be applied to the alternative #8869.

To answer this, if I download wheels from the current state of this PR, I get 522.92mb in total.
If I remove

-DCONFIG_AV1_DECODER=0 \
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL \

I get 488.77mb.

@radarhere radarhere dismissed their stale review April 10, 2025 11:08

A different strategy has been chosen

@doublex
Copy link

doublex commented Apr 12, 2025

Not in wheels: this is regrettable.
AVIF is supported by all modern browsers.
Corresponding xkcd: https://xkcd.com/619/

@aclark4life
Copy link
Member

aclark4life commented Apr 12, 2025

Not in wheels: this is regrettable.

@doublex Please see my comments here and clarify if you find the decision "regrettable" even with concerns expressed about wheel size? I'm not sure any feature is worth universally increasing the wheel size by a factor of 10. If we can do it optionally, then I would agree not getting it into this release is regrettable, but I'm not sure how that would work just yet.

@radarhere
Copy link
Member

radarhere commented Apr 13, 2025

If anyone watching this would like to download prebuilt Pillow wheels with libavif, be aware that you can use the artifacts from the GitHub Actions of this PR, for example by going to https://github.com/python-pillow/Pillow/actions/runs/14424832221 and scrolling down.

@fdintino
Copy link
Contributor Author

@aclark4life Those comments were made with regard to the initial 11.2.0 release. Those did indeed result in wheels that were as much as 4x larger and uncompressed sizes of 6x or more. Because of various size optimizations, the AVIF feature now only increases both by about 60% (a little more or less, depending on the platform).

I think the door was left open for AVIF ending up in the 11.3.0 wheels, judging from @hugovk's comment here: #8869 (comment). The number of people who would be negatively affected by the addition to their virtualenv of a 50 MB file is, I suspect, reasonably large. But 5 MB? For people looking to use Pillow in such resource-constrained environments, we could perhaps provide guidance on how to delete undesired bundled dependencies.

@@ -105,7 +105,7 @@ function build_libavif {
python3 -m pip install meson ninja

if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fyi, if the nasm.us outage is temporary, then the presence of https://github.com/python-pillow/pillow-depends/blob/main/nasm-2.16.03.tar.gz should allow this to continue working until it comes back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's temporary, since it's been down now for nearly a week. But I had forgotten about the pillow-depends backup. I've reverted this change.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@fdintino fdintino Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into that. The source tarballs on nasm.us have the autotools artifacts included. So we couldn't just swap out with a GitHub tag archive here, as we would need to update this script to call autogen.sh. Not an insurmountable obstacle, but it couldn't use the build_simple bash function from multibuild.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nasm.us is back now.

if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags+=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I compare the wheel sizes from https://github.com/python-pillow/Pillow/actions/runs/14773502497?pr=8858,
with a commit where I remove this line, I find that https://github.com/radarhere/Pillow/actions/runs/14773542213 is actually 8kb smaller.

If this line is purely about reducing file size, then I don't think it's fulfilling that role.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it's because there is a typo. I'll fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually had to do with a cmake policy that was disabling LTO on linux. See the build here: https://github.com/fdintino/Pillow/actions/runs/14777836226

Copy link
Contributor Author

@fdintino fdintino May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the PR description, I've updated the paragraph that begins with "In absolute terms,..." and the last column of the table with the latest wheel and shared library file sizes. You can look at the github diff under the list of edits to see the change.

@@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0
LIBAVIF_VERSION=1.2.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LIBAVIF_VERSION=1.2.1
LIBAVIF_VERSION=1.3.0

libavif 1.3.0 has been released - https://github.com/AOMediaCodec/libavif/releases/tag/v1.3.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggested change. I suggest two related changes if you will only be building with libavif v1.3.0 or later.

  1. Please delete all occurrences of CONFIG_AV1_DECODER=0 in your build files (including the comments). libavif v1.3.0 or later sets CONFIG_AV1_DECODER to 0 if you pass -DAVIF_CODEC_AOM_DECODE=OFF to libavif.
  2. Please try removing "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",. We fixed all the old cmake_minimum_required() versions in libavif v1.3.0. You should not need this workaround now.

@@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0
LIBAVIF_VERSION=1.2.1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggested change. I suggest two related changes if you will only be building with libavif v1.3.0 or later.

  1. Please delete all occurrences of CONFIG_AV1_DECODER=0 in your build files (including the comments). libavif v1.3.0 or later sets CONFIG_AV1_DECODER to 0 if you pass -DAVIF_CODEC_AOM_DECODE=OFF to libavif.
  2. Please try removing "-DCMAKE_POLICY_VERSION_MINIMUM=3.5",. We fixed all the old cmake_minimum_required() versions in libavif v1.3.0. You should not need this workaround now.

# CONFIG_AV1_DECODER=0 is a flag for libaom (included as a subproject of
# libavif) to disable the compilation and inclusion of aom's AV1 decoder.
# CONFIG_AV1_HIGHBITDEPTH=0 is another flag for libaom that disables support
# for encoding high bit depth images.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not disbale support for encoding high bit depth images. Most AVIF HDR images are 10 bits. Can we avoid this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pillow does not yet support high bit depth images (see #1888). It is currently a work-in-progress, but at the moment there can only ever be 8-bit images passed to the C API.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reply.

Nit: Change "another flag" to "a flag" because we are removing the previous paragraph (lines 131-132).

We can look into disabling support for decoding high bit depth images in the dav1d library. It has this build option:

option('bitdepths',
    type: 'array',
    choices: ['8', '16'],
    description: 'Enable only specified bitdepths')

But it seems hard to pass this build option to dav1d through libavif's cmake command line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might still want to allow decoding of 10-bit images, even if that necessarily means downsampling to 8 bit to load them into Pillow.

@@ -116,7 +119,7 @@ def cmd_msbuild(
"HARFBUZZ": "11.1.0",
"JPEGTURBO": "3.1.0",
"LCMS2": "2.17",
"LIBAVIF": "1.2.1",
"LIBAVIF": "1.3.0",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also change the libavif version to 1.3.0 in Pillow/depends/install_libavif.sh.

@radarhere radarhere mentioned this pull request May 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce AVIF wheel size?
6 participants