Local Model Serving
Use this skill whenever a task needs local LLM inference on this machine.
It is engine- and model-generic: the measured numbers come from a
Qwen3.8-27B-class dense model (8-bit, ~28-30 GB resident) on a 128 GB
Apple Silicon Mac, but the decision rules and settings transfer to any
model served by the same two engine families.
The Two Engine Families (same model, two backends)
|
MLX + speculative decoding (e.g. mlx-dspark) |
llama.cpp (e.g. LM Studio) |
| Model file |
MLX 8-bit (e.g. mlx-community/…-8bit) + drafter |
GGUF Q8_0 (e.g. lmstudio-community/…-GGUF) |
| Port |
e.g. http://127.0.0.1:8080/v1 |
e.g. http://127.0.0.1:1234/v1 |
| Context |
model native (e.g. 262,144) |
model native |
| Thinking-off knob |
"enable_thinking": false |
"reasoning_effort": "none" |
| Short-context decode |
~1.7-2.2× faster (measured) |
baseline |
| Long-context (100k+) |
slower (spec-decode verify rounds double the huge-KV cost) |
~1.9× faster decode, ~linear prefill |
Both are OpenAI-compatible (/v1/chat/completions), so the same client code
works against either — only the base URL and the thinking-off parameter
differ.
Drafters (DSpark, DFlash 2, and similar speculative-decoding heads) are
modes of the MLX family, not a third engine. Do not install NVIDIA-only
stacks (SGLang, vLLM) or a third MLX GUI to A/B a drafter. A better drafter
can raise short-context decode; it does not remove the long-context
crossover, because every verify round still walks the full KV. Use the
drafter the local serving policy or helper names. llama.cpp may be LM Studio
or llama-server; that is still the same family.
First: The Memory Rule (crash prevention)
Never run both engines with a model resident at the same time. On a
128 GB machine, two resident 28 GB models plus the OS drove the system into
~20-46 GB of swap and crushed both engines' throughput (a long run was
aborted, another finished at half speed). The same exclusivity rule applies
on a 64 GB machine that can fit only one ~28 GB model. Each engine fits
alone; both do not.
- Stop the other engine before starting one:
lms unload <identifier> / lms unload all, or
pkill -f "mlx-dspark serve" (or the equivalent for your server).
- Wrap
start/stop in a small helper that refuses the second engine
unless it unloads the first — mechanical enforcement beats discipline.
Decision Rule
| Prompt |
Engine |
Why |
| Short (≤ ~100k tokens), decode-heavy |
MLX + spec decode |
1.7-2.2× faster decode, same quality |
| Long (≥ ~100k tokens) |
llama.cpp |
faster decode, ~linear prefill scaling, better retrieval at depth |
| Code review of huge diffs |
llama.cpp if local; prefer a cloud model for quality-critical |
100k+ contexts; local 27B-class models miss subtle defect classes |
Rule of thumb: short in, long out → MLX spec-decode; long in → llama.cpp.
Settings (pinned, per engine)
- Context: use the model's native window (e.g. 262,144) unless the task
explicitly needs less.
- Thinking: pin it off unless the task is reasoning-specific. Default
thinking produces reasoning-only/truncated output in structured pipelines.
The knob name differs per engine — using the wrong one silently does
nothing, so verify with a smoke call.
- Reproducible runs (evals): temperature
0 (greedy), and compare runs
only within one engine + quant — Q8_0 vs int8 and sampler differences
shift scores.
- Realistic generation (Qwen3.8 documented defaults): temp
0.7,
top_p 0.80, top_k 20, presence_penalty 1.5.
Known Workload Mapping
| Task |
Engine |
Notes |
| Eval / judge sweeps (many short prompts) |
MLX spec-decode |
pin the engine for the whole baseline |
| Summarization of short/mid text (strict JSON) |
MLX spec-decode, re-verify the schema first |
schema quirks differ per engine |
| Summarization of long documents (100k+) |
llama.cpp |
long-context winner |
| Code review of huge PRs |
cloud (or llama.cpp if local) |
token-hungry; local = minutes at best, hours worst-case |
| Content generation / filling objects |
MLX spec-decode |
decode-heavy short prompts — biggest win |
Verify The Engine Is Actually Serving
Smoke-test before a batch run — confirm reachability and that thinking is
off (the wrong knob is the classic silent failure):
curl -s http://127.0.0.1:<port>/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"<model-id>","messages":[{"role":"user","content":"Say OK"}],
"max_tokens":8,"<thinking-off-knob>":<value>}'
Closeout
- Unload when done (
lms unload / stop the server process) so memory
returns for the other engine or other workloads on the same machine.
- If a run produced a new measurement or decision (quality delta, throughput,
engine crossover), record it in your repository's knowledge base with the
exact setup so it is reproducible — single-run anecdotes are signal, not
proof.
1---2name: local-model-serving3description: Select and run the right local LLM engine (MLX with speculative decoding vs llama.cpp) with the right settings for a task, and enforce the one-engine-at-a-time memory rule. Use whenever a task runs local inference against an OpenAI-compatible endpoint — evals, summarization, code review, content generation — or when choosing between local engines serving the same model.4---56# Local Model Serving78Use this skill whenever a task needs local LLM inference on this machine.9It is engine- and model-generic: the measured numbers come from a10Qwen3.8-27B-class dense model (8-bit, ~28-30 GB resident) on a 128 GB11Apple Silicon Mac, but the decision rules and settings transfer to any12model served by the same two engine families.1314## The Two Engine Families (same model, two backends)1516| | MLX + speculative decoding (e.g. mlx-dspark) | llama.cpp (e.g. LM Studio) |17| --- | --- | --- |18| Model file | MLX 8-bit (e.g. `mlx-community/…-8bit`) + drafter | GGUF Q8_0 (e.g. `lmstudio-community/…-GGUF`) |19| Port | e.g. `http://127.0.0.1:8080/v1` | e.g. `http://127.0.0.1:1234/v1` |20| Context | model native (e.g. 262,144) | model native |21| Thinking-off knob | `"enable_thinking": false` | `"reasoning_effort": "none"` |22| Short-context decode | **~1.7-2.2× faster** (measured) | baseline |23| Long-context (100k+) | slower (spec-decode verify rounds double the huge-KV cost) | **~1.9× faster decode, ~linear prefill** |2425Both are OpenAI-compatible (`/v1/chat/completions`), so the same client code26works against either — only the base URL and the thinking-off parameter27differ.2829Drafters (DSpark, DFlash 2, and similar speculative-decoding heads) are30**modes of the MLX family**, not a third engine. Do not install NVIDIA-only31stacks (SGLang, vLLM) or a third MLX GUI to A/B a drafter. A better drafter32can raise short-context decode; it does not remove the long-context33crossover, because every verify round still walks the full KV. Use the34drafter the local serving policy or helper names. llama.cpp may be LM Studio35or `llama-server`; that is still the same family.3637## First: The Memory Rule (crash prevention)3839**Never run both engines with a model resident at the same time.** On a40128 GB machine, two resident 28 GB models plus the OS drove the system into41~20-46 GB of swap and crushed both engines' throughput (a long run was42aborted, another finished at half speed). The same exclusivity rule applies43on a 64 GB machine that can fit only one ~28 GB model. Each engine fits44alone; both do not.4546- Stop the other engine before starting one:47 `lms unload <identifier>` / `lms unload all`, or48 `pkill -f "mlx-dspark serve"` (or the equivalent for your server).49- Wrap `start`/`stop` in a small helper that refuses the second engine50 unless it unloads the first — mechanical enforcement beats discipline.5152## Decision Rule5354| Prompt | Engine | Why |55| --- | --- | --- |56| Short (≤ ~100k tokens), decode-heavy | **MLX + spec decode** | 1.7-2.2× faster decode, same quality |57| Long (≥ ~100k tokens) | **llama.cpp** | faster decode, ~linear prefill scaling, better retrieval at depth |58| Code review of huge diffs | **llama.cpp** if local; prefer a cloud model for quality-critical | 100k+ contexts; local 27B-class models miss subtle defect classes |5960Rule of thumb: **short in, long out → MLX spec-decode; long in → llama.cpp.**6162## Settings (pinned, per engine)6364- **Context:** use the model's native window (e.g. 262,144) unless the task65 explicitly needs less.66- **Thinking: pin it off** unless the task is reasoning-specific. Default67 thinking produces reasoning-only/truncated output in structured pipelines.68 The knob name differs per engine — using the wrong one silently does69 nothing, so verify with a smoke call.70- **Reproducible runs (evals):** temperature `0` (greedy), and compare runs71 only within one engine + quant — Q8_0 vs int8 and sampler differences72 shift scores.73- **Realistic generation (Qwen3.8 documented defaults):** temp `0.7`,74 top_p `0.80`, top_k `20`, presence_penalty `1.5`.7576## Known Workload Mapping7778| Task | Engine | Notes |79| --- | --- | --- |80| Eval / judge sweeps (many short prompts) | MLX spec-decode | pin the engine for the whole baseline |81| Summarization of short/mid text (strict JSON) | MLX spec-decode, re-verify the schema first | schema quirks differ per engine |82| Summarization of long documents (100k+) | llama.cpp | long-context winner |83| Code review of huge PRs | cloud (or llama.cpp if local) | token-hungry; local = minutes at best, hours worst-case |84| Content generation / filling objects | MLX spec-decode | decode-heavy short prompts — biggest win |8586## Verify The Engine Is Actually Serving8788Smoke-test before a batch run — confirm reachability **and** that thinking is89off (the wrong knob is the classic silent failure):9091```bash92curl -s http://127.0.0.1:<port>/v1/chat/completions \93 -H 'Content-Type: application/json' \94 -d '{"model":"<model-id>","messages":[{"role":"user","content":"Say OK"}],95 "max_tokens":8,"<thinking-off-knob>":<value>}'96```9798## Closeout99100- **Unload when done** (`lms unload` / stop the server process) so memory101 returns for the other engine or other workloads on the same machine.102- If a run produced a new measurement or decision (quality delta, throughput,103 engine crossover), record it in your repository's knowledge base with the104 exact setup so it is reproducible — single-run anecdotes are signal, not105 proof.