A single-model (Bonsai-27B) inference engine for Intel Arc GPUs
Something went wrong. Try again.
qxmx Dockerfile
6.7 kB · 134 lines
Dockerfile
at extra-debug
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135# qxmx Docker build.## Produces an image that can build AND run qxmx end-to-end on the GPU.# Builder stage pulls in oneAPI icpx (the SYCL/ESIMD toolchain) + meson;# runtime stage keeps the binaries + oneAPI runtime libs + the Debian# Level-Zero stack (libze1 loader + libze-intel-gpu1 backend + libigc2 JIT).## Build:# docker build -t qxmx .## The build needs NO GPU — only icpx. Running the engine needs the Intel Arc# Pro B70 (Xe2), passed in via --device /dev/dri (the i915 kernel module runs# on the host; the userspace Level-Zero + Intel GPU backend + IGC live in the# image). Mount the model and run the server (MVP done-criterion #7):## docker run --rm --device /dev/dri -p 8080:8080 \# -v ~/models/bonsai:/models:ro \# qxmx ./build/qxmx_serve --host 0.0.0.0 --port 8080 \# /models/Ternary-Bonsai-27B-Q2_g64.gguf## (--host 0.0.0.0 is required inside a container: the default 127.0.0.1 is# unreachable through -p port publishing.) CLI tools work the same way:## docker run --rm --device /dev/dri -v ~/models/bonsai:/models:ro \# qxmx ./build/qxmx_run /models/Ternary-Bonsai-27B-Q2_g64.gguf \# "The capital of France is"## Validated in-container (2026-07-20, post-reorg): qxmx_serve /health +# /v1/chat/completions (stream + non-stream + tool-call fixture) green,# qxmx_diff mean|diff|=0.108 (matches native).
# ---- Stage 1: builder (oneAPI icpx + meson, compiles qxmx) --------------FROM debian:trixie AS builder
# Build tools: meson/ninja/pkg-config, gcc (for --gcc-install-dir + C sources),# and oneAPI apt setup. ca-certificates + wget for the oneAPI GPG key.RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential meson ninja-build pkg-config \ ca-certificates gnupg wget \ && rm -rf /var/lib/apt/lists/*
# oneAPI apt repo (provides intel-oneapi-dpcpp-cpp, the icpx compiler).# Key is fetched over HTTPS; keyring stored per Debian apt best practice.RUN wget -qO /tmp/intel-key.asc \ https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ && gpg --dearmor < /tmp/intel-key.asc \ > /usr/share/keyrings/oneapi-archive-keyring.gpg \ && echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \ > /etc/apt/sources.list.d/oneapi.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ intel-oneapi-dpcpp-cpp-2026.1 \ && rm -rf /var/lib/apt/lists/*
# Put icpx on PATH (setvars.sh is heavy; just add the bin dir).ENV PATH=/opt/intel/oneapi/compiler/2026.1/bin:$PATH
WORKDIR /srcCOPY meson.build meson_options.txt ./COPY meson/ ./meson/COPY src/ ./src/COPY tools/ ./tools/COPY tests/ ./tests/COPY third_party/ ./third_party/
# gcc-14 ships with Debian trixie's build-essential; the native file pins that# path for icpx's --gcc-install-dir workaround. If the image's gcc major# differs, override: --build-arg gcc_install_dir=...ARG gcc_install_dir=/usr/lib/gcc/x86_64-linux-gnu/14RUN sed -i "s|/usr/lib/gcc/x86_64-linux-gnu/14|${gcc_install_dir}|g" meson/icpx.ini \ && meson setup build --native-file meson/icpx.ini \ && meson compile -C build
# ---- Stage 2: runtime (binaries + oneAPI runtime libs + Level-Zero) ----FROM debian:trixie AS runtime
# ca-certificates is needed to verify apt.repos.intel.com's HTTPS cert# (not in the base trixie image).RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/*
# oneAPI apt source (for libsycl / compiler runtime libs).COPY --from=builder /etc/apt/sources.list.d/oneapi.list /etc/apt/sources.list.d/COPY --from=builder /usr/share/keyrings/oneapi-archive-keyring.gpg /usr/share/keyrings/
# oneAPI compiler runtime (libsycl, libimf, libsvml, libintlc, libur_loader —# what the binaries link against) + Level-Zero stack:# libze1 — the Level-Zero loader (libze_loader.so.1)# libze-intel-gpu1 — the Intel GPU backend (libze_intel_gpu.so.1)# libigdgmm12 — transitive dep (libze-intel-gpu1 needs >= 22.9)# libigc2 libigdfcl2 — Intel Graphics Compiler (JIT-compiles SPIR-V kernels# at runtime; without it the engine aborts at first kernel)# These ship in Debian testing/unstable but NOT yet in trixie (stable), so we# add a testing source and prefer it for the Intel GPU stack (via the pin).RUN echo 'deb http://deb.debian.org/debian testing main' \ > /etc/apt/sources.list.d/testing.list \ && printf 'Package: libze1 libze-intel-gpu1 libigdgmm12 libigc2 libigdfcl2\nPin: release a=testing\nPin-Priority: 600\n\nPackage: *\nPin: release a=testing\nPin-Priority: 1\n' \ > /etc/apt/preferences.d/99-testing-pin \ && apt-get update && apt-get install -y --no-install-recommends \ intel-oneapi-compiler-dpcpp-cpp-runtime-2026.1 \ libze1 libze-intel-gpu1 libigc2 libigdfcl2 \ libhwloc15 \ libstdc++6 libgcc-s1 zlib1g \ && rm -rf /var/lib/apt/lists/*
# Grant container access to /dev/dri/renderD128 (the GPU render node). The# host's `render` group owns the node; we create a matching group and add root# so kernels can open the device without `--group-add` at run time.RUN groupadd -r -g 127 render && usermod -aG render root
# Built binaries + source tree (source included for re-builds / inspection).COPY --from=builder /src /srcWORKDIR /src
# oneAPI runtime libs live outside the default linker search path. On the host# `setvars.sh` sets this up; in the container we pin LD_LIBRARY_PATH. Needs:# - compiler/2026.1/lib : libsycl, libimf, libsvml, libintlc, libur_loader,# libur_adapter_level_zero* (the SYCL→Level-Zero glue)# - umf/1.1/lib : libumf.so.1 (dependency of every ur_adapter)ENV LD_LIBRARY_PATH=/opt/intel/oneapi/compiler/2026.1/lib:/opt/intel/oneapi/umf/1.1/lib
# Sanity check: the binaries can find all their shared libs (oneAPI + Level-Zero).RUN ldd build/qxmx_run build/qxmx_serve | grep -E 'not found' && exit 1 || trueRUN ldd build/qxmx_serve | grep -E 'libze|libsycl'
# The server default port (documentation only; publish with -p at run time).EXPOSE 8080
# NO entrypoint wrapper: a bash -lc entrypoint swallows `docker run qxmx A B# C` args (bash -c takes only the first token as the command string; the rest# become $0/$1 it never sees -- qxmx_serve would run with zero args and print# usage). LD_LIBRARY_PATH is already pinned via ENV, so args can flow# straight to the binary. Bare `docker run qxmx` prints qxmx_serve's usage.CMD ["./build/qxmx_serve"]