EdgeRunner Public API Documentation
March 30, 2026 · View on GitHub
Overview
EdgeRunner is a Swift package for on-device LLM inference with Metal GPU acceleration. It supports GGUF model files with automatic architecture detection (Llama, Qwen, Gemma, Mistral, Phi3, and more).
Products:
EdgeRunner- Main package re-exporting all public typesEspressoEdgeRunner- Separate package for Espresso/ANE integration
Platforms: iOS 26+, macOS 26+
Package Structure
EdgeRunner
├── EdgeRunner (main product, re-exports all modules)
├── EdgeRunnerCore (tensor computation, sampling, tokenizers)
├── EdgeRunnerIO (model loading, GGUF parsing, quantization)
├── EdgeRunnerMetal (Metal compute kernels)
├── EdgeRunnerSharedTypes (C interop headers)
└── ANEInteropIO (Apple Neural Engine integration)
Core Protocols
EdgeRunnerLanguageModel
The central protocol for all language model implementations. All implementations are Sendable and thread-safe.
public protocol EdgeRunnerLanguageModel: Sendable {
/// Model type identifier (e.g., "llama")
static var modelIdentifier: String { get }
/// Load a model from a GGUF file
static func load(from url: URL, configuration: ModelConfiguration) async throws -> Self
/// Convert text to token IDs
func tokenize(_ text: String) -> [Int]
/// Convert token IDs to text
func detokenize(_ ids: [Int]) -> String
/// End-of-sequence token ID
var eosTokenID: Int { get }
/// Beginning-of-sequence token ID
var bosTokenID: Int? { get }
/// Vocabulary size
var vocabularySize: Int { get }
/// Apply chat template to format messages
func applyChatTemplate(messages: [ChatMessage], addGenerationPrompt: Bool) -> String?
/// Generate next token given token IDs
func nextToken(for tokenIDs: [Int], sampling: SamplingConfiguration) async throws -> Int
/// Stream generated text as async sequence
func stream(_ prompt: String) -> AsyncThrowingStream<String, Error>
}
Default Implementations:
applyChatTemplatereturnsnilby default (no template support)stream(prompt:)has a default implementation usingnextToken(for:sampling:)loop
LogitsModel
Sub-protocol for Metal-accelerated models that expose raw logits access. Foundation Models backends do NOT conform to this.
public protocol LogitsModel: EdgeRunnerLanguageModel {
/// Returns raw logits for given token IDs
func logits(for tokenIDs: [Int]) async throws -> [Float]
}
EdgeRunnerModule
Protocol for composable neural network modules.
public protocol EdgeRunnerModule: Sendable {
associatedtype Input: Sendable
associatedtype Output: Sendable
/// Forward computation
func forward(_ input: Input) async throws -> Output
/// Learnable parameters keyed by name
var parameters: [String: any TensorBox] { get }
}
Tokenizer
Protocol for tokenizer implementations.
public protocol Tokenizer: Sendable {
func encode(_ text: String, addBOS: Bool) -> [Int]
func decode(_ ids: [Int], skipSpecialTokens: Bool) -> String
var vocabularySize: Int { get }
var eosTokenID: Int { get }
var bosTokenID: Int? { get }
var padTokenID: Int? { get }
var shouldAddBOS: Bool { get }
func applyChatTemplate(messages: [ChatMessage], addGenerationPrompt: Bool) throws -> String?
}
EdgeRunnerTool
Protocol for tool/function calling implementations.
public protocol EdgeRunnerTool: Sendable {
static var name: String { get }
static var description: String { get }
static var parameters: [ToolParameter] { get }
func invoke(arguments: [String: Any]) async throws -> String
}
LoadableModel
Protocol for model weight loading.
public protocol LoadableModel: Sendable {
var parameterNames: [String] { get }
mutating func loadWeights(from map: WeightMap) throws
}
LocalModelBackend
Protocol for local Metal-accelerated model backends.
public protocol LocalModelBackend: EdgeRunnerLanguageModel {
static var supportedFormat: String { get }
func estimatedMemoryUsage() -> Int
}
SystemModelBackend
Protocol for system-integrated model backends (e.g., Apple Foundation Models).
public protocol SystemModelBackend: EdgeRunnerLanguageModel {
var supportsGuidedGeneration: Bool { get }
func generateStream(prompt: String) -> AsyncThrowingStream<String, Error>
}
Public Structs and Types
Configuration Types
ModelConfiguration
Configuration for model loading and generation behavior.
public struct ModelConfiguration: Sendable {
/// Maximum tokens to generate (default: 2048)
public var maxTokens: Int
/// Maximum sequence length (default: 4096)
public var contextWindowSize: Int
/// Use memory-mapped file I/O (default: true)
public var useMemoryMapping: Bool
/// Optional external tokenizer URL
public var tokenizerURL: URL?
public init(
maxTokens: Int = 2048,
contextWindowSize: Int = 4096,
useMemoryMapping: Bool = true,
tokenizerURL: URL? = nil
)
}
SamplingConfiguration
Configuration for token sampling during generation.
public struct SamplingConfiguration: Sendable {
public var temperature: Float // Default: 1.0
public var topK: Int // Default: 40
public var topP: Float // Default: 0.9
public var repetitionPenalty: Float // Default: 1.0
public var seed: UInt64? // Optional, for reproducibility
public init(
temperature: Float = 1.0,
topK: Int = 40,
topP: Float = 0.9,
repetitionPenalty: Float = 1.0,
seed: UInt64? = nil
)
/// Convert to composable SamplingPipeline
public func toPipeline() -> SamplingPipeline
}
TransformerConfig
Configuration for decoder-only transformer models.
public struct TransformerConfig: Sendable {
public let hiddenDim: Int
public let numHeads: Int
public let numKVHeads: Int
public let intermediateSize: Int
public let numLayers: Int
public let vocabSize: Int
public let maxSeqLen: Int
public let rmsNormEps: Float
public let ropeTheta: Float
public var headDim: Int { hiddenDim / numHeads }
public var kvGroupSize: Int { numHeads / numKVHeads }
}
EdgeRunnerMemoryPolicy
Memory management policy for model loading.
public struct EdgeRunnerMemoryPolicy: Sendable, Equatable {
public let fallbackChain: [QuantisationLevel]
public let evictBufferCacheOnPressure: Bool
public let maxMemoryBytes: Int
public static let `default` = EdgeRunnerMemoryPolicy(
fallbackChain: [.q8_0, .q4_k_m, .q4_0],
evictBufferCacheOnPressure: true
)
}
public enum QuantisationLevel: String, Sendable, Equatable, CaseIterable {
case q8_0 // 8 bits
case q4_k_m // 4.5 bits
case q4_0 // 4 bits
}
LlamaConfig
Llama-specific model configuration parsed from GGUF metadata.
public struct LlamaConfig: Sendable, Equatable {
public let embeddingDim: Int
public let layerCount: Int
public let headCount: Int
public let kvHeadCount: Int
public let vocabSize: Int
public let intermediateDim: Int
public let ropeFreqBase: Double
public let rmsNormEpsilon: Double
public let explicitHeadDim: Int? // Qwen 3 uses non-standard head dim
public var headDim: Int
public var gqaRatio: Int
public init(fromGGUFMetadata metadata: [String: MetadataValue]) throws
}
ModelConfig
Generic model configuration with metadata accessors.
public struct ModelConfig: Sendable, Equatable {
public let architectureName: String
public let metadata: [String: MetadataValue]
public func string(forKey key: String) -> String?
public func int(forKey key: String) -> Int?
public func float(forKey key: String) -> Float?
public func bool(forKey key: String) -> Bool?
public func array(forKey key: String) -> [MetadataValue]?
}
Chat and Conversation Types
ChatMessage
A single message in a chat conversation.
public struct ChatMessage: Identifiable, Sendable {
public let id: UUID
public let role: MessageRole
public var content: String
public let timestamp: Date
public enum MessageRole: String, Sendable {
case user, assistant, system
}
public init(role: MessageRole, content: String)
}
Conversation
Lightweight message history manager for multi-turn conversations.
public struct Conversation: Sendable {
public private(set) var messages: [ChatMessage]
public init(systemPrompt: String? = nil)
public mutating func addUser(_ content: String)
public mutating func addAssistant(_ content: String)
public mutating func addSystem(_ content: String)
public mutating func reset(keepSystem: Bool = true)
public var messageCount: Int { messages.count }
public var isEmpty: Bool { messages.isEmpty }
}
ChatViewModelState
Pure state container for chat UI (testable without SwiftUI).
public struct ChatViewModelState: Sendable {
public var messages: [ChatMessage]
public var isGenerating: Bool
public var currentInput: String
public var selectedModel: ModelInfo?
public var memoryUsedMB: Double
public var memoryTotalMB: Double
public var tokensPerSecond: Double
public var error: String?
public var memoryUsagePercent: Double
public mutating func addUserMessage(_ content: String)
public mutating func addAssistantMessage(_ content: String)
public mutating func appendToLastMessage(_ text: String)
public mutating func clearMessages()
public mutating func updateMemoryUsage(usedMB: Double, totalMB: Double)
}
Model Information Types
ModelInfo
Metadata about an available model file.
public struct ModelInfo: Identifiable, Sendable {
public let id: UUID
public let name: String
public let path: URL
public let format: String
public let parameterCount: String
public let quantization: String
public let fileSizeBytes: Int64
public var fileSizeFormatted: String // e.g., "4.2 GB"
}
Tool Calling Types
ToolParameter
Definition of a tool parameter.
public struct ToolParameter: Sendable {
public let name: String
public let type: ToolParameterType
public let description: String
public let required: Bool
}
public enum ToolParameterType: String, Sendable {
case string, integer, number, boolean, array, object
}
ToolCall
Represents a tool call request.
public struct ToolCall: Sendable {
public let name: String
public let arguments: [String: any Sendable]
}
ToolChoice
Strategy for tool selection in function calling.
public enum ToolChoice: Sendable, Equatable {
case auto
case required
case none
case specific(String) // Specific tool name
}
ToolExecutor
Executes tool calls.
public struct ToolExecutor: Sendable {
public init(tools: [any EdgeRunnerTool])
public func execute(_ call: ToolCall) async throws -> String
public func executeAll(_ calls: [ToolCall]) async throws -> [String]
public func toolDescriptions() -> String
public func shouldAttemptToolCall(choice: ToolChoice, modelOutput: String) -> Bool
}
Streaming and Generation Types
GenerationSession
Manages a single text generation session with streaming output.
public struct GenerationSession<Model: EdgeRunnerLanguageModel>: Sendable {
public let maxTokens: Int
public init(
model: Model,
sampling: SamplingConfiguration = SamplingConfiguration(),
maxTokens: Int = 2048,
onToken: (@Sendable (Int, String) -> Void)? = nil
)
/// Stream generated tokens
public func stream(prompt: String) -> AsyncThrowingStream<String, Error>
/// Generate complete response (non-streaming)
public func generate(prompt: String) async throws -> String
}
StreamToken
A token emitted during streaming generation.
public struct StreamToken: Sendable {
public let id: Int
public let text: String
public let isEOS: Bool
}
GenerationStats
Statistics collected during a generation session.
public struct GenerationStats: Sendable {
public var tokenCount: Int = 0
public var timeToFirstToken: Double = 0
public var totalTime: Double = 0
public var tokensPerSecond: Double
}
Module Types
Sequential
Container that chains modules in sequence.
public struct Sequential<M: EdgeRunnerModule>: EdgeRunnerModule
where M.Input == M.Output {
public init(_ modules: M...)
public init(_ modules: [M])
public func forward(_ input: Input) async throws -> Output
public var parameters: [String: any TensorBox]
}
AnyModule
Type-erased module wrapper for heterogeneous module composition.
public struct AnyModule<Value: Sendable>: EdgeRunnerModule, Sendable {
public init<M: EdgeRunnerModule>(_ module: M)
public func forward(_ input: Value) async throws -> Value
public var parameters: [String: any TensorBox]
}
LinearModule
A fully-connected linear layer: y = x @ W^T + b.
public struct LinearModule: EdgeRunnerModule, Sendable {
public typealias Input = [Float]
public typealias Output = [Float]
public let inFeatures: Int
public let outFeatures: Int
public init(
inFeatures: Int,
outFeatures: Int,
weight: [Float],
bias: [Float]?
) throws
public func forward(_ input: [Float]) async throws -> [Float]
public var parameters: [String: any TensorBox]
}
TensorBox
Type-erased container for tensor parameter data.
public protocol TensorBox: Sendable {
var elementCount: Int { get }
var floatArray: [Float] { get }
var shape: [Int] { get }
}
public struct ScalarTensorBox: TensorBox, Sendable {
public let value: Float
public var elementCount: Int { 1 }
public var floatArray: [Float] { [value] }
public var shape: [Int] { [] }
}
public struct ArrayTensorBox: TensorBox, Sendable {
public let data: [Float]
public let shape: [Int]
public var elementCount: Int { data.count }
public var floatArray: [Float] { data }
}
Transformer Types
TransformerBlockInput
Input to a single transformer block.
public struct TransformerBlockInput: Sendable {
public let hidden: [Float]
public let seqLen: Int
public let startPos: Int
}
TransformerBlockOutput
Output from a single transformer block.
public struct TransformerBlockOutput: Sendable {
public let hidden: [Float]
}
Metrics Types
Perplexity
Perplexity computation utilities for language model evaluation.
public enum Perplexity: Sendable {
public static func negLogLikelihood(logits: [Float], targetId: Int) -> Float
public static func compute(logitsPerToken: [[Float]], targetIds: [Int]) -> Float
}
Error Types
GenerationError
Errors during model loading and text generation.
public enum GenerationError: Error, Sendable, CustomStringConvertible {
case modelLoadFailed(reason: String)
case contextWindowExceeded(requested: Int, maximum: Int)
case invalidTokenID(Int)
case decodingFailed(String)
case cancelled
case samplingFailed(String)
case toolCallFailed(name: String, reason: String)
case structuredOutputFailed(reason: String)
}
ModelLoadError
Errors specific to model loading.
public enum ModelLoadError: Error, Sendable, Equatable {
case unsupportedFormat(String)
case unknownArchitecture(String)
case loadFailed(description: String)
}
WeightLoaderError
Errors during weight loading from GGUF files.
public enum WeightLoaderError: Error, Sendable, Equatable {
case deviceNotAvailable
case fileNotFound(URL)
case invalidFormat(String)
case unsupportedVersion(UInt32)
case unsupportedDataType(UInt32)
case allocationFailed(byteCount: Int)
case mmapFailed(errno: Int32)
case missingMetadata(String)
case tensorNotFound(String)
case shapeMismatch(name: String, expected: [Int], actual: [Int])
case checksumMismatch(name: String)
}
LlamaConfigError
Errors during Llama configuration parsing.
public enum LlamaConfigError: Error, Sendable, Equatable {
case missingMetadataKey(String)
case invalidMetadataValue(key: String, description: String)
}
TokenizerFactoryError
Tokenizer creation errors.
public enum TokenizerFactoryError: Error, Sendable, Equatable
ShapeError
Tensor shape errors.
public enum ShapeError: Error, Sendable
TensorStorageError
Tensor storage errors.
public enum TensorStorageError: Error, Sendable
Backend Registry
BackendRegistry
Registry for loading models by format.
public final class BackendRegistry: Sendable {
public init()
public func register<T: EdgeRunnerLanguageModel>(_ type: T.Type, for format: String)
public func backend(for format: String) -> (any EdgeRunnerLanguageModel.Type)?
public var availableBackends: Set<String>
public func load(
from url: URL,
format: String,
configuration: ModelConfiguration = ModelConfiguration()
) async throws -> any EdgeRunnerLanguageModel
}
Foundation Models Backend
FoundationModelsAvailability
Availability check for Foundation Models integration.
public enum FoundationModelsAvailability {
public static var isAvailable: Bool
}
Model Loading
ModelLoader
High-level model loading with automatic architecture detection.
public enum ModelLoader: Sendable {
/// Load a GGUF model with automatic architecture detection
public static func load(
from url: URL,
configuration: ModelConfiguration = ModelConfiguration()
) async throws -> any EdgeRunnerLanguageModel
}
Supported Architectures:
llama,qwen2,qwen3,gemma,gemma2,gemma3phi3,mistral,starcoder,starcoder2internlm2,yi,deepseek,deepseek2command-r,falcon
Sampling Pipeline
SamplingPipeline
Composable sampling pipeline with transforms and selectors.
public struct SamplingPipeline: Sendable {
public init(
transforms: [any LogitsTransform],
selector: any TokenSelector,
repetitionPenalty: RepetitionPenalty? = nil
)
public func sample(logits: [Float], previousTokens: [Int] = []) -> Int
public static var greedy: SamplingPipeline
public static func nucleus(temperature: Float = 0.8, topP: Float = 0.9, seed: UInt64 = 0) -> SamplingPipeline
public static func topK(k: Int = 40, temperature: Float = 0.8, seed: UInt64 = 0) -> SamplingPipeline
}
LogitsTransform
Protocol for logits transformation (temperature, top-k, top-p).
public protocol LogitsTransform: Sendable {
func transformLogits(_ logits: [Float]) -> [Float]
}
TokenSelector
Protocol for token selection (greedy, stochastic).
public protocol TokenSelector: Sendable {
func sample(logits: [Float]) -> Int
}
Built-in Transforms
public struct TemperatureSampler: LogitsTransform, Sendable
public struct TopKSampler: LogitsTransform, Sendable
public struct TopPSampler: LogitsTransform, Sendable
public struct MinPSampler: LogitsTransform, Sendable
public struct GreedySampler: TokenSelector, Sendable
public struct RepetitionPenalty: Sendable
public struct SeededRandomSource: RandomNumberGenerator, Sendable
Stochastic Sampler
public final class StochasticSampler<RNG: RandomNumberGenerator & Sendable>: TokenSelector, @unchecked Sendable {
public init(randomSource: inout RNG)
public func sample(logits: [Float]) -> Int
}
Concrete Model Implementations
LlamaLanguageModel
Full Llama inference engine conforming to LogitsModel.
public struct LlamaLanguageModel: LogitsModel, @unchecked Sendable {
public static let modelIdentifier = "llama"
/// Load from GGUF file
public static func load(
from url: URL,
configuration: ModelConfiguration
) async throws -> LlamaLanguageModel
/// Tokenize text
public func tokenize(_ text: String) -> [Int]
/// Detokenize tokens
public func detokenize(_ ids: [Int]) -> String
public var eosTokenID: Int
public var bosTokenID: Int?
public var vocabularySize: Int
/// Apply chat template
public func applyChatTemplate(
messages: [EdgeRunnerCore.ChatMessage],
addGenerationPrompt: Bool
) -> String?
/// Generate next token
public func nextToken(for tokenIDs: [Int], sampling: SamplingConfiguration) async throws -> Int
/// Raw logits access
public func logits(for tokenIDs: [Int]) async throws -> [Float]
}
Architecture Support:
- Llama 2, Llama 3
- Qwen 2, Qwen 3
- Mistral, Gemma
- Any GGUF model with standard Llama architecture
Quantization Support:
- Q2_K, Q3_K, Q4_0, Q4_K_M, Q5_0, Q5_1, Q5_K, Q6_K, Q8_0
Usage Examples
Basic Generation
import EdgeRunner
// Load a model
let model = try await ModelLoader.load(
from: modelURL,
configuration: ModelConfiguration()
)
// Simple generation
let tokens = model.tokenize("Hello, world!")
for _ in 0..<50 {
let next = try await model.nextToken(for: tokens, sampling: SamplingConfiguration())
tokens.append(next)
}
let text = model.detokenize(tokens)
Streaming Generation
import EdgeRunner
let model = try await ModelLoader.load(from: modelURL)
// Using AsyncThrowingStream
let stream = model.stream("Tell me a story")
for try await text in stream {
print(text, terminator: "")
}
Using GenerationSession
import EdgeRunner
let model = try await ModelLoader.load(from: modelURL)
let session = GenerationSession(
model: model,
sampling: SamplingConfiguration(temperature: 0.7, topP: 0.9),
maxTokens: 1024
)
// Streaming
let stream = session.stream(prompt: "Write a haiku")
for try await text in stream {
print(text, terminator: "")
}
// Non-streaming
let result = try await session.generate(prompt: "Write a haiku")
Chat with Conversation
import EdgeRunner
import EdgeRunnerCore
var convo = Conversation(systemPrompt: "You are a helpful assistant.")
convo.addUser("What is 2+2?")
let prompt = model.applyChatTemplate(
messages: convo.messages,
addGenerationPrompt: true
)
// Generate response...
convo.addAssistant(response)
convo.addUser("And 3+3?")
// Continue conversation...
Tool Calling
import EdgeRunner
// Define a tool
struct CalculatorTool: EdgeRunnerTool {
static let name = "calculator"
static let description = "Perform calculations"
static let parameters: [ToolParameter] = [
ToolParameter(name: "expression", type: .string, description: "Math expression", required: true)
]
func invoke(arguments: [String: Any]) async throws -> String {
let expr = arguments["expression"] as! String
// Evaluate...
return "\(result)"
}
}
// Execute tools
let executor = ToolExecutor(tools: [CalculatorTool()])
if executor.shouldAttemptToolCall(choice: .auto, modelOutput: modelOutput) {
let results = try await executor.executeAll(parsedToolCalls)
}
Custom Sampling Pipeline
import EdgeRunner
// Greedy sampling
let pipeline = SamplingPipeline.greedy
// Nucleus sampling with seed
let pipeline = SamplingPipeline.nucleus(temperature: 0.8, topP: 0.95, seed: 42)
// Custom pipeline
let pipeline = SamplingPipeline(
transforms: [
TemperatureSampler(temperature: 0.7),
TopPSampler(p: 0.9)
],
selector: StochasticSampler(randomSource: &rng),
repetitionPenalty: RepetitionPenalty(penalty: 1.1)
)
Module Organization
EdgeRunner (Main Product)
Re-exports:
EdgeRunnerCoreEdgeRunnerIOEdgeRunnerMetalEdgeRunnerSharedTypes
EdgeRunnerCore
Tensor,Shape,StridesComputeGraph,TensorOp,FusionEngine,AutoTunerSamplingPipeline,SamplingConfiguration- All sampling transforms and selectors
Tokenizer,BPETokenizer,SentencePieceTokenizerChatMessage,ChatTemplateEngineGenerationError,SpeculativeDecoderStructuredGenerator,GrammarState
EdgeRunnerIO
ModelLoader,ModelConfiguration,ModelConfigGGUFLoader,GGUFParser,GGUFMetadataLlamaConfig,LlamaModel,LlamaBlockWeightLoader,WeightMap,TensorDataTypeSafeTensorLoader,NPZLoader,NPYParserEdgeRunnerMemoryPolicyLoadableModelprotocol
EdgeRunnerMetal
MetalBackendGEMMKernel,GEMVKernelRMSNormKernel,LayerNormKernelRoPEKernel,GQAKernelFlashAttentionKernel,SoftmaxKernelDequantQ4_0Kernel,DequantQ8_0Kernel, etc.KVCache,BufferCache- All Metal shaders
Type Aliases and Re-exports
The main EdgeRunner module re-exports all public types from its dependencies via @_exported import, so users typically only need:
import EdgeRunner
Key re-exported types from EdgeRunnerCore:
ChatMessage(from EdgeRunnerCore.Tokenizer.ChatMessage)GenerationErrorSamplingPipeline,SamplingConfigurationTokenizer,BPETokenizer,SentencePieceTokenizer- All sampling types