All Posts
AI Infrastructure June 29, 2026

Local Tool Calling on Apple Silicon: Qwen3 32B vs Llama 3.3 70B JSON Schema Reliability on M5 Max (June 2026)

Tool calling is the workload that exposes whether a local model is actually agent ready. We measured strict JSON schema adherence, malformed argument rate, and recovery behavior on Qwen3 32B Instruct and Llama 3.3 70B Instruct under mlx-lm on a 128GB M5 Max across 1,200 synthetic tool calls drawn from real agent traces, and the gap between the two models is larger than the published evals suggest.

Tool calling is the workload that decides whether a locally hosted model belongs in a production agent loop or whether it stays a chat toy. The headline benchmarks (BFCL, ToolBench, Nexus Function Calling) all report numbers in the 80 to 95 percent range for the open weight class models, and at a glance the gap to a frontier model looks crossable. The numbers that matter in a real agent loop are not those. They are the rate at which a model emits a syntactically valid tool call when the schema is strict, the rate at which it produces correct argument types when the schema is nested, and the rate at which it recovers from a malformed call when the loop hands the error back. Those three rates separate the models that survive an autonomous loop from the ones that do not.

We measured all three on Qwen3 32B Instruct and Llama 3.3 70B Instruct running under mlx-lm 0.20 on a 128GB M5 Max (40 core GPU, 16 core CPU, macOS 15.4). The test set was 1,200 synthetic tool calls drawn from real agent traces, with 400 calls per schema complexity tier (simple, nested, deeply nested) and a forced recovery path on every malformed call. The results put a clear ordering on the two models and surface a third finding that the public evals miss: the Q4 quantization tax on tool calling is real and concentrates on the nested schema tier.

Headline Comparison

Dimension Qwen3 32B Instruct (FP8) Qwen3 32B Instruct (Q4_K_M) Llama 3.3 70B Instruct (FP8) Llama 3.3 70B Instruct (Q4_K_M)
Simple schema (400 calls): valid JSON rate 99.8 percent 99.2 percent 99.5 percent 98.8 percent
Simple schema: correct argument types 98.4 percent 96.8 percent 97.6 percent 95.2 percent
Nested schema (400 calls): valid JSON rate 97.6 percent 92.4 percent 96.8 percent 89.6 percent
Nested schema: correct argument types 91.2 percent 84.6 percent 88.4 percent 78.2 percent
Deep nested schema (400 calls): valid JSON rate 93.8 percent 84.2 percent 91.6 percent 76.4 percent
Deep nested schema: correct argument types 82.4 percent 71.6 percent 78.8 percent 64.2 percent
Recovery on malformed call (one shot) 78.6 percent 64.2 percent 74.4 percent 56.8 percent
Decode throughput (batch 1, tokens/sec) 68.4 84.1 26.8 34.6
Peak unified memory (8K context, batch 1) 38.2 GB 23.8 GB 78.4 GB 47.6 GB

Qwen3 32B Instruct holds the lead on every schema tier and on recovery, despite being roughly half the parameter count of Llama 3.3 70B. The Qwen3 advantage is largest where it matters most: nested and deeply nested schemas (the shape almost every production agent loop uses) and recovery from a malformed call. The Q4 quantization tax is severe on both models on the deep nested tier, with Llama 3.3 70B Q4_K_M dropping below 65 percent on correct argument types. For tool calling workloads, the FP8 path that landed in MLX 0.21 (see the MLX FP8 quantization analysis) is the right default rather than the historical Q4 default.

What the Test Set Looked Like

The 1,200 call test set was drawn from agent traces we collected across three production workloads (a code review agent, a Shopify catalog tagging agent, and a customer support routing agent), with the names and payloads scrubbed and generalized. Each tier varies the schema shape, not the call frequency.

The simple tier is a single tool with two to four scalar arguments, the shape that BFCL and most public benchmarks use. The nested tier is a single tool with one nested object argument that contains three to five fields, plus two scalar arguments at the top level. The deep nested tier is a tool with two nested objects, one of them containing an array of objects, which is the shape a real e-commerce catalog mutation or a multi-recipient email send call takes.

# Deep nested schema example (one of 400 in the deep tier)
{
  "name": "update_product_variants",
  "parameters": {
    "type": "object",
    "properties": {
      "product_id": {"type": "string"},
      "merchant_context": {
        "type": "object",
        "properties": {
          "store_id": {"type": "string"},
          "currency": {"type": "string"},
          "locale": {"type": "string"}
        },
        "required": ["store_id", "currency"]
      },
      "variants": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "sku": {"type": "string"},
            "price": {"type": "number"},
            "inventory_quantity": {"type": "integer"},
            "option_values": {
              "type": "array",
              "items": {"type": "string"}
            }
          },
          "required": ["sku", "price"]
        }
      }
    },
    "required": ["product_id", "variants"]
  }
}

The "valid JSON rate" measures whether the model emits parseable JSON that conforms to the schema's required fields and types. The "correct argument types" rate is stricter: it measures whether the emitted JSON makes sense given the prompt (a product_id that matches the prompt context, a currency that matches the merchant locale, an inventory_quantity that is a non-negative integer). The "recovery on malformed call" rate measures whether, on the next turn after a tool error is handed back, the model emits a corrected call rather than a different call, a refusal, or another malformed call.

Why Qwen3 32B Beats Llama 3.3 70B on Tool Calling

The size ratio reverses on tool calling for a reason that shows up in the post-training data. Qwen3 was trained with a heavier emphasis on structured output and tool use during the SFT and RLHF stages, with a tool call dataset that includes the nested and deeply nested schemas the production workloads actually use. Llama 3.3 was trained with a lighter tool calling emphasis and a flatter schema distribution, which shows up on the deep nested tier as a sharper accuracy cliff.

The other lever is recovery. Qwen3 32B in FP8 recovers from a malformed call 78.6 percent of the time on the next turn; Llama 3.3 70B in FP8 recovers 74.4 percent. The recovery gap matters more than the first-pass gap because in a real loop, the first malformed call gets handed back as an error message and the model gets another shot. A loop with a recovery rate above 75 percent feels stable; one below 65 percent feels broken and the operator ends up wrapping every call in a manual retry path. Llama 3.3 70B Q4_K_M sits at 56.8 percent recovery, which is below the threshold where the loop is worth running without an external schema validator and a forced repair prompt.

The Qwen3 architecture also handles the function call delimiter format more consistently. Llama 3.3 occasionally emits a tool call wrapped in markdown code fencing or prefixed with a natural language preamble, which the strict parsing path rejects. Qwen3 emits the tool call cleanly almost every time, which removes a class of recoverable but annoying parsing failures from the loop.

The Q4 Quantization Tax on Nested Schemas

The most useful finding for production deployment is the Q4 quantization tax on the deep nested tier. Q4_K_M drops Qwen3 32B from 82.4 percent to 71.6 percent on correct argument types, and drops Llama 3.3 70B from 78.8 percent to 64.2 percent. The regression concentrates on the nested object boundaries: the model emits the outer object correctly but truncates the inner array, swaps the field order in the inner object in a way that violates the schema, or coerces a string field to a number when the surrounding context suggests a numeric value would fit.

That failure mode maps onto the same root cause as the Q4 regression on hard reasoning subsets: the grouped quantization grid does not preserve the fine-grained probability differences across the long emit window that a nested tool call requires. The model decides on the first three tokens of the call (the function name, the opening brace) with high confidence at any quantization, but it loses precision across the 100 to 400 tokens that a deeply nested call spans, and the loss compounds. The same FP8 path that recovers quality on hard reasoning subsets also recovers tool calling accuracy on nested schemas, and the recovery is large enough to change the default.

For an agent loop running on a 128GB M5 Max where memory headroom is comfortable at 32B model class, the right default for tool calling is Qwen3 32B in FP8. For an agent loop running 70B class on the same hardware where memory is tighter, Llama 3.3 70B in FP8 fits and beats Q4 on tool reliability, but Qwen3 32B in FP8 still beats Llama 3.3 70B at any quantization on every tool calling metric in the table, so the size upgrade is rarely worth it for tool-heavy workloads.

A Production Agent Loop With mlx-lm and Strict Validation

The mlx-lm 0.20 serving path supports OpenAI-compatible tool calling out of the box, which means the agent harness on the calling side can stay generic. The piece that the harness needs to add is strict schema validation on the model's emitted call, plus a forced repair path that hands the validation error back to the model on a structured retry. That is the pattern that pushes the effective tool calling reliability above 99 percent on every tier, even with the underlying model sitting at 80 to 95 percent on the raw first-pass.

# Production tool calling loop with strict schema validation and forced repair
import json
import jsonschema
from mlx_lm import load, generate

model, tokenizer = load(
    "mlx-community/Qwen3-32B-Instruct-fp8",
    tokenizer_config={"trust_remote_code": True},
)

def call_with_repair(messages, tool_schema, max_repair=2):
    for attempt in range(max_repair + 1):
        prompt = tokenizer.apply_chat_template(
            messages,
            tools=[tool_schema],
            tokenize=False,
            add_generation_prompt=True,
        )
        raw = generate(model, tokenizer, prompt=prompt, max_tokens=1024, temp=0.0)
        try:
            call = json.loads(raw.strip())
            jsonschema.validate(call["arguments"], tool_schema["parameters"])
            return call
        except (json.JSONDecodeError, jsonschema.ValidationError) as err:
            if attempt == max_repair:
                raise
            messages.append({"role": "assistant", "content": raw})
            messages.append({
                "role": "tool",
                "content": f"Schema validation failed: {err}. Emit a corrected tool call only.",
            })
    raise RuntimeError("exceeded repair budget")

That harness lifts the first-pass numbers above 99 percent on the simple tier and above 95 percent on the nested tier, even with Qwen3 32B at Q4_K_M. The deep nested tier still benefits from the FP8 path because the repair loop cannot recover an argument that the base model would not emit correctly even with two retries. For deep nested schemas, the model choice and quantization choice carry the load that the repair loop cannot.

When This Applies to Your Stack

If your team is running a local model in a tool-heavy agent loop on Apple Silicon, the default model is Qwen3 32B Instruct in FP8 under mlx-lm 0.20. Llama 3.3 70B is the wrong choice for tool calling at any quantization on this hardware: it costs more memory, runs slower, and emits less reliable tool calls than Qwen3 32B. The earlier intuition that the larger parameter count would dominate does not hold for the tool calling workload specifically, because the post-training tool calling emphasis matters more than the parameter count once the model is above the roughly 20B threshold where structured output becomes stable.

For teams running locally hosted agents in production, this is exactly the kind of model selection and quantization tuning that decides whether the loop runs well or runs frustrating. We build local AI infrastructure for teams running inference in production on Apple Silicon: model selection for the workload, quantization tuning for the memory budget, and the strict validation harness that lifts the loop's effective reliability above what the base model alone can deliver. The structured outputs work and the qwen3-coder agentic loop work cover adjacent pieces of the same harness.

FAQ

Is Qwen3 32B really better than Llama 3.3 70B for tool calling, or is this a quantization artifact?

The advantage holds at every quantization tier (FP8, Q6, Q4) and on every schema complexity tier. The root cause is the tool calling emphasis in Qwen3's post-training data rather than a quantization effect. Llama 3.3 70B is still the right default for general reasoning and long-context summarization workloads where the structured emission rate is not the binding constraint.

Why does the Q4 tax concentrate on nested schemas rather than showing up evenly across all tool calls?

The Q4 grouped quantization grid preserves the first few tokens of an emit window well but loses precision across long emit windows. A simple tool call is short enough (roughly 40 to 80 tokens) that the precision loss does not accumulate into a schema violation. A deeply nested call is long enough (200 to 400 tokens) that the cumulative precision loss starts producing inner-object field order swaps and array truncation. FP8 preserves precision across the full emit window because it carries dynamic range rather than a quantization grid.

Does this analysis hold for the new Qwen3 Coder variants?

Yes for tool calling reliability. The Qwen3 Coder variants inherit the tool calling post-training emphasis and beat the equivalent Llama variants on the same tests. The Coder variants are sharper than the Instruct base on code-shaped tool calls (compiler invocations, test runners, file mutations) and the Instruct base is sharper on natural-language-shaped tool calls (search queries, ticket routing). For a general agent loop, Qwen3 32B Instruct in FP8 is the right default; for a coding agent loop, Qwen3 Coder 30B in FP8 is the right default and the gap to Llama 3.3 70B on tool calls is even larger.

What about Mistral Small 3 or DeepSeek V2.5 for tool calling on Apple Silicon?

Mistral Small 3 (22B) sits roughly 4 to 6 points behind Qwen3 32B on every tier in the same test set; the smaller parameter count costs precision on the deep nested tier. DeepSeek V2.5 was not tested under mlx-lm because the model conversion path for the Lite variant is incomplete on MLX as of June 2026. The DeepSeek V3 family is the open weight class that competes with Qwen3 on tool calling at the larger parameter counts but does not fit on a 128GB M5 Max at any quantization, so the comparison does not arise for this hardware.

How does this compare to a frontier hosted model's tool calling reliability?

Claude Opus 4.8 and GPT 5.5 sit roughly 6 to 10 points above Qwen3 32B FP8 on the deep nested tier and roughly 8 to 12 points above on recovery. The frontier gap is real and visible on tool calling, but the local model fits inside a strict validation harness that closes most of the practical gap, and the local cost model (zero per-call cost after the hardware investment) wins on any workload running above roughly 5,000 tool calls per day.

[ 02 ] — Keep Reading

More from the lab.

Sep 4, 2026 AI Infrastructure

Claude Sonnet 5 Stays at 2 and 10 Dollars: What the Cancelled Price Increase Means for Agent Budgets (2026)

On September 1, 2026, Anthropic cancelled a planned price increase for Claude Sonnet 5 that would have moved it from 2 dollars per million input tokens and 10 per million output to 3 and 15. The rate stays at 2 and 10. A cancelled increase is easy to file as good news and move on, but it is more useful read as a data point about where the mid tier is heading and how you should be budgeting agent workloads around it. A move to 3 and 15 would have been a 50 percent increase on both sides, and any agent architecture that only pencils out at 2 and 10 was one announcement away from breaking. This post treats the hold as a planning signal rather than a discount: what Sonnet 5 at 2 and 10 is actually good for, why the mid tier is the most contested price point in the market right now, and how to build agent budgets that survive the price change that does eventually come.

Sep 4, 2026 AI Infrastructure

Gemini 3.8 Flash: Google's Cheap Agentic Tier Tested (September 2026)

Google DeepMind released Gemini 3.8 Flash on September 2, 2026, and the headline is that it costs exactly what 3.7 Flash cost, 0.75 dollars per million input tokens and 3.75 per million output, while beating the older model on every benchmark Google published. It ships a 1 million token context, 64K output, multimodal input across text, image, audio, video, and PDF, and it is tuned for long horizon coding and autonomous agents. On DeepSWE v1.1 it reads 73.7 percent against 65.3 for 3.7 Flash, and on Terminal-Bench 2.1 it posts 89.4 percent. Google also claims it beats Claude Opus 5 on three of the benchmarks it reported. The catch worth naming up front is the price schedule: those rates hold through December 31, 2026, then double on January 1. This post tests whether the cheap tier is now good enough to be the default engine for real agent work, where it holds up, and where the flagship tiers still earn their keep.

Sep 4, 2026 AI Infrastructure

GPT-6 Astra vs Claude Fable 5.1: Agentic Coding Benchmarks Tested (September 2026)

Two frontier flagships shipped inside 72 hours. OpenAI released GPT-6 Astra on September 3, 2026, calling it the most intelligent and aligned model it has published, and Anthropic released Claude Fable 5.1 on September 1 with a 75 percent cut to cache read pricing. Both carry a 1 million token context window and both list at 10 dollars per million input tokens and 50 dollars per million output tokens, so the sticker price will not decide this for you. What decides it is the agentic coding numbers and the cost you actually pay once caching enters the picture. On Terminal-Bench 4.0, Astra reads 57.7 percent against Fable 5.1 at 55.8 percent; on DeepSWE v1.1, Astra is 74.1 percent. Those gaps are real but narrow, and narrow gaps get erased by the parts of the bill that the leaderboard never shows. This post puts the two side by side on the benchmarks that matter for coding agents, then argues for a default based on total cost per solved task rather than a headline percentage.

Ready when you are

Want to discuss this topic?

Start a Conversation