
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.
Free plan with 30 credits/mo, no card required. Paid plans from $25/mo USD.
Keep reading

IA en React Native: De APIs en la nube a modelos en el dispositivo
Como agregar funciones de IA a apps React Native. Integracion de API en la nube con fetch, inferencia en el dispositivo con bindings de llama.cpp, y una ruta de migracion practica de uno al otro.

IA en apps Android: ML Kit, APIs en la nube y LLMs en el dispositivo comparados
Tres caminos para agregar IA a tu app Android. Google ML Kit para tareas comunes, APIs en la nube para capacidad completa de LLM y modelos en el dispositivo via llama.cpp para costo y privacidad. Una comparacion practica para desarrolladores Kotlin.

IA en apps Flutter: APIs en la nube, TFLite y LLMs en el dispositivo
Tres caminos para IA en Flutter. APIs en la nube via el paquete http, TensorFlow Lite para tareas clasicas de ML y LLMs en el dispositivo via llama.cpp para generacion de texto. Una comparacion practica para desarrolladores Dart.