Qwen 3.6 35B A3B on AMD Ryzen AI Max+ 395 (Strix Halo) via Vulkan with Kubernetes
Summary
April has been quite a month for local LLMs! Shortly after Gemma 4 26B was released, it quickly became my favorite model to use. However, just as Gemma 4 26B established itself, Qwen released the new Qwen 3.6 35B A3B model.
I haven not yet done much in depth testing; and I find Qwen 3.6 to be worse at linguistic tasks than Gemma 4 for instance, the model promises to be a strong contender - especially for agentic coding tasks. From a performance perspective, it is in the same ballpark as Gemma 4 26B. So interestingly, both models released in April are very similar in size, active parameters, performance, and compute requirements.
Since I have already established my llama.cpp server with GPT OSS 120B (read more here), and more recently, with Gemma 4 (here), adding Qwen 3.6 was quite straightforward as well. I did have to add the spirv-headers to the Dockerfile in order to rebuild llama.cpp, as they are now required.
Setup
As usual, the first step is downloading the models from huggingface - I recommend using the Unsloth Quant at Q8, which can be found here.
Afterwards, updating / rebuilding llama.cpp is recommended; but otherwise we're ready to go.
Here's the command I use in llama-swap, which can also be used to run the llama.cpp web interface directly.
qwen-3.6-35b-a3b:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/qwen-3.6-35b-a3b/Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf --mmproj /models/chat/qwen-3.6-35b-a3b/mmproj-BF16.gguf --jinja --temp 1.0 --top-p 0.95 --min-p 0 --top-k 20 --repeat-penalty 1.0 --presence-penalty 1.5 --reasoning-budget -1 -c 262144
The command can probably still be optimized, but at least it works without issue / looping.
Performance
During the performance test, the models are incorrectly recognized as qwn35moe models; but that does not matter, as the loaded models are in fact the new qwen 3.6 models.
Prompt processing seems to be a bit slower than expected, but text generation speed is quite good.
On my setup I do recommend Q8 instead of BF16 in order to achieve optimal performance. With that quantization, prompt processing hovers around 600 - 800 tokens / s, and text generation lands at 44 tokens / s. So comparing it to Gemma 4 26B, it is slower on prompt processing, but faster in text generation. On BF16, the prompt processing performance drops to around 50%, which is about expected. The text generation drops even more to just 25% of the Q8 performance, which is a speed where it definitely starts to hurt - at around 10 tokens / s.
As for Qwen 27B dense at Q8, the raw performance is unfortunately a bit lacking - as expected for a dense model. But it can run, and it runs well enough if necessary, and if a higher quality output is absolutely required.
Qwen 3.6 27B Q8
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
|---|---|---|---|---|---|---|---|---|---|
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 263.53 ± 1.63 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 260.32 ± 1.31 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 229.73 ± 0.57 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 141.32 ± 1.47 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 6.25 ± 0.00 |
llama-bench -m /models/chat/qwen-3.6-27b/Qwen3.6-27B-UD-Q8_K_XL.gguf -t 1 -fa 1 -b 2048 -ub 2048 -p 2048,8192,16384,32768
ggml_vulkan: Found 1 Vulkan devices:
ggml_vulkan: 0 = Radeon 8060S Graphics (RADV STRIX_HALO) (radv) | uma: 1 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 1 | matrix cores: KHR_coopmat
load_backend: loaded Vulkan backend from /app/llama.cpp/build/bin/libggml-vulkan.so
load_backend: loaded CPU backend from /app/llama.cpp/build/bin/libggml-cpu-zen4.so
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | ------: | -------: | -: | --------------: | -------------------: |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 263.53 ± 1.63 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 260.32 ± 1.31 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 229.73 ± 0.57 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 141.32 ± 1.47 |
| qwen35 27B Q8_0 | 32.89 GiB | 26.90 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 6.25 ± 0.00 |
Qwen 3.6 35B A3B Q8
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
|---|---|---|---|---|---|---|---|---|---|
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 777.48 ± 11.72 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 794.97 ± 3.63 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 735.51 ± 4.80 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 660.42 ± 3.08 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 44.58 ± 0.08 |
./llama-bench -m /models/chat/qwen-3.6-35b-a3b/Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf -t 1 -fa 1 -b 2048 -ub 2048 -p 2048,8192,16384,32768
ggml_vulkan: Found 1 Vulkan devices:
ggml_vulkan: 0 = Radeon 8060S Graphics (RADV STRIX_HALO) (radv) | uma: 1 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 1 | matrix cores: KHR_coopmat
load_backend: loaded Vulkan backend from /app/llama.cpp/build/bin/libggml-vulkan.so
load_backend: loaded CPU backend from /app/llama.cpp/build/bin/libggml-cpu-zen4.so
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | ------: | -------: | -: | --------------: | -------------------: |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 777.48 ± 11.72 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 794.97 ± 3.63 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 735.51 ± 4.80 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 660.42 ± 3.08 |
| qwen35moe 35B.A3B Q8_0 | 35.80 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 44.58 ± 0.08 |
build: 59accc886 (8837)
Qwen 3.6 35B A3B BF16
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
|---|---|---|---|---|---|---|---|---|---|
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 363.12 ± 2.70 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 356.65 ± 3.86 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 346.26 ± 3.68 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 325.38 ± 1.87 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 10.66 ± 0.01 |
./llama-bench -m /models/chat/qwen-3.6-35b-a3b/Qwen3.6-35B-A3B-BF16-00001-of-00002.gguf -t 1 -fa 1 -b 2048 -ub 2048 -p 2048,8192,16384,3
2768
ggml_vulkan: Found 1 Vulkan devices:
ggml_vulkan: 0 = Radeon 8060S Graphics (RADV STRIX_HALO) (radv) | uma: 1 | fp16: 1 | bf16: 0 | warp size: 64 | shared memory: 65536 | int dot: 1 | matrix cores: KHR_coopmat
load_backend: loaded Vulkan backend from /app/llama.cpp/build/bin/libggml-vulkan.so
load_backend: loaded CPU backend from /app/llama.cpp/build/bin/libggml-cpu-zen4.so
| model | size | params | backend | ngl | threads | n_ubatch | fa | test | t/s |
| ------------------------------ | ---------: | ---------: | ---------- | --: | ------: | -------: | -: | --------------: | -------------------: |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp2048 | 363.12 ± 2.70 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp8192 | 356.65 ± 3.86 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp16384 | 346.26 ± 3.68 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | pp32768 | 325.38 ± 1.87 |
| qwen35moe 35B.A3B BF16 | 64.60 GiB | 34.66 B | Vulkan | 99 | 1 | 2048 | 1 | tg128 | 10.66 ± 0.01 |
build: 59accc886 (8837)
Setup and Config
As said previously, only the Dockerfile is updated with the added spirv-headers, otherwise everything stays the same as for Gemma 4. I'll just leave the link to the Gemma 4 setup here for more details. Especially if you want to run the setup with docker-compose or on ROCm, please refer to the other blog.
Dockerfile / Container for Vulkan Backend
FROM debian:testing
ARG NODE_VERSION=24
## Container
RUN mkdir /models
RUN mkdir /conf
## Install dependencies
RUN apt update \
&& apt upgrade -y \
&& apt install -y \
build-essential \
git \
python3 \
python3-pip \
python3-wheel \
cmake \
wget \
xz-utils \
npm \
nodejs \
curl \
libcurl4-openssl-dev \
libcpp-httplib-dev \
libminiaudio-dev \
glslc \
libxcb-xinput0 \
libxcb-xinerama0 \
libxcb-cursor-dev \
libvulkan-dev \
vulkan-tools \
radeontop \
spirv-headers \
&& apt autoremove -y \
&& apt clean -y \
&& rm -rf /tmp/* /var/tmp/* \
&& find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete \
&& find /var/cache -type f -delete
RUN curl --output go-linux-amd64.tar.gz --location https://go.dev/dl/go1.26.1.linux-amd64.tar.gz \
&& rm -rf /usr/local/go && tar -C /usr/local -xzf go-linux-amd64.tar.gz \
&& rm go-linux-amd64.tar.gz
ENV PATH=$PATH:/usr/local/go/bin
## Clone repositories
WORKDIR /app
RUN git clone https://github.com/ggerganov/llama.cpp.git
RUN git clone https://github.com/mostlygeek/llama-swap
## Build llama.cpp
WORKDIR /app/llama.cpp
RUN cmake -B build -DGGML_NATIVE=OFF -DGGML_VULKAN=ON -DLLAMA_BUILD_TESTS=OFF -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON && \
cmake --build build --config Release -j$(nproc)
RUN pip install --break-system-packages --upgrade setuptools
RUN pip install --break-system-packages -r requirements.txt
WORKDIR /app/llama.cpp/build/bin
## Build llama-swap
WORKDIR /app/llama-swap
RUN make clean all
WORKDIR /app/llama-swap/build
CMD ["/bin/bash"]
Kubernetes Manifest
The following Kubernetes manifest sets up the whole llm magic on the kubernetes cluster. As a note, there are some flux specific configurations, so if you do not use flux in your cluster, remove those.
---
apiVersion: v1
kind: Namespace
metadata:
name: llm
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: llm
namespace: llm
annotations:
cert-manager.io/cluster-issuer: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd
spec:
ingressClassName: traefik
rules:
- host: llm.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: llm
port:
number: 8080
tls:
- hosts:
- llm.example.com
secretName: llm-example-com
---
kind: Service
apiVersion: v1
metadata:
name: llm
namespace: llm
spec:
selector:
app: llm
ports:
- protocol: TCP
port: 8080
targetPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-v42
namespace: llm
spec:
selector:
matchLabels:
app: llm
replicas: 1
template:
metadata:
labels:
app: llm
spec:
securityContext:
seccompProfile:
type: Unconfined
fsGroup: 0
runAsUser: 0
runAsGroup: 0
runAsNonRoot: false
supplementalGroups:
- 44
- 991
hostIPC: true
containers:
- name: llm
securityContext:
privileged: true
allowPrivilegeEscalation: true
capabilities:
add:
- SYS_PTRACE
image: registry.akehir.com/infra/llama-swap-llama-cpp-vulkan/llama-release:master-ce8f1363-1776414432 # {"$imagepolicy": "llm:image-policy"}
command: ['/app/llama-swap/build/llama-swap-linux-amd64']
ports:
- containerPort: 8080
volumeMounts:
- name: llama-swap-config
mountPath: /app/llama-swap/build/config.yaml
subPath: config.yaml
readOnly: true
- name: dev-kfd
mountPath: /dev/kfd
securityContext:
privileged: true
- name: dev-dri
mountPath: /dev/dri
securityContext:
privileged: true
- name: models
mountPath: /models
volumes:
- name: llama-swap-config
configMap:
name: llama-swap-config-v42
items:
- key: config.yaml
path: config.yaml
- name: dev-kfd
hostPath:
path: /dev/kfd
- name: dev-dri
hostPath:
path: /dev/dri
- name: models
hostPath:
path: /models
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
- key: kubernetes.io/hostname
operator: In
values:
- srv-7
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: module
operator: In
values:
- llm
topologyKey: 'kubernetes.io/hostname'
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageRepository
metadata:
name: image-repository
namespace: llm
spec:
image: registry.akehir.com/infra/llama-swap-llama-cpp-vulkan/llama-release
interval: 5m
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImagePolicy
metadata:
name: image-policy
namespace: llm
spec:
imageRepositoryRef:
name: image-repository
filterTags:
pattern: '^master-[a-fA-F0-9]+-(?P<ts>[1-9][0-9]*)'
extract: '$ts'
policy:
numerical:
order: asc
---
apiVersion: image.toolkit.fluxcd.io/v1
kind: ImageUpdateAutomation
metadata:
name: image-update-automation
namespace: llm
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: flux
git:
checkout:
ref:
branch: master
commit:
author:
email: mr.robot@akehir.com
name: mr.robot
messageTemplate: |
Automated image update
Automation name: {{ .AutomationObject }}
Files:
{{ range $filename, $_ := .Changed.FileChanges -}}
- {{ $filename }}
{{ end -}}
Objects:
{{ range $resource, $changes := .Changed.Objects -}}
- {{ $resource.Kind }} {{ $resource.Name }}
Changes:
{{- range $_, $change := $changes }}
- {{ $change.OldValue }} -> {{ $change.NewValue }}
{{ end -}}
{{ end -}}
push:
branch: master
update:
path: ./clusters/k8s-cluster-1
strategy: Setters
---
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: flux
namespace: llm
spec:
interval: 1m0s
ref:
branch: master
url: https://git.akehir.com/machina/flux.git
secretRef:
name: mr-robot
---
apiVersion: v1
kind: ConfigMap
metadata:
name: llama-swap-config-v42
namespace: llm
data:
config.yaml: |
healthCheckTimeout: 600
startPort: 14001
models:
gemma-4-26b:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/gemma-4-26B-A4B/gemma-4-26B-A4B-it-UD-Q8_K_XL.gguf --mmproj /models/chat/gemma-4-26B-A4B/mmproj-BF16.gguf --chat-template-file /app/llama.cpp/models/templates/google-gemma-4-31B-it-interleaved.jinja --jinja --temp 1.0 --top-p 0.95 --top-k 64 -c 255555
aliases:
- "gpt-4.1-mini"
- "gemma-4-26b--q8"
- "default"
gpt-oss-120b:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/gpt-oss-120b/gpt-oss-120b-UD-Q8_K_XL-00001-of-00002.gguf --jinja --temp 1.0 --top-p 1.0 --top-k 0 -c 111111
gemma-4-31b:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/gemma-4-31B-it/gemma-4-31B-it-UD-Q8_K_XL.gguf --jinja --temp 1.0 --top-p 0.95 --top-k 64 -c 255555
qwen-3.6-35b-a3b:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/qwen-3.6-35b-a3b/Qwen3.6-35B-A3B-UD-Q8_K_XL.gguf --mmproj /models/chat/qwen-3.6-35b-a3b/mmproj-BF16.gguf --jinja --temp 1.0 --top-p 0.95 --min-p 0 --top-k 20 --repeat-penalty 1.0 --presence-penalty 1.5 --reasoning-budget -1 -c 262144
glm-4.7-flash:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/glm-4.7-flash/GLM-4.7-Flash-UD-Q8_K_XL.gguf --jinja --temp 1.0 --top-p 0.95 --top-k 20 --repeat-penalty 1.0 -c 131072
minimax-2.7:
cmd: /app/llama.cpp/build/bin/llama-server --port ${PORT} --host 0.0.0.0 --model /models/chat/minimax-2.7/MiniMax-M2.7-UD-IQ4_XS-00001-of-00004.gguf --jinja --temp 1.0 --top-p 0.95 --top-k 40 -c 42000
groups:
default:
swap: false
members:
- "gemma-4-26b"
hooks:
on_startup:
preload:
- "gemma-4-26b"
---