AB
AiBoss
Tutorials

在手机上本地运行大模型:React Native 端侧推理实战教程

Tutorials

在手机上本地运行大模型:React Native 端侧推理实战教程

把大语言模型装进手机里跑,不联网、不上传数据,这件事在今天已经可行。这篇教程从选模型、配环境讲起,用 React Native 搭配 llama.rn 加载 GGUF 量化模型,一步步搭出一个能离线对话的移动应用,并说明量化格式怎么挑、设备性能怎么权衡。

大语言模型正在变小,也正在变聪明。参数量在 1B 到 2B 之间的指令微调模型,已经可以在普通手机上以可接受的速度完成对话生成。这意味着一种新的应用形态:模型文件下载到设备本地,推理全部在手机里完成,对话内容不出设备,断网也能用。这篇教程讲的就是怎么把这件事做成一个能跑起来的 React Native 应用——从挑选合适的 GGUF 量化模型,到配置开发环境,再到用 llama.rn 加载模型、管理对话状态,最后给出一个完整可运行的最小示例。

适合读这篇的人有三类:想把 AI 能力接进移动端应用的开发者;想用一套代码同时覆盖 Android 和 iOS 的 React Native 使用者;以及在意隐私、希望应用完全离线运行的开发者。读完并跟着做,你会得到一个能在模拟器或真机上跑起来的本地对话应用。

准备工作

开发环境

React Native 用 JavaScript 和 React 写移动应用,Android 与 iOS 共享大部分代码,开发与维护成本都比分别写两套原生应用低。开始之前需要准备这些:

  • Node.js:JavaScript 运行时,负责管理项目依赖和包。从 Node.js 官方渠道安装即可。
  • React Native 命令行工具:用于创建、构建和管理项目。安装命令是 npm i @react-native-community/cli
  • 虚拟设备:开发阶段需要一个模拟器或仿真器来跑应用。

虚拟设备的配置按操作系统分情况:

  • macOS 上做 iOS 开发:安装 Xcode,打开 Developer Tools,启动 Simulator。
  • macOS 上做 Android 开发:安装 Java Runtime 和 Android Studio,进入 Device Manager 创建模拟器。
  • Windows 或 Linux 上做 Android 开发:同样安装 Java Runtime 和 Android Studio,在 Device Manager 里创建模拟器。
  • Windows 或 Linux 上做 iOS 开发:本地没有官方模拟器,只能借助云端仿真服务,比如 LambdaTest 或 BrowserStack。

顺带说清一个容易混的概念:仿真器(emulator)同时模拟硬件和软件,模拟器(simulator)只模拟软件。Android 侧用的是仿真器,iOS 侧用的是模拟器。

模型文件从哪来

模型从 Hugging Face Hub 下载,格式选 GGUF。GGUF 是 llama.cpp 生态使用的模型文件格式,量化后的模型体积小、加载快,适合端侧运行。llama.rn 就是 llama.cpp 在 React Native 上的绑定,专门用来加载这类文件。

找模型的方式:在 Hugging Face 上进入 GGUF 模型列表页,用搜索框按模型规模筛选,名字里带 chatinstruct 的通常是对话微调版本。选模型时要同时看参数量和量化等级两个维度,具体怎么权衡见下一节。

操作步骤

第一步:选对模型和量化格式

端侧推理的第一道门槛是体积。按参数量粗分:

  • 小模型(1B–3B):适合绝大多数手机,延迟低,体验流畅。
  • 中等模型(4B–7B):在新款高端设备上表现不错,老机型上会明显变慢。
  • 大模型(8B 以上):对多数手机来说资源开销过大,除非量化到很低的精度,比如 Q2_K 或 Q4_K_M。

量化格式决定了同样的参数量下文件多大、精度损失多少。GGUF 生态里主要有三代量化方案:

类别代表格式特点
传统量化Q4_0、Q4_1、Q8_0每个块存量化值和一到两个缩放常数,实现简单、速度快,但效率不如新方案,现在用得少了
K 量化Q3_K_S、Q5_K_M 等混合量化,不同层分配不同位宽,精度更好
I 量化IQ2_XXS、IQ3_S 等仍是分块量化,但引入了受 QuIP 启发的改进,文件更小,部分硬件上速度偏慢

K 量化里的后缀含义值得记一下:_XS_S_M_L 表示不同的混合比例,字母越小压缩越狠。以 Q3 系列为例:

  • Q3_K_S:所有张量都用 Q3_K。
  • Q3_K_Mattention.wvattention.wofeed_forward.w2 这几个张量用 Q4_K,其余用 Q3_K。
  • Q3_K_L:上述几个张量用 Q5_K,其余用 Q5_K。

I 量化适合算力强但内存紧张的设备——文件更小,代价是某些硬件上推理更慢。

一个常见的选型误区是只看参数量。实际上 7B 模型配 Q2_K 量化,可能比 2B 模型配 Q8_0 跑得更好。如果设备放得下,优先考虑更大的模型配更低的量化,而不是小模型配高精度。

几个在手机上表现不错的候选:SmolLM2-1.7B-Instruct、Qwen2-0.5B-Instruct、Llama-3.2-1B-Instruct、DeepSeek-R1-Distill-Qwen-1.5B。具体哪个版本可用、许可如何,以模型页面当前信息为准。

第二步:创建项目

用命令行工具初始化项目:

npx @react-native-community/cli@latest init <ProjectName>

生成的项目结构大致如下:

  • android/:Android 原生工程文件,用于在 Android 设备上构建和运行。
  • ios/:iOS 原生工程文件,用于在 iOS 设备上构建和运行。
  • node_modules/:所有 npm 依赖。
  • App.tsx:应用根组件,用 TypeScript 编写,是 UI 和逻辑的入口。
  • index.js:注册根组件,是 React Native 运行时的入口,一般不需要改动。
  • tsconfig.json:TypeScript 配置。
  • babel.config.js:Babel 配置,负责把现代 JS/TS 代码转译成旧环境能跑的代码。
  • jest.config.js:测试配置。
  • metro.config.js:Metro 打包器配置。Metro 是专为 React Native 设计的 JavaScript 打包器,把代码和资源打成单个或多个文件供应用加载,支持增量构建、热重载,以及 .ios.js.android.js 这类平台特定文件。
  • .watchmanconfig:Watchman 文件监听服务配置,热重载依赖它。

第三步:跑通空壳应用

在写业务代码之前,先确认项目能跑起来。进入项目目录后执行:

npm install
npm start

npm start 启动 Metro 打包器。再开一个终端窗口,按目标平台启动应用:

# iOS
npm run ios

# Android
npm run android

如果做的是 iOS,进入 ios 目录还需要装一次 Pod 依赖:

cd ios
pod install

这一步能跑通,说明环境没问题,可以开始写代码了。

第四步:安装推理相关依赖

应用需要三样东西:从 Hugging Face Hub 下载模型、把模型存到设备文件系统、在本地加载并推理。对应的依赖是:

npm install axios react-native-fs llama.rn
  • llama.rn:llama.cpp 的 React Native 绑定,负责加载 GGUF 文件并执行推理。
  • react-native-fs:在 React Native 里操作设备文件系统,用来保存下载好的模型。
  • axios:向 Hugging Face Hub 的 API 发请求,获取可下载的模型文件列表。

第五步:规划应用状态

先把 App.tsx 清空,搭一个最小骨架:

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

function App(): React.JSX.Element {
  return (
    <View>
      <Text>Hello World</Text>
    </View>
  );
}

const styles = StyleSheet.create({});

export default App;

注意这里用的是 View,文字可能显示不正常,后面换成 SafeAreaView 就能正常渲染。

接下来想清楚应用要跟踪哪些状态。可以分成两组:

对话相关

  • 对话历史,即用户和助手之间的消息列表
  • 当前用户输入

模型相关

  • 当前选中的模型格式,比如 Llama 1B 或 Qwen 1.5B
  • 该格式下可用的 GGUF 文件列表
  • 选中要下载的 GGUF 文件
  • 下载进度
  • 已加载模型的上下文对象
  • 是否正在下载的布尔标志
  • 是否正在生成回复的布尔标志

useState 实现这些状态:

import { useState } from 'react';

type Message = {
  role: 'system' | 'user' | 'assistant';
  content: string;
};

const INITIAL_CONVERSATION: Message[] = [
  {
    role: 'system',
    content: 'This is a conversation between user and assistant, a friendly chatbot.',
  },
];

const [conversation, setConversation] = useState<Message[]>(INITIAL_CONVERSATION);
const [selectedModelFormat, setSelectedModelFormat] = useState<string>('');
const [selectedGGUF, setSelectedGGUF] = useState<string | null>(null);
const [availableGGUFs, setAvailableGGUFs] = useState<string[]>([]);
const [userInput, setUserInput] = useState<string>('');
const [progress, setProgress] = useState<number>(0);
const [context, setContext] = useState<any>(null);
const [isDownloading, setIsDownloading] = useState<boolean>(false);
const [isGenerating, setIsGenerating] = useState<boolean>(false);

对话历史用一个 Message 数组表示,每条消息带 rolecontent。初始时放一条 system 消息,用来设定助手的角色。模型相关的状态里,context 保存 llama.rn 加载模型后返回的上下文句柄,后续所有推理调用都要用到它。

第六步:下载模型并加载

模型下载走 Hugging Face Hub 的 API:用 axios 请求某个模型仓库的文件列表,筛出 GGUF 文件,让用户选择,然后用 react-native-fs 把文件写到设备存储里。下载过程中通过回调更新 progress,让界面能显示进度条。

下载完成后,用 llama.rn 加载本地文件路径,拿到 context。加载是异步的,期间把 isDownloading 或类似的标志置位,避免用户重复触发。

第七步:组织对话与生成

生成回复时,把 conversation 数组按模型要求的格式拼成提示词,交给 context 执行推理。生成过程中把 isGenerating 置为 true,禁用发送按钮。生成结束后,把助手的回复追加到 conversation 里,触发界面刷新。

对话历史会随轮次增长,而模型的上下文窗口是有限的。实际使用中需要在历史过长时做截断或摘要,否则推理会失败或变慢。

一个完整示例

下面把前面的步骤串成一个最小可运行流程。假设项目已经初始化完成,依赖也已安装。

1. 启动开发环境

npm install
npm start

2. 另开终端,启动应用

npm run ios

3. 在 App.tsx 中定义状态

import React, { useState } from 'react';
import { SafeAreaView, Text, TextInput, Button, FlatList } from 'react-native';

type Message = {
  role: 'system' | 'user' | 'assistant';
  content: string;
};

const INITIAL_CONVERSATION: Message[] = [
  { role: 'system', content: 'You are a helpful assistant.' },
];

function App(): React.JSX.Element {
  const [conversation, setConversation] = useState<Message[]>(INITIAL_CONVERSATION);
  const [userInput, setUserInput] = useState<string>('');
  const [context, setContext] = useState<any>(null);
  const [isGenerating, setIsGenerating] = useState<boolean>(false);

  // 其余逻辑:下载模型、加载 context、发送消息

  return (
    <SafeAreaView>
      <FlatList
        data={conversation}
        keyExtractor={(_, i) => String(i)}
        renderItem={({ item }) => <Text>{item.role}: {item.content}</Text>}
      />
      <TextInput value={userInput} />
      <Button title="Send" => { /* 触发推理 */ }} />
    </SafeAreaView>
  );
}

export default App;

4. 下载模型

用 axios 拉取目标模型仓库的文件列表,筛出 GGUF 文件,展示给用户选择。选定后用 react-native-fs 下载到应用可访问的目录,下载过程中更新进度状态。

5. 加载模型

下载完成后,把本地文件路径传给 llama.rn 的加载接口,得到 context,存入状态。这一步是异步的,加载大文件需要几秒到几十秒不等,取决于设备。

6. 发送消息并生成

用户点击发送后,把输入追加到 conversation,把 isGenerating 置为 true,调用 context 的推理接口。推理结果返回后追加到对话历史,把 isGenerating 复位。

整个流程跑通后,你就有了一个完全离线的本地对话应用:模型文件在设备上,推理在设备上,对话内容不离开设备。

注意事项

  • 模型体积与设备性能强相关。1B–3B 的模型在多数手机上体验良好;4B–7B 需要较新的高端设备;8B 以上对多数手机来说负担过重,除非量化到 Q2_K 这类低精度格式。
  • 量化等级不是越高越好。7B 配 Q2_K 可能优于 2B 配 Q8_0。设备放得下的话,优先选更大的模型配更低的量化。
  • I 量化在部分硬件上更慢。它文件更小,适合算力强、内存紧的设备,但不是所有平台都能受益。
  • 上下文窗口有限。对话历史会持续增长,超过模型窗口后需要截断或摘要,否则推理会失败或明显变慢。
  • Windows 和 Linux 上无法本地跑 iOS 模拟器。只能借助云端仿真服务,这会带来额外的网络依赖和成本。
  • 模型许可和可用性会变。具体某个模型能否商用、在哪些地区可用、当前有哪些量化版本,以模型页面和官网的当前信息为准。
  • 下载和加载都是耗时操作。需要在界面上给出明确的状态反馈,并防止用户重复触发。