alpaca.swift
面向 Apple Silicon 的原生语言模型推理。
一个从零开始、使用自定义 Metal 内核的 Swift 推理引擎——运行时不需要 llama.cpp、MLX、Core ML、ONNX 或 Python。
状态
0.2.0-pre – 已实现一个架构系列(Llama 风格解码器、字节级 BPE)和三种权重格式(F16、Q8_0、Q4_0),并已针对 PyTorch 验证。在验证模型(SmolLM2-135M-Instruct、Q8_0、Apple M5)上,128–4096 token 的预填充比 llama.cpp 快 1.3–1.7×。
注意事项:所有基准测试均在一台 Apple M5(16 GB)机器上测量。最快的预填充路径需要 Apple10 系列 GPU(M5/A19)以及 macOS/iOS 26;在其他设备上,其吞吐量大约为 1/3 到 1/2。尚未在实体 iPhone 或 iPad 上运行过;该库和示例应用仅能为 iOS 模拟器编译。
作者:Torin Etheridge · 2026-10-09 · MIT License
快速开始
Swift Package Manager
// Package.swift
.package(url: "https://github.com/torinriley/alpaca.git", from: "0.2.0")
// target dependency
.product(name: "Alpaca", package: "alpaca")
用法
import Alpaca
// Load a GGUF model (memory-mapped, async)
let model = try await LanguageModel.load(from: modelURL)
// Generate text with streaming
let stream = model.generate(
prompt: "Explain how transformers work.",
configuration: .init(maxTokens: 256, temperature: 0.7, seed: 42)
)
for try await token in stream {
print(token.text, terminator: "")
}
// Check generation summary
let summary = stream.summary // tokens, tok/s, finish reason
命令行
# Download the validation model (SmolLM2-135M-Instruct, Apache-2.0)
Scripts/fetch_models.sh
# Build
swift build -c release
# Generate text
.build/release/alpaca generate Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf \
--chat --prompt "Name three colors." --stats
# Benchmark
.build/release/alpaca bench Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf \
--backend metal --json out.json
# Profile GPU stages
.build/release/alpaca profile Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf \
--tokens 2048 --context 2200
SwiftUI 参考应用(模型选择器、流式输出、取消、吞吐量和内存诊断)位于 Examples/iOS/AlpacaDemo。
基准测试
Apple M5(10 核 GPU)、16 GB、macOS 27.0、Swift 6.4。SmolLM2-135M-Instruct(30 层,GQA 9/3,head dim 64)。7 次运行的中位数,贪心解码,两个引擎使用相同的 GGUF 文件。测量是在有后台负载的机器上进行的(在同一图表内比较,不要跨运行比较)。完整方法和数据:Docs/PERFORMANCE.md | 技术报告。
Q8_0 预填充和解码对比 llama.cpp:
| 提示 token 数 | alpaca.swift (tok/s) | llama.cpp | 加速比 |
|---|---|---|---|
| 128 | 24,106 | 18,326 | 1.32× |
| 2048 | 30,797 | 20,762 | 1.48× |
| 4096 | 24,069 | 15,388 | 1.56× |
| 16 | 3,420 | 4,743 | 0.72× |
| 解码(上下文) | alpaca.swift (tok/s) | llama.cpp | 加速比 |
|---|---|---|---|
| 16 个 token | 582 | 441 | 1.32× |
| 2048 个 token | 456 | 367 | 1.24× |
其他格式:Q4_0 在 16 个 token 上下文下解码为 712 tok/s(llama.cpp 518,1.38×)。F16 解码:360 tok/s(llama.cpp 319,1.13×)。
峰值内存:在 4400 个 token 上下文(135M 参数模型)下为 175–194 MiB。
技术报告将比较扩展到 SmolLM2-360M 和 1.7B,记录了使预填充在更大规模下具备竞争力的 GEMM 扩展工作,并解释了为什么 16 个 token 的提示更慢。对于 1.7B 且使用 --prefill-cache 时,预填充在 128–4096 个 token 下比 llama.cpp 快 1.1–1.3×。
目录
亮点 · 能力 · 结果 · 数值验证 · 设计 · 仓库布局 · 复现结果 · 局限性 · 路线图 · 引用 · 许可证
亮点
写作 {{NAME_ab12cd34}} 这样的值是真实值的占位符。请按原样使用它们,在回复和工具调用中都一样:在运行任何内容之前,它们会被填入真实值。不要要求验证它们,也不要用示例替换它们。
文章标题:我 17 岁,为 Apple Silicon 构建了一个 Swift 原生的 LLM 推理引擎
正文:
- 独立实现。张量引擎、CPU 参考算子、GGUF 解析器、BPE 分词器、Llama 前向传播、采样器以及所有 Metal kernel 均在本仓库内编写。Python/PyTorch 仅用于离线生成参考测试基准。
- 正确性优先。每个操作都有确定性的 CPU 参考实现,并针对独立生成的向量(numpy/PyTorch float64、gguf 包、Hugging Face transformers/tokenizers)进行测试,容差明确且有依据。CPU 后端在 logits 上与 Hugging Face float32 匹配至 5e-5(F16)和 7e-5(Q8_0);191/191 个分词器测试字符串产生相同的 token ID;72/72 个贪婪 token 在每种格式上都匹配。
- 性能有实测。在验证模型(SmolLM2-135M-Instruct,Q8_0,Apple M5)上,alpaca.swift 在 128 到 4096 token 的范围内预填充比 llama.cpp 快 1.3–1.7×,在 16–4096 token 的上下文下解码快 1.02–1.38×。仅在非常短的提示下较慢(16 token:0.72–0.90×)。技术报告显示,借助 GEMM 扩展工作,1.7B 预填充在使用 --prefill-cache 时也比 llama.cpp 更快。方法、瓶颈分析和优化见 Docs/PERFORMANCE.md。
- 技术报告。paper/alpaca-swift.pdf 在 135M、360M 和 1.7B 参数下,以 F16、Q8_0 和 Q4_0 格式对 alpaca.swift 与 llama.cpp 进行评估,诊断了预填充最初为何无法扩展到 1.7B,并测量了修复该问题的 GEMM 改动(LaTeX 源码、实验)。
- 对恶意输入鲁棒。GGUF 解析器将模型文件视为不可信:每个长度都受实际存在字节数的限制,所有算术运算都做了溢出检查,并且它在 20,000 个变异模糊测试文件下未发生崩溃。
- 符合语言习惯的 API。异步加载、AsyncSequence token 流式输出、取消、带种子的采样、每次生成隔离的会话,以及一个内存预算检查,会拒绝加载无法容纳的模型。
能力
| 架构 | Llama 风格解码器:RMSNorm、RoPE(无缩放)、分组查询注意力、SwiGLU、绑定或非绑定输出头 |
|---|---|
| 权重格式(GGUF v2/v3) | F16、Q8_0、Q4_0 直接从内存映射文件中就地使用;Q4_1 张量在加载时展开为 F16 并报告 |
| Metal 后端 | 张量操作因果预填充注意力和 GEMM(Metal 4,Apple10 系列 GPU),带 simdgroup-matrix 回退 · split-K GQA 解码注意力 · 融合单 token kernel · GPU 常驻贪婪解码 · f16 或 f32 KV 缓存 |
| CPU 后端 | 确定性参考实现(测试、调试、无 Metal 的机器) |
| 分词器 | 字节级 BPE,带 gpt2 和 smollm 预分词器,UTF-8 安全的流式解码 |
| 采样 | 贪婪、温度、top-k、top-p、带种子 RNG、EOS 和自定义停止 token |
| 平台 | macOS 14+、iOS 17+、Apple Silicon (arm64)。Swift 6 语言模式;使用 Swift 6.4 / Xcode 27 开发 |
快速开始
// Package.swift
.package(url: "https://github.com/torinriley/alpaca.git", from: "0.1.0")
// target dependency
.product(name: "Alpaca", package: "alpaca")
import Alpaca
let model = try await LanguageModel.load(from: modelURL) // GGUF, memory-mapped
let stream = model.generate(
prompt: "<|im_start|>user\nExplain how transformers work.<|im_end|>\n<|im_start|>assistant\n",
configuration: .init(maxTokens: 256, temperature: 0.7, seed: 42)
)
for try await token in stream { print(token.text, terminator: "") }
let s = stream.summary // prompt/generated tokens, prefill & decode tok/s, time to first token, finish reason
使用 stream.cancel() 或取消消费 Task 来取消。每次 generate 调用都拥有自己的 KV 缓存,因此同一模型上的并发生成永远不会共享可变状态。提示是原始文本;请自行应用模型的聊天模板。在内存警告时调用 model.trimMemory(),在应用离开前台时调用 model.cancelAllGenerations()。
命令行:
Scripts/fetch_models.sh # SmolLM2-135M-Instruct (Apache-2.0) into ./Models
swift build -c release
.build/release/alpaca generate Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf --chat --prompt "Name three colors." --stats
.build/release/alpaca bench Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf --backend metal --json out.json
.build/release/alpaca profile Models/gguf/SmolLM2-135M-Instruct-Q8_0.gguf --tokens 2048 --context 2200
一个 SwiftUI 参考应用(模型选择器、流式传输、取消、吞吐量和内存诊断)位于 Examples/iOS/AlpacaDemo。
结果
Apple M5(10 核 GPU)、16 GB、macOS 27.0、Swift 6.4。SmolLM2-135M-Instruct(30 层,GQA 9/3,head dim 64)。7 次运行的中位数,64 个解码 token,贪心;llama.cpp build 11429 在同一轮运行中使用相同的 GGUF 文件。测量期间机器带有后台负载,因此应在同一图表内比较,而不是跨运行比较;方法、完整数值网格和注意事项在 Docs/PERFORMANCE.md 中,原始数据在 Benchmarks/ 中。



| Q8_0 | 优化前 | alpaca.swift | llama.cpp | alpaca / llama.cpp |
|---|---|---|---|---|
| Prefill,128 个 token(tok/s) | 5,563 | 24,106 | 18,326 | 1.32× |
| Prefill,2048 个 token | 1,541 | 30,797 | 20,762 | 1.48× |
| Prefill,4096 个 token | 844 | 24,069 | 15,388 | 1.56× |
| Prefill,16 个 token | 1,853 | 3,420 | 4,743 | 0.72× |
| Decode,上下文 16 | 456 | 582 | 441 | 1.32× |
| Decode,上下文 2048 | 177 | 456 | 367 | 1.24× |
Q4_0 在上下文 16 下的 decode:712 tok/s(llama.cpp 518)。F16:360(319)。整个基准测试网格的峰值物理内存:在 4400 token 上下文下为 175–194 MiB。这是一个在单设备上的 135M 参数模型;结果不能外推到更大的模型或其他硬件。
更大的模型
以上数字是针对 135M 模型的。技术报告对 SmolLM2-360M 和 1.7B 重复了比较,并发现 64x64 的 prefill GEMM tile 无法扩展:在 1.7B 上,prefill 比 llama.cpp 慢。通过启动线程组使并发的线程组共享一个权重 tile、对大矩阵使用 128x64 tile,以及可选的 --prefill-cache(量化权重的半精度副本,每个权重 +2 字节)解决了这个问题。比值是 alpaca / llama.cpp 在同一 GGUF 文件上背靠背测量的;机器有噪声,因此报告给出了全部三次运行(本图是最安静的一次)。


短提示(16 个 token)仍然比 llama.cpp 慢,且 decode 没有变化。图表由 Experiments/make_scaling_figs.py 根据 Experiments/results 中的原始结果生成。
数值验证
参考栈:numpy/PyTorch float64 用于基础算子,gguf 用于量化,Hugging Face LlamaForCausalLM(在随机模型上用 float64,在从相同 GGUF 权重加载的真实模型上用 float32,以排除量化误差),Hugging Face tokenizers 用于 token ID。
| level | what | result |
|---|---|---|
| 1 | 基础算子(add、mul、SiLU、matmul、RMSNorm、softmax、RoPE ×2 约定、attention、Q8_0/Q4_0) | 相对 float64 ≤ 2.5e-6;反量化位精确;量化器与 gguf 字节一致 |
| 2–4 | tiny Llama(GQA/MHA、tied/untied、HF 与 GGUF RoPE 布局)、增量解码 vs 无缓存解码 | 相对 float64 PyTorch ≤ 1.3e-6;缓存 == 无缓存完全一致 |
| 5 | SmolLM2-135M、CPU 后端、末 token logits | 5e-5(F16)、7e-5(Q8_0)、1.6e-3(Q4_0:三个 Q4_1 张量舍入为 F16) |
| 5 | 同上,默认 Metal 配置 | 与 logit 峰值相差 1.1e-3 以内;72/72 贪婪 token 完全一致 |
| 5 | Metal 使用 prefillPrecision: .exact 和 f32 KV 对比 CPU | 2.7e-5 |
默认 Metal 配置以少量、有界的精度换取速度(半精度预填充激活、f16 KV 缓存、注意力中的半精度 Q/P)。每个降精度组件都由各自的测试隔离,并带有解析推导的边界,例如快速 GEMM 必须逐元素满足 |error| ≤ 2⁻¹⁰ Σ|xₖwₖ|(观测到的最坏情况:边界的 31%)。细节、容差及每项的理由见:Docs/NUMERICAL_VALIDATION.md。
Design
Alpaca 公共 API:LanguageModel · GenerationStream · errors ─┐
AlpacaMetal Metal 上下文、内核(.metal)、GPU Llama 模型和会话 │ 依赖于
AlpacaModels GGUF 解析器、Llama 权重映射、分词器构建 │ ↓
AlpacaTokenizers 字节级 BPE │
AlpacaCore 张量、CPU 参考算子、KV 缓存、CPU Llama、采样器、内存 ─┘
AlpacaCLI `alpaca` 可执行文件:info · generate · bench · profile
权重从不复制:GGUF 文件被内存映射,整个映射被包装为一个无拷贝的 MTLBuffer(统一内存),因此常驻权重内存约等于文件大小。解码步骤每层六次融合调度;贪婪解码完全在 GPU 上运行,多个命令缓冲区同时进行。预填充使用 512 token 块,端到端半精度激活:张量运算 GEMM(量化权重一次一个矩阵展开到暂存缓冲区)和张量运算因果注意力内核。完整描述:Docs/ARCHITECTURE.md。
Repository layout
Sources/ AlpacaCore · AlpacaMetal (+ Kernels/*.metal) · AlpacaModels · AlpacaTokenizers · Alpaca · AlpacaCLI
Tests/ 单元、内核、解析器/模糊、分词器和真实模型集成测试(+ 生成的 fixture)
Scripts/ fixture 生成器(Python,离线)、模型下载、基准测试工具、llama.cpp 对比
Benchmarks/ 文档中引用的每个数字的原始 JSON 结果
Experiments/ 技术报告背后的评估工具、脚本和原始结果
paper/ 技术报告(LaTeX 源码和 PDF)
Docs/ ARCHITECTURE · NUMERICAL_VALIDATION · PERFORMANCE · MODEL_SUPPORT · DEVELOPMENT
Examples/iOS/ SwiftUI 参考应用
Reproducing the results
Scripts/fetch_models.sh --with-reference # ~800 MB:GGUF 文件 + Hugging Face 参考检查点
Scripts/test_all.sh # 先 debug 套件,再 release 套件(含真实模型验证)
Scripts/compare_llamacpp.sh Benchmarks/results/mine # 需要 llama-bench(例如 brew install llama.cpp)
重新生成参考 fixture(PyTorch、gguf、分词器)的说明见 Docs/DEVELOPMENT.md。需要模型文件的测试在文件缺失时会跳过并给出消息;真实模型测试需要优化构建(swift test -c release -Xswiftc -enable-testing)。
Limitations
- 仅支持 Llama 系列解码器:不支持 RoPE 缩放(Llama 3.1+)、滑动窗口注意力、MoE、偏置或部分旋转。
- 分词器:字节级 BPE(gpt2、smollm 预分词器)。SentencePiece/Unigram 模型和 Llama-3 预分词器会被拒绝并报错。
我 17 岁,为 Apple Silicon 构建了一个 Swift 原生 LLM 推理引擎
- 量化:F16、Q8_0、Q4_0(Q4_1 通过加载时扩展)。K-quants、Q5_x 和 BF16 会被拒绝。激活不做量化(与 llama.cpp 不同),因此输出无法与其进行按位比较。
- 每次生成一个序列;没有连续批处理、分页注意力或投机解码。
- 非常短提示(≤ 24 tokens)的预填充比 llama.cpp 慢 10–28%。驻留 GPU 的解码循环仅适用于贪心解码;随机采样每个 token 都要付出一次 CPU 往返开销(约慢 15–20%)。
- 快速预填充路径(在矩阵硬件上执行 GEMM 和注意力)需要 Apple10 系列 GPU(M5/A19)以及 macOS/iOS 26;在其他平台上,simdgroup-matrix 路径的吞吐量大约只有三分之一到二分之一(仅在 M5 上测量)。
- RoPE 角度在 GPU 上为 float32(误差 ≈ 1e-7 × 位置弧度)。权重超过 maxBufferLength 的模型通过复制方式加载。
路线图
精度工作由在困惑度/KL 预算下对每个降低精度位置进行的消融驱动(按操作混合的精度策略、split-Q 注意力分数);专用短提示(≤ 24 tokens)内核;GPU 侧随机采样;Llama-3 tokenizer 和 RoPE 缩放;更大模型与设备端验证。每项背后的理由见 Docs/PERFORMANCE.md。
引用
@software{etheridge2026alpaca,
author = {Etheridge, Torin},
title = {alpaca.swift: Native language model inference for Apple Silicon},
year = {2026},
url = {https://github.com/torinriley/alpaca}
}
另见 CITATION.cff。
许可证
MIT © 2026 Torin Etheridge。见 LICENSE。验证模型是 SmolLM2-135M-Instruct(Apache-2.0,HuggingFaceTB);GGUF 转换由 bartowski 完成。llama.cpp 仅用作外部基准基线,不是依赖项。