
IA en el dispositivo en React Native con llama.rn
Como ejecutar modelos de lenguaje directamente en el telefono del usuario en una app React Native. Configuracion, carga de modelos, generacion con streaming y consideraciones multiplataforma usando llama.rn.
llama.rn es una libreria React Native que proporciona bindings JavaScript a llama.cpp. Ejecuta modelos de lenguaje GGUF nativamente en iOS (Metal) y Android (CPU/Vulkan) a traves de la misma API JavaScript.
Para desarrolladores React Native, esto significa IA en el dispositivo con un solo codebase, una API y cero dependencia de la nube.
Instalacion
npm install llama.rn
# o
yarn add llama.rn
Para iOS, ejecuta pod install:
cd ios && pod install
Para Android, la libreria nativa se incluye automaticamente via autolinking.
Expo
Si usas Expo, necesitas un development build (no Expo Go) ya que llama.rn incluye codigo nativo:
npx expo prebuild
npx expo run:ios # o run:android
Cargando un modelo
import { initLlama, LlamaContext } from "llama.rn";
let context: LlamaContext | null = null;
async function loadModel(modelPath: string) {
context = await initLlama({
model: modelPath,
n_ctx: 2048, // Ventana de contexto
n_threads: 4, // Hilos CPU
n_gpu_layers: 99, // Descargar a GPU (Metal/Vulkan)
use_mlock: true, // Bloquear modelo en memoria
});
console.log("Modelo cargado exitosamente");
}
Ruta del modelo
La ruta del modelo debe apuntar a un archivo local en el sistema de archivos del dispositivo. Como llega el archivo ahi depende de tu estrategia de entrega:
Incluido en la app:
// iOS: Copiar al bundle de la app, referenciar via RNFS
import RNFS from "react-native-fs";
const modelPath = `${RNFS.MainBundlePath}/model.gguf`;
// Android: Copiar desde assets al directorio de archivos en primer lanzamiento
const modelPath = `${RNFS.DocumentDirectoryPath}/model.gguf`;
Descargado post-instalacion:
import RNFS from "react-native-fs";
const modelUrl = "https://cdn.example.com/model.gguf";
const modelPath = `${RNFS.DocumentDirectoryPath}/model.gguf`;
const download = RNFS.downloadFile({
fromUrl: modelUrl,
toFile: modelPath,
progress: (res) => {
const percentage = (res.bytesWritten / res.contentLength) * 100;
setDownloadProgress(percentage);
},
});
await download.promise;
Generando texto
Generacion simple
async function generate(prompt: string): Promise<string> {
if (!context) throw new Error("Modelo no cargado");
const result = await context.completion({
prompt: prompt,
n_predict: 256,
temperature: 0.7,
top_p: 0.9,
stop: ["</s>", "<|eot_id|>"], // Tokens de parada
});
return result.text;
}
Generacion con streaming
async function generateStream(
prompt: string,
onToken: (token: string) => void
): Promise<string> {
if (!context) throw new Error("Modelo no cargado");
const result = await context.completion(
{
prompt: prompt,
n_predict: 256,
temperature: 0.7,
stop: ["</s>", "<|eot_id|>"],
},
(data) => {
// Se llama por cada token generado
onToken(data.token);
}
);
return result.text;
}
Completacion de chat
Para conversaciones multi-turno, formatea el prompt usando la plantilla de chat del modelo:
interface Message {
role: "system" | "user" | "assistant";
content: string;
}
function formatChat(messages: Message[]): string {
// Plantilla de chat Llama 3.2
let prompt = "<|begin_of_text|>";
for (const msg of messages) {
prompt += `<|start_header_id|>${msg.role}<|end_header_id|>\n\n${msg.content}<|eot_id|>`;
}
prompt += "<|start_header_id|>assistant<|end_header_id|>\n\n";
return prompt;
}
async function chat(messages: Message[], onToken: (token: string) => void) {
const prompt = formatChat(messages);
return generateStream(prompt, onToken);
}
Patron de React Hook
import { useState, useCallback, useRef } from "react";
import { initLlama, LlamaContext } from "llama.rn";
export function useLlama(modelPath: string) {
const contextRef = useRef<LlamaContext | null>(null);
const [isLoaded, setIsLoaded] = useState(false);
const [isGenerating, setIsGenerating] = useState(false);
const [response, setResponse] = useState("");
const load = useCallback(async () => {
contextRef.current = await initLlama({
model: modelPath,
n_ctx: 2048,
n_threads: 4,
n_gpu_layers: 99,
});
setIsLoaded(true);
}, [modelPath]);
const generate = useCallback(async (prompt: string) => {
if (!contextRef.current) return;
setIsGenerating(true);
setResponse("");
await contextRef.current.completion(
{
prompt,
n_predict: 256,
temperature: 0.7,
stop: ["</s>", "<|eot_id|>"],
},
(data) => {
setResponse((prev) => prev + data.token);
}
);
setIsGenerating(false);
}, []);
const unload = useCallback(() => {
contextRef.current?.release();
contextRef.current = null;
setIsLoaded(false);
}, []);
return { load, generate, unload, isLoaded, isGenerating, response };
}
Uso en un componente
function AiChat() {
const { load, generate, unload, isLoaded, isGenerating, response } =
useLlama(modelPath);
const [input, setInput] = useState("");
useEffect(() => {
load();
return () => unload();
}, []);
return (
<View style={styles.container}>
<ScrollView style={styles.responseArea}>
<Text>{response}</Text>
</ScrollView>
<View style={styles.inputRow}>
<TextInput
value={input}
onChangeText={setInput}
style={styles.input}
editable={!isGenerating}
/>
<Button
title="Enviar"
onPress={() => {
generate(input);
setInput("");
}}
disabled={!isLoaded || isGenerating}
/>
</View>
</View>
);
}
Consideraciones multiplataforma
Paridad de rendimiento
llama.rn ejecuta codigo nativo en ambas plataformas. El puente JavaScript solo se usa para pasar prompts y recibir tokens. El rendimiento real de inferencia es identico a la integracion nativa con Swift/Kotlin:
| Dispositivo | Modelo 1B (tok/s) | Modelo 3B (tok/s) |
|---|---|---|
| iPhone 15 Pro | 35-45 | 18-25 |
| iPhone 14 | 25-32 | 14-18 |
| Galaxy S24 | 35-45 | 18-25 |
| Android gama media | 18-25 | 8-12 |
El puente JS agrega menos de 1ms por token de overhead. Despreciable.
Diferencias en rutas de archivos del modelo
iOS y Android almacenan archivos en diferentes ubicaciones. Usa react-native-fs para obtener rutas apropiadas por plataforma:
import RNFS from "react-native-fs";
import { Platform } from "react-native";
const modelDir = Platform.OS === "ios"
? RNFS.DocumentDirectoryPath
: RNFS.DocumentDirectoryPath; // Misma API, ruta subyacente diferente
Gestion de memoria
React Native no expone APIs directas de memoria. Usa verificaciones especificas de plataforma via un modulo nativo si necesitas verificar RAM disponible antes de cargar. Alternativamente, captura fallos de carga y muestra un mensaje apropiado.
Entrega de modelos en React Native
Estrategia 1: Bundle para modelos pequenos
Para modelos 1B (~600MB), incluir en el bundle de la app es factible:
- iOS: Agregar al proyecto Xcode como recurso
- Android: Usar Android Asset Delivery para archivos mayores de 150MB
Estrategia 2: Descarga para todos los modelos
Para modelos 3B (~1.7GB) o para mantener la descarga inicial pequena:
async function ensureModelReady(): Promise<string> {
const modelPath = `${RNFS.DocumentDirectoryPath}/model.gguf`;
const exists = await RNFS.exists(modelPath);
if (exists) return modelPath;
// Descargar con progreso
await RNFS.downloadFile({
fromUrl: MODEL_CDN_URL,
toFile: modelPath,
progress: (res) => {
updateProgress(res.bytesWritten / res.contentLength);
},
}).promise;
// Verificar integridad
const hash = await RNFS.hash(modelPath, "sha256");
if (hash !== EXPECTED_HASH) {
await RNFS.unlink(modelPath);
throw new Error("Descarga del modelo corrupta");
}
return modelPath;
}
Mejores practicas para produccion
- Carga el modelo de forma perezosa. Solo carga cuando el usuario accede a la funcion de IA.
- Descarga al perder foco. Libera memoria del modelo cuando la pantalla de IA no tiene foco.
- Maneja errores correctamente. La carga del modelo puede fallar en dispositivos con poca memoria. Muestra un mensaje claro.
- Verifica las descargas. Verificacion de hash SHA256 despues de la descarga. Los modelos corruptos causan crashes.
- Agrupa tokens. Agrupa 2-3 tokens antes de actualizar la UI para una visualizacion de texto mas fluida.
- Soporte de cancelacion. Permite que los usuarios detengan la generacion a mitad de camino.
La calidad del modelo depende del fine-tuning. Un modelo base da respuestas genericas. Un modelo fine-tuned con tus datos de dominio (via Ertas o plataformas similares) da respuestas adaptadas al caso de uso especifico de tu app, ejecutandose a la misma velocidad en el mismo hardware.
Ship AI that runs on your users' devices.
Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.
Keep reading

AI in React Native: From Cloud APIs to On-Device Models
How to add AI features to React Native apps. Cloud API integration with fetch, on-device inference with llama.cpp bindings, and a practical migration path from one to the other.

AI in iOS Apps: CoreML, Cloud APIs, and On-Device LLMs Compared
Three paths to AI in your iOS app. CoreML for Apple's ecosystem, cloud APIs for capability, and on-device LLMs via llama.cpp for cost and privacy. A practical comparison for Swift developers.

AI in Android Apps: ML Kit, Cloud APIs, and On-Device LLMs Compared
Three paths to AI in your Android app. Google ML Kit for common tasks, cloud APIs for full LLM capability, and on-device models via llama.cpp for cost and privacy. A practical comparison for Kotlin developers.