
在手机上本地运行大模型:React Native 端侧推理实战教程
在手机上本地运行大模型: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 模型列表页,用搜索框按模型规模筛选,名字里带 chat 或 instruct 的通常是对话微调版本。选模型时要同时看参数量和量化等级两个维度,具体怎么权衡见下一节。
操作步骤
第一步:选对模型和量化格式
端侧推理的第一道门槛是体积。按参数量粗分:
- 小模型(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_M:attention.wv、attention.wo、feed_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 startnpm 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.rnllama.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 数组表示,每条消息带 role 和 content。初始时放一条 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 start2. 另开终端,启动应用
npm run ios3. 在 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 模拟器。只能借助云端仿真服务,这会带来额外的网络依赖和成本。
- 模型许可和可用性会变。具体某个模型能否商用、在哪些地区可用、当前有哪些量化版本,以模型页面和官网的当前信息为准。
- 下载和加载都是耗时操作。需要在界面上给出明确的状态反馈,并防止用户重复触发。