LiteRT-LM Swift API 实战:在 iOS 与 macOS 应用中集成端侧大模型
【免费下载链接】LiteRT-LMLiteRT-LM is Google's production-ready, high-performance, open-source inference framework for deploying Large Language Models on edge devices.项目地址: https://gitcode.com/GitHub_Trending/li/LiteRT-LM
本文以仓库 samples/ios_and_mac/README.md 为骨架,讲解如何使用 LiteRT-LM 的 Swift API,通过 Swift Package Manager(SPM)把
.litertlm格式的端侧大模型(如 Gemma 4 E2B)原生集成进 iOS 15+ 与 macOS 12+ 应用,覆盖依赖接入、模型打包、引擎初始化、对话创建与流式输出等完整链路;并结合仓库内 Swift 封装层与 C 底层接口的源码,说明每个 API 背后的实际调用与配置含义,读完即可在 Xcode 中跑通一个 SwiftUI 聊天 Demo。
前置条件
官方示例目录要求的最低环境如下(与仓库根目录 Package.swift 中声明的平台版本一致):
| 条件 | 要求 |
|---|---|
| iOS | 15.0 或更高 |
| macOS | 12.0 或更高 |
| Xcode | 15.0 或更高 |
| 模型文件 | 一个.litertlm格式的模型文件(例如 Gemma 系列) |
.litertlm是 LiteRT-LM 的端侧模型封装格式,包含模型权重、分词器与元数据。仓库中models/目录下有 gemma4、qwen3 等多个模型族的元数据与 chat template 示例,runtime/testdata/下则有大量.litertlm测试模型,可用于理解该格式的构成。
第 1 步:通过 SPM 添加 LiteRTLM 依赖
在 Xcode 中按如下步骤把 LiteRTLM 的 Swift 包加入工程:
- 选择File>Add Package Dependencies...;
- 在右上角搜索栏输入 LiteRT-LM 的 GitHub 仓库地址(
google-ai-edge/LiteRT-LM)并回车; - 从列表中选中该包,点击Add Package;
- 勾选要添加依赖的目标 App,点击Finish。
仓库根目录的 Package.swift 就是 SPM 清单,从中可以看到包的完整结构:
- 包名
LiteRTLM,swift-tools-version: 5.9,同时支持.iOS(.v15)与.macOS(.v12)两个平台; - 通过
.binaryTarget分别声明了 iOS(CLiteRTLM)与 macOS(CLiteRTLM_mac)两个预编译的CLiteRTLM.xcframework二进制目标,并带有校验和(checksum)用于完整性验证; LiteRTLM目标封装 Swift 层,源码位于 swift/ 目录(Engine.swift、Conversation.swift、Config.swift、Message.swift等),按平台条件依赖对应的 C 预编译库;- 另有
LiteRTLMFoundationModels库(适配 Apple Foundation Models,位于 swift/apple_fm/),以及分布在 swift 目录下的多个独立测试 target(EngineTests、ConversationTests、MessageTests等)。
[!NOTE] 若添加包后出现
no such module LiteRTLM报错,说明 App target 还没有链接该库,按以下步骤手动补上:
- 在项目导航器中点击你的工程;
- 选中你的 App target;
- 进入General标签页;
- 滚动到Frameworks, Libraries, and Embedded Content;
- 点击
+按钮;- 选择LiteRTLM Package->LiteRTLM;
- 点击Add。
第 2 步:把模型文件加入 App Bundle
- 获取一个兼容的
.litertlm模型文件(例如 Gemma 4 E2B,可在模型社区搜索litert-community/gemma-4-E2B-it-litert-lm); - 把模型文件拖入 Xcode 的项目导航器,在弹出的对话框中确保勾选了你的 App target,使其被复制进 App Bundle;
- (备选方案)如果运行时找不到模型文件,可到工程的Build Phases>Copy Bundle Resources中手动添加该文件。
模型文件的文件名(不含扩展名)会在代码中作为Bundle.main.path(forResource:ofType:)的forResource参数使用。仓库示例 ContentView.swift 中使用的资源名是gemma-4-E2B-it、类型是litertlm,实际使用时请替换为你自己模型的资源名。
第 3 步:编写 SwiftUI 聊天页面
仓库的 samples/ios_and_mac/ContentView.swift 给出了一个可直接运行的 SwiftUI 聊天 Demo,核心流程分为五步:找模型 → 构造配置 → 初始化引擎 → 创建会话 → 流式发送消息。
3.1 从 App Bundle 定位模型文件
guard let modelPath = Bundle.main.path( forResource: "gemma-4-E2B-it", ofType: "litertlm") else { statusMessage = "Model file not found in app bundle!" return }forResource/ofType对应第 2 步添加到 Bundle 的模型文件名与扩展名。找不到文件时,优先检查是否已通过Copy Bundle Resources正确打包。
3.2 构造EngineConfig并初始化Engine
let fileManager = FileManager.default guard let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first else { fatalError("Could not find caches directory") } let config = try EngineConfig( modelPath: modelPath, backend: .gpu, cacheDir: cacheDirectory.path) // let config = try EngineConfig( // modelPath: modelPath, backend: .cpu(), cacheDir: cacheDirectory.path) let newEngine = Engine(engineConfig: config) try await newEngine.initialize()这里有两个值得注意的实践点:
- backend:
.gpu走 Metal 加速(iOS/macOS 上的 GPU 后端);.cpu()为 CPU 后端,可通过Backend.cpu(threadCount:)指定线程数。两种后端的定义见 swift/Config.swift。 - cacheDir:必须指向应用可写的目录(如 Caches),用于放置模型加载产生的缓存文件。若不传,默认使用模型文件所在目录。
Engine是一个actor(见 swift/Engine.swift),initialize()内部最终调用 C 层litert_lm_engine_settings_create(...)创建设置、litert_lm_engine_create(settings)创建原生引擎句柄。源码注释特别提醒:初始化可能耗时较长(视模型大小和硬件可达秒级),不要在主线线程执行,SwiftUI 的.task {}修饰符天然在后台执行,正适合此场景。
3.3 创建Conversation会话
self.engine = newEngine self.conversation = try await newEngine.createConversation()createConversation()(swift/Engine.swift)内部做了这些事:
- 校验引擎已初始化(否则抛
LiteRTLMError.engine(.notInitialized)); - 校验系统消息数量:
ConversationConfig中systemMessage与initialMessages里的 system 角色消息不能同时存在、且 system 消息不能多于一条,否则抛LiteRTLMError.config(.multipleSystemMessages); - 把 sampler 参数(topK/topP/temperature/seed)、LoRA 路径、thinking 配置、工具描述等序列化后通过 C 接口写入会话配置,最终调用
litert_lm_conversation_create(engineHandle, cConversationConfig)创建原生会话。
3.4 流式发送消息并实时渲染
responseText = "" for try await chunk in conversation.sendMessageStream(Message(prompt)) { if let firstContent = chunk.contents.first { switch firstContent { case .text(let text): responseText += text // Append the chunk live! default: break } } }sendMessageStream(_:)(swift/Conversation.swift)返回AsyncThrowingStream<Message, Error>,每个 chunk 是一个Message,其contents中.text类型的Content即增量文本。底层原理是:Swift 层通过 C 回调函数streamCallback(swift/Conversation.swift)接收原生流式分片,把tool_calls缓存、把包含content或channels的 JSON 分片解析成Message后yield给AsyncThrowingStream,最终在isFinal时结束流或继续执行工具调用循环。
与之对应的还有同步接口sendMessage(_:)(swift/Conversation.swift),一次性返回完整回复;sendMessage还内置了自动工具调用(automaticToolCalling)与重复工具调用上限(recurringToolCallLimit = 25)的保护逻辑。
运行 App
在 iOS 真机上运行
- 用数据线把 iPhone 连接到 Mac;
- 在 Xcode 窗口顶部中央的运行目标菜单中选择你的 iPhone 名称;
- 点击Run按钮(或按
Cmd + R)构建并安装到设备上运行。
在 macOS 上运行
- 在 Xcode 运行目标菜单中选择My Mac;
- 点击Run按钮(或按
Cmd + R)。
常见问题排查
[!IMPORTANT] 如果在 Mac 上使用本地构建的、未签名的库,macOS 可能会弹出 “Malware”(恶意软件)警告阻止加载。仅用于本地测试时,可在 Mac 终端执行以下命令解除隔离属性:
xattr -rd com.apple.quarantine path/to/CLiteRTLM.xcframework其中
path/to/CLiteRTLM.xcframework替换为实际的 xcframework 路径。
[!TIP] 如果真机的 iOS 版本低于 Xcode 中设置的部署目标(Deployment Target),可以到 target 的General标签页,在Minimum Deployments(或Deployment Info)中把最低 iOS 版本调低到与设备版本一致,即可继续运行。
深入:Swift API 与底层 C 接口的映射
LiteRT-LM Swift 层是一层薄封装,核心价值在于把 C 层的句柄式 API 转换为类型安全、并发友好的 Swift 接口。梳理 swift 目录可得如下对应关系:
| Swift 层 | 底层 C 接口 | 说明 |
|---|---|---|
EngineConfig | litert_lm_engine_settings_create/..._set_max_num_tokens/..._set_cache_dir/..._set_lora_rank等 | 引擎级配置 |
Engine.initialize() | litert_lm_engine_create(settings) | 创建原生引擎,持有OpaquePointer句柄 |
Engine.createConversation() | litert_lm_conversation_create(engineHandle, config) | 创建会话,同时建立ToolManager与工具注册表 |
Conversation.sendMessage | litert_lm_conversation_send_message | 同步推理,返回完整 JSON 响应 |
Conversation.sendMessageStream | litert_lm_conversation_send_message_stream+ C 回调 | 流式推理,回调桥接AsyncThrowingStream |
Conversation.cancel() | litert_lm_conversation_cancel_process | 取消进行中的推理 |
Engine.deinit | litert_lm_engine_delete(handle) | 句柄释放,防内存泄漏 |
配置参数在底层的行为可以通过 swift/Engine.swift 中initializeInternal的实现确认:maxNumTokens会映射为litert_lm_engine_settings_set_max_num_tokens(等价于 KV Cache 的规模),loraRank同时写入 rank 与supported_lora_ranks,benchmark 与 speculative decoding(投机解码)等实验能力通过ExperimentalFlags(见 swift/ExperimentalFlags.swift)按需开启。
进阶:EngineConfig 与 ConversationConfig 参数详解
EngineConfig(swift/Config.swift)
| 参数 | 默认值 | 含义与约束 |
|---|---|---|
modelPath | 必填 | .litertlm模型文件路径 |
backend | .cpu() | CPU 或 GPU 后端;.cpu(threadCount:)可指定线程数 |
visionBackend | nil | 视觉执行器后端;为nil时不初始化视觉能力(多模态模型需要时设置) |
audioBackend | nil | 音频执行器后端;为nil时不初始化音频能力 |
maxNumTokens | nil | 输入+输出 token 总数上限,等价于 KV Cache 大小;nil时用模型/引擎默认值,且必须 > 0,否则构造抛错 |
cacheDir | nil | 缓存文件目录,必须应用可写;nil时使用模型文件所在目录 |
loraRank | nil | 文本 LoRA 权重 rank;0 或nil表示禁用 LoRA |
audioLoraRank | nil | 音频 LoRA 权重 rank;0 或nil表示禁用 |
ConversationConfig(swift/Config.swift)
systemMessage/initialMessages:预设系统提示与初始对话历史(system 消息只能有一条);tools:注册给模型调用的工具列表,配合Tool/ToolManager(swift/Tool.swift、swift/ToolManager.swift)使用,automaticToolCalling默认为true,模型发起工具调用后 SDK 会自动执行并把结果回传给模型;samplerConfig:采样参数,见SamplerConfig,包含topK(>0)、topP(∈[0,1])、temperature(≥0)与seed(默认 0),非法值会在构造时抛LiteRTLMError.config;loraPath/audioLoraPath:按会话加载 LoRA 权重文件;thinkingConfig:思维链/推理生成开关与 token 预算(ThinkingConfig,thinkingTokenBudget默认 -1 表示无限预算);enableResponseFormat:是否启用约束解码(constrained decoding,配合 swift/ResponseFormat.swift 使用,底层走 llguidance 约束提供器),默认false;enableToolCallStreaming、visualTokenBudget、enableSpeculativeDecoding:分别控制工具调用流式化、视觉 token 预算与投机解码(nil时继承引擎设置)。
生成期可选参数
sendMessage/sendMessageStream还支持按次传入:extraContext(附加上下文)、RepetitionPenaltyConfig(重复惩罚,支持 repetition/presence/frequency penalty 与窗口大小)、NoRepeatNgramConfig(n-gram 禁止重复)、SuppressTokensConfig(按 token ID 屏蔽)、maxOutputTokens(输出上限;对 thinking 模型,思维 token 与最终回答共同计入该上限)以及ResponseFormat。这些参数在 swift/Conversation.swift 中逐一映射为 C 层litert_lm_conversation_optional_args_*系列接口。
Message与Content类型
swift/Message.swift 定义了多模态消息模型:Content支持.text、.imageData、.imageFile、.audioData、.audioFile与.toolResponse六种形态;Message由Contents(RandomAccessCollection,可容纳多个 Content)、role(system/user/model/tool)与可选的channels、toolCalls组成。也就是说,同一个聊天框架天然支持图文混排与音频输入,构造示例:
let textContent = Content.text("这张图片里有什么?") let imageContent = Content.imageData(imageData) let message = Message(of: textContent, imageContent)完整可运行 Demo 一览
把上述步骤组装起来,就是一个功能完整的 SwiftUI 流式聊天页面:samples/ios_and_mac/ContentView.swift。其状态管理(@State持有engine/conversation)、按钮禁用逻辑(引擎就绪前Generate Response置灰)与错误兜底(初始化失败/推理失败均写回statusMessage)均可直接复用。想进一步验证 Swift API 行为,可参考仓库内对应的测试:EngineTests.swift、ConversationTests.swift、MessageTests.swift,它们展示了各 API 的典型调用方式与错误分支。
小结
集成路径总结为四步:SPM 加包 → Bundle 放模型 → 初始化Engine→ 创建Conversation流式对话。其中no such module LiteRTLM、macOS 隔离警告、部署目标不匹配这三个高频问题,按上文指引即可快速解决。若需要更细的配置能力(LoRA、约束解码、工具调用、多模态输入、benchmark 等),Swift 层全部以类型安全的方式暴露,且每个参数都能在仓库 swift 目录的源码注释中找到默认值与取值范围,是排查行为差异的第一手资料。
【免费下载链接】LiteRT-LMLiteRT-LM is Google's production-ready, high-performance, open-source inference framework for deploying Large Language Models on edge devices.项目地址: https://gitcode.com/GitHub_Trending/li/LiteRT-LM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考