Bitácora


Columna dedicada a detallar mis proyectos informáticos. En la primera entrada detallo más extensamente los objetivos de esta sección. Aquí tienes el archivo completo.

LLMs y sus fundamentos

Primeros pasos ejecutando modelos de lenguaje en el Mini PC.

Después de dejar funcionando la parte de Bitcoin en el Mini PC, el siguiente paso era empezar a experimentar con modelos de lenguaje en local. En este terreno parto con bastante menos experiencia y no hay (o no conozco) guías especializadas como Minibolt en el caso de Bitcoin, así que la idea es avanzar poco a poco, entender qué hace cada pieza y comprobar hasta dónde puede llegar el hardware que ya tengo. Para recordar el punto de partida, el Mini PC sobre el que estoy haciendo estas pruebas cuenta con un hardware bastante modesto para este tipo de cargas, pero suficiente para empezar a experimentar con modelos locales:

  • Un AMD Ryzen 7 8745H de CPU
  • Una Radeon 780M integrada como GPU
  • 32 GB de RAM DDR5.

llama.cpp

Como punto de partida elegí llama.cpp, un proyecto que permite ejecutar modelos de lenguaje de forma local sobre distintos tipos de hardware. Aunque existen binarios precompilados, preferí descargar el código fuente, del mismo modo que hice con Bitcoin Core. Esto permite tener más control sobre la versión utilizada y, sobre todo, sobre las opciones de compilación y los backends de aceleración disponibles.

La compilación se prepara con CMake. La opción -B indica en qué carpeta quiero generar los archivos del build. Para las pruebas mantengo dos compilaciones separadas: una normal para CPU y otra con soporte Vulkan (más tarde explicaré lo que es) para utilizar la Radeon 780M.

# CPU (build-cpu es el nombre de la carpeta)
$ cmake -B build-cpu
$ cmake --build build-cpu -j

# GPU: compilar llama.cpp con soporte Vulkan
$ cmake -B build-gpu -DGGML_VULKAN=ON
$ cmake --build build-gpu -j

El flag -DGGML_VULKAN=ON compila llama.cpp con soporte para Vulkan.

Entre los ejecutables generados me interesan principalmente tres:

  • llama-cli, para cargar un modelo e interactuar con él desde la terminal.
  • llama-bench, para medir el rendimiento del modelo y comparar distintas configuraciones.
  • llama-server, que más adelante permitirá levantar el modelo como un servicio accesible a través de una API.

Para mantener los modelos organizados he reservado el directorio /srv/llm/models.

Qwen

Qwen es una familia de modelos desarrollada por Alibaba Cloud. En sus versiones recientes se ha convertido en una de las principales familias de modelos de pesos abiertos junto a proyectos como DeepSeek. En la reciente carrera de la IA China parece haber apostado por el código abierto, algo a celebrar; no profundizaré mucho sobre el tema pero mi sensación es que los tech popes estadounidenses se han creido que le podían poner puertas al campo y se han llevado un buen chasco.

Los modelos que voy a descargar para mi MiniPC están muy lejos de los grandes modelos actuales de Anthropic y OpenAI como Claude Fable 5 o GPT Sol 5.6, pero si son comparables a modelos de hace un par de años como GPT4 o Claude 3 Haiku, lo que no es moco de pavo teniendo en cuenta que mi MiniPC es de uso generalista.

La familia Qwen3 incluye modelos densos y modelos Mixture of Experts (MoE), además de variantes de distintos tamaños. Voy a utilizar modelos densos de 8B y 14B parámetros, ambos en una versión cuantizada a 4 bits (Q4_K_M), y el modelo 8B cuantizado a 8 bits (Q8_0).

Descarga y ejecución de los modelos

llama.cpp puede descargar directamente un modelo desde Hugging Face y guardarlo en su caché local. Es la opción más sencilla:

# Ejecución descargando el modelo automáticamente desde Hugging Face
$ ./build/bin/llama-cli -hf Qwen/Qwen3-8B-GGUF:Q4_K_M

Yo he preferido descargar los archivos .gguf de forma explícita y guardarlos en /srv/llm/models, porque así sé exactamente qué modelos tengo instalados, cuánto ocupan y dónde se encuentran:

# Descarga explícita del archivo GGUF
$ wget https://huggingface.co/Qwen/Qwen3-8B-GGUF/resolve/main/Qwen3-8B-Q4_K_M.gguf \
    -O /srv/llm/models/Qwen3-8B-Q4_K_M.gguf

# Ejecución del modelo descargado
$ ./build/bin/llama-cli -m /srv/llm/models/Qwen3-8B-Q4_K_M.gguf

Cuantización y número de parámetros

Antes de descargar los modelos conviene distinguir dos conceptos ya mencionados anteriormente: el número de parámetros y la cuantización.

Un mismo modelo, como Qwen3, puede encontrarse en versiones de distintos tamaños. Normalmente se identifican con cifras como 8B, 14B, etc, donde la B hace referencia a miles de millones de parámetros. De forma muy simplificada, cuantos más parámetros tiene un modelo, mayor es su capacidad para representar relaciones complejas y, en general, mejores pueden ser sus respuestas. A cambio, necesita más memoria y más capacidad de cálculo.

La cuantización actúa sobre esos parámetros. Consiste en almacenarlos con menor precisión numérica para reducir el tamaño del archivo y los recursos necesarios para ejecutar el modelo. Una cuantización más agresiva permite ahorrar bastante memoria, pero también puede introducir cierta pérdida de calidad.

Por tanto, al descargar un modelo no solo hay que elegir cuántos parámetros queremos, sino también con qué cuantización. En la práctica se trata de buscar un equilibrio entre calidad, memoria disponible y velocidad de ejecución.

Benchmark

Para medir el rendimiento utilicé llama-bench.

Las dos pruebas principales son:

  • pp512, que mide la velocidad con la que el modelo procesa un prompt de 512 tokens.
  • tg128, que mide la velocidad de generación de 128 tokens nuevos.

Los resultados se expresan en tokens por segundo (t/s).

Qwen3 8B Q4_K_m

Este modelo tiene unos 8,19 mil millones de parámetros, está cuantizado en Q4_K_M, aproximadamente 4 bits por parámetro, y ocupa unos 4,68 GiB.

CPU

Primero probé el modelo utilizando únicamente la CPU y variando el número de hilos:

Hilospp512tg128
445,13 t/s5,49 t/s
866,70 t/s5,57 t/s
1268,93 t/s5,57 t/s
1654,34 t/s2,22 t/s

El Ryzen 7 8745H tiene 8 núcleos físicos y 16 hilos, pero, como se observa en los resultados, utilizar los 16 hilos no mejora el rendimiento. De hecho, tanto en el procesamiento del prompt como en la generación de tokens queda por debajo de las configuraciones con 8 y 12 hilos, y en tg128 llega incluso a rendir peor que con 4.

Esto se debe a que esos 16 hilos no equivalen a 16 núcleos independientes. Los hilos lógicos comparten recursos dentro de cada núcleo físico y, en una carga como esta, llega un punto en el que añadir más hilos aumenta la competencia por la caché, el ancho de banda de memoria y otros recursos internos.

Vulkan y GPU

Para utilizar la Radeon 780M integrada instalé el soporte necesario para Vulkan y utilicé la compilación build-gpu creada antes. Como el usuario llm ejecuta el proceso, también fue necesario darle acceso al dispositivo de renderizado:

$ sudo adduser llm render

La diferencia práctica entre los benchmarks de CPU y GPU queda reflejada directamente en los comandos:

# CPU: probar 4, 8, 12 y 16 hilos
$ ./build/bin/llama-bench \
    -m /srv/llm/models/Qwen3-8B-Q4_K_M.gguf \
    -t 4,8,12,16 > ../cpu.txt

# GPU: descargar a la Radeon tantas capas como sea posible con -ngl 99
$ ./build-vulkan/bin/llama-bench \
    -m /srv/llm/models/Qwen3-8B-Q4_K_M.gguf \
    -ngl 99 > ../gpu.txt

En CPU interesa variar el número de hilos. En GPU deja de ser una variable importante para esta comparación, porque el trabajo se descarga a la Radeon.

Los resultados fueron:

PruebaCPURadeon 780M
pp51268,93 t/s231,59 t/s
tg1285,57 t/s8,21 t/s

La diferencia es especialmente grande al procesar el prompt: la GPU es aproximadamente 3,4 veces más rápida. En generación de texto la mejora es menor, alrededor de 1,5 veces.

Qwen3 14B Q4_K_m

Este modelo tiene unos 14,77 mil millones de parámetros, también cuantizado en Q4_K_M, y ocupa aproximadamente 8,38 GiB. El 14B contiene alrededor de 1,8 veces más parámetros que el 8B. En principio puede ofrecer mayor capacidad, pero para generar cada token tiene que mover y procesar una cantidad de datos considerablemente mayor.

CPU

Hilospp512tg128
423,11 t/s3,01 t/s
836,00 t/s3,06 t/s
1236,26 t/s3,07 t/s
1633,31 t/s1,54 t/s

Se repite el comportamiento del 8B. El mejor resultado aparece antes de llegar a los 16 hilos y la generación cae especialmente cuando se utilizan todos los hilos lógicos.

GPU

Con la Radeon 780M y Vulkan:

pp512tg128
127,21 t/s4,33 t/s

Comparando directamente los dos tamaños:

ModeloTamañoBackendpp512tg128
Qwen3 8B Q4_K_M4,68 GiBCPU68,93 t/s5,57 t/s
’’4,68 GiBRadeon 780M231,59 t/s8,21 t/s
Qwen3 14B Q4_K_M8,38 GiBCPU36,26 t/s3,07 t/s
’’8,38 GiBRadeon 780M127,21 t/s4,33 t/s

El modelo de 14B funciona sin problemas en el Mini PC, pero genera texto aproximadamente a la mitad de velocidad que el 8B. Con Vulkan, tg128 pasa de 8,21 t/s a 4,33 t/s.

Esto muestra de forma bastante clara el intercambio entre ambos tamaños: el 8B es más rápido, mientras que el 14B sacrifica velocidad a cambio de utilizar un modelo con mayor capacidad.

Qwen3 8B Q8_0

Hasta aquí había comparado dos modelos con la misma cuantización Q4_K_M: uno de 8B y otro de 14B. La siguiente prueba cambia una sola cosa: vuelvo al modelo de 8,19 mil millones de parámetros, pero utilizo una cuantización de 8 bits, Q8_0. El modelo ocupa 8,11 GiB, casi lo mismo que el Qwen3 14B Q4_K_M. Esta comparación de dos modelos que utilizan aproximadamente la misma cantidad de memoria nos permitirá comprobar si para las pruebas resulta mejor tener menos parámetros pero mayor precisión o más parámetros con una cuantización más agresiva.

CPU

Hilospp512tg128
437,69 t/s3,17 t/s
855,02 t/s3,25 t/s
1249,40 t/s3,25 t/s
1645,81 t/s1,16 t/s

GPU

pp512tg128
263,47 t/s4,49 t/s

Aquí está la comparación entre los tres modelos usando la Radeon:

ModeloTamañoParamsQuantpp512tg128
Qwen3 8B Q4_K_M4,68 GiB8,19 B~4 bits231,59 t/s8,21 t/s
Qwen3 8B Q8_08,11 GiB8,19 B8 bits263,47 t/s4,49 t/s
Qwen3 14B Q4_K_M8,38 GiB14,77 B~4 bits127,21 t/s4,33 t/s

El 8B Q8_0 procesa el prompt más rápido que el 8B Q4_K_M, pero genera texto bastante más despacio. En velocidad de generación, el 8B Q8_0 y el 14B Q4_K_M quedan prácticamente empatados.

La comparación de rendimiento no dice por sí sola cuál es el mejor modelo. Precisamente el siguiente paso es comprobar si la mayor precisión de un 8B Q8_0 compensa tener muchos menos parámetros que un 14B Q4_K_M, o si para una cantidad similar de memoria resulta preferible dedicar ese espacio a un modelo más grande aunque esté más cuantizado.

Memoria y cuello de botella

Durante estas pruebas, los 32 GB de RAM no causaron problemas. Mientras se ejecutaba el benchmark con la Radeon, el sistema seguía mostrando alrededor de 17 GiB de memoria disponible. Con estos modelos el límite actual no es la capacidad de memoria, sino su ancho de banda.

La Radeon 780M es una GPU integrada y no dispone de una gran memoria VRAM dedicada, por lo que CPU y GPU acceden a la misma DDR5 del sistema. Durante la generación de cada token hay que leer una gran cantidad de los pesos del modelo desde esa memoria. Al pasar de un archivo de 4,68 GiB en el 8B a 8,38 GiB en el 14B, aumenta aproximadamente en la misma proporción la cantidad de información que debe recorrerse, mientras que el ancho de banda disponible no cambia.

Los resultados reflejan este comportamiento de forma bastante clara: con la Radeon, el 14B conserva aproximadamente la mitad de la velocidad de generación del 8B.

Así, por ahora pueden distinguirse dos límites diferentes. El límite físico de memoria todavía está lejos: ambos modelos caben con holgura. El que empieza a aparecer es el límite práctico de rendimiento. Qwen3 8B genera a unos 8,2 t/s con Vulkan, mientras que Qwen3 14B baja a unos 4,3 t/s.

Para próximas entregas relacionadas con la IA profundizaré más en casos de uso prácticos.

Bitcoin y sus fundamentos

Instalación de Bitcoin Core, gestionando el ancho de banda en la descarga de la blockchain y algunos apuntes sobre la red de Satoshi

El antiguo nodo con el que operaba en Madrid lo instalé en una Raspberry Pi, la misma que hoy utilizo para Pi-hole y Unbound. Esta vez, con un Mini PC bastante más potente, esperaba una instalación más sencilla y rápida y, aunque así ha sido, también me he encontrado con algunos problemas que comentaré más adelante.

Si entonces seguí la guía de RaspiBolt, esta vez he utilizado MiniBolt, su adaptación para Mini PC. No voy a repetir aquí los pasos de instalación, que ya están bien documentados, sino explicar qué he instalado, para qué sirve cada componente y algunas cosas que he ido aprendiendo durante el proceso. MiniBolt está pensada principalmente para Ubuntu y yo estoy utilizando Debian, pero las diferencias han sido ínfimas ya que Ubuntu deriva de Debian.

Bitcoin Core

El componente principal es Bitcoin Core, la implementación de Bitcoin más utilizada. Es el software que descarga y valida la blockchain, comprueba que los bloques y las transacciones cumplen las reglas del protocolo y se comunica con otros nodos de la red.

Es importante distinguir entre Bitcoin y Bitcoin Core. Bitcoin es el protocolo y la red; Bitcoin Core es solamente una implementación de ese protocolo. Existen otras alternativas, todas ellas usadas de forma marginal salvo Bitcoin Knots, en la que es interesante detenerse.

Knots ha ganado bastante popularidad a raíz de la discusión sobre Ordinals e inscriptions: desde finales de 2022 empezó a utilizarse el espacio de los bloques para incluir imágenes y otros datos. Ha habido debate en la comunidad al respecto: algunos consideran que si una transacción paga las comisiones correspondientes y cumple las reglas de Bitcoin, ese uso es perfectamente válido; otros, por contra, catalogan estas imagenes como spam y creen que el espacio de la blockchain debe utilizarse únicamente para transacciones monetarias. Bitcoin Knots aplica por defecto políticas más restrictivas a las transacciones que un nodo guarda en su mempool y retransmite a otros nodos. Pero esto no significa que pueda ignorar esas transacciones si finalmente aparecen dentro de un bloque válido. Hay una diferencia importante entre las políticas de mempool, que cada nodo puede configurar, y las reglas de consenso, que determinan qué bloques se aceptan como válidos.1

Esta discusión ha desembocado recientemente en el BIP-1102, una propuesta de soft fork temporal que buscaba trasladar algunas de estas restricciones desde la política local de cada nodo hasta las propias reglas de consenso. La idea era que los nodos que ejecutasen software que aplicase BIP-110 comenzasen, llegado cierto punto, a rechazar como inválidos los bloques que no señalizasen su apoyo a las nuevas reglas. Al alcanzarse el bloque 961632, minado hace menos de una semana, comenzó la fase de señalización obligatoria para esos nodos y se produjo una separación de la cadena. La inmensa mayoría del hashrate continuó minando sobre Bitcoin con las reglas anteriores, donde los bloques siguieron produciéndose aproximadamente cada diez minutos, mientras que la rama que aplicaba BIP-110 quedó con una fracción mínima del hashrate y apenas ha sido capaz de producir unos pocos bloques desde entonces.

El episodio recuerda en cierta medida al BIP-148 de 2017. Los nodos que ejecutasen software que aplicase las reglas de ese BIP rechazarían los bloques que no señalizasen la activación de SegWit, una modificación del protocolo que permitió aprovechar mejor el espacio disponible en cada bloque sin tener que aumentar su tamaño. En aquel caso la presión ejercida por el BIP-148 terminó contribuyendo a desbloquear la activación de SegWit, que hasta entonces no había conseguido el apoyo minero necesario.

Una de las cosas que esta discusión sobre el BIP-110 deja bastante clara es que ejecutar un nodo no equivale simplemente a tener un voto sobre la red, como explica Satoshi en el Whitepaper de 2009:

If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote.

Mi nodo puede decidir qué reglas acepta y, por tanto, qué considera Bitcoin, pero no puede obligar a los mineros, exchanges o demás usuarios a seguir esas mismas reglas. Para que una modificación impulsada por quienes operan nodos tenga efectos sobre el conjunto de la red hace falta que exista suficiente apoyo económico dispuesto a seguir esa cadena. Parte de quienes apoyaban el BIP-110 parece haber interpretado lo sucedido en 2017 como una demostración de que una minoría suficientemente «intolerante» de usuarios —los plebs, como ellos mismos se llaman— podía imponerse por sí sola a la mayoría económica. Una especie de sueño febril populista que se ha roto esta última semana.

Personalmente nunca he creído que ejecutar un nodo me otorgue algún tipo de poder sobre la red. Lo hago principalmente porque me interesa entender cómo funciona Bitcoin, aunque también tiene utilidades prácticas: puedo verificar la blockchain por mí mismo, consultar y retransmitir mis propias transacciones sin depender de nodos de terceros y conectar otros servicios a mi propia infraestructura. Nada de esto es imprescindible para un usuario de Bitcoin que simplemente quiera utilizar la red.

Problemas de ancho de banda

Relacionado con todo esto está el hecho de que la IBD (Initial Block Download) me provocó un problema que inicialmente no relacioné con Bitcoin Core.

Durante un par de días el Wi-Fi funcionaba bastante peor de lo habitual. Algunas páginas tardaban en cargar y la conexión parecía inestable. Pensé que podía ser la descarga de la blockchain, pero recordé que cuando la descargué un año antes desde una Raspberry Pi no había tenido estos problemas, así que empecé buscando la causa en la propia red inalámbrica.

Separé las redes de 2,4 y 5 GHz, que inicialmente compartían el mismo nombre, para poder elegir de forma explícita a cuál conectarme. La idea era utilizar preferentemente la de 5 GHz, que ofrece más ancho de banda y, en mi caso, estaba bastante menos congestionada por las redes vecinas. En 2,4 GHz revisé los canales utilizados alrededor, probé distintas configuraciones y realicé mediciones con iperf3; también fui ajustando el ancho de canal para reducir las interferencias.

Otra de las cosas que comprobé fue la MTU (Maximum Transmission Unit), es decir, el tamaño máximo de un paquete IP que puede atravesar un enlace sin tener que fragmentarse. Para ello utilicé ping indicando que los paquetes no pudiesen fragmentarse:

ping -M do -s *TAMAÑO_A_PROBAR* -c 20 1.1.1.1

La opción -M do obliga a enviar el paquete con la indicación de que no debe fragmentarse, mientras que -s establece el tamaño de los datos enviados. A este último valor hay que añadir 28 bytes correspondientes a las cabeceras IP e ICMP. Así, por ejemplo, si escribimos -s 1464 lo que realmente se está probando es un paquete IP de 1492 bytes. Las pruebas con tamaños de 1500, 1492 y 1480 bytes fallaban, mientras que 1428 bytes sí pasaban correctamente. Afinando después el límite comprobé que el tamaño máximo admitido a lo largo de todo el recorrido era de 1440 bytes. En mi router la opción que venía configurada por defecto era MTU: 1500, por lo que lo bajé a 1440.

Todas estas pruebas conllevaron leves mejoras y me sirvieron para refrescar algunos conceptos y aprender otros, pero la lentitud al navegar seguía ahí. Cuando probé a detener Bitcoin Core, la conexión volvió inmediatamente a la normalidad.

La diferencia con la Raspberry Pi que había utilizado para mi nodo anterior era el propio rendimiento del equipo. La Raspberry tardaba mucho más en procesar y verificar los bloques, así que no podía descargar datos de forma continua a gran velocidad. Eso hizo que la sincronización completa tardase semanas, pero también evitaba que ocupase todo el ancho de banda disponible. El Mini PC, en cambio, puede validar los bloques mucho más rápido y pedir continuamente los siguientes, por lo que durante la IBD estaba utilizando prácticamente todo el ancho de banda que tenía disponible. Además, yo mismo había favorecido una sincronización rápida aumentando la caché de Bitcoin Core y reduciendo el tráfico de transacciones mediante estas opciones en bitcoin.conf:

# Aumenta la memoria RAM, reduciendo accesos a disco .
dbcache=8192

# Evita el tráfico de transacciones no confirmadas: solo bloques.
blocksonly=1

En esos momentos en los que la lentitud navegando por Internet era exasperante, Bitcoin Core estaba descargando bloques del año 2023, coincidiendo con el auge de Ordinals e inscriptions con los bloques llenos de imágenes, videos y demás datos no monetarios. La sensación era parecida a circular por una carretera prácticamente vacía y encontrarse de repente con bastante más tráfico.

Para solucionar este problema decidí limitar el ancho de banda del Mini PC desde el router a 5.000 KB/s de descarga y 3.000 KB/s de subida. La sincronización tarda así un poco más, pero mientras tanto puedo utilizar Internet con normalidad.

Una solución mejor habría sido utilizar Smart Queue Management. En lugar de imponer simplemente un límite fijo, estos sistemas gestionan las colas de tráfico para evitar que una descarga intensiva monopolice la conexión y aumente la latencia del resto de dispositivos. Mi router no dispone de esta funcionalidad, así que el límite manual es suficiente por ahora. Este problema es temporal: una vez terminada la IBD, mantener el nodo actualizado requiere mucho menos ancho de banda.

Indexador y explorador

Fulcrum

Una vez finalizada la IBD —me llevó solo cuatro días; por comparar, con la Raspberry Pi había tardado unas cuatro semanas— pasé al siguiente paso de la guía: instalar un indexador.

Bitcoin Core permite validar la blockchain y consultar transacciones concretas, pero llevamos ya 17 años de transacciones y es muchísima información almacenada, por lo que cuando le preguntamos a Bitcoin Core «¿qué transacciones están relacionadas con esta dirección?» o «¿cuál es el historial completo de esta cartera?» puede tardar en responder. Un indexador recorre toda la blockchain y construye una base de datos adicional organizada para poder hacer este tipo de consultas de forma rápida. Es la pieza que permite, por ejemplo, conectar una cartera Electrum o un explorador de bloques a mi propio nodo en vez de depender de servidores externos.

En mi anterior nodo había utilizado Electrs, una implementación del protocolo Electrum escrita en Rust y optimizada para equipos con recursos modestos. Su propia documentación destaca que mantiene un índice bastante reducido —aproximadamente un 10 % adicional sobre el tamaño de la blockchain— y un consumo bajo de CPU y memoria una vez terminada la indexación.

La guía que ofrece MiniBolt no es la de electrs sino la de Fulcrum, otro servidor compatible con el protocolo Electrum pero con un planteamiento diferente. Fulcrum construye un índice bastante más completo, lo que le permite responder más rápido a consultas grandes, a cambio de consumir más espacio en disco. En principio pensé en repetir con electrs por lo explicado en este podcast de Lunaticoin. Fulcrum tenía una desventaja importante frente a electrs, y es que, pese a ser mucho más rápido, una interrupción inesperada mientras escribía en la base de datos podía corromperla y obligar a reconstruir todo el índice. Sin embargo, este problema parece haber quedado resuelto con Fulcrum 2.0, publicado con un nuevo formato de base de datos.

Teniendo eso en cuenta, y dado que en el Mini PC tengo recursos de sobra para asumir su mayor consumo de disco y memoria, finalmente opté por Fulcrum; para mi sorpresa la indexación inicial ha resultado incluso más lenta que la propia descarga de la blockchain, llevándome seis días. Una vez terminada la indexación, el siguiente paso fue conectar la cartera directamente a Fulcrum; si de carteras hablamos, recomiendo Sparrow Wallet. Sparrow ofrece tres formas principales de consultar la red Bitcoin: conectarse a un servidor público Electrum, conectarse directamente a Bitcoin Core o utilizar un servidor Electrum privado, como Fulcrum o electrs. La primera opción es la más sencilla, pero obliga a confiar en un tercero. Las otras dos permiten utilizar nuestra propia infraestructura. Un servidor Electrum privado tiene además dos ventajas frente a conectarse directamente a Bitcoin Core: las consultas son más rápidas y ofrece algo más de privacidad, ya que a diferencia de la wallet de Bitcoin Core no mantiene almacenada información específica sobre las claves y el saldo de nuestra cartera.

De esta forma, Bitcoin Core valida la blockchain, Fulcrum la indexa para que pueda consultarse de forma eficiente y Sparrow utiliza ese índice para obtener la información de mis carteras sin depender de infraestructura ajena.

BTC RPC

El último componente que he instalado es BTC RPC Explorer, un explorador de bloques que se conecta directamente a Bitcoin Core. Un explorador permite consultar de forma visual información de la blockchain: bloques, transacciones, direcciones, comisiones o datos generales de la red, sin tener que hacerlo mediante comandos. En mi caso, BTC RPC Explorer obtiene toda esa información de mi propio nodo, por lo que no depende de servicios externos. Lo he configurado como se indica en la guía y ya puedo acceder a él desde cualquier dispositivo conectado a mi red local.

La idea es exponer también el explorador a Internet para poder acceder a él desde fuera de casa y dejarlo disponible como parte del homelab. Esto se puede hacer con Tor, pero también quiero que se pueda acceder desde clearnet. Para hacerlo utilizaré Cloudflare Tunnel, que permite publicar un servicio local sin abrir directamente puertos del router.

Más detalle sobre esto en la siguiente entrada.


  1. El propio manual de MiniBolt da la opción de aplicar un parche muy sencillo al código fuente de Bitcoin Core para filtrar este tipo de datos y añade opciones de configuración que permiten aplicar políticas todavía más restrictivas a las transacciones que el nodo acepta en su mempool y retransmite. Como explico en el texto, esto afecta únicamente al comportamiento local del nodo, no a las reglas de consenso. ↩︎

  2. BIP son las siglas de Bitcoin Improvement Proposal ↩︎

Servidor y miscelánea

Instalando Debian en mi MiniPC, montando el primer SSD y corrigiendo algunos problemas de mi red local

Antes de empezar con la instalación del Mini PC, ajusté en mi equipo principal la prioridad de las conexiones de red. Linux Mint recordaba varias redes y tendía a conectarse automáticamente a la del edificio en vez de a la mía. Con NetworkManager establecí una prioridad distinta para cada conexión. En mi caso dejé una prioridad mayor para la red que quiero utilizar normalmente y una menor para la alternativa. De esta forma, cuando ambas están disponibles, el sistema intenta conectarse primero a la red con mayor prioridad.

A esto se sumaba otro problema. El router emitía las bandas de 2,4 GHz y 5 GHz bajo el mismo nombre, y mi ordenador tendía a terminar conectado a la primera. La banda de 2,4 GHz estaba además configurada automáticamente en el canal 3 y con un ancho de 40 MHz. Aunque el canal 3 parecía más despejado, en 2,4 GHz los canales se solapan entre sí, por lo que no basta con contar cuántas redes aparecen en cada uno. Tras hacer varias pruebas fijé esta banda en el canal 6, uno de los canales que permite evitar buena parte de ese solapamiento, y reduje el ancho a 20 MHz. La banda de 5 GHz la dejé en automático.

Ahora ya sí, empecé con el MiniPC.

El Mini PC

La Raspberry Pi ya se encarga de algunos servicios básicos de la red, pero para las tareas que requieren más capacidad necesitaba algo más potente. El siguiente elemento del proyecto es un MINISFORUM UM870 Slim, equipado con un AMD Ryzen 7 8745H, 32 GB de memoria DDR5 y un SSD interno de 512 GB. El equipo venía con Windows 11, así que para la primera puesta en marcha tuve que conectarlo por HDMI a la televisión y utilizar un teclado. Una vez preparado el USB de instalación, reinicié el Mini PC, entré en la BIOS y seleccioné el USB como dispositivo de arranque.

Descarga y verificación de Debian

En el flashdrive había grabado la imagen de Debian 13.6 (Trixie), utilizando la versión netinst para equipos de 64 bits. Antes de hacerlo, desde la página de descarga de Debian descargué tres archivos:

  • debian-13.6.0-amd64-netinst.iso, la imagen de instalación.
  • SHA512SUMS.txt, que contiene las sumas de comprobación de las imágenes publicadas.
  • SHA512SUMS.sign, la firma criptográfica del archivo anterior.

Antes de utilizar la ISO quise comprobar tanto su autenticidad como su integridad. Son dos comprobaciones relacionadas, pero distintas: la firma permite verificar que el archivo de sumas procede de Debian, mientras que la suma SHA-512 permite comprobar después que la ISO descargada coincide exactamente con la publicada.

Comencé verificando la firma:

$ gpg --verify SHA512SUMS.sign SHA512SUMS.txt

gpg: Firmado el sáb 11 jul 2026 14:25:53 CST
gpg:                usando RSA clave DF9B9C49EAA9298432589D76DA87E80D6294BE9B
gpg: Imposible comprobar la firma: No hay clave pública

El comando identifica la clave utilizada para crear la firma, pero no puede comprobarla porque su clave pública todavía no está en mi llavero. La propia salida muestra la huella completa, que pude contrastar con las claves publicadas en la página de verificación de imágenes de Debian. Allí descargué la clave correspondiente y, antes de importarla, comprobé su huella:

$ gpg --show-keys --fingerprint clave_a_importar.txt

pub   rsa4096 2011-01-05 [SC]
      DF9B 9C49 EAA9 2984 3258 9D76 DA87 E80D 6294 BE9B
uid                      Debian CD signing key <debian-cd@lists.debian.org>
sub   rsa4096 2011-01-05 [E]

La huella coincidía con la publicada por Debian, así que la importé:

$ gpg --import clave_a_importar.txt

gpg: key DA87E80D6294BE9B: 64 firmas no comprobadas por falta de claves
gpg: clave DA87E80D6294BE9B: clave pública "Debian CD signing key <debian-cd@lists.debian.org>" importada
gpg: Cantidad total procesada: 1
gpg:               importadas: 1
gpg: no se encuentran claves absolutamente fiables

Con la clave ya importada repetí la verificación, esta vez con resultado correcto:

$ gpg --verify SHA512SUMS.sign SHA512SUMS.txt

gpg: Firmado el sáb 11 jul 2026 14:25:53 CST
gpg:                usando RSA clave DF9B9C49EAA9298432589D76DA87E80D6294BE9B
gpg: Firma correcta de "Debian CD signing key <debian-cd@lists.debian.org>" [desconocido]
gpg: ATENCIÓN: ¡Esta clave no está certificada por una firma de confianza!
gpg:          No hay indicios de que la firma pertenezca al propietario.
Huellas dactilares de la clave primaria: DF9B 9C49 EAA9 2984 3258  9D76 DA87 E80D 6294 BE9B

La advertencia sobre la confianza de la clave no significa que la firma sea incorrecta. GPG indica que la firma criptográfica es válida, pero que la clave importada no forma parte de una cadena de confianza establecida en mi llavero, lo que da igual porque se comprobó manualmente que la huella coincidía con la publicada en la página oficial de Debian.

Por último quedaba comprobar que la ISO descargada correspondía con las sumas contenidas en el archivo que acababa de verificar:

$ sha512sum -c SHA512SUMS.txt 2>/dev/null | grep debian-13.6.0-amd64-netinst.iso

debian-13.6.0-amd64-netinst.iso: La suma coincide.

Con la firma de SHA512SUMS.txt verificada y la suma SHA-512 de la ISO coincidiendo, podía dar por buena la imagen y pasar a preparar el USB de instalación.

Instalación de Debian

Con el USB preparado arranqué el Mini PC desde él e instalé Debian sin entorno gráfico. Durante el proceso asigné al equipo el nombre minipc y el dominio home.arpa, utilicé el particionado guiado con LVM sin cifrado sobre el SSD interno y, en la selección de software, instalé únicamente lasutilidades estándar del sistema y, importante, el servidor SSH. A partir del primer reinicio podría continuar toda la configuración desde mi ordenador, sin necesidad de mantener el Mini PC conectado a una pantalla y un teclado.

Primeros ajustes

Durante la instalación había creado tanto la cuenta de root como mi usuario habitual. Una vez dentro del nuevo sistema instalé sudo y añadí este último al grupo correspondiente, de forma que no fuese necesario trabajar directamente como root para las tareas administrativas. A partir de aquí repetí buena parte de la configuración que ya había hecho con la Raspberry Pi. Reservé para el Mini PC la dirección 192.168.0.3 en el router, configuré el acceso por SSH mediante clave pública para no utilizar contraseña y endurecí la configuración del servidor SSH siguiendo el proceso ya explicado en la entrada anterior. Si para la Raspberry había elegido el nombre de la máquina en color rosa, al Mini PC le otorgué el cian.

Preparación del disco para Bitcoin

El Mini PC dispone de un SSD NVMe interno de 512 GB, pero la blockchain va a almacenarse en el Samsung T7 de 2 TB que usaba anteriormente en la Raspberry Pi para el mismo propósito

Para comprobar como detecta Debian los discos:

lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINTS,MODEL,SERIAL

El SSD externo aparecía como /dev/sda, con aproximadamente 1,8 TB de capacidad útil. Preparé una partición para utilizar todo el espacio disponible y la formateé como ext4, un sistema de archivos apropiado para un disco que va a permanecer conectado permanentemente a una máquina Linux.

Después creé el punto de montaje, configuré el disco para que quedase montado en el SSD y hice una comprobación final:

$ df -h /data

S.ficheros     Tamaño Usados  Disp Uso% Montado en
/dev/sda1        1.8T   2.1M  1.7T   1% /data

El SSD de 2 TB estaba correctamente formateado y montado en /data, prácticamente vacío y listo para alojar la blockchain. Pero esto ya será objetivo de la siguiente entrada.

Cacharreando con la Raspberry

Instalación y configuración de una Raspberry Pi, con Pi-hole, Unbound y acceso remoto mediante Tailscale.

En la entrada anterior expliqué cómo había creado una red local propia dentro de la red compartida de mi edificio. Para seguir con esta infraestructura doméstica me he comprado un MINISFORUM UM870 Slim, con el que planeo cacharrear y ejecutar varios servicios: un nodo de Bitcoin, un modelo local de Qwen y un servidor multimedia con Jellyfin. Pero como ya mencioné en la anterior entrada, tenía inutilizada una Raspberry Pi 5 que he querido reaprovechar. Antes la usaba como nodo de Bitcoin, pero en la última ocasión tardó más de un mes en descargarse la blockchain entera, mostrando así sus límites. Así que he decidido asignarle una función más modesta, pero también más adecuada: actuar como servidor de red. De momento ejecutará Pi-hole y Unbound, dos servicios poco exigentes que pueden permanecer encendidos de forma permanente, y también me servirá como punto de acceso remoto a la red mediante Tailscale.

Instalación del sistema operativo

Para instalar el sistema operativo utilicé Raspberry Pi Imager. Mi intención inicial era usar una tarjeta microSD de 64 GB, pero no conseguí que funcionase correctamente, así que terminé utilizando otra de 32 GB; a priori no hay problema ya que para los servicios que voy a instalar es más que suficiente. Instalé Raspberry Pi OS Lite de 64 bits, sin entorno gráfico, ya que la Raspberry se administrará mediante SSH.

Como apunte, mencionar que la configuración de SSH realizada desde Raspberry Pi Imager no funcionó como esperaba, algo que ya me había pasado en la anterior instalación del SO, por lo que tuve que activarlo manualmente, creando un archivo vacío ssh en la partición de arranque de la tarjeta:

touch /ruta/a/bootfs/ssh

También es necesario crear manualmente un usuario. Para ello generé una contraseña cifrada:

openssl contraseña -6

El comando devuelve una cadena que comienza normalmente por $6$. Después creé, en la misma partición, un archivo llamado userconf con el usuario y la contraseña cifrada:

usuario:contraseña_cifrada

Una vez hecho esto ya es posible conectarse mediante ssh.

Reserva de una dirección IP

En el primer arranque, el router asigna automáticamente a la Raspberry una dirección mediante DHCP, el sistema utilizado por el router para repartir automáticamente direcciones IP entre los dispositivos conectados. Pero esta dirección puede cambiar tras un reinicio del router, una desconexión prolongada o la renovación de la concesión DHCP. En un teléfono o un ordenador portátil me da igual, pero aquí si importa porque necesito que el resto de los dispositivos sepan siempre donde encontrar a la Raspberry (pasará lo mismo con el MiniPC) Así que entré en el router y creé una reserva DHCP; de este modo, la Raspberry sigue solicitando automáticamente su configuración de red, pero el router le entrega siempre la misma dirección. Para crear la reserva es necesario utilizar la dirección MAC de la interfaz Ethernet de la Raspberry.

Elegí la dirección .2 porque está fuera del rango DHCP dinámico, que está entre .100 y .200, y porque la .1 ya pertenece al router:

192.168.0.1   Router
192.168.0.2   Raspberry Pi

Después de reiniciar la conexión comprobé con éxito que podía acceder mediante la nueva dirección:

ssh pablo@192.168.0.2

Acceso sin contraseña y endurecimiento de SSH

Tras ejecutar el anterior comando tuve que introducir la contraseña, algo que quiero evitar en futuras ocasiones, así que copié mi clave pública a la Raspberry:

ssh-copy-id pablo@192.168.0.2

Una vez comprobado que el acceso mediante clave pública funcionaba correctamente, desactivé la autenticación mediante contraseña. Accedí a la Raspberry y en el archivo /etc/ssh/sshd_config dejé estas tres opciones; tras guardarlo, reinicié el servicio.

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

Un color para cada máquina

Cuando se administran varios equipos desde una misma terminal es fácil olvidar en cuál de ellos se está trabajando. Para distinguirlos modifiqué el prompt de la Raspberry. En mi ordenador local está por defecto todo en verde, tanto el nombre de usuario como el nombre del equipo. Aquí dejé el nombre del usuario en verde y asigné al equipo un tono magenta claro recordando así al nombre de la máquina. Para el MiniPC haré lo mismo pero con otro color, y aquí también crearé distintos usuarios para las distintas funciones (Bitcoin, LLM, Jellyfin) y otorgaré un color distinto a cada usuario (el usuario bitcoin será obviamente de color naranja, etc)

La configuración se guarda en ~/.bashrc.

Pi-hole

Con una dirección estable ya podía instalar Pi-hole, el servicio principal de la Raspberry.

Pi-hole funciona como servidor DNS para toda la red local. Cuando un dispositivo intenta acceder a un dominio, la consulta pasa primero por la Raspberry. Si el dominio aparece en una lista de publicidad o seguimiento, Pi-hole impide su resolución. La principal ventaja es que el bloqueo se aplica a toda la red, sin tener que instalar extensiones en cada navegador o dispositivo.

La instalación se realiza mediante el script oficial:

curl -sSL https://install.pi-hole.net | bash

Una vez instalado, configuré el router para que anunciase la Raspberry como servidor DNS de la red.

Unbound

Pi-hole decide qué dominios bloquear, pero necesita otro servidor para resolver los que sí están permitidos. Por defecto en Pi-Hole usaba los DNS de Google y también da la opción automática de usar los de Cloudflare u OpenDNS, pero también es posible usar Unbound en la misma Raspberry.

Unbound es un resolutor DNS recursivo. En lugar de depender de un resolutor público como Google (8.8.8.8), Cloudflare (1.1.1.1) o cualquier otro proveedor, busca por sí mismo la respuesta recorriendo los distintos niveles del sistema DNS.

Se puede instalar Unbound desde los repositorios de Debian:

sudo apt install unbound

Configuré Unbound para escuchar en el puerto 5335 y, desde el panel de Pi-hole, desactivé los servidores DNS externos que venían seleccionados por defecto. En su lugar, añadí 127.0.0.1#5335 como servidor DNS personalizado, de modo que Pi-hole enviase a Unbound todas las consultas que no hubiera bloqueado. Ambos servicios quedan configurados para arrancar automáticamente con el sistema.

Tailscale

La dirección 192.168.0.2 solo es accesible desde dentro de mi red local. Para poder administrar la Raspberry cuando estoy fuera instalé Tailscale, que crea una red privada entre varios dispositivos utilizando WireGuard. Gracias a ello puedo conectarme a la Raspberry sin abrir el puerto de SSH en el router ni exponerla directamente a Internet.

# Instalación de tailscale
curl -fsSL https://tailscale.com/install.sh | sh

# Inicio de conexión
sudo tailscale up

El comando muestra un enlace para autenticar el dispositivo. También instalé Tailscale en mi ordenador e inicié sesión con la misma cuenta. Una vez conectados ambos equipos a la misma red de Tailscale, pude acceder a la Raspberry mediante SSH utilizando la dirección que Tailscale le había asignado:

ssh pablo@IP_DE_TAILSCALE

Dentro de casa puedo utilizar la dirección local 192.168.0.2; fuera de ella utilizo la dirección de Tailscale. También configuré la Raspberry como subnet router, lo que permite usarla como puerta de entrada a mi red local y acceder desde fuera a otros dispositivos que no tengan Tailscale instalado. Para ello anuncié la red 192.168.0.0/24 desde la Raspberry y autoricé la ruta desde el panel de administración de Tailscale.

Nombres para los dispositivos de la red

Para no tener que recordar la dirección IP de cada dispositivo, también asigné nombres DNS locales desde el panel de Pi-hole. La red queda organizada así:

192.168.0.1   router.home.arpa
192.168.0.2   raspberry.home.arpa
192.168.0.3   minipc.home.arpa 

De este modo puedo acceder a cada equipo utilizando su nombre en lugar de su dirección IP. La dirección del MiniPC queda reservada para cuando termine de incorporarlo a la red, lo que detallaré en el siguiente artículo.

Una red propia

Instalación de un router propio y futura arquitectura de mi red local

En mi edificio la conexión a Internet forma parte de los servicios comunes. Además del WiFi disponible en las zonas comunes, cada vivienda tiene una toma Ethernet desde la que puede conectarse directamente a la red general, sin contratar una línea propia ni instalar un módem individual. El sistema es muy cómodo, pero tiene un inconveniente para quienes queremos cacharrear: no puedo administrar el router del edificio. Así que me he comprado uno.

En mi piso de Madrid monté un nodo de Bitcoin, sobre todo para entender mejor cómo funciona la Red. Tras mudarme a México desmonté la infraestructura y me traje parte del hardware:

  • una Raspberry Pi 5;
  • un SSD de 1 TB;
  • un SSD de 2 TB;
  • una tarjeta microSD de 64 GB

El SSD de 1 TB, que al principio parecía más que suficiente, terminó quedándose pequeño. La cadena de bloques de Bitcoin ya se acerca a los 800 GB y todavía hay que reservar espacio para los índices y su crecimiento futuro. Por eso compré después el de 2 TB. La Raspberry también ha empezado a mostrar sus límites. Descargar y verificar la cadena completa puede llevar varias semanas y, aunque sigue siendo una máquina muy útil para servicios ligeros, ya no me parece el mejor lugar para concentrar todo el proyecto. He decidido añadir un mini-PC: un MINISFORUM UM870 Slim con un Ryzen 7 8745H, 32 GB de memoria y un SSD interno de 512 GB. Todavía no lo tengo; está de camino. Además de ejecutar Bitcoin Core, quiero utilizarlo para Jellyfin y para probar modelos de lenguaje locales. No pretendo entrenar modelos ni montar un pequeño OpenAI en el salón, sino ejecutar modelos ya existentes y utilizarlos con documentos propios mediante un sistema RAG. Más adelante volveré con detalle sobre cada una de estas piezas.

Antes de instalar nada, sin embargo, necesitaba construir el lugar en el que todas estas máquinas van a convivir: una red local propia.

El nuevo router

He comprado un Steren COM-870+ y he conectado la toma Ethernet de la vivienda a su puerto WAN. El router obtiene automáticamente conexión desde la red del edificio y la utiliza como salida a Internet. Al otro lado crea una red distinta, administrada por mí:

Internet
   │
Red compartida del edificio
   │
Toma Ethernet de la vivienda
   │
Puerto WAN del router propio
   │
Red LAN
   ├── Ordenador
   ├── Raspberry Pi 5
   ├── Mini-PC
   └── Otros dispositivos

La infraestructura del edificio sigue proporcionando Internet, pero deja de decidir cómo se comunican mis dispositivos. Desde la perspectiva de mi LAN, la red del edificio pasa a formar parte del exterior.

WAN y LAN

El router mantiene dos redes separadas. En la interfaz WAN se comporta como cualquier otro dispositivo conectado al edificio: solicita automáticamente una dirección y utiliza la puerta de enlace que recibe. No necesito configurar PPPoE ni introducir un usuario y una contraseña.

En la interfaz LAN crea una subred privada con un rango distinto al de la red exterior. El propio router actúa como puerta de enlace, servidor DHCP y punto de acceso inalámbrico para todos mis dispositivos. Los equipos de uso cotidiano pueden recibir direcciones automáticamente. Las máquinas del servidor, en cambio, tendrán reservas DHCP para conservar siempre la misma IP. De esta forma podré acceder a la Raspberry y al mini-PC sin tener que buscar cada vez qué dirección les ha asignado el router.

Una red detrás de otra

Al colocar mi router detrás del del edificio aparece una configuración de doble NAT.

El primer router traduce el tráfico entre la red del edificio e Internet. Mi router vuelve a traducirlo entre su interfaz WAN y mi LAN. Esto puede complicar la apertura tradicional de puertos, ya que no controlo el primer router, pero tampoco tengo intención de exponer directamente las máquinas de casa. Los servicios internos permanecerán accesibles únicamente desde la LAN. Para entrar desde fuera podré utilizar Tailscale, Tor o túneles específicos, según el caso. Mi idea es almacenar aquí mi propia web, y para ello podré utilizar Cloudflare Tunnel sin necesidad de abrir puertos en el router del edificio.

El doble NAT no es la solución más elegante, pero me permite construir una red propia sin modificar una infraestructura que no controlo.

La arquitectura del servidor

La Raspberry Pi 5 funcionará como equipo auxiliar de la red. En ella instalaré servicios ligeros que conviene mantener disponibles aunque el servidor principal esté apagado o reiniciándose:

  • Pi-hole para filtrar DNS y publicidad;
  • Unbound como resolvedor DNS;
  • Tailscale para acceder remotamente a la red;
  • Uptime Kuma para supervisar los servicios;
  • DNS local para identificar las máquinas por su nombre.

El mini-PC concentrará las tareas que requieren más potencia. Tendrá 32 GB de memoria y un SSD interno de 512 GB, que utilizaré para el sistema operativo, Docker, el modelo de lenguaje local y los documentos relacionados con este entorno. El SSD externo de 1 TB estará dedicado a la biblioteca de Jellyfin. El SSD de 2 TB se reservará para Bitcoin Core, la cadena de bloques, chainstate, electrs y un explorador de bloques. Más adelante podría añadir otros servicios relacionados, pero esa parte tendrá su propia entrada.

La arquitectura prevista será, por tanto:

Router
│
├── Raspberry Pi 5
│   ├── Pi-hole
│   ├── Unbound
│   ├── Tailscale
│   ├── Uptime Kuma
│   └── DNS local
│
└── Mini-PC
    ├── SSD interno de 512 GB
    │   ├── Sistema operativo
    │   ├── Docker
    │   ├── LLM local
    │   └── RAG y documentos
    │
    ├── SSD externo de 1 TB
    │   └── Jellyfin
    │
    └── SSD externo de 2 TB
        ├── Bitcoin Core
        ├── electrs
        └── Explorador de bloques

En la próxima entrada empezaré a configurar la Raspberry Pi y los primeros servicios de red.