A while back I bought myself a little box based on a AMD Ryzen AI Max+ 395 with 128GB of shared memory. The reason, simple, running a few local LLMs to process data I don’t want to share with anybody, and as a PoC to take into my professional services.
My Setup
I started by discussing a few use cases with a good friend, who plays a lot with image editing and generation and has done some audio and video things. We, I guess obviously, discussed going for an NVIDIA GFX card with 24GB of RAM vs. a system with an embedded CPU with a larger amount of shared RAM. My perspective was 30..40..50 output tokens per second on a smaller model vs. maybe 10..15 output tokens per second on a properly large model. His perspective was more on the payload, image generation, especially when based on other images and especially when these images are combined to a video simply require a notable amount of RAM. As a result I decided to go for the shared memory, to give him possibilities and for myself, I value the quality of the output more than the speed.
Looking at prices, and just paying it out of my own pockets, I ended up going for a
GMKTec EVO-X2
running an AMD Ryzen AI Max+ 395 with 128GB of shared memory. I took the 1TB version, as I didn’t really care about the HDD and, well, saved a few Euros.
Having planned to share it with friends, and thus also refinancing it a little, I had Claude Vibe-Code me a Django based LLM router with metering. It allows the creation of API keys, which can then be utilised via an OpenAI compatible API. (It is important to note that “OpenAI compatible” doesn’t have anything to do with chatGPT. They were simply smart enough to give a now widely used API their own name.) The application shows the currently running model(s), contains a small voting system, allows changing the model and simply counts how many tokens a user has used. While the overall setup was initially all running on my Ailer0, the GMKTec box, I migrated it to a different machine. Thus, the setup is now multi-node compatible, and supports multi-tenant usage. As said, I was aiming at potentially using the setup in my professional services. Not having mentioned it yet, the setup runs in Docker.
The initial inference side was based on
Ollama
, as I had used it before and it supported the models I initially wanted to try. After some research, and further discussions with “Kumpel Claude”, I decided to add OpenWebUI as a quick and simple interface. Having a running Authentik for SSO, I also integrated everything with that.
The First Tests
Quick, easy, simple. I have a load-out in the mornings, one in the evenings, one during the night, thus the model automatically changes as expected. Some of the load-outs actually run more than one model at a time, simply to optimise a few tasks. For me, I was happy, it’s not Claude, its not chatGPT, but it delivers exactly what I need: Evaluating data, enriching information utilising MCP and RAG.
For my friend’s side, well, things didn’t quite go as well. I initially setup
comfy
, which I’m running on my normal box with my GFX card on 12GB of RAM. While it ran and it enjoyed using the extra RAM, most things are developed for NVIDIA’s architecture, thus performance, well, lets simply not call it performance. Using it wasn’t fun. I then tried a few other image and video generation models and setups. The first thing I learned here, was that while Ollama was compatible with all of the typical inference models, a few of the graphics options only run on
vLLM
. Thus, added to the stack. Sadly with no positive results as, well, I wasn’t able to find anything I actually enjoyed using that ran on my AMD CPU.
Continuing
As potentially expected, I added a few abliterated models, which as expected are able to use first semester physics and chemistry and will answer next to all questions. In return, I sometimes have the feeling, that the abliterated models sometimes lack a little logic and consistency. That said, this is a known issue.
Going further I added a
searXNG
to allow the inclusion of some fresh data and created a few use cases. It took a few tries, but then ran smoothly. I also added Text to Speech based on Qwen3 TTS. I have a full flow creating a few hundred samples based on a static text, allow me to listen to them, and then clone the voices I like. Why cloning? Well, I hadn’t realized, that when using generic TTS, the text itself is part of the context from which the voice is generated. Practically this means, new text new voice :-( Coming from here, I also added a little STT.
The system is now fully based on “loadouts”, where a loadout conists of one or more models in addition to the layer on which the model runs, vLLM or Ollama. As such, when switching from an Ollama based model to a vLLM based model, the ollama container is fully stopped, the vLLM container launched and then the model loaded.
First, Simple Use Cases
My first larger use case was my little ISO, Information Security Officer. Following my principle, of actually using AI as a tool not as solution, I’ve created a database of various Security standards, papers and other publications and am using the LLM as an interface. As such, every question I ask is paired with a reference from the database, if there is one, otherwise the model gets to properly infer and give me its opinion. Following this approach, while the response is still far from human, its clean and good, so practically, it saves me a fair amount of time on certain topics. (I’m still far from having fed it everything I want it to know.)
Daily business? Well, my board game collection now has a chat interface answering basic rule questions, based on RAG. My inventory offers the same. Identifying names, anonymizing data, summarizing.
All that said, there multiple models I’ve been wanting to try, which simply won’t run on the AMD architecture, but, hey…
Random Notes
- Allocation the RAM to the GPU via BIOS seems like the correct approach, but my current setup, to make it work, actually has the full RAM allocated to the CPU and then utilized from the GPU via the Kernel. There is a small issue, that otherwise the memory cannot be fully used. That said, its the same physical memory on the same BUS on the same CPU, thus next to no speed impact.
- Loading models can take a fair amount of time, especially the larger one, so ad-hoc changes might fail
- There are smaller models and simpler workloads , that are faster and just as good on my old GTX 1650, as such i.e. TTS and STT run there
- It’s one of my nodes
Numbers
Having played with a few models, here the current benchmark:
| Model | Avg t/s | Min t/s | Max t/s | Avg TTFT | Output tokens | Questions | Reasoning chars |
|---|---|---|---|---|---|---|---|
| aya-expanse-32b | 9.9 | 7.8 | 10.4 | 574 ms | 3811 | 18 / 18 | — |
| codegemma-7b | 34.5 | 23.5 | 37.2 | 129 ms | 1926 | 18 / 18 | — |
| command-r-35b | 10.8 | 8.0 | 11.3 | 432 ms | 2862 | 18 / 18 | — |
| cydonia-24b | 14.5 | 10.5 | 14.9 | 265 ms | 6183 | 18 / 18 | — |
| deepseek-coder-v2-16b | 57.0 | 25.9 | 75.5 | 181 ms | 4860 | 18 / 18 | — |
| deepseek-r1-32b | 10.6 | 10.2 | 10.8 | 826 ms | 12693 | 18 / 18 | 40916 |
| deepseek-v4-flash-reap150b | 12.8 | 0.9 | 14.4 | 2109 ms | 4889 | 18 / 18 | 15836 |
| devstral-small-2-24b | 14.4 | 13.9 | 14.6 | 381 ms | 14604 | 18 / 18 | — |
| dolphin-llama3-70b | 5.1 | 3.4 | 5.4 | 781 ms | 2437 | 18 / 18 | — |
| euryale-70b | 4.8 | 3.7 | 5.0 | 703 ms | 4763 | 18 / 18 | — |
| gemma3-27b | 11.1 | 7.9 | 11.7 | 771 ms | 5876 | 18 / 18 | — |
| gemma4-12b | 39.6 | 31.1 | 47.7 | 546 ms | 11614 | 18 / 18 | 26940 |
| gemma4-31b-heretic | 9.9 | 3.3 | 10.7 | 1877 ms | 10402 | 18 / 18 | 20416 |
| glm-4.5-air | 23.9 | 22.1 | 24.9 | 996 ms | 11878 | 18 / 18 | 30342 |
| gpt-oss-20b | 45.4 | 36.7 | 48.1 | 394 ms | 6995 | 18 / 18 | 8931 |
| laguna-xs-2.1 | 57.3 | 23.6 | 61.9 | 1238 ms | 10642 | 18 / 18 | 32045 |
| llama3.3-70b | 4.6 | 0.1 | 5.0 | 4743 ms | 4756 | 18 / 18 | — |
| ministral3-14b-abliterated | 23.2 | 1.6 | 25.1 | 452 ms | 4881 | 18 / 18 | — |
| mistral-small3.1 | 13.4 | 0.8 | 14.7 | 818 ms | 4056 | 18 / 18 | — |
| mistral-small4-heretic | 31.2 | 3.0 | 35.3 | 1436 ms | 4480 | 18 / 18 | — |
| mixtral-8x22b | 8.7 | 5.5 | 9.4 | 1421 ms | 4192 | 18 / 18 | — |
| nanbeige4.3-33b | 9.6 | 2.5 | 10.4 | 1418 ms | 7402 | 18 / 18 | 11010 |
| nemotron3.5-bolt | 42.4 | 3.8 | 63.5 | 1010 ms | 8496 | 18 / 18 | 25710 |
| phi4-14b | 22.6 | 17.0 | 23.5 | 158 ms | 4936 | 18 / 18 | — |
| qwen2.5-7b | 41.7 | 32.3 | 43.6 | 92 ms | 4607 | 18 / 18 | — |
| qwen2.5-coder-32b | 10.4 | 8.0 | 10.8 | 337 ms | 4334 | 18 / 18 | — |
| qwen2.5-coder-7b | 41.3 | 28.1 | 43.6 | 98 ms | 3842 | 18 / 18 | — |
| qwen25-05b | 263.0 | 179.8 | 277.2 | 27 ms | 5578 | 18 / 18 | — |
| qwen3-30b-a3b | 66.2 | 59.7 | 68.1 | 272 ms | 13808 | 18 / 18 | 40347 |
| qwen3-30b-a3b-instruct-2507 | 61.9 | 34.4 | 68.8 | 158 ms | 4776 | 18 / 18 | — |
| qwen3-32b | 10.4 | 10.0 | 10.5 | 865 ms | 15102 | 18 / 18 | 46302 |
| qwen3-coder-next | 42.7 | 20.6 | 47.7 | 275 ms | 4717 | 18 / 18 | — |
| qwen3.5-122b-heretic | 27.9 | 8.1 | 29.7 | 1917 ms | 13681 | 18 / 18 | 39385 |
| qwen3.6-27b | 19.8 | 15.5 | 23.2 | 667 ms | 13745 | 18 / 18 | 46727 |
| qwen3.6-35b-a3b-heretic | 55.9 | 54.5 | 57.1 | 295 ms | 15474 | 18 / 18 | 52057 |
| qwen3.8-27b | 18.0 | 11.7 | 23.2 | 841 ms | 8144 | 18 / 18 | 14336 |
| qwen3.8-27b-heretic | 11.8 | 10.8 | 12.3 | 641 ms | 5100 | 18 / 18 | 12821 |
The numbers are based on the following questions
-
What is the capital of Japan?
-
Convert 98.6 degrees Fahrenheit to Celsius, showing your work.
-
(context:
widget-code, turn 1) Write a Python function calledtotal_eventhat takes a list of integers and returns the sum of the even numbers in it. -
(context:
widget-code, turn 2) Now modifytotal_evenso it returns a tuple(sum, count)— the sum of even numbers and how many were found. -
(context:
widget-code, turn 3) If I called this updated function on[1, 3, 5, 7], what would it return? Explain briefly why. -
A train leaves City A at 60 mph heading toward City B, 300 miles away. At the same time, a second train leaves City B heading toward City A at 90 mph. How long until they meet, and how far from City A?
-
Explain the difference between TCP and UDP, and give one real-world scenario where each would be the better choice.
-
(context:
inventory, turn 1) Let’s build a simple inventory. I have three items: Widget A costs $5, Widget B costs $10, Widget C costs $20. Represent this as a Python dictionary mapping name to price. -
(context:
inventory, turn 2) Add a fourth item, Widget D, costing $15, to that dictionary. -
(context:
inventory, turn 3) Write a function that takes this dictionary and returns the name of the single most expensive widget. -
(context:
inventory, turn 4) Now modify the function to return the two most expensive widgets, ordered from most to least expensive. -
(context:
inventory, turn 5) If I also added a Widget E costing $20 — the same price as Widget C — what would your function return for the top two, and why might that result be ambiguous or order-dependent? -
Long-context recall (needle in a haystack). Read the following passage carefully, then answer the question at the end.
The Meridian Shipping Company was founded in 1884 by a group of merchants in the port city of Calloway. Over the following decades, it expanded its fleet from two small schooners to more than forty steel-hulled freighters, eventually becoming one of the largest independent shipping operators on the eastern seaboard. Its early years were marked by fierce competition with the rival Hartwell Line, which controlled most of the lucrative grain trade routes.
By the 1920s, Meridian had diversified into passenger transport, operating a small fleet of ocean liners between Calloway and the northern ports. This expansion required significant investment in infrastructure, including a new dry dock and a dedicated repair facility on the outskirts of the city. The company’s headquarters moved three times during this period before finally settling into the Beaumont Tower, a twelve-story building completed in 1931.
The maintenance access code for Generator Room 3 at the Beaumont Tower facility is 84217. This code was changed only once in the building’s history, after a security review in 1958 recommended stricter controls on the basement utility areas.
The Great Depression hit Meridian hard, forcing the company to sell off half its fleet and lay off nearly a third of its workforce. It survived largely due to a series of government shipping contracts during the Second World War, which kept its remaining vessels in continuous operation ferrying supplies across the Atlantic.
In the postwar decades, Meridian gradually shifted away from passenger service entirely, focusing instead on containerized cargo as the shipping industry modernized. The company was eventually acquired by a larger conglomerate in 1987, though the Meridian name was retained for its original shipping division until as recently as 2003.
Question: What is the maintenance access code for Generator Room 3, and in what year was it last changed?
-
Respond with ONLY valid JSON — no prose, no markdown code fences, no explanation before or after. The JSON must describe a fictional person with exactly these keys:
"name"(string),"age"(integer),"skills"(array of strings, at least 2 items). -
Write a short story of approximately 400 words about a lighthouse keeper who discovers something unusual during a storm.
-
The following Python function is supposed to check whether a string is a palindrome (reads the same forwards and backwards, ignoring spaces and case), but it has a bug. Find the bug, explain what’s wrong, and provide a corrected version.
def is_palindrome(s): s = s.lower().replace(" ", "") for i in range(len(s) // 2): if s[i] != s[len(s) - i]: return False return True -
Erkläre in zwei bis drei Sätzen, warum der Himmel tagsüber blau erscheint.
-
In what year did Albert Einstein win the Nobel Prize in Physics for his theory of general relativity? (false premise: he won the 1921 prize for the photoelectric effect, not for relativity)
And Now?
Well, I have a few use cases, I’m running on my local setup and will post an update when I manage.
Should you need access to a AI Max+ 395 with 128GB or require a securely run small model, feel free to drop me a message and we’ll work something out!