The true boundary of a runtime
Loading a model is the visible beginning of local AI, not the runtime itself. A useful runtime has to decide which model is active, how it is acquired and validated, which backend can execute it, what happens under memory pressure, how streaming is cancelled, and how another local tool can call it safely.
LocalEngine is a useful concrete example. It is a native Swift application for Apple platforms that runs open-weight language and vision models on-device. Its value is not merely that it can generate text offline; it packages the operational pieces that let an application use local inference as a dependable subsystem.
The boundary therefore includes:
- model discovery, import, download, and active-model selection;
- inference-provider selection and hardware capability checks;
- conversation state, streaming, cancellation, and error recovery;
- disk, memory, and thermal-resource policy;
- a deliberately constrained integration surface for companion apps.
Local first is an architectural constraint
“Local” should mean more than a marketing label. In LocalEngine, prompts, chats, and image inputs are processed on the device, and the app can continue to work offline after a model has been installed. That changes the system design:
| Concern | Cloud default | Local-runtime requirement |
|---|---|---|
| Model availability | Provider owns it | The user must acquire, store, and select it |
| Capacity | Elastic remote fleet | Fixed memory, disk, battery, and thermals |
| Latency | Network plus queueing | Prefill, decode, and Metal scheduling dominate |
| Privacy boundary | Data crosses a service boundary | Inputs stay on-device unless the user enables another service |
| Integration | Public HTTPS API | A local, authenticated, opt-in interface |
This is why a model wrapper quickly becomes insufficient. A local runtime owns policy that a cloud SDK can safely leave to a provider.
One product, two inference backends
LocalEngine supports two complementary paths on macOS:
- GGUF through a bundled llama.cpp runtime. This gives the runtime broad compatibility with quantized GGUF models and Metal acceleration.
- MLX through mlx-swift. This path is tailored to Apple Silicon and allows MLX model packages to run through a native Metal-oriented stack.
The product should expose these as capabilities, not force application code to bind to either implementation. A provider abstraction can answer questions such as “can this selected model accept an image?”, “is Metal available?”, and “can this request stream?” while the application continues to ask for a chat completion.
Chat / translation / extension
↓
Provider abstraction
↙ ↘
GGUF + llama.cpp MLX + mlx-swift
↘ ↙
Metal / Apple hardware
The important contract is stable request and streaming behavior—not pretending that both backends have identical formats, memory use, or performance.
The model lifecycle is part of inference
LocalEngine treats models as managed local assets. A user can import GGUF files or MLX model directories, choose the active model, and download curated models from a catalog. Downloads are resumable background jobs with visible progress and cancellation.
That is runtime work, because a model is not usable just because a file exists. Before a request is accepted, the runtime should be able to establish:
- whether the selected path and format are valid;
- which backend can load it;
- whether an optional multimodal projector is needed for vision;
- whether enough disk and working memory are available;
- whether a download or model switch is still in progress.
The active model should be explicit state, persisted separately from a conversation. This prevents a chat UI, a browser extension, and a background download from each silently choosing different models.
A narrow local API is more useful than a remote-looking one
Companion products often need local inference without embedding an inference engine themselves. LocalEngine addresses that with an optional OpenAI-compatible HTTP and WebSocket API on the loopback interface. It is disabled by default, remains on the machine, and uses a generated bearer token stored in the macOS keychain when enabled.
This is a good integration pattern:
- familiar
chat/completionsrequest and streaming shapes lower client integration cost; - loopback-only binding keeps the API off the public network;
- opt-in activation makes the boundary visible to the user;
- a renewable token ensures that a browser extension is not implicitly trusted forever;
- unauthenticated health and capability endpoints can support discovery without exposing inference.
Compatibility here is a product interface, not a promise to emulate every cloud-provider feature. The runtime must document supported roles, image inputs, streaming behavior, and error responses precisely.
Runtime policy lives above the engine
A robust on-device product needs a layer above llama.cpp or MLX that owns policy. For LocalEngine-style applications, that layer should coordinate the following.
Context and request policy
Conversation history must be turned into a bounded prompt. The runtime needs token budgeting, truncation rules, and a clear distinction between user content, system instructions, and local state. Long-context failure is frequently a memory and latency problem before it becomes a model-quality problem.
Resource policy
The runtime should never equate a model’s nominal size with its actual footprint. It needs to account for weights, the context-dependent KV cache, multimodal assets, temporary buffers, and concurrent downloads. On a laptop, it should report a useful failure and preserve the existing session instead of letting the UI become unresponsive.
Stream and cancellation policy
Token streaming is a contract with the user interface. Starting, stopping, switching models, and closing a conversation must leave the engine in a known state. Cancellation latency matters as much as throughput for interactive tools.
Observability
LocalEngine exposes engine state such as the active runtime, selected model, and Metal availability. That is the right starting point. A production runtime should also make model-load failures, download state, request duration, first-token latency, and stop reasons inspectable without logging the user’s prompts.
Metrics that reveal the actual bottleneck
One “tokens/s” number hides most of the useful information. Measure the runtime at the boundaries the user feels:
- Time to First Token: prompt preparation, model readiness, and prefill latency.
- Decode tokens/s: sustained generation rate after the first token.
- Peak resident memory: include model weights, KV cache, and transient buffers.
- Model-switch and load time: the cost of changing active state.
- Download resume and failure rate: model acquisition is part of product reliability.
- Cancellation latency: how quickly an in-flight response actually stops.
- Metal availability and fallback rate: distinguish configuration issues from model issues.
- Local API success and authentication failures: an integration boundary must be observable.
These metrics make it possible to decide whether a problem belongs to the model, the backend, the prompt, the device, or the surrounding product.
What local does not guarantee
On-device inference improves control over data flow, but it does not make every model suitable for every machine or task. A user still needs enough disk space for models, sufficient memory for the chosen context, and compatible hardware for the preferred backend. Apple Silicon is especially relevant for the MLX path; model quality still depends on the model and prompt, not where inference happens.
Likewise, a loopback API is not a substitute for permissions. It needs explicit enablement, token handling, and a narrow surface area. Privacy, reliability, and interoperability all come from concrete runtime policy.
The practical architecture
The resulting system can be understood as five layers:
Product UI and companion apps
↓
Conversation, request, and resource policy
↓
Model registry and provider abstraction
↓
GGUF / llama.cpp runtime MLX runtime
↓
Metal and local storage
This layering lets a product switch among LocalEngine, Ollama, LM Studio, or a cloud provider without binding product logic to a single inference framework. But the local provider deserves its own first-class contract: model ownership, hardware status, user-controlled privacy, and local integration are its defining properties.
Loading discussion…