# 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 /src COPY 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/14 RUN 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 /src WORKDIR /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 || true RUN 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"]