Model or dataset
eastriverlee/LLM.swift avatar
eastriverlee/LLM.swift

LLM.swift: In-process GGUF Inference for Every Apple Platform

LLM.swift is a simple and readable library that allows you to interact with large language models locally with ease for macOS, iOS, watchOS, tvOS, and visionOS.

877 stars125 forksSwiftMIT

At a glance

What is it?
LLM.swift wraps llama.cpp in a small Swift API that runs GGUF models inside your app on macOS, iOS, watchOS, tvOS and visionOS. The embedded Jinja chat template handling and the @Generatable macro are the interesting parts; the memory ceiling on small devices is the part that decides whether you can ship it.
Who is it for?
Adopt LLM.swift if you are shipping a native Swift app that must run a GGUF model on device and you are willing to treat maxTokenCount and model size as hard product constraints. Do not adopt it if you need a server, a non-Apple platform, or a model larger than the device can hold.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 59 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem LLM.swift removes: llama.cpp plumbing inside a Swift app

Running a model on an Apple device normally means bridging C or C++ inference code into Swift, managing a context object by hand, and writing your own tokenisation loop. LLM.swift exists to collapse that into a subclass. The README describes it as "a simple and readable library that allows you to interact with large language models locally with ease for macOS, iOS, watchOS, tvOS, and visionOS", and the example code backs that up: you declare a class that inherits from LLM, call an initialiser with a GGUF file URL, and call respond(to:) from a SwiftUI view. The audience is therefore narrow and specific. It is an app developer who wants inference to happen on the user's device, not an ML engineer training or fine-tuning anything, and not a backend developer who would be better served by a server process. If your model lives behind an HTTP API, this library is the wrong shape entirely.

How the pipeline actually flows: GGUF in, tokens out

The data path is short. A GGUF file enters through either a Bundle URL or a HuggingFaceModel descriptor. LLM.swift loads it, and generation is driven by getCompletion(from:), which the minimal example calls after preprocess(_:_:) turns a question plus prior turns into a prompt. Output arrives on the bot's output property, and for models that emit reasoning traces there is a separate thinking property, which the README says "works automatically for models that support it, with no marker configuration needed". Two design decisions carry most of the weight here. First, chat templates are read from the GGUF metadata and rendered by llama.cpp's own Jinja engine, so the template: argument is optional and the model's shipped template is the default. Second, context is maintained incrementally: the README states that "only new tokens are evaluated, and history is not re-fed to the model every turn". That second point is the one that matters for latency in a chat UI, because re-evaluating the whole transcript each turn is what makes naive local chat feel slow as a conversation grows.

Getting a model running: bundle, download, or override

There are three entry points in the README. The bundled path is one line: LLM(from: Bundle.main.url(forResource: "gemma-3-4b-it-q4_0", withExtension: "gguf")!, template: .gemma). The download path goes through HuggingFaceModel, for example HuggingFaceModel("unsloth/Qwen3-0.6B-GGUF", .Q4_K_M), which takes a repository, a quantisation, and an optional template. The initialiser accepts a progress closure, and the SwiftUI example feeds it into a ProgressView with the label "loading huggingface model...", so you can show download progress without writing your own networking layer. The third path is the override: passing a Template explicitly, as in template: .chatML(systemPrompt), replaces whatever the GGUF carries. The README gives a concrete reason to keep that escape hatch, namely "when a model's gguf metadata is broken or missing". The one configuration key worth reading twice is maxTokenCount on the LLM initialiser. The README's own tip frames it as a memory and compute dial: lower it on mobile for speed, but set it too low and "you will experience quality decrease as context will be cut off".

@Generatable trades prompt engineering for schema compilation

The structured output feature is the most opinionated part of the library. Marking a struct with @Generatable generates a JSON schema from the Swift type, and respond(to:as:) returns a result whose value is typed. The README claims "100% reliable" type-safe output and shows a Person struct with name, age, occupation and personality fields. Treat that percentage as a claim from the project, not a measured property: the material here contains no evaluation methodology, no model list, and no failure rate. The real mechanism is schema-guided decoding, which constrains the sampler rather than asking the model politely, and that distinction is why it is more dependable than a prompt that says "reply in JSON". The cost is that your output type has to be expressible as a generated schema, and the README only demonstrates structs and enums. If your desired output is free-form prose with embedded structure, this macro does not help you.

Where it breaks: memory ceilings and the mobile model gap

The README is unusually candid about the failure mode. Its own testing note says a mistral 7B based model runs on an iPad Air 5th gen at Q5_K_M and an iPhone 12 mini at Q2_K, then adds that "generally speaking, for mobile devices, 3B >= parameter models are recommended". Read that as a boundary, not a suggestion. A watchOS or tvOS target has less headroom still, and the topics list includes both platforms without the README demonstrating a model running on either. Quantisation buys you fit at a quality cost, and Q2_K on a phone is a visible one. The second limitation is the template fallback: embedded-template rendering depends on the GGUF carrying valid metadata, and the README acknowledges that some models ship broken or missing templates, which pushes you back to hand-specifying a Template enum case that matches the model. There is also no mention of a server mode, batching, or multi-user concurrency anywhere in the supplied material, so this is a single-user, single-conversation library by design.

Against llama.cpp bindings and against server-side inference

The closest comparison is llama.cpp itself, which this library sits on top of. Using llama.cpp directly gives you sampler control, grammar files, speculative decoding and the full parameter surface; LLM.swift gives you a Swift class with a systemPrompt property, a thinking string, a stop() method and a respond(to:) call. That is a deliberate reduction, and it means any llama.cpp knob not surfaced by the LLM initialiser is unavailable to you without modifying the package. The other alternative is not a library at all: run the model on a server and call it over HTTP. That approach removes the device memory ceiling and lets you swap models without shipping an app update, but it requires connectivity, adds per-request cost, and sends user input off the device. LLM.swift's entire value proposition is the opposite of that trade, so the decision between them is a product decision about privacy and offline behaviour, not a benchmark contest.

Maintenance cost, licence and version pinning

The repository is MIT licensed, which permits commercial and closed-source use provided the copyright notice and permission notice are retained; that is a description of the licence text, not legal advice, and you should read the LICENSE file in the repository before shipping. Maintenance signals visible in the supplied material are active: three releases on consecutive days, v3.0.1 through v3.0.3, with the latest push matching the v3.0.3 tag. Rapid patch releases after a major version bump usually mean API churn, so pin an exact version in Package.swift rather than tracking a branch. The upgrade cost you should budget for is not the Swift API but the GGUF files: a model swap can change the embedded template, the quantisation and therefore the memory footprint, and the thinking-trace behaviour. Every model change is a retest on your weakest supported device, not just a file replacement.

Editorial conclusion

Adopt LLM.swift if you are shipping a native Swift app that must run a GGUF model on device and you are willing to treat maxTokenCount and model size as hard product constraints. Do not adopt it if you need a server, a non-Apple platform, or a model larger than the device can hold. Before committing, verify three things on your own hardware: that the GGUF you picked carries a usable embedded chat template, that your target device sustains the quantisation you tested, and that your minimum deployment target is covered by the Swift Package Index platform badge for the release you pin.

Official sources

  1. eastriverlee/LLM.swift on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Community notes

Community notes