移动端调用大模型详解

发布于:2025-08-13 ⋅ 阅读:(17) ⋅ 点赞:(0)

🌟 大家好,我是“没事学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)

步骤

  1. 模型转换(PC端):用optimum将Hugging Face模型转为ONNX并量化(如Llama 3-8B→INT4)。
  2. 安卓集成:通过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)

步骤

  1. 模型转换(PC端):用coremltools将Hugging Face模型转为.mlmodel(如Llama 3-8B→INT8)。
  2. 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 的世界里找到属于自己的乐趣和成就!