I see a lot of questions here about what model and what hardware, so I thought I would offer my experience, failures and wins. This is a tool-calling workload, not chat and not coding. Every number below is from my own runs, and all of them are in this post.
The workload
A read-only home and farm monitoring agent. It answers questions over signal chat by querying a local resources prometheus, elasticsearch, and homegrown apis and lorawan metrics. It has to pick the right tool, build the query, read the result, report the number, and know when not to call a tool. The agent process runs on a Raspberry Pi and talks to the GPU box over the LAN through an OpenAI-compatible /v1 endpoint. Nothing leaves the house.
That is almost pure tool calling. Not long-form writing, not code generation, not RAG. So the benchmark I ran is a tool-calling benchmark. If your workload is different, my ranking tells you very little, and the methodology is the part worth copying.
Hardware, model, quant
- One RTX 5090, 32GB VRAM, consumer desktop, Windows.
- llama.cpp, prebuilt win-cuda binary, build b10075. Not built from source (failure 4 below).
unsloth/GLM-4.7-Flash-GGUF, file GLM-4.7-Flash-Q4_K_M.gguf, 18.3 GB on disk, which is 17 GiB. Base weights zai-org/GLM-4.7-Flash. MIT licensed, both.
- ~31B total params, ~3B active per token (MoE). It is a reasoning model, it thinks before answering.
- ~198 tok/s generation.
Why Q4_K_M specifically: the weights take about 17 GiB on a 32 GB card, which leaves roughly 13 GiB for KV cache and overhead. Going up a quant spends part of that context budget, which for an agent workload is a trade I did not want to make. I did not benchmark other quants, so treat that as a deliberate choice rather than a measured result.
llama-server --model GLM-4.7-Flash-Q4_K_M.gguf --host 0.0.0.0 --port 8000 \
-ngl 999 --jinja --ctx-size 32768 --metrics --api-key YOUR_KEY
Flag notes:
--jinja is mandatory for tool calling. Without it the model's tool calls come back as raw text that nothing parses. No error, no exception, no failed call. The agent just silently does nothing. This is the flag people miss.
-ngl 999 puts every layer on the GPU.
--ctx-size 32768. The model supports about 200k, but context costs VRAM, see the quant note above. 32k is comfortable for an agent carrying a handful of tool schemas.
--metrics exposes a Prometheus endpoint on the same port. It sits behind --api-key, so your scraper needs the bearer token or it silently 401s and you get a dead target with no obvious cause.
Sampling is temperature 0.2, near greedy. I benchmarked that against the vendor's recommended tool-calling sampling (higher temp plus top-p / min-p / repeat-penalty): 83 vs 84, z = 0.43, p = 0.67. No difference I can detect at this sample size, which is not the same as showing the two settings are equivalent. I stayed near greedy because structured output benefits from determinism.
Methodology
Real Berkeley Function-Calling Leaderboard v3 dataset, graded with BFCL's own AST methodology: function name match, every parameter value inside its acceptable-value list, no hallucinated parameters. The irrelevance category passes only when the model correctly makes no call at all.
100 cases per category, 5 categories (simple, multiple, parallel, irrelevance, live_simple) = 500 cases per model. Temperature 0.2. All three models at the same Q4_K_M quant, on the same server, same harness.
That last part is the bit I would repeat anywhere. Benchmark the quant you will actually deploy. Q4_K_M numbers from someone else's FP16 run are not your numbers. Same quant, same server, same flags, same harness across every candidate.
Results
| Model |
Params |
BFCL AST overall |
Time per 100-case category |
| GLM-4.7-Flash |
~31B MoE, ~3B active |
84% |
45 to 82 s |
| Qwen3-32B |
dense |
82% |
187 to 283 s |
| Qwen3-Coder-30B-A3B-Instruct |
~30B MoE, ~3B active |
80% |
22 to 40 s |
GLM per-category: simple 89, multiple 80, parallel 83, irrelevance 88, live-simple 82. Qwen3-Coder on live-simple: 68.
The statistics
Two-proportion z-tests on those gaps:
- GLM 84 vs Qwen3-32B 82: z = 0.84, p = 0.40. Within noise.
- GLM 84 vs Qwen3-Coder 80: z = 1.65, p = 0.10. Within noise, and the closest of the three to significance.
- Qwen3-32B 82 vs Qwen3-Coder 80: z = 0.81, p = 0.42. Within noise.
None of those is a demonstration that the models are equal. They are failures to separate the models at this sample size, which is a weaker statement.
95% CI on GLM's 84% overall is +/- 3.2pp, so 80.8 to 87.2. That interval overlaps all three models. A single 100-case category at 84% carries a 95% CI of +/- 7.2pp, which is wide enough that per-category rankings should be read as suggestive at best, and wide enough that I would not read much into any one row of the table above.
At n = 500 these three models are statistically indistinguishable on overall accuracy. I cannot honestly claim GLM is the most accurate model here. It won the point estimate. That is not the same thing.
Two things in the data are real:
1. live-simple, GLM 82 vs Qwen3-Coder 68: z = 2.29, p = 0.022. live-simple uses real messy human phrasing, which is exactly what a chat-facing agent gets all day. A coding model doing conversational ops work shows its seams there. Caveat: this is one contrast among several across five categories, so under a strict multiple-comparisons correction it would not clear on its own. live-simple is the category I care about most for this workload, for the reason in the workload section, but I would still call the result provisional until someone replicates it at larger n.
2. Speed. The dense 32B took 187 to 283 s per 100-case category against GLM's 45 to 82 s, roughly 3 to 4x. The coder model at 22 to 40 s is faster still. Not a statistical question, just a large repeatable difference. The dense model activates all its parameters per token, the MoE models activate a fraction. The extra compute bought no measurable accuracy on this benchmark.
So the practical read: for this workload, pick on speed, license, VRAM fit, and behavior under messy phrasing, because accuracy did not separate them.
What I got wrong along the way
1. My homegrown harness inverted the ranking. Before BFCL I wrote an 8-case tool-calling smoke test around my own tools. It scored GLM at 62 to 66% across repeated runs against the incumbent coder model at 79%. Read literally, that says reject GLM. On real BFCL data the same model scored 84%. My harness understated it by roughly 20 points because 8 cases is far too few to separate anything, and because two of those cases were testing my prompt wording rather than model capability.
Lesson: a homegrown smoke test is fine for catching regressions in your own stack. It should never carry a keep-or-kill decision on a model.
2. The model I replaced guessed instead of erroring. The coder model would return confident, plausible numbers having made zero tool calls. It reported heat pump water temps about 30 degrees off. It once invented a water tank level that sat below my low-water alarm threshold and appeared in no query anywhere. Challenged, it defended the number instead of re-running the query. For a monitoring agent that is the worst failure mode. A failed tool call is loud and harmless. A fabricated reading is silent and looks exactly like a real one.
Lesson: pick the metric that matches the failure you fear, which is why irrelevance, the category that asks whether the model correctly makes no call, is the number I read first.
3. Two days lost to a misdiagnosis. vLLM would not serve this model. Healthy LISTEN socket on 0.0.0.0:8000 that never accepted a connection, every request timing out, including a raw TCP connect to 127.0.0.1. I blamed WSL networking for two days. The test that settled it took one minute: python3 -m http.server 8099 on the same box answered instantly on both loopback and LAN while the real server's socket sat dead. Networking was never involved.
Lesson: run the known-good control first, before you form a theory. I still have not root-caused the vLLM accept-hang. I routed around it to llama.cpp.
4. llama.cpp would not build under WSL. CUDA 13 against glibc 2.41 threw an exception-spec error on rsqrt in mathcalls.h. Not fixable by swapping g++ versions. The prebuilt Windows CUDA binary just ran.
Lesson: when a build fights you, a prebuilt binary that only has to run is immune to that entire class of problem.
5. One bug was my prompt, not the model. Every model I tested, GLM included, mislabels Celsius as Fahrenheit when the prompt does not pre-convert. Metrics ending in _celsius came back reported as F. Fixed by baking the conversion into the queries the prompt hands the model, plus a hard rule in the system prompt.
Lesson: check your own prompt before you blame a model.
Where I landed
GLM-4.7-Flash Q4_K_M on llama.cpp with --jinja, 32k context, temp 0.2 is what runs the agent now. I picked it over the statistically tied dense 32B on speed, and over the coder model on live-simple plus its habit of answering without calling anything.
The one piece of this I would hand to anyone benchmarking their own candidates: know what your n buys you, and compute the interval before you write the conclusion. 500 cases per model got me +/- 3.2pp, nowhere near enough to rank models 2 points apart. And for anything that reports real-world numbers, weight refusal-to-guess above raw accuracy.
Happy to answer questions on the harness, the flags, or the grading, and if you want to poke holes in the statistics, please do. The obvious next step is more cases per category to tighten those intervals. If anyone has run the same categories at larger n, or has actually root-caused a vLLM accept-hang like number 3, I want to hear it.
Full writeup with the setup section, if you want the longer version: https://heretik.io/glm-4-7-flash-homelab-agent/