Proposal: Add Google Highway portable-SIMD tier for non-x86/aarch64 platforms
Summary
SVT-AV1 ships hand-written SIMD for x86 and aarch64. On every other architecture the encoder falls back to plain C.
I propose an additional, strictly additive SIMD tier written against Google Highway: kernels are written once against Highway's portable ops, compiled for every Highway target of the build architecture, and dispatched at runtime to the best target the CPU supports.
I have almost completely implemented and tested the SVT-AV1 Highway backend. See my repository:
This is not a new idea inside AOMedia, libaom has been merging Highway kernels since 2025. The dependency form and the C++-in-a-C-library mix therefore both have an in-organization precedent. My proposal extends the same model to the platforms that have no hand-written SIMD at all.
Motivation
Outside x86 and aarch64, SVT-AV1 has no hand-written SIMD at all. On RISC-V, LoongArch and similar platforms the encoder runs on the C reference plus whatever the compiler's auto-vectorizer manages to make of it, which is slow in practice (measured below, distro interest in these targets already came up in #2239 (closed)).
Writing and maintaining per-architecture kernels for each of these platforms by hand is not realistic, but a single Highway version of each kernel covers all of them and still lets each CPU run its widest vector ISA. RVV is the most natural fit: Highway's scalable vectors are RVV's own programming model.
Design
The tier never replaces a native SIMD kernel. After the native rtcd setup, each covered slot is filled only if it still points at the C reference:
x86/aarch64 behavior is unchanged. The tier defaults to OFF there and is
not even compiled unless -DENABLE_HIGHWAY=ON is passed.
- On other platforms it defaults to ON. It is currently the only SIMD tier there.
- Highway is an optional dependency: if not found, the build continues with a warning and pure-C behavior.
- Dispatch uses Highway's per-target mechanism with the function pointer resolved once at init. The hot path is a plain indirect call.
--asm hwyselects the Highway tier alone (useful for exercising it on x86/aarch64 hosts).--asm cstill forces pure C.
Performance
I've tested my implementation on the devices I have access to. I don't have a device that supports RVV, so I haven't been able to test the performance of RVV.
Loongson 3C5000L (LoongArch64's LA464, dual socket), clang build
Command: ffmpeg -i input.webm -c:a copy -c:v libsvtav1 -f null - -benchmark
(ffmpeg 8.1.2, default libsvtav1 settings; input: 3840x2160, yuv420p, bt709, 23.98 fps). With the SVT-AV1 branch hwy-upstream-v4.2.0 on my repo.
| build | speed | vs upstream |
|---|---|---|
| upstream master (auto-vectorized by compiler for LSX) | 0.151x realtime (3.6 fps) | 1.0 |
| Highway tier, LSX (128-bit) | 0.404x (9.7 fps) | 2.7x |
| Highway tier, LASX (256-bit) | 0.44x (11 fps) | 2.9x |
The LSX to LASX scaling on the same source also demonstrates that the runtime
dispatch actually uses the widest ISA the CPU offers rather than a fixed
128-bit baseline; the same sources scale to RVV VLEN and AVX-512 widths.
These are end-to-end figures, the input is software-decoded in the same run and that fixed decode cost is included in all three builds, so the speedup of the encoder itself is strictly larger than the ratios above.
Apple M4 Pro (aarch64), 1080p 300 frames, preset 8
End-to-end (median of 3 runs, run-to-run variance on this host is ±15%):
| Level | FPS | Relative |
|---|---|---|
--asm c |
45.5 fps | 1.0x |
--asm hwy |
124.1 fps | 2.7x |
--asm max |
135.0 fps | 3.0x |
Bitstreams are byte-identical across all three. Per-kernel, measured via direct rtcd function-pointer calls (ns/call, min of 3, note the "C" column is clang-auto-vectorized NEON, not scalar):
| kernel | C | Highway | hand-written NEON |
|---|---|---|---|
| sad64x64 | 484 | 45 | 45 |
| nxm_sad64x64 | 488 | 45 | 46 |
| variance64x64 | 338 | 66 | 65 |
| subpel_variance64x64 | 733 | 280 | 260 |
| convolve_y_sr 64x64 | 9813 | 350 | 276 |
| convolve_2d_sr 64x64 | 14605 | 1300 | 803 |
| fwd_txfm2d_32x32 | 2298 | 548 | 360 |
SAD/variance/reduction kernels reach parity with the hand-written NEON. Convolve and sub-pixel variance are within 1.3-1.6x. The remaining gaps come from NEON-only instruction fusions that portable code cannot express they shrink as Highway backends improve, without any change on our side.
Correctness
Every Highway kernel is checked against the C reference in the gtest
suites, with random data over all block sizes, and must match bit exactly.
5740 such tests pass. Encoder output is byte-identical between --asm c,
--asm hwy and --asm max aarch64, and also byte-identical between
--asm c, --asm hwy on LoongArch64.
Kernels that do not beat the auto-vectorized C reference on the validation host are kept unregistered on purpose. They are still covered by the tests and listed in the design doc.
Dependency
Highway >= 1.4.0 is currently found via find_package/pkg-config as an
optional system dependency.
- The tier is C++ (required by Highway) compiled as an object library inside the otherwise-C encoder. That's the arrangement libaom already uses.
COMPILE_C_ONLY=ONdisables the tier entirely.
Scope
~24 kernel families, ~27.5k lines: SAD, variance/MSE, sub-pixel variance, 8-bit and highbd convolve, joint compound, OBMC, warped motion, intra prediction, CDEF, deblocking, loop restoration, temporal filtering, picture operators, forward/inverse transforms, quantization/encodetxb, compound blend/masks, and misc DSP. Full list: https://gitlab.com/wszqkzqk/SVT-AV1/-/blob/master/Docs/Appendix-Highway-Portable-SIMD.md?ref_type=heads.