La guía definitiva para desplegar Qwen3 sobre NPU en Orange Pi 5 Pro/Max/Plus/Ultra con RKLLama y MicroK8s
6 de mayo de 2026
Introducción
¿Qué se pretende conseguir?
Esta guía muestra el proceso completo para ejecutar modelos de lenguaje (LLM) en local sobre la NPU (Neural Processing Unit) de la Orange Pi 5 Plus (SoC Rockchip RK3588(S)), desde la conversión del modelo en un PC x86/64 hasta su despliegue en la SBC como servicio API mediante RKLLama orquestado con Kubernetes (MicroK8s).
Esta guía está desglosada en tres partes:
- Conversión del modelo: Veremos cómo convertir un modelo LLM de Hugging Face, como Qwen3-8B, al formato
.rkllmoptimizado para la NPU, mediante cuantización W8A8 o W4A16. - Inferencia nativa: Exploraremos cómo ejecutar el modelo directamente en la Orange Pi con el binario
llm_demodel SDK de Rockchip para validar que funciona. - Despliegue como servicio: Por último, levantaremos un servidor RKLLama en un Deployment de Kubernetes, exponiendo una API REST compatible con Ollama y OpenAI.

Beneficios
- Privacidad total: Los datos nunca salen de nuestra infraestructura. No dependemos de APIs externas (OpenAI, Anthropic, etc.) para tareas de inferencia con LLMs.
- Coste cero en inferencia: Una vez adquirido el hardware (~100-200 €), no hay costes recurrentes por tokens ni suscripciones a servicios cloud.
- Baja latencia en red local: El servicio corre en la misma red, eliminando la latencia de ida y vuelta a servidores externos.
- Aprovechamiento del hardware: La NPU del RK3588(S) (6 TOPS) queda infrautilizada sin este tipo de carga de trabajo; esta guía le da un propósito real.
- API estándar: RKLLama expone endpoints compatibles con Ollama (
/api/chat,/api/generate) y OpenAI (/v1/chat/completions), lo que permite integrarlo directamente con herramientas como Open WebUI, LangChain, Continue.dev u otros clientes sin modificación. - Resiliencia: Al correr en Kubernetes, el servicio se reinicia automáticamente ante fallos y es fácil de actualizar o escalar.
Problemas que soluciona
| Problema | Cómo lo resuelve esta guía |
|---|---|
| Dependencia de APIs externas de pago para LLMs | Modelo ejecutándose 100% en local sobre la NPU, sin costes por uso |
| Falta de privacidad al enviar datos a terceros | Todo el procesamiento ocurre dentro de nuestra red; los datos no salen del dispositivo |
| Proceso de conversión de modelos poco documentado y propenso a errores | Pasos verificados con soluciones a errores comunes (Git LFS, CUDA, cuantización) |
| Ejecución manual del servidor LLM sin gestión de ciclo de vida | Deployment en Kubernetes con restart automático, health checks y persistencia de modelos |
| Incompatibilidad con herramientas estándar del ecosistema LLM | RKLLama implementa las APIs de Ollama y OpenAI, permitiendo usar clientes existentes sin esfuerzo |
| Dificultad para mantener y actualizar el servicio | Un solo kubectl rollout restart actualiza la imagen; un kubectl delete -f limpia todo |
Glosario de conceptos clave
Antes de empezar, conviene tener claros algunos términos que aparecen a lo largo de esta guía:
LLM (Large Language Model)
Modelo de lenguaje de gran tamaño entrenado con grandes volúmenes de texto. Ejemplos: GPT-4, Llama 3, Qwen3. Estos modelos pueden generar texto, responder preguntas, resumir documentos, etc. En su forma original ocupan decenas de GB y requieren hardware potente para ejecutarse.
Inferencia
Es el proceso de usar un modelo ya entrenado para generar respuestas a partir de una entrada (prompt). No se modifica el modelo; simplemente se le pasa un texto y devuelve una predicción. Toda la ejecución en la Orange Pi es inferencia — el entrenamiento se hizo previamente por el equipo que creó el modelo (Alibaba para Qwen3, Meta para Llama, etc.).
NPU (Neural Processing Unit)
Procesador especializado en operaciones de redes neuronales (multiplicaciones de matrices, convoluciones, etc.). A diferencia de la CPU (propósito general) o la GPU (paralelismo gráfico/cómputo), la NPU está diseñada específicamente para inferencia de modelos de IA con bajo consumo energético. El RK3588(S) integra una NPU de 6 TOPS.
TOPS (Tera Operations Per Second)
Unidad que mide la capacidad de cómputo de un procesador de IA: billones (10¹²) de operaciones por segundo. Los 6 TOPS del RK3588 significan que la NPU puede realizar 6 billones de operaciones enteras (INT8) por segundo. Como referencia, algunos ejemplos:
| Procesador | TOPS | Uso típico |
|---|---|---|
| RK3588(S) NPU | 6 | SBCs, edge AI |
| Apple M2 Neural Engine | 15.8 | Portátiles |
| NVIDIA Jetson Orin Nano | 40 | Robótica, edge |
| NVIDIA RTX 4090 (INT8) | ~660 | Datacenter/escritorio |
Cuantización
Técnica para reducir el tamaño y los requisitos de cómputo de un modelo, convirtiendo sus pesos (y opcionalmente activaciones) de punto flotante de alta precisión (FP32/FP16) a formatos de menor precisión (INT8, INT4). Esto permite ejecutar modelos que de otro modo no cabrían en la memoria del dispositivo, a costa de una ligera pérdida de calidad en las respuestas.
Tipos de cuantización soportados por RKLLM
| Tipo | Significado | Pesos | Activaciones | Tamaño aprox. | Calidad | Uso recomendado |
|---|---|---|---|---|---|---|
| W8A8 | Weight 8-bit, Activation 8-bit | INT8 | INT8 | ~1× del original en INT8 | Mayor calidad | Recomendado cuando hay RAM suficiente (≥12 GB para modelos de 8B) |
| W4A16 | Weight 4-bit, Activation 16-bit | INT4 | FP16 | ~50% del W8A8 | Menor calidad, puede perder coherencia | Cuando la RAM es limitada o se necesitan modelos más grandes |
Ejemplo práctico: El modelo Qwen3-8B (originalmente ~16 GB en FP16) queda en ~8.3 GB con W8A8 y ~4.5 GB con W4A16.
Algoritmos de cuantización
normal: Algoritmo estándar, usado con W8A8. Cuantización simétrica por canal.grq(Group-wise Refined Quantization): Usado con W4A16. Divide los pesos en grupos y aplica factores de escala por grupo para minimizar la pérdida de precisión.
Formato RKLLM
Formato binario propietario de Rockchip que contiene el modelo cuantizado y optimizado para ejecutarse sobre la NPU del RK3588/RK3576. No es compatible con otros frameworks (ONNX, GGUF, etc.); se genera mediante el rkllm-toolkit a partir de modelos de Hugging Face.
Tokens
Unidad mínima de texto que procesa un LLM. No es exactamente una palabra: puede ser una palabra completa, una subpalabra, un carácter o un signo de puntuación. Como referencia aproximada, 1 token ≈ 0.75 palabras en español. Cuando se habla de "velocidad de inferencia en tokens/s", se refiere a cuántos tokens genera el modelo por segundo.
Parámetros del modelo (8B, 14B...)
El número de parámetros (pesos) que tiene la red neuronal. 8B = 8 mil millones de parámetros. A más parámetros, mayor capacidad del modelo para entender y generar texto complejo, pero también mayor consumo de memoria y más lento en inferencia. En el RK3588 con 16 GB de RAM, modelos de hasta ~8B con W8A8 o ~14B con W4A16 son viables.
API compatible Ollama / OpenAI
RKLLama implementa los mismos endpoints HTTP que usan Ollama (/api/chat, /api/generate, /api/tags) y OpenAI (/v1/chat/completions, /v1/completions). Esto significa que cualquier aplicación cliente que funcione con Ollama o la API de OpenAI puede conectarse directamente a RKLLama sin cambios en el código, simplemente apuntando la URL base al servidor.
¿Qué necesitas para empezar?
-
PC con arquitectura x86/64 con Linux (en nuestro caso se usó Debian).
-
Python 3.8 o 3.10 en el PC (o usar Conda para crear un entorno con la versión adecuada; aquí se usó Python 3.11 vía Conda).
-
En la Orange Pi 5 Pro/Plus/Max/Ultra: Ubuntu 24.04 con controlador RKNPU 0.9.6+. Comprobar con:
sudo cat /sys/kernel/debug/rknpu/version
Parte 1 — Pasos en el PC x86/64 (conversión del modelo)
1.1. Instalación del entorno de desarrollo RKLLM
1.1.1. Clonar el SDK de Rockchip:
git clone https://github.com/airockchip/rknn-llm.git
cd rknn-llm
En nuestro caso se usó la versión release-v1.2.3 del SDK (rkllm-toolkit 1.2.3).
1.1.2. Crear el entorno Conda e instalar rkllm-toolkit:
conda create -n rkllm-converter python=3.11 -y
conda activate rkllm-converter
pip install rkllm-toolkit/packages/rkllm_toolkit-1.2.3-cp311-cp311-linux_x86_64.whl
Ajustar el nombre del
.whlsegún la versión de Python y del toolkit.
1.1.3. Verificar la instalación:
python3 -c "from rkllm.api import RKLLM; print('OK')"
1.2. Descargar el modelo desde Hugging Face
Los modelos se descargan desde Hugging Face. Es fundamental descargar los archivos reales y no los punteros Git LFS.
1.2.1. Instalar herramientas necesarias:
pip install huggingface_hub
1.2.2. Descargar el modelo:
huggingface-cli download Qwen/Qwen3-8B --local-dir ~/Qwen3-8B
⚠ IMPORTANTE: Si se clona con
git clonedesde Hugging Face, los archivos grandes (.safetensors,tokenizer.json) quedan como punteros Git LFS de ~135 bytes. Esto provoca errores como:
ERROR: expected value at line 1 column 1(tokenizer.json es un puntero LFS, no JSON válido)ERROR: Error while deserializing header: MetadataIncompleteBuffer(archivos .safetensors incompletos/truncados)Solución: Usar
huggingface-cli download(recomendado) o ejecutargit lfs pulldentro del directorio del modelo.
1.2.3. Verificar que los archivos se descargaron correctamente:
# Verificar que tokenizer.json es JSON válido
python3 -c "import json; json.load(open('Qwen3-8B/tokenizer.json')); print('OK')"
# Verificar que los safetensors no son punteros LFS
for f in ~/Qwen3-8B/*.safetensors; do
size=$(wc -c < "$f")
if [ "$size" -lt 1000 ]; then
echo "ERROR: $(basename $f) es un puntero LFS [$size bytes]"
else
echo "OK: $(basename $f) [$(numfmt --to=iec $size)]"
fi
done
1.3. Preparar el archivo de datos de cuantización
Crear el archivo data_quant.json en la ruta del script de exportación:
cat > ~/rknn-llm/examples/rkllm_api_demo/export/data_quant.json << 'EOF'
[{"input": "Humano: ¡Hola!\nAsistente: ", "target": "¡Hola! ¡Soy el asistente de inteligencia artificial Qwen3!"}]
EOF
1.4. Configurar y ejecutar el script de conversión
1.4.1. Editar el script de exportación (~/rknn-llm/examples/rkllm_api_demo/export/export_rkllm.py):
from rkllm.api import RKLLM
import os
os.environ['CUDA_VISIBLE_DEVICES']='0'
modelpath = '/home/wincrash32/Qwen3-8B'
llm = RKLLM()
# Cargar modelo (device='cpu' si no hay GPU CUDA disponible)
ret = llm.load_huggingface(model=modelpath, model_lora=None, device='cpu',
dtype="float32", custom_config=None, load_weight=True)
if ret != 0:
print('Load model failed!')
exit(ret)
# Construir modelo cuantizado
dataset = "/home/wincrash32/rknn-llm/examples/rkllm_api_demo/export/data_quant.json"
target_platform = "RK3588"
quantized_dtype = "W8A8" # W8A8 o W4A16
quantized_algorithm = "normal" # "normal" para W8A8, "grq" para W4A16
num_npu_core = 3
ret = llm.build(do_quantization=True, optimization_level=1,
quantized_dtype=quantized_dtype, quantized_algorithm=quantized_algorithm,
target_platform=target_platform, num_npu_core=num_npu_core,
extra_qparams=None, dataset=dataset, hybrid_rate=0, max_context=4096)
if ret != 0:
print('Build model failed!')
exit(ret)
# Exportar modelo RKLLM
ret = llm.export_rkllm(f"./{os.path.basename(modelpath)}_{quantized_dtype}_{target_platform}.rkllm")
if ret != 0:
print('Export model failed!')
exit(ret)
Parámetros clave:
modelpath: ruta absoluta al modelo descargado.device: usar'cpu'si no hay GPU CUDA, o'cuda'si la hay.target_platform:"RK3588"para Orange Pi 5 Plus.quantized_dtype:"W8A8"(8 bits peso y activación) o"W4A16"(4 bits peso, 16 activación).num_npu_core:3para usar los 3 núcleos NPU del RK3588.
1.4.2. Ejecutar la conversión:
conda activate rkllm-converter
python ~/rknn-llm/examples/rkllm_api_demo/export/export_rkllm.py
Este proceso consume ~32 GB de RAM y tarda varias horas en CPU. El modelo resultante se genera en el directorio sobre el que se ejecuta el script.
1.4.3. Resultado obtenido:
~/Qwen3-8B_W8A8_RK3588.rkllm (8.3 GB)
1.5. Compilar el ejecutable de demostración (compilación cruzada para aarch64)
1.5.1. Instalar cmake y descargar el toolkit:
sudo apt install cmake
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.2-2020.11/binrel/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xf gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu.tar.xz -C ~/rknn-llm/
1.5.2. Editar build-linux.sh en ~/rknn-llm/examples/rkllm_api_demo/:
GCC_COMPILER_PATH=~/rknn-llm/gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu
1.5.3. Compilar:
cd ~/rknn-llm/examples/rkllm_api_demo
bash build-linux.sh
1.5.4. El ejecutable llm_demo se genera en:
~/rknn-llm/examples/rkllm_api_demo/build/build_linux_aarch64_Release/llm_demo
Parte 2 — Inferencia en la Orange Pi 5 Plus (16GB de RAM)
2.1. Copiar los siguientes archivos a la Orange Pi 5 Plus:
llm_demo(compilado en el paso anterior)librkllmrt.so(del SDK:~/rknn-llm/runtime/Linux/librkllm/aarch64/)Qwen3-8B_W8A8_RK3588.rkllm(modelo convertido)
2.2. Dar permisos de ejecución:
chmod +x llm_demo
chmod +x librkllmrt.so
2.3. Exportar la librería compartida:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd)
2.4. Aumentar el límite de descriptores de archivo (ejecutar cada vez que se abra una nueva terminal):
ulimit -HSn 102400
2.5. Ejecutar el modelo:
./llm_demo ./Qwen3-8B_W8A8_RK3588.rkllm 256 320
2.6. Monitorizar uso de la NPU (en otra terminal):
watch sudo cat /sys/kernel/debug/rknpu/load
Parte 3 — Deployment de RKLLama en Kubernetes (MicroK8s) en la Orange Pi 5 Plus
RKLLama es un servicio compatible con la API de Ollama y OpenAI que ejecuta modelos LLM sobre la NPU de Rockchip RK3588(S)/RK3576. La imagen Docker oficial está disponible en ghcr.io/notpunchnox/rkllama:main.
3.1.Requisitos previos en la Orange Pi 5 Plus
3.1.1. MicroK8s instalado:
sudo snap install microk8s --classic
sudo usermod -aG microk8s $USER
newgrp microk8s
microk8s status --wait-ready
3.1.2. Habilitar addons necesarios:
microk8s enable dns storage
3.1.3. Crear el directorio de modelos (si no existe):
mkdir -p ~/rkllama-models
3.1.4. Copiar los modelos .rkllm previamente convertidos (p. ej. Qwen3-8B_W8A8_RK3588.rkllm) dentro de subdirectorios con su Modelfile:
~/rkllama-models/
└── Qwen3-8B
├── Modelfile
└── Qwen3-8B_W8A8_RK3588.rkllm
Ejemplo de Modelfile:
FROM="Qwen3-8B_W8A8_RK3588.rkllm"
HUGGINGFACE_PATH="Qwen/Qwen3-8B"
SYSTEM="Eres un asistente útil."
TEMPERATURE=1.0
3.2. Manifiests de Kubernetes
Crear el archivo rkllama-k8s.yaml:
cat > ~/rkllama-k8s.yaml << 'EOF'
---
apiVersion: v1
kind: Namespace
metadata:
name: rkllama
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: rkllama-models-pv
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: rkllama-local
hostPath:
# Ajustar a la ruta real donde están los modelos en el nodo
path: /home/wincrash32/rkllama-models
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rkllama-models-pvc
namespace: rkllama
spec:
accessModes:
- ReadWriteOnce
storageClassName: rkllama-local
resources:
requests:
storage: 50Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: rkllama
namespace: rkllama
labels:
app: rkllama
spec:
replicas: 1
selector:
matchLabels:
app: rkllama
template:
metadata:
labels:
app: rkllama
spec:
containers:
- name: rkllama
image: ghcr.io/notpunchnox/rkllama:main
ports:
- containerPort: 8080
name: http
volumeMounts:
- name: models
mountPath: /opt/rkllama/models
# Necesario para acceso a la NPU
- name: dev-npu
mountPath: /dev
readOnly: true
env:
- name: RKLLAMA_PLATFORM_PROCESSOR
value: "rk3588"
securityContext:
privileged: true
resources:
requests:
memory: "2Gi"
cpu: "500m"
limits:
memory: "14Gi"
cpu: "4"
readinessProbe:
httpGet:
path: /api/version
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
livenessProbe:
httpGet:
path: /api/version
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: models
persistentVolumeClaim:
claimName: rkllama-models-pvc
- name: dev-npu
hostPath:
path: /dev
# No scheduling en nodos que no tengan NPU Rockchip
nodeSelector:
kubernetes.io/arch: arm64
---
apiVersion: v1
kind: Service
metadata:
name: rkllama
namespace: rkllama
labels:
app: rkllama
spec:
type: NodePort
selector:
app: rkllama
ports:
- port: 8080
targetPort: 8080
nodePort: 30080
protocol: TCP
name: http
EOF
Notas importantes:
privileged: truees obligatorio para que el contenedor acceda a la NPU (/dev/rknpu*).nodeSelector: kubernetes.io/arch: arm64asegura que el Pod se programe solo en nodos ARM64. También se puede asignar un label a los nodos RK3588(S) y asignarlo en el selector.- El
nodePort: 30080expone el servicio enhttp://<IP_NODO>:30080. - Ajustar
hostPathdel PV a la ruta real de los modelos. - Los límites de memoria (
14Gi) deben ajustarse según el modelo cargado y la RAM disponible.
3.3. Aplicar los manifiests
microk8s kubectl apply -f ~/rkllama-k8s.yaml
3.4. Verificar el despliegue
# Ver estado del namespace
microk8s kubectl get all -n rkllama
# Ver logs del servicio
microk8s kubectl logs -n rkllama deployment/rkllama -f
# Verificar que la NPU es accesible dentro del Pod
microk8s kubectl exec -n rkllama deployment/rkllama -- ls -la /dev/rknpu*
3.5. Probar la API
Una vez que el Pod esté en estado Running y el readinessProbe pase:
# Verificar versión
curl http://localhost:30080/api/version
# Listar modelos disponibles
curl http://localhost:30080/api/tags
# Inferencia (compatible con API de Ollama)
curl -X POST http://localhost:30080/api/chat \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen3-8B",
"messages": [{"role": "user", "content": "¡Hola! ¿Qué eres?"}],
"stream": false
}'
3.6. Actualizar la imagen
# Forzar re-pull de la imagen y reiniciar el deployment
microk8s kubectl rollout restart deployment/rkllama -n rkllama
3.7. Desinstalar
microk8s kubectl delete -f ~/rkllama-k8s.yaml
Errores comunes y soluciones
| Error | Causa | Solución |
|---|---|---|
expected value at line 1 column 1 |
tokenizer.json es un puntero Git LFS, no JSON válido |
Descargar con huggingface-cli download o ejecutar git lfs pull |
MetadataIncompleteBuffer |
Archivos .safetensors incompletos o truncados |
Esperar a que la descarga termine, verificar tamaños con la API de HF |
Cuda device not available |
No hay GPU NVIDIA/CUDA en el PC | Cambiar device='cuda' a device='cpu' en el script (solo afecta al rendimiento de conversión) |
Pod en CrashLoopBackOff (rkllama) |
Falta acceso a la NPU o modo privilegiado | Verificar privileged: true en el securityContext y que /dev/rknpu* exista en el nodo |
Connection refused en /api/version |
El servidor rkllama aún no ha arrancado | Esperar a que el readinessProbe pase; revisar logs con microk8s kubectl logs -n rkllama deployment/rkllama |
PVC en estado Pending |
No hay PV disponible con el storageClassName correcto | Verificar que el PV rkllama-models-pv fue creado y que la ruta hostPath existe |
Nuestras últimas novedades
¿Te interesa saber cómo nos adaptamos constantemente a la nueva frontera digital?
Corporate news
21 de abril de 2026
Integramos AMS Solutions para reforzar nuestras capacidades en servicios gestionados y desarrollo de software asistido por IA
Insight
5 de febrero de 2026
Infinite Worlds, cuando el mejor gráfico es tu imaginación
Insight
26 de diciembre de 2025
Ferrovial mejora la eficiencia del ciclo productivo con Connected Works
Evento
24 de noviembre de 2025
White Mirror 2025: el espejo en blanco donde se refleja lo que aún está por llegar