perf(docker): dispatch CPU kernels at runtime and unify the three images - #48
Conversation
The CPU image was built with GGML_NATIVE=OFF, which on x86 means baseline
scalar kernels — no AVX2, no FMA — and on arm64 a plain armv8.0 build with
neither dotprod nor i8mm. The `native` profile existed to escape that, at the
cost of an image that could not be published.
Build with GGML_CPU_ALL_VARIANTS instead. ggml compiles one CPU backend per
micro-architecture and dlopens the best one the host reports at startup, so a
portable image runs the host's instruction set. Verified on arm64: the image
loads libggml-cpu-armv8.2_2.so (dotprod + fp16 arithmetic) rather than the
armv8.0 baseline. The `native` profile and its service are gone.
This needs GGML_BACKEND_DL, so the backends are sorted into a compiled-in
GGML_BACKEND_DIR and the build no longer targets whisper-cli alone — the
variants are dlopen'd modules nothing links against, and a whisper-cli-only
build produced a binary with no CPU backend to load.
Two measurements corrected assumptions rather than confirming them:
- OpenBLAS earns its place and stays on. tiny.en on an 11 s clip, --cpus 4:
~2.0 s with BLAS against ~3.2 s without, over five runs each.
- OPENBLAS_NUM_THREADS is deliberately left unset. OpenBLAS-pthread cannot
see the cgroup quota, which looks like guaranteed oversubscription, but
ggml issues BLAS matmuls from one thread at a time so the pools never nest.
Pinned and unset were within noise for tiny.en and base.en at --cpus 4 and
--cpus 2 on a 10-CPU host.
Fix the Vulkan profile, which could not reach the GPU at all: /dev/dri was
passed through but the render node is root:render and the gateway runs as uid
10001, so the open failed with EACCES and whisper.cpp fell back to Mesa's
software rasteriser — slower than the CPU image, and silent about it.
Collapse Dockerfile.cuda and Dockerfile.vulkan into the one Dockerfile, chosen
by an ACCEL build arg, and lift the repeated service body in compose.yaml into
an anchor. The runtime half was copy-pasted three times and each service
repeated ten lines.
Build and runtime work besides:
- apt, uv and ccache cache mounts; the uv cache no longer ships in the image
- UV_COMPILE_BYTECODE, so a restart does not pay to compile the dependency
tree on its first request
- gcc-14 on the Ubuntu builders: GCC 13 rejects -march=...+sme and fails the
armv9.2 variants on an arm64 builder
- BUILD_JOBS, because nvcc with one job per core OOM-kills a small builder
partway through and reports only Error 137
- json-file log caps, cap_drop, no-new-privileges, and a tmpfs /tmp that also
takes per-request transcripts off the overlay filesystem
- .dockerignore becomes an allowlist, so a new directory is out of the build
context by default
CI builds all three accelerators on a matrix rather than leaving cuda and
vulkan to be discovered on a laptop, and smoke-tests the cpu image: that a
non-baseline CPU backend loads, that a clip transcribes, and that the container
serves the WebUI under the same hardening compose applies.
Claude-Session: https://claude.ai/code/session_014r21LLf1huM8UNq7tGgk2x
✅ Deploy Preview for voca-gateway canceled.
|
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Mr-Sunglasses has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
Problem
The portable CPU image disabled native code generation without replacing it with
runtime dispatch, leaving x86 hosts on baseline kernels and arm64 hosts without
dot-product or i8mm acceleration. The separate CPU, CUDA, and Vulkan Dockerfiles
also duplicated most of the image and had already drifted: the Vulkan service
could see
/dev/dribut its unprivileged user could not open the render node.The first CI run exposed one more build problem. An empty
BUILD_JOBSreachedCMake as bare
--parallel; with Make that means unlimited jobs, not one job perCPU. The Vulkan runner launched all ggml variants together and was terminated
with signal 143 under the resulting resource pressure. The full portable CUDA
architecture spread also made a compile-only PR check take more than 45 minutes.
Summary
ACCEL=cpu|cuda|vulkanmulti-stageDockerfile and share the common Compose service configuration.
loading the best supported host backend.
distribution-specific GIDs configurable in
.env.with BLAS versus 3.2 seconds without it for the same
tiny.ensample.cache out of the final image; and expose build-job/CMake tuning controls.
cap_drop: ALL,no-new-privileges, and a bounded tmpfs forper-request temporary files.
backend, transcript, health endpoint, WebUI, and runtime hardening.
BUILD_JOBStonproc, and give CI explicit memory-safe jobcaps: 4 for CPU/Vulkan and 3 for CUDA.
and omit CPU variants already covered by the CPU job. Normal Dockerfile,
Compose, operator, and release builds retain the portable CUDA defaults.
contributor guidance with the unified images and current CI behavior.
Verification
just test(Ruff, format, WPS, strict mypy, package/lock checks, audit,543 tests, and default Compose validation)
actionlint .github/workflows/container.ymlzizmor .github/workflows/container.ymldocker buildx build --check .(only the expected false positive for theVOCAGATEWAY_TOKEN_FILEsecret-file path)Compose 5s
The M1 laptop is useful for native Linux arm64 and static checks, but it does not
cover the Linux amd64 or NVIDIA CUDA paths. The GitHub-hosted matrix is the
acceptance test for those images. Its CUDA result is compile validation, not a
published or release-ready image.
Privacy and security
without discussion