# Metodología de medición Este documento existe porque una cifra sin método no es un dato: es una afirmación. Aquí se registra **qué se midió, cómo, con qué binario, cuántas veces**, y —lo más importante— **qué no se midió**. Es el único documento de metodología del repositorio. Al final lleva un [anexo histórico](#anexo-histórico--metodología-de-medida-v4) con la metodología de la iteración v4, conservada para que una cifra antigua pueda rastrearse hasta el método que la produjo; las dos versiones estuvieron en ficheros separados y se fusionaron para que no hubiera dos lugares donde buscar cómo se mide. ## Reglas 1. **Siempre binario de release.** El benchmark de cómputo da **0,49 GFLOPS en debug** y **34-37 GFLOPS en release**: 70× de diferencia. El binario se niega a medir en debug (`--allow-debug` lo fuerza, y entonces la cifra no sirve para decidir nada). `verify.sh` comprueba que la negativa ocurre de verdad. 2. **Mediciones intercaladas cuando se comparan dos versiones.** Un A/B en dos sesiones distintas mide la deriva térmica del equipo, no el cambio. En la fase 6 una comparación secuencial dio «681 → 627 rps» (una regresión del 8 %); al intercalar A/B en el mismo proceso la diferencia real era **613,8 → 614,2 rps**, ruido puro. Ver [`hot-paths.md`](performance/hot-paths.md). 3. **Nada se declara medido sin un comando reproducible.** Cada tabla indica cómo se obtuvo. 4. **Lo que no se midió se dice.** Un hueco honesto es información; un número inventado es un error con fecha. ## Qué mide cada herramienta | Herramienta | Qué mide | Repeticiones | Dónde se guarda | | --- | --- | --- | --- | | `hecate-node profile` | GFLOPS, ancho de banda, disco, latencia de red del nodo local | 3 (matmul), 21 muestras (red) | `docs/benchmarks/hardware-local.json` | | `hecate-node bench --mock` | Latencia y throughput del plano de control con datos simulados | `--requests` por nivel, 3 niveles | tabla en `README.md` | | `hecate-node bench --compute-latency-ms N` | Lo mismo, con cómputo inyectado para que el camino caliente tenga carga realista | 5 repeticiones intercaladas | [`hot-paths.md`](performance/hot-paths.md) | | `scripts/measure-host-guarantee.ps1` | CPU y RAM del proceso, y latencia bajo carga externa del host | ventana de 25 s, muestreo 250 ms | `docs/benchmarks/host-guarantee.json` | | `cargo test --workspace` | Corrección, no rendimiento | 1 | — | | `just fuzz` | Robustez ante bytes basura | 60 s por defecto | — | ## Por qué `bench` necesita `--compute-latency-ms` Con el plano de datos simulado a 0 ms, una petición atraviesa el camino caliente en microsegundos: el tiempo se va en TCP y en el framing, y **cualquier punto de serialización queda invisible**. Eso explica que optimizaciones estructurales correctas (quitar un cerrojo exclusivo del camino caliente) no mostraran mejora en la fase 6. Con `--compute-latency-ms 5` el mock tarda 5 ms por fragmento, como un modelo pequeño real. Un pipeline de 3 nodos y 12 capas atraviesa unos diez fragmentos, de ahí p50 ≈ 50 ms. En ese escenario el cerrojo **sí** se solapa entre peticiones… y aun así la diferencia medida fue inexistente. La conclusión honesta está en [`hot-paths.md`](performance/hot-paths.md). ## Qué NO se ha medido | Cosa | Por qué | | --- | --- | | Perfil de los tres nodos del plan (Geekom, T14, ThinkPad antiguo) | **no hay acceso de red**; sus perfiles son declarados, no medidos (ver `portability.md`) | | Garantía del 30 % de CPU/RAM en esos tres nodos | misma razón: sólo se ha medido este equipo | | Rendimiento con un modelo de verdad (PyTorch) | el plano de datos de las mediciones es simulado; con cómputo real las cifras serían otras | | Ancho de banda de red entre nodos | sólo hay loopback en este equipo (`0,036 ms`), que no es un enlace | | Consumo energético | el catálogo lo predice (`predict_energy_j`), no se mide con vatímetro | | GPU AMD / Apple Silicon | no hay hardware para probarlo | ## Cómo leer las cifras de este repositorio - **`34-37 GFLOPS`** es un *suelo*, no un techo: el benchmark es una multiplicación de matrices sin SIMD explícito ni BLAS. Un `torch.matmul` daría mucho más. - **`12-20 GB/s`** varía entre ejecuciones (copia de 32 MiB); el rango importa más que el punto medio. - **Las latencias del pipeline** son del plano de control con datos simulados. Incluyen TCP, framing y enrutamiento; **no** incluyen inferencia real. - **La huella del 8,1 % de CPU** es del *plano de control*. Con el plano de datos cargando un modelo, la CPU será otra cosa: la garantía de convivencia se refiere al enjambre completo y hoy sólo está verificada para el control. ## Reproducir desde cero ```bash cargo build --release -p hecate_node ./target/release/hecate-node profile --out docs/benchmarks/hardware-local.json just bench 400 just guarantee 25 just fuzz 60 ``` Todo sin red salvo `just links`, que es la única tarea que sale a Internet y por eso no está en `just check`. --- ## Anexo histórico · metodología de medida v4 Este anexo se conserva por trazabilidad: describe cómo se comparó la iteración **v4** de la arquitectura interna contra la **v3**, con las cifras marcadas `[medido]` y `[pendiente]`. No es la metodología vigente —ésa es la de arriba— y sus comparaciones se refieren a versiones internas que ya no existen en el árbol. Se documenta para que una cifra antigua pueda rastrearse hasta el método con el que se obtuvo, no para volver a usarla. ## Metodología de medida v4: qué comparar y cómo no engañarse > Esta metodología describe **cómo medir** la mejora de v4 sobre v3. Las cifras que > aparecen como **[medido]** son reproducibles hoy; las que aparecen como **[pendiente]** > requieren hardware o campañas que no se han ejecutado. La distinción no es retórica: un > documento de benchmarks que mezcla ambas es un documento de marketing. ### 1. Qué se mide | Magnitud | Unidad | Por qué importa | | --- | --- | --- | | Latencia p50 / p95 / p99 por token | ms | p50 engaña: el clúster se juzga por la cola | | Throughput sostenible | tok/s | lo que aguanta sin divergir | | Tiempo de failover | ms | cuánto se degrada el servicio cuando cae un nodo | | Tráfico entre nodos por token | bytes | distingue MoE (0) de pipeline (>0) | | Utilización por etapa | ρ | si alguna etapa supera 1.0, la latencia diverge | | Memoria por nodo | bytes | el coste real de replicar el tronco | **La regla que ordena todo:** un benchmark que sólo publica p50 no está midiendo un clúster, está midiendo una máquina. La cola es donde aparece el comportamiento distribuido. ### 2. v3 frente a v4, magnitud a magnitud #### 2.1 Latencia de red | | v3 | v4 | Cómo se mide | | --- | --- | --- | --- | | Transporte | gRPC sobre HTTP/2 | `TransportEngine` con TCP + sonda RDMA | `transport::tests` (loopback) | | Framing | Protobuf por mensaje | `u32` BE + payload crudo | `round_trip_preserves_bytes` | | Coste por salto en el planificador | no modelado | `PlanOptions::hop_ms` | `network_hops_cost_proportionally` | **[medido]** el framing: ida y vuelta de 1 KiB en loopback, con contadores de bytes y tramas. **[pendiente]** el RTT real LAN y la comparación con gRPC: requiere dos máquinas y un arnés de captura. Lo que **no** se puede afirmar: que v4 tenga menos latencia de red que v3 en producción. El motor TCP es más ligero que gRPC por mensaje, pero el plano de datos sigue hablando gRPC. La mejora está en el *planificador* (modela el coste de red) y en la *abstracción* (cambiar de transporte no toca la lógica), no en un número de milisegundos medido. #### 2.2 Partición | | v3 | v4 | | --- | --- | --- | | Unidad | capa completa | capa, cabeza de atención o sub-bloque FFN | | Selección | reparto ponderado fijo | mínimo coste entre 3 granularidades | | Estabilidad | no comprobada | `lyapunov_stability_check` con margen 0.95 | **[medido]** con la topología de ejemplo (12 capas, 3 nodos de 7.32 / 2.76 / 0.96 capas-equivalentes de capacidad): | Granularidad | Capas colocadas | Desbordamiento | Latencia estimada | | --- | --- | --- | --- | | `layer` | 9.0 | 3.0 capas → 120 ms | ~165 ms | | `attention` | 11.04 | ~1.0 capa → 40 ms | ~44 ms | La relación 165/44 ≈ **3.7×** sale del modelo de coste, no de una medición de reloj. Es una predicción del planificador, y debe contrastarse con `cargo run -p hecate_node -- bench` antes de publicarla como mejora real. **[pendiente]** la comparación de extremo a extremo con un modelo real (LLaMA-7B) en dos nodos físicos. Es el criterio de aceptación de la semana 2 del plan y **no está cumplido**: falta hardware con capacidad para el modelo. #### 2.3 MoE **[medido]** OLMoE sobre la topología de ejemplo: ```text covered_experts: 1024/1024 balanced: true (342/341/341) per_token_cross_node_traffic_bytes: 0 session_histogram: {gpu: 67, laptop: 68, mini: 65} de 200 peak_memory_utilization: 0.17 ``` **[medido]** Mixtral en dos nodos domésticos: rechazado por capacidad, con el motivo (`384 GB de expertos`). Un planificador que dice "no cabe" es más útil que uno que genera un plan que revienta. **[pendiente]** cargar un MoE real en dos nodos y verificar con captura de red que no hay tráfico de inferencia. Hoy el cero es una invariante del plan, no una observación de red. #### 2.4 Crecimiento **[medido]** `grow_depth` sobre un sustrato congelado: la pérdida baja, el 90 %+ de los parámetros no se entrena, y el test verifica que **sólo** la capa nueva conserva `requires_grad`. **[medido]** `merge_with_nash` con pérdidas 0.05 y 5.0: el resultado queda más cerca del adaptador bueno que del punto medio que daría FedAvg. **[pendiente]** la mejora en una tarea de razonamiento real, que es el criterio de la semana 5. La tarea sintética demuestra el mecanismo, no la utilidad. #### 2.5 Decisión (JEV) **[medido]** contra un servidor local: elección válida respetada, opción inexistente descartada, timeout < 350 ms con `timeout = 50 ms`, endpoint inalcanzable degradado. **[pendiente]** todo lo que implique el servicio real: precisión de ruta, latencia p50/p95 de decisión, y comparación con el router determinista. ### 3. Plantilla de publicación ```markdown ## Entorno CPU: ( hilos) GPU: ( GB) Red: Rust: Python: Modelo: ## Latencia por token (n = 1000 peticiones) | Etapas | p50 | p95 | p99 | tok/s | |---|---|---|---|---| | 2 | | | | | | 3 | | | | | ## Failover | Escenario | Tiempo hasta primera respuesta correcta | |---|---| | caída del nodo de cola | | | caída del nodo de cabeza | | ## Tráfico entre nodos | Paradigma | bytes/token (captura) | |---|---| | pipeline denso | | | MoE replicado | 0 (esperado) | ``` ### 4. Cómo no engañarse 1. **Tres repeticiones como mínimo, y publicar la dispersión.** Un número sin varianza no es un resultado, es una anécdota. 2. **Calentar antes de medir.** El primer token paga compilación de kernels, apertura de conexiones y carga de pesos. 3. **Separar control y datos.** `bench --mock` mide el plano de control; `--data-plane` incluye el salto real. Mezclarlos atribuye al diseño lo que es coste de serialización. 4. **No comparar con una configuración por defecto distinta.** Si v3 usaba 16 hilos y v4 seis, la mejora es del cambio de hilos. 5. **Publicar los rechazos.** "Mixtral no cabe en dos nodos" es un resultado. Ocultarlo convierte el benchmark en un folleto. ### 5. Estado real de la campaña | Semana | Entregable | Estado | | --- | --- | --- | | 1 | Transporte con abstracción | ✅ TCP completo + sonda RDMA · ❌ motor RoCEv2 | | 2 | Partición de grano fino | ✅ planificador · ❌ medida end-to-end con LLaMA-7B | | 3 | MoE sin tráfico entre nodos | ✅ planificador · ❌ captura de red | | 4 | JEV como orquestador | ✅ cliente + degradación · ❌ contraste con el servicio real | | 5 | Crecimiento modular | ✅ mecanismo medido en tarea sintética · ❌ tarea de razonamiento | | 6 | Benchmarks v3–v4 | ❌ no iniciada (depende de las anteriores) | Esta tabla es la parte más importante del documento. Un plan de seis semanas con seis semanas verdes sería la señal de que no se está midiendo nada.