移动端调用大模型详解
🌟 大家好,我是“没事学AI”!
🤖 在AI的星辰大海里,我是那个执着的航海者,带着对智能的好奇不断探索。
📚 每一篇技术解析都是我打磨的罗盘,每一次模型实操都是我扬起的风帆。
💻 每一行代码演示都是我的航线记录,每一个案例拆解都是我的藏宝图绘制。
🚀 在人工智能的浪潮中,我既是领航员也是同行者。让我们一起,在AI学习的航程里,解锁更多AI的奥秘与可能——别忘了点赞、关注、收藏,跟上我的脚步,让“没事学AI”陪你从入门到精通!
目录
- 一、端侧调用大模型支持的格式(iOS与安卓对比)
- 二、格式的应用场景与核心区别
- 1. `.onnx`格式(开放神经网络交换格式)
- 2. `.tflite`格式(TensorFlow Lite专用格式)
- 3. `.mlmodel/.mlpackage`格式(Core ML专用格式)
- 4. `.mnn`格式(阿里MNN框架格式)
- 三、技术实现(端侧本地调用)
- (一)安卓端实现
- 1. `.onnx`格式(基于ONNX Runtime)
- 2. `.mnn`格式(基于MNN框架)
- (二)iOS端实现
- 1. `.mlmodel`格式(基于Core ML)
- 2. `.onnx`格式(基于ONNX Runtime for iOS)
一、端侧调用大模型支持的格式(iOS与安卓对比)
移动端端侧调用大模型需依赖模型格式与硬件架构的兼容性,iOS和安卓支持的核心格式及对应框架如下:
模型格式 | 安卓支持框架 | iOS支持框架 | 核心依赖硬件加速 |
---|---|---|---|
.onnx | ONNX Runtime | ONNX Runtime | 安卓:ARM CPU/GPU(Vulkan);iOS:Metal GPU |
.tflite | TensorFlow Lite | TensorFlow Lite | 安卓:NNAPI;iOS:Core ML Delegate(间接调用ANE) |
.mlmodel/.mlpackage | ❌ | Core ML | iOS:ANE(苹果神经网络引擎) |
.mnn | MNN(阿里) | ❌ | 安卓:ARM NEON/Vulkan |
二、格式的应用场景与核心区别
1. .onnx
格式(开放神经网络交换格式)
- 应用场景:
中大型模型(7B-13B参数,量化后),如本地文档问答、代码补全、多轮对话。适合需要跨平台(安卓/iOS)部署、模型精度要求较高的场景(如医疗辅助诊断的本地推理)。 - 核心优势:
跨框架兼容(支持PyTorch/TensorFlow模型转换),支持INT4/INT8量化,推理速度均衡,适合对兼容性要求高的复杂任务。 - 局限性:
模型体积较大(7B参数INT4量化后约4GB),对移动端内存要求高(需6GB以上运存)。
2. .tflite
格式(TensorFlow Lite专用格式)
- 应用场景:
轻量级模型(<1B参数),如输入法智能补全、实时语音转文本、简单问答机器人(如儿童教育APP的离线互动)。 - 核心优势:
体积小(1B参数INT8量化后约250MB),推理速度快(<300ms),适合资源受限的移动端,支持安卓/iOS跨平台。 - 局限性:
复杂任务精度较低,中大型模型转换后性能损耗明显。
3. .mlmodel/.mlpackage
格式(Core ML专用格式)
- 应用场景:
苹果生态专属场景,如iPhone/iPad本地语音助手、照片内容生成(如相册智能分类+文案生成)、离线翻译。适合依赖低功耗、高性能的场景(如续航敏感的移动设备)。 - 核心优势:
深度优化苹果硬件(ANE加速),推理延迟低(<200ms),功耗比其他格式低30%-50%。 - 局限性:
仅支持iOS,需专门转换,兼容性差。
4. .mnn
格式(阿里MNN框架格式)
- 应用场景:
安卓高性能场景,如实时图像生成(如电商APP的虚拟试衣间)、多模态推理(图文结合的商品推荐)。 - 核心优势:
针对ARM架构深度优化,推理速度比ONNX Runtime快10%-20%,支持动态shape输入。 - 局限性:
仅支持安卓,生态较封闭,模型转换工具链较少。
三、技术实现(端侧本地调用)
(一)安卓端实现
1. .onnx
格式(基于ONNX Runtime)
步骤:
- 模型转换(PC端):用
optimum
将Hugging Face模型转为ONNX并量化(如Llama 3-8B→INT4)。 - 安卓集成:通过ONNX Runtime加载模型,实现推理。
代码实现:
// 1. 依赖配置(app/build.gradle)
dependencies {implementation 'com.microsoft.onnxruntime:onnxruntime-android:1.16.3'implementation 'androidx.appcompat:appcompat:1.6.1'
}// 2. 模型管理类
import ai.onnxruntime.OrtEnvironment;
import ai.onnxruntime.OrtException;
import ai.onnxruntime.OrtSession;
import android.content.Context;
import java.io.File;
import java.util.Collections;public class ONNXModelRunner {private OrtEnvironment env;private OrtSession session;private Tokenizer tokenizer;// 初始化模型(从手机本地加载)public void init(Context context) throws OrtException, IOException {// 模型路径(假设已复制到/files目录)File modelFile = new File(context.getFilesDir(), "llama3-8b-int4.onnx");env = OrtEnvironment.getEnvironment();OrtSession.SessionOptions options = new OrtSession.SessionOptions();options.setIntraOpNumThreads(4); // 启用4线程options.enableVulkanAcceleration(); // 启用GPU加速(需设备支持)session = env.createSession(modelFile.getAbsolutePath(), options);// 初始化分词器(加载vocab.txt)tokenizer = new Tokenizer(new File(context.getFilesDir(), "vocab.txt"));}// 文本生成推理public String generate(String input) throws OrtException {// 分词:文本→token IDlong[] inputTokens = tokenizer.encode(input);// 构建输入TensorOrtSession.Result result = session.run(Collections.singletonMap("input_ids", ai.onnxruntime.Tensor.createTensor(env, inputTokens, new long[]{1, inputTokens.length})));// 解码:token ID→文本long[] outputTokens = (long[]) result.get("output_ids").getValue();return tokenizer.decode(outputTokens);}// 释放资源public void close() throws OrtException {session.close();env.close();}
}// 3. 分词器实现(核心逻辑)
class Tokenizer {private Map<String, Long> vocab;private Map<Long, String> reverseVocab;public Tokenizer(File vocabFile) throws IOException {// 加载词表(格式:"词 编号")vocab = new HashMap<>();List<String> lines = Files.readAllLines(vocabFile.toPath(), StandardCharsets.UTF_8);for (String line : lines) {String[] parts = line.split(" ");vocab.put(parts[0], Long.parseLong(parts[1]));}// 构建反向映射reverseVocab = vocab.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));}public long[] encode(String text) {// 简化实现:按空格分割(实际需用Hugging Face分词逻辑)String[] words = text.split(" ");long[] tokens = new long[words.length + 2];tokens[0] = 1; // 起始符<s>for (int i = 0; i < words.length; i++) {tokens[i + 1] = vocab.getOrDefault(words[i], 0L); // 未知词用0}tokens[tokens.length - 1] = 2; // 结束符</s>return tokens;}public String decode(long[] tokens) {StringBuilder sb = new StringBuilder();for (long token : tokens) {if (token == 1 || token == 2) continue; // 跳过特殊符sb.append(reverseVocab.getOrDefault(token, "[UNK]")).append(" ");}return sb.toString().trim();}
}// 4. 调用示例(Activity)
public class ONNXActivity extends AppCompatActivity {private ONNXModelRunner runner;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_onnx);// 复制模型到本地(首次运行)copyAssetsToFiles("llama3-8b-int4.onnx", "vocab.txt");// 初始化并推理try {runner = new ONNXModelRunner();runner.init(this);String result = runner.generate("请介绍ONNX格式的优势");Log.d("ONNX Result", result);} catch (Exception e) {e.printStackTrace();}}// 复制assets文件到/files目录private void copyAssetsToFiles(String... filenames) {new Thread(() -> {try {for (String filename : filenames) {InputStream in = getAssets().open(filename);File outFile = new File(getFilesDir(), filename);OutputStream out = new FileOutputStream(outFile);byte[] buffer = new byte[1024];int len;while ((len = in.read(buffer)) > 0) out.write(buffer, 0, len);in.close();out.close();}} catch (IOException e) {e.printStackTrace();}}).start();}@Overrideprotected void onDestroy() {super.onDestroy();try {runner.close();} catch (OrtException e) {e.printStackTrace();}}
}
2. .mnn
格式(基于MNN框架)
代码实现:
// 1. 依赖配置
dependencies {implementation 'com.aliyun.mnn:mnn:1.3.0'
}// 2. 模型管理类
import com.aliyun.mnn.MNNNetInstance;
import com.aliyun.mnn.Tensor;
import com.aliyun.mnn.MNNForwardType;public class MNNModelRunner {private MNNNetInstance instance;private MNNNetInstance.Session session;private Tokenizer tokenizer;public void init(Context context) {// 加载模型(启用GPU加速)String modelPath = new File(context.getFilesDir(), "multimodal-model.mnn").getAbsolutePath();MNNNetInstance.Config config = new MNNNetInstance.Config();config.forwardType = MNNForwardType.FORWARD_VULKAN; // Vulkan GPU加速instance = MNNNetInstance.createFromFile(modelPath, config);MNNNetInstance.SessionConfig sessionConfig = new MNNNetInstance.SessionConfig();sessionConfig.numThread = 4;session = instance.createSession(sessionConfig);// 初始化分词器tokenizer = new Tokenizer(new File(context.getFilesDir(), "vocab.txt"));}// 多模态推理(输入文本,输出文本)public String generate(String input) {int[] inputTokens = tokenizer.encodeInt(input); // 转为int型token// 输入Tensor配置Tensor inputTensor = Tensor.create(new int[]{1, inputTokens.length}, Tensor.DataType.INT32, session);inputTensor.copyFrom(inputTokens);session.input("input_ids", inputTensor);// 推理session.run();// 输出TensorTensor outputTensor = session.getOutput("output_ids");int[] outputTokens = outputTensor.getIntData();// 释放资源inputTensor.release();outputTensor.release();return tokenizer.decodeInt(outputTokens);}
}
(二)iOS端实现
1. .mlmodel
格式(基于Core ML)
步骤:
- 模型转换(PC端):用
coremltools
将Hugging Face模型转为.mlmodel(如Llama 3-8B→INT8)。 - iOS集成:Xcode导入模型,通过Core ML API调用。
代码实现(Swift):
// 1. 导入模型(拖入Xcode,自动生成Llama3_8b类)
import CoreML// 2. 模型管理类
class CoreMLModelRunner {private let model: Llama3_8bprivate let tokenizer: Tokenizerinit() throws {// 初始化模型guard let model = try? Llama3_8b(configuration: .init()) else {throw NSError(domain: "ModelError", code: 0)}self.model = model// 初始化分词器(加载vocab.txt)guard let vocabURL = Bundle.main.url(forResource: "vocab", withExtension: "txt") else {throw NSError(domain: "TokenizerError", code: 0)}tokenizer = try Tokenizer(vocabURL: vocabURL)}// 异步推理func generate(text: String) async throws -> String {// 1. 分词let inputTokens = try tokenizer.encode(text: text)// 2. 构建输入(Core ML要求MLMultiArray)let inputShape = [1, inputTokens.count] as [NSNumber]guard let inputArray = try? MLMultiArray(shape: inputShape, dataType: .int32) else {throw NSError(domain: "InputError", code: 0)}// 填充输入数据for (index, token) in inputTokens.enumerated() {inputArray[index] = token as NSNumber}let modelInput = Llama3_8bInput(input_ids: inputArray)// 3. 推理(利用ANE加速)let output = try await model.prediction(input: modelInput)// 4. 解码let outputTokens = output.output_ids as! [Int32]return try tokenizer.decode(tokens: outputTokens)}
}// 3. 分词器实现(Swift)
class Tokenizer {private var vocab: [String: Int32] = [:]private var reverseVocab: [Int32: String] = [:]init(vocabURL: URL) throws {let content = try String(contentsOf: vocabURL, encoding: .utf8)let lines = content.components(separatedBy: .newlines)for line in lines {let parts = line.components(separatedBy: .whitespaces)if parts.count == 2, let id = Int32(parts[1]) {vocab[parts[0]] = idreverseVocab[id] = parts[0]}}}func encode(text: String) throws -> [Int32] {let words = text.components(separatedBy: .whitespaces)var tokens: [Int32] = [1] // 起始符for word in words {tokens.append(vocab[word] ?? 0)}tokens.append(2) // 结束符return tokens}func decode(tokens: [Int32]) throws -> String {var text = ""for token in tokens {guard token != 1, token != 2 else { continue }text += reverseVocab[token] ?? "[UNK]"text += " "}return text.trimmingCharacters(in: .whitespaces)}
}// 4. 调用示例(ViewController)
class CoreMLViewController: UIViewController {private var runner: CoreMLModelRunner!override func viewDidLoad() {super.viewDidLoad()do {runner = try CoreMLModelRunner()Task {let result = try await runner.generate(text: "介绍Core ML的优势")print("Core ML Result: \(result)")}} catch {print("Error: \(error)")}}
}
2. .onnx
格式(基于ONNX Runtime for iOS)
代码实现(Swift):
// 1. 依赖配置(Podfile)
// pod 'ONNXRuntime'import ONNXRuntimeclass ONNXiOSRunner {private let env: ORTEnvironmentprivate let session: ORTSessionprivate let tokenizer: Tokenizerinit(modelName: String) throws {env = try ORTEnvironment(loggingLevel: .error)// 加载模型(从bundle)guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "onnx") else {throw NSError(domain: "ModelError", code: 0)}let options = ORTSessionOptions()try options.setIntraOpNumThreads(4)try options.enableMetalAcceleration() // GPU加速session = try ORTSession(environment: env, modelPath: modelURL.path, options: options)// 初始化分词器guard let vocabURL = Bundle.main.url(forResource: "vocab", withExtension: "txt") else {throw NSError(domain: "TokenizerError", code: 0)}tokenizer = try Tokenizer(vocabURL: vocabURL)}func generate(text: String) throws -> String {let inputTokens = try tokenizer.encode(text: text)let inputShape: [NSNumber] = [1, NSNumber(value: inputTokens.count)]let inputTensor = try ORTTensor(data: inputTokens,shape: inputShape,elementType: .int64,environment: env)> 🌈 我是“没事学AI”!要是这篇文章让你学 AI 的路上有了点收获:
👁️ 【关注】跟我一起挖 AI 的各种门道,看看它还有多少新奇玩法等着咱们发现
👍 【点赞】为这些有用的 AI 知识鼓鼓掌,让更多人知道学 AI 也能这么轻松
🔖 【收藏】把这些 AI 小技巧存起来,啥时候想练手了,翻出来就能用
💬 【评论】说说你学 AI 时的想法和疑问,让大家的思路碰出更多火花
学 AI 的路还长,咱们结伴同行,在 AI 的世界里找到属于自己的乐趣和成就!