# Telomerasa: integración de IAs locales Cómo un nodo autónomo cede lo que ha aprendido al enjambre **sin ceder sus datos**, y cómo el enjambre lo devuelve integrado a todos. ## 1. La metáfora y su traducción | Biología | HECATE | Por qué | | --- | --- | --- | | Telómero | Adaptador LoRA local | Es la parte renovable del material genético: pequeña, reemplazable, no toca el núcleo. | | Telomerasa | Protocolo de fusión federada | La enzima que permite renovar sin perder información. | | Carga neuronal | `AdapterPayload` | Lo que la célula cede al organismo. | | Linaje | `base_model` del payload | Dos adaptadores de modelos distintos no se pueden fusionar. | | Rechazo inmunitario | `TelomeraseError` por rango o capas incompatibles | Un adaptador que no encaja se rechaza en vez de corromper el modelo. | ## 2. LoRA: por qué parámetros eficientes y no pesos completos Reentrenar el fragmento compartido sería insostenible: cada nodo tendría que enviar millones de parámetros y el promedio los destruiría (dos nodos que aprendieron cosas distintas no se pueden promediar sin perder ambas). LoRA añade una actualización de rango bajo a cada capa lineal $W$: $$ y = Wx + \frac{\alpha}{r}\,BAx, \qquad A \in \mathbb{R}^{r \times d_{in}},\quad B \in \mathbb{R}^{d_{out} \times r} $$ con $r \ll \min(d_{in}, d_{out})$. Dos propiedades hacen que esto funcione: 1. **`B` se inicializa a cero** → el adaptador recién creado es la identidad. Aplicarlo no cambia el modelo, así que un nodo que no entrenó nada aporta **neutro**, no ruido. 2. **Solo se entrena `A` y `B`** → el adaptador es una fracción mínima del sustrato (el test `test_adapter_wraps_the_target_layers_only` lo comprueba), y promediarlo tiene sentido porque todos parten del mismo punto. En el modelo de tamaño de desarrollo, el adaptador supone menos del 10 % de los parámetros del fragmento. En un NCM real la proporción es mucho menor. ## 3. Entrenamiento local ```python adapter = TelomeraseAdapter(fragment, LoraConfig(rank=4, alpha=8.0)) history = adapter.fit_local(local_pairs, epochs=30, learning_rate=0.05) ``` `local_pairs` son pares `(entrada, objetivo)` con la distribución del nodo. El objetivo es que el fragmento adaptado reproduzca el comportamiento que ese nodo necesita con **sus** datos: no se mueve un solo peso compartido. Detalle de implementación que importa: el `forward` del fragmento está decorado con `@torch.no_grad()` porque en inferencia no hace falta el grafo. Para entrenar se recorre la lista de bloques directamente (`_forward_trainable`), que hace el mismo cálculo con el grafo vivo. Sin eso, `loss.backward()` falla con *"does not require grad"*. ## 4. Fusión federada (FedAvg) ```python merged = merge_payloads([payload_a, payload_b]) ``` ```text P_fusionado = Σ (n_i / Σn) · P_i ``` donde `n_i` es el número de muestras locales del nodo `i`. Un nodo con 1 000 ejemplos influye diez veces más que uno con 100, sin que nadie comparta un solo dato. Si **todos** los payloads declaran cero muestras, se usa promedio uniforme. Inventar una ponderación que nadie ha justificado sería peor que promediar plano. ### Emparejamiento de capas (*federated layer matching*) Los tensores se emparejan por **nombre canónico** (`blocks.0.qkv.lora_a`), no por posición. Dos nodos que entrenaron el mismo fragmento producen exactamente los mismos nombres. Si a uno le falta un tensor, la fusión **falla** en lugar de alinear mal y corromper el resultado. Validaciones de la fusión: | Comprobación | Motivo | | --- | --- | | Mismo `base_model` | Adaptadores de modelos distintos no describen el mismo espacio. | | Mismo `rank` | Las formas de `A` y `B` dependen del rango. | | Mismo `alpha` | La escala forma parte de las matemáticas, no del estilo. | | Mismos nombres de tensor | Emparejamiento exacto de capas. | | Formas compatibles | Se valida al decodificar cada tensor. | ## 5. Ida y vuelta completos ```mermaid sequenceDiagram participant A as Nodo A (autónomo) participant O as Orquestador (Rust) participant F as Fusionador participant B as Nodo B (enjambre) A->>A: fit_local(datos locales) A->>O: Extract → AdapterPayload(samples=500) B->>O: Extract → AdapterPayload(samples=100) O->>F: Merge([A, B]) F-->>O: AdapterPayload fusionado O->>B: Apply(fusionado) B->>B: adopta el conocimiento colectivo Note over A: conserva su adaptador y sigue siendo autónomo ``` El nodo A **no pierde** su adaptador: sigue siendo autónomo. El enjambre incorpora una copia fusionada. ## 6. El servicio gRPC `Telomerase` se registra en el **mismo puerto** que `DataPlane`: ```bash python -m hecate.dataplane --listen 127.0.0.1:50051 --layers 0-5 \ --telomerase --adapter-rank 4 --vram-gb 6 --ram-gb 32 --npu-tops 8 ``` | RPC | Qué hace | Cuándo se llama | | --- | --- | --- | | `Extract` | Devuelve el adaptador local serializado. | Cada `merge_interval`, por nodo autónomo. | | `Merge` | Fusiona los payloads recibidos. | Una vez por ronda, en el fusionador. | | `Apply` | Inyecta el adaptador fusionado. | Tras la fusión, en cada nodo. | | `Stats` | Contadores del subsistema. | Monitor del orquestador. | Ninguna está en la ruta crítica de inferencia. Un nodo sin telomerasa responde `FAILED_PRECONDITION` a `Extract` y `Apply`: existe, pero no participa del Pilar C. ## 7. Qué mejora y cuánto Medido con la tarea local de los tests (desplazar y escalar activaciones con una distribución concreta): | Escenario | MSE sobre la tarea local | | --- | --- | | Adaptador recién creado (identidad) | línea base | | Tras `fit_local` en el mismo nodo | mejora monótona con las épocas | | Nodo B tras recibir el fusionado de A | **mejora sin haber visto los datos de A** | El test `test_growth_by_fusion_improves_a_second_node` verifica exactamente eso: el nodo B reduce su error aplicando un adaptador que se entrenó con datos que nunca tuvo. ## 8. Límites honestos - **El crecimiento es gradual, no instantáneo.** Fusionar un adaptador mejora el comportamiento en la dirección de los datos locales; no convierte un modelo pequeño en uno grande. Hacen falta muchas rondas y datos representativos. - **Promediar puede empeorar a alguien.** FedAvg no garantiza que todos los nodos mejoren: si dos nodos tienen distribuciones opuestas, el promedio queda en un punto intermedio que puede ser peor que cualquiera de los dos extremos. Mitigarlo requiere ponderación por pérdida o *clustering* de adaptadores; hoy no está implementado. - **`rank` y `alpha` deben coincidir.** Es una restricción deliberada: fusionar rangos distintos exigiría proyecciones que inventarían información. - **No hay verificación de procedencia.** Un nodo podría enviar un adaptador entrenado con datos envenenados. La fusión valida *forma*, no *calidad*; en un clúster de confianza (LAN doméstica) es aceptable, en uno abierto no lo sería. - **El adaptador no sustituye al modelo.** Aporta ajuste fino local; la capacidad del modelo la fija el plan de capas y los pesos del sustrato.