🌟 大家好,我是“没事学AI”!
🤖 在AI的星辰大海里,我是那个执着的航海者,带着对智能的好奇不断探索。
📚 每一篇技术解析都是我打磨的罗盘,每一次模型实操都是我扬起的风帆。
💻 每一行代码演示都是我的航线记录,每一个案例拆解都是我的藏宝图绘制。
🚀 在人工智能的浪潮中,我既是领航员也是同行者。让我们一起,在AI学习的航程里,解锁更多AI的奥秘与可能——别忘了点赞、关注、收藏,跟上我的脚步,让“没事学AI”陪你从入门到精通!
一、端侧调用大模型支持的格式(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 ID
long[] inputTokens = tokenizer.encode(input);
// 构建输入Tensor
OrtSession.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;
@Override
protected 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();
}
@Override
protected 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();
// 输出Tensor
Tensor 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_8b
private let tokenizer: Tokenizer
init() 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]] = id
reverseVocab[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 ONNXRuntime
class ONNXiOSRunner {
private let env: ORTEnvironment
private let session: ORTSession
private let tokenizer: Tokenizer
init(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 的世界里找到属于自己的乐趣和成就!