Back to blog
    IA en el dispositivo en React Native con llama.rn
    React Nativellama.cppon-device AIcross-platformllama.rnsegment:mobile-builder

    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.

    EErtas Team·

    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:

    DispositivoModelo 1B (tok/s)Modelo 3B (tok/s)
    iPhone 15 Pro35-4518-25
    iPhone 1425-3214-18
    Galaxy S2435-4518-25
    Android gama media18-258-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

    1. Carga el modelo de forma perezosa. Solo carga cuando el usuario accede a la funcion de IA.
    2. Descarga al perder foco. Libera memoria del modelo cuando la pantalla de IA no tiene foco.
    3. Maneja errores correctamente. La carga del modelo puede fallar en dispositivos con poca memoria. Muestra un mensaje claro.
    4. Verifica las descargas. Verificacion de hash SHA256 despues de la descarga. Los modelos corruptos causan crashes.
    5. Agrupa tokens. Agrupa 2-3 tokens antes de actualizar la UI para una visualizacion de texto mas fluida.
    6. 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