ManzGPU

Guía de Laya.cpp

Hype-Generator 3000™: It's a game-changer

Aprende a utilizar Laya.cpp, mediante un modelo Laya multilingüe y hacer inferencia de decisiones tipadas (noul, choice, score) con un servidor compatible con Jev.

1. Introducción

Hasta ahora hemos visto motores de inferencia para generar texto como llama.cpp o imágenes como stable-diffusion.cpp. Laya.cpp es diferente: es un motor para clasificar y decidir, no para escribir texto.

Laya es un modelo de pensamiento rápido para clasificar decisiones tipadas, y es una excelente alternativa open source a TypeSafe Jev que podemos utilizar en local. La forma de hacer funcionar estos modelos es que, en lugar de generar un texto que tengamos que parsear, le enviamos un estado (email, ticket, JSON, texto plano...) y un conjunto de preguntas tipadas, y el clasificador nos devuelve probabilidades calibradas en una sola pasada:

Estado laya.cpp Probabilidades

¿Cuándo interesa? En el caso de Jev, se trata de una API de pago (muy barata, pero cerrada), que no puedes utilizar en local. Por otro lado, Laya tiene licencia Apache 2.0, es gratuita y se puede utilizar en local. Además, es mucho más rápida.

En cualquier proceso donde necesites una decisión automática y rápida sin gastar tokens de un LLM, este tipo de clasificadores puede ser una buena opción:

  • 🎫 Triage de tickets: ¿departamento? ¿urgencia? ¿pide reembolso?
  • 🛡️ Guardrails: ¿está intentando romper el sistema? ¿tiene datos sensibles?
  • 📧 Filtrado: ¿es spam? ¿es phishing? ¿es legítimo?
  • 📊 Scoring: puntuar contenido en una rúbrica (0, 1, 2...)

2. Requisitos

A diferencia de un modelo LLM de varios GB, los modelos Laya son pequeños (322-421M parámetros), ni siquiera llega a 1GB, así que los requisitos son muy modestos:

DetalleMínimoRecomendado
🧠 RAM 4 GB 8 GB
🎮 GPU No hace falta GPU NVIDIA (CUDA) o Vulkan
💾 Disco 2 GB 5 GB

3. Compilación

En el momento de escribir esta guía, laya.cpp no tiene releases precompilados, por lo tanto tenemos que compilarlo para nuestro sistema de forma obligatoria. El primer paso es instalar las dependencias que necesitaremos y clonar con los submódulos (obligatorio, incluye ggml):

sudo apt update
sudo apt install git build-essential cmake \
                 ninja-build libicu-dev nlohmann-json3-dev
git clone --recurse-submodules https://github.com/lkarlslund/laya.cpp.git
cd laya.cpp
git submodule update --init --recursive

Elegir backend

Recuerda que lo ideal es elegir un backend gráfico dependiendo de nuestra GPU. En general, si tenemos Nvidia, elegir CUDA, y sino, elegir Vulkan. La opción de CPU/RAM sólo si no tenemos GPU o último remedio, ya que es la que tiene peor rendimiento:

BackendRendimientoNvidiaAMDObservaciones
CUDA (Nvidia) 🟩🟩🟩🟩🟩 ✅ ❌ Máximo rendimiento en NVIDIA.
Vulkan 🟩🟩🟩🟩⬛ ✅ ✅ Multiplataforma (NVIDIA/AMD/Intel).
CPU/RAM 🟨🟨⬛⬛⬛ — — Sin GPU. Funciona en cualquier PC.

Instala las dependencias y el CUDA Toolkit (12.x o 13.x).

Compila con CUDA. Ajusta CMAKE_CUDA_ARCHITECTURES a tu GPU (86=RTX 30, 89=RTX 40, 120=Blackwell; consulta la tabla de arquitecturas si no lo sabes):

cmake -S . -B build-cuda -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_CUDA_ARCHITECTURES=89
cmake --build build-cuda --parallel $(nproc)

Una vez terminado, ejecuta el siguiente comando. Si te muestra información de ayuda, todo ha ido correctamente:

./build-cuda/bin/laya-cli --help

Vulkan es la opción recomendada si tienes AMD. Si tienes una GPU Nvidia, suele ser mejor utilizar CUDA. Instala las dependencias específicas de Vulkan:

sudo apt update
sudo apt install -y libvulkan-dev glslc spirv-headers

Luego, compila orientado al backend Vulkan:

cmake -S . -B build-vulkan -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DLAYA_CUDA=OFF \
  -DLAYA_VULKAN=ON
cmake --build build-vulkan --parallel $(nproc)

Al ejecutar, recuerda pasar siempre el parámetro --vulkan:

./build-vulkan/bin/laya-cli --vulkan --help

Si te decides por utilizar CPU como backend gráfico:

cmake -S . -B build-cpu -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DLAYA_CUDA=OFF \
  -DLAYA_VULKAN=OFF
cmake --build build-cpu --parallel $(nproc)

Una vez terminado, puedes comprobar ejecutar con el parámetro --cpu:

./build-cpu/bin/laya-cli --cpu --help

Con Laya, los modelos son pequeños (< 1GB), así que en CPU funciona aceptablemente para uso puntual, aunque la GPU es mucho más rápida.

4. Modelos

Laya tiene tres checkpoints oficiales. Para trabajos en español o inglés (o mezcla de ambos), el que necesitas es multilingual:

VarianteModeloParamsContextoPara qué
english ModernBERT-large 421M 512 Solo inglés.
multilingual mmBERT-base 322M 1024 100+ idiomas (español incluido). ~2x más rápido. ✅
typed-decisions ModernBERT-large 421M 1024 Workflows fine-tuneados (triage, facturas...).

Los descargamos con el script oficial (necesita Python y huggingface_hub):

uv pip install huggingface_hub
uv run python scripts/download_model.py --variant multilingual

Si quieres los tres:

python scripts/download_model.py --variant all

Se creará la estructura models/laya/ dentro del repositorio:

models/laya/
├── model.safetensors      ← english (raíz)
├── multilingual/
│   ├── model.safetensors
│   ├── encoder/
│   └── tokenizer/
└── typed-decisions/
    └── ...

Cada variante ocupa aproximadamente 350-800MB en safetensors. Son modelos ligeros: caben sobradamente en cualquier GPU moderna y hasta en RAM de un portátil modesto.

5. Uso básico

Antes de empezar, vamos a asegurarnos de tener instaladas algunas dependencias:

sudo apt install curl jq

La forma más rápida de probar que todo funciona es levantar un servidor y pasarle un JSON para comprobar las salidas:

./build-cuda/bin/laya-cli \
  --model models/laya --variant multilingual \
  --server --host 0.0.0.0 --port 8080 \
  --tensor-core-fp32 --flash-fp32

Si todo va bien, nos saldrá algo similar a lo siguiente:

Ready: CUDA0 (NVIDIA GeForce RTX 5060 Ti)
Listening: http://0.0.0.0:8080/v1/systemone

Esto significa que en nuestro localhost, en el puerto 8080 tenemos un endpoint esperando recibir contenido JSON vía POST para procesarlo y devolvernos la salida. Este es un endpoint compatible con la API de JEV.

Creamos un fichero input.json:

{
  "state": "Cobraste 2 veces por el mismo importe, devuélveme el dinero.",
  "questions": {
    "reembolso": {
      "type": "noul",
      "instructions": "¿El cliente pide un reembolso?"
    }
  }
}

Ahora, vamos a simular una petición JSON mediante curl:

curl -s --fail-with-body http://localhost:8080/v1/systemone \
  -H 'Content-Type: application/json' \
  -d @input.json | jq .

La respuesta debería ser algo parecido a esto:

{
  "model": "laya-multilingual",
  "answers": {
    "reembolso": {
      "type": "noul",
      "confidence": 0.9994,
      "action": {
        "act_probability": 1.0
      },
      "noul": 0.9994
    }
  },
  "usage": {
    "input_tokens": 51,
    "output_tokens": 0
  }
}

Un noul de 0.99 significa 99% de probabilidad de que sea cierto. Las probabilidades están calibradas: puedes confiar en ese número para tomar decisiones con umbral. Por ejemplo, si >= 0.85 la decisión es automática, si no, la decisión debe ser revisada por un humano (no puedes confiar en ella).

6. Modalidades

Laya soporta 3 tipos de pregunta (typed questions), y puedes mezclarlas en la misma petición:

TipoSalidaCaso de uso
noul Probabilidad de 0.0 a 1.0 Sí/No calibrado: spam, phishing, booleanos...
choice Etiqueta elegida + distribución + confianza Clasificación: ¿departamento?, ¿categoría?
score Nivel esperado + distribución Puntuación: urgencia 0..2, gravedad...

1) noul

El que vimos en el ejemplo anterior. Pregunta booleana: Devuelve la probabilidad de que sea verdadero:

{
  "model": "laya-latest",
  "state": "Estimado cliente, su factura nº 4451 de 120€ está pendiente de pago desde el día 3.",
  "questions": {
    "factura": {
      "type": "noul",
      "instructions": "¿El texto menciona una factura pendiente de pago?"
    },
    "urgente": {
      "type": "noul",
      "instructions": "¿Hay alguna urgencia o plazo crítico mencionado?"
    }
  }
}

Observa que hay dos preguntas con nombre, por lo que habrán dos respuestas:

{
  "factura":  { "noul": 0.97, "confidence": 0.98 },
  "urgente":  { "noul": 0.12, "confidence": 0.85 }
}

2) choice

Clasificación multi-opción. Definimos las opciones posibles en criteria:

{
  "model": "laya-latest",
  "state": "No he conseguido iniciar sesión, me dice contraseña incorrecta desde ayer.",
  "questions": {
    "departamento": {
      "type": "choice",
      "instructions": "¿Qué departamento debe atender esta consulta?",
      "criteria": {
        "billing": "pagos, facturas, reembolsos",
        "technical": "bugs, errores, caídas, acceso",
        "sales": "precios, contratos, nuevos clientes",
        "otro": "todo lo demás"
      }
    }
  }
}

En este caso, la respuesta sería algo similar a lo siguiente:

{
  "departamento": {
    "choice": "technical",
    "probs": {
      "billing": 0.04,
      "technical": 0.89,
      "sales": 0.03,
      "otro": 0.04
    },
    "confidence": 0.89
  }
}

Lo ideal es mantener choice por debajo de ~20 opciones para buenos resultados. Si necesitas más, considera utilizar una jerarquía de dos preguntas.

3) score

Los niveles son ordinales (0, 1, 2...). Cada entrada de criteria describe ese nivel:

{
  "model": "laya-latest",
  "state": "Llevo 3 llamadas sin darme una solución y nadie me ha devuelto la llamada. Esto es inaceptable.",
  "questions": {
    "urgencia": {
      "type": "score",
      "instructions": "¿Qué nivel de urgencia tiene esta solicitud?",
      "criteria": [
        "sin prisa, consulta general",
        "pronta respuesta recomendada",
        "crítico, bloquea el negocio"
      ]
    }
  }
}
{
  "urgencia": {
    "score": 1.87,
    "probs": {
      "0": 0.05,
      "1": 0.18,
      "2": 0.77
    },
    "confidence": 0.77
  }
}

Un score de 1.87 es la esperanza matemática en los niveles 0..2 (cuanto más cerca de 2, más crítico).

Ten en cuenta que puedes hacer múltiples peticiones diferentes combinadas. Por ejemplo, en el campo questions, puedes añadir varios campos que tengan peticiones noul, choice y score. El state puede ser tanto un string, como un objeto o un array.