Source document
Perfilado de nodos: medir, no asumir
Spanish text, transcribed from the repository. The unformatted
source is
data-architecture/profiling.md.
Regla del documento: cada número que aparece aquí viene de una medición o de una detección, y está marcado como tal. Lo que no se puede medir se dice, no se rellena.
1. El problema
Un enjambre heterogéneo no puede tratar a todos los nodos igual. La forma habitual de distinguirlos es una tabla de especificaciones: "un ThinkPad T14 tiene 16 GB de RAM". Esa tabla miente por tres motivos, y los tres se han visto en este proyecto:
- El hardware se degrada. Un portátil con el disipador lleno de polvo rinde la mitad que el día que se compró, con las mismas especificaciones.
- La carga cambia. Un nodo al 85 % de CPU no sirve para lo mismo que uno vacío, aunque sean el mismo modelo.
- Las especificaciones no dicen lo que importa. "6 GB de VRAM" no dice nada sobre si esa VRAM se puede escribir a 700 GB/s o a 90.
ASA resuelve esto en entornos IoT heterogéneos perfilando primero y agrupando después: los tiers salen de los datos del parque, no de un catálogo de modelos de dispositivo. HECATE adopta la misma idea con dos diferencias: el perfil es continuo (no sólo de arranque) y cada campo lleva registrada su procedencia.
2. Qué mide el benchmark de arranque
crates/mesh-profiler/src/bench.rs. Cuatro pruebas, acotadas para que el perfilado completo ronde uno o dos segundos:
| Prueba | Método | Qué obtiene | Límite declarado |
|---|---|---|---|
| Cómputo | multiplicación de matrices 192³ × 3, orden i-k-j | GFLOPS fp32 | sin SIMD ni BLAS: es un suelo, no el techo del nodo |
| Memoria | suma de un buffer de 32 MiB × 3 pasadas, cuatro acumuladores | GB/s | un solo hilo: no satura un bus multicalanal |
| Disco | escritura y lectura de 24 MiB con sync_all | MB/s | secuencial; no mide IOPS |
| Red | eco TCP en 127.0.0.1, 21 muestras, mediana | RTT en ms | mide la pila del sistema, no el enlace entre máquinas |
Los dos últimos "límite declarado" importan. El benchmark de red usa el bucle local: si un nodo tarda 2 ms en hablar consigo mismo, ningún enlace real será más rápido. Sirve como suelo, no como predicción.
Que el benchmark de cómputo sea un suelo es la razón de que el selector no use un roofline clásico sobre esos GFLOPS: predeciría segundos donde hay milisegundos. Ver model-zoo.md §4.
Medido en esta máquina (Windows 11, Core Ultra 7 255H, RTX PRO 500)
| Métrica | Binario debug | Binario release |
|---|---|---|
| GFLOPS fp32 | 0,49 | 34–37 (3 medidas) |
| Ancho de banda | 1,32 GB/s | 12–20 GB/s (varía entre ejecuciones) |
| Tier resultante | mid | mid |
Dos conclusiones que no son obvias y conviene tener escritas:
- Hay que perfilar con el binario de release. La diferencia es de 70× en cómputo. Un perfil tomado con
cargo rundescribe un nodo que no existe, y el tier podría caer alowen un equipo que no lo es. Los benchmarks viven en el mismo crate que el resto, así que el compilador de depuración también los ralentiza. - El benchmark de memoria mejoró al romper la cadena de dependencias. Con un solo acumulador daba 7,5 GB/s; con cuatro, entre 12 y 20 según la ejecución. La primera cifra era del bucle, no de la memoria. La varianza de la segunda (casi 2× entre pasadas) es en sí un dato: tres pasadas de 32 MiB son pocas para un benchmark estable, y el selector sólo necesita el orden de magnitud. Si se quiere una cifra publicable, hay que subir
MEMORY_BYTESy repetir. Aun así, 12–20 GB/s sigue por debajo de los ~50 GB/s que esta máquina sostiene con una herramienta multihilo: un hilo escalar no satura el bus, y por eso el límite está declarado en la tabla.
3. Qué detecta el sistema operativo
crates/mesh-profiler/src/detect.rs:
| Dato | Fuente | Plataforma |
|---|---|---|
| Núcleos, frecuencia, marca de CPU | sysinfo | Linux, macOS, Windows |
| RAM total y disponible | sysinfo | Linux, macOS, Windows |
| Tipo de disco (SSD/HDD) | sysinfo | Linux, macOS, Windows |
| GPU y VRAM | nvidia-smi | donde haya driver NVIDIA |
| TOPS de NPU | no se detecta | ninguna API portable los expone |
Lo que no se detecta, y por qué
GPU AMD y Apple. rocm-smi e ioreg tienen formatos distintos y no hay máquina aquí para probarlos. Un nodo con GPU AMD se perfila como nodo sin acelerador: la respuesta conservadora. El efecto es que pide variantes para CPU, que funcionan; lo contrario —asumir una GPU que luego no está— daría un plan que falla al cargar.
TOPS de NPU. Los declara el operador con --npu-tops y el perfil los marca FieldSource::Declared, no Measured. La distinción es la que impide que un dato inventado se cuele en una decisión como si fuera una medición.
Multi-GPU
nvidia-smi devuelve todas las tarjetas. El perfil guarda la VRAM de la mayor, no la suma, y el nombre incluye el número (2x RTX 3080). Sumar llevaría a aceptar variantes que no caben en ninguna tarjeta por separado.
4. Procedencia: el campo sources
Cada perfil lleva un mapa campo → origen:
"sources": {
"cpu_gflops": "measured",
"memory_bandwidth_gbps": "measured",
"ram_gb": "detected",
"gpu_vram_gb": "absent",
"npu_tops": "declared"
}
FieldSource::trustworthy() devuelve true sólo para Measured y Detected. Un consumidor que quiera ser estricto puede exigir que cpu_gflops sea medido antes de fiarse de una predicción; el dato está ahí para decidirlo, en lugar de tener que confiar en el formateador.
5. Clasificación en tiers
Dos caminos, con la misma puntuación de capacidad:
score = 1.0·(gflops/100) + 0.5·(GB/s/50) + 1.5·(VRAM/8) + 0.5·(RAM/16) + 1.5·(NPU/10)
− 0.3·ln(1 + latencia_ms/10)
- Por umbrales (
classify_tier):≥ 6.0alto,≥ 1.8medio, resto bajo. Determinista y explicable. Sirve con pocos nodos. - Por histórico (
classify_tier_from_history): K-Means conk = min(3, n)sobre los perfiles del parque. Los tiers son relativos: en un parque de portátiles antiguos, el mejor sigue siendoHigh. Es lo deseable, porque la etiqueta describe el parque y no un ideal absoluto.
El K-Means está implementado a mano (capability.rs) con inicialización por punto más lejano en lugar de k-means++ aleatorio: dos ejecuciones con el mismo histórico producen el mismo agrupamiento, que es lo que permite publicar y reproducir el resultado. Un test lo verifica.
Por qué la latencia entra con logaritmo
Un castigo lineal convertiría un nodo con buena GPU y 100 ms de red en el peor del parque. Con ln(1 + ms/10), 1 ms cuesta 0.03 puntos y 100 ms cuesta 0.72: penaliza sin anular. Otro test comprueba que un nodo con GPU y 100 ms sigue siendo mejor que uno sin GPU.
6. Telemetría continua y contención
crates/mesh-profiler/src/host.rs. Dos señales, no una:
- Uso de CPU y memoria de
sysinfo(porcentaje del sistema). - Contención medida: se ejecuta dos veces la misma carga aritmética (una al perfilar, otra al muestrear) y se compara el rendimiento.
1 − actual/línea_basees cuánto se ha ralentizado este proceso.
La segunda no se puede inferir del porcentaje de CPU: un nodo al 40 % con el planificador saturado rinde peor que uno al 85 % con núcleos libres. HostMetrics::effective_load() toma el máximo de las tres señales (CPU, memoria, contención) porque basta con que una esté mal para que la IA local deba ceder.
7. Uso
# Perfil completo del nodo local, con benchmarks de disco y red
./target/release/hecate-node profile --node-id t14
# Perfil rápido (sin disco ni red) guardado para reutilizarlo
./target/release/hecate-node profile --fast --out perfil-t14.json
Usa siempre ./target/release/...: con el binario de depuración las cifras no describen el nodo (ver §2).
8. Estado real de la campaña de perfiles
| Semana | Entregable | Estado |
|---|---|---|
| 1 | Perfilador y clasificación por tiers | ✅ benchmarks reales, dos vías de clasificación · ❌ no se han perfilado los tres nodos físicos (Geekom, T14, ThinkPad): no están accesibles desde esta máquina |
| 1 | El perfil se actualiza al cambiar la carga | ✅ HostSampler con contención medida · ❌ no hay bucle de re-perfilado periódico programado en el orquestador |
Que la primera casilla esté a medias es el dato más importante de esta sección: el código perfila esta máquina, y la clasificación de los tres nodos del plan sigue siendo una predicción hasta que se ejecute en ellos.
9. Límites
- El benchmark de cómputo no usa SIMD ni BLAS: sus GFLOPS son un suelo.
- El benchmark de memoria usa un solo hilo: mide menos de la mitad del bus real.
- Hay que perfilar con el binario de release;
cargo runinfravalora por un orden de magnitud. - El benchmark de red mide el bucle local, no el enlace con otros nodos.
- No se detectan GPU AMD ni Apple, ni TOPS de NPU.
- El re-perfilado no está programado; hay que invocarlo.
- La contención se mide con una carga aritmética corta: un pico momentáneo puede colarse en una muestra. La histéresis del guardián está precisamente para eso.