El problema: una actualización aún no es un modelo utilizable
Un sensor industrial mide temperatura y vibraciones y utiliza un pequeño modelo de inteligencia artificial para clasificar el estado de una máquina. Llega una nueva versión de sus pesos, los números aprendidos durante el entrenamiento. Mientras el dispositivo los guarda, alguien desconecta la alimentación. Cuando vuelve la corriente, el sensor debe decidir qué cargar. El problema no consiste solo en terminar la transferencia: hay que evitar que una copia incompleta parezca un modelo listo y que, entretanto, se destruya la única copia funcional.
La pregunta es concreta: ¿podemos organizar la actualización para que cada reinicio encuentre la versión antigua íntegra o la nueva íntegra? Construiremos una máquina de estados, una descripción de operaciones y de los estados persistentes que dejan. Ejecutaremos todos los puntos de interrupción incluidos en ese modelo, comparando la sobrescritura directa con dos áreas separadas y un paso final de activación. El resultado será una propiedad condicionada por hipótesis explícitas, no una medida de fiabilidad de una placa comercial.
Antes de los detalles: tres significados de «listo»
Recibido significa que la transferencia ha entregado datos. Verificado significa que esos datos superan controles definidos: por ejemplo, tamaño esperado, resumen criptográfico y compatibilidad con el programa que ejecuta el modelo. Activo significa que el procedimiento de arranque puede seleccionarlos. Los tres eventos no tienen por qué coincidir. Un aviso de descarga terminada no demuestra que los datos sean persistentes; una verificación positiva no obliga a activar inmediatamente el candidato. Separarlos permite razonar sobre lo que ocurre entre ellos.
En nuestro escenario, el programa de inferencia, llamado runtime, permanece instalado: actualizamos los pesos como datos. La memoria flash conserva información sin alimentación; la RAM normalmente no. Llamaremos ranura a una región reservada para una copia. El registro de activaciones, o journal, guarda pequeñas descripciones de las copias seleccionables. Puede imaginarse como un índice de ediciones disponibles de un libro, pero la analogía termina ahí: la memoria real impone bloques de borrado, restricciones de programación y posibles escrituras interrumpidas que un índice de papel no representa.
El contrato que hace posible el razonamiento
Suponemos escrituras persistentes y ordenadas: si el programa completa un paso antes de iniciar el siguiente, ese paso permanece tras reiniciar. También suponemos un marcador final atómico: aparece sin establecer o establecido, nunca en un estado ambiguo aceptado por error. Las ranuras son independientes, la copia antigua confirmada permanece íntegra y los metadatos y el resumen esperado proceden de una fuente fiable. El contador de generaciones crece sin desbordarse. Son precondiciones del modelo matemático; un proyecto hardware debe demostrar cómo realizarlas o cambiar el protocolo cuando no se cumplan.
Introducimos interrupciones solo entre operaciones abstractas terminadas. No simulamos el circuito flash mientras cae la tensión, una escritura a medias, una caché sin vaciar ni daños en un sector vecino. La distinción es decisiva: enumerar todos los estados del programa Python no equivale a probar todos los fallos de un microcontrolador. El pequeño experimento responde a una pregunta lógica sobre el protocolo. Las pruebas eléctricas, temporales y de resistencia de la memoria siguen siendo una segunda capa de verificación, necesaria antes de utilizar el diseño en un dispositivo.
El caso mínimo: cuatro partes y una sola copia
Reducimos el modelo antiguo a cuatro bytes [1, 1, 1, 1] y el nuevo a [2, 2, 2, 2]. No son pesos útiles para inferencia: representan cuatro partes de un archivo y hacen visible cuándo una copia está completa. El programa ingenuo borra la ranura y escribe una parte cada vez. Tiene cinco operaciones y seis puntos observables de interrupción, incluido el anterior a la primera operación. Antes del borrado existe el modelo antiguo; después de todas las escrituras, el nuevo. En los cuatro puntos intermedios no existe ninguna copia íntegra.
La secuencia [2, 2, vacío, vacío] no se convierte en un modelo correcto porque la transferencia vaya a reanudarse después. Al reiniciar ya falta material necesario. Podemos detener la inferencia y esperar una recuperación, una opción a veces aceptable; no podemos afirmar continuidad operativa. Escribir primero una etiqueta «versión 2» tampoco resuelve el problema: una etiqueta no completa los datos. Si se interpreta como permiso para cargarlos, adelantarla facilita seleccionar una copia parcial. La propiedad útil afecta tanto a los datos como al procedimiento de selección.
Dos copias y una decisión tomada al final
Conservamos la copia confirmada en A y preparamos el candidato en B. Las ocho operaciones son: borrar B, escribir sus cuatro partes, verificar su resumen, añadir al journal un registro todavía inactivo y establecer el marcador de commit. Commit significa hacer visible la decisión tras reiniciar. El registro contiene generación, nombre de ranura, versión de runtime requerida y resumen esperado. La generación ordena las activaciones: no mide la calidad del modelo ni debe confundirse con una evaluación científica de su rendimiento.
Al reiniciar leemos los registros del más reciente al más antiguo. Aceptamos el primero que esté confirmado por commit, sea compatible y apunte a datos completos con el resumen correcto. Repetimos la verificación sobre los datos realmente presentes: no basta recordar que la descarga se comprobó antes de la interrupción. Si el candidato falla, probamos el registro anterior. Si ninguno supera los controles, el resultado es «ningún modelo válido». Esta respuesta explícita evita convertir la ausencia de una copia utilizable en una carga arbitraria aparentemente exitosa.
La fórmula responde a «¿qué copias pueden seleccionarse?». La letra r representa un registro; AND exige que se cumplan todas las condiciones. complete comprueba que no falten partes y hash_ok compara el resumen calculado con el esperado. El código reúne ambas comprobaciones en valid. No hay unidades físicas: son predicados lógicos. Por ejemplo, un registro con commit verdadero que exige runtime 2 no es seleccionable en un dispositivo con runtime 1, aunque todos los bytes coincidan con los recibidos.
Qué muestra realmente el experimento
La figura reúne la ejecución de ambos programas. Cada punto es un reinicio después de cierto número de operaciones completadas. En el panel superior, los puntos rojos indican una copia inutilizable; en el inferior, la versión anterior sigue seleccionada hasta el commit final. Las escalas horizontales cuentan operaciones distintas, no segundos: el punto 5 del primer panel no corresponde al mismo instante que el punto 5 del segundo. Comparamos los resultados posibles de selección, no la velocidad de actualización.

| Protocolo | Puntos examinados | Antiguo | Nuevo | Ninguno válido |
|---|---|---|---|---|
| A: overwrite | 6 | 1 | 1 | 4 |
| B: dual + commit | 9 | 8 | 1 | 0 |
Cuatro puntos problemáticos de seis no significan una probabilidad de fallo del 66,7 %. No hemos asignado una distribución temporal a las interrupciones, medido la duración de las operaciones ni modelado la tensión. Borrar un bloque y escribir un byte no tienen por qué durar lo mismo. Asimismo, cero estados inválidos entre nueve no es una estimación estadística de riesgo cero. Es una comprobación exhaustiva solo de los puntos de corte admitidos por nuestra abstracción. Confundir ambas lecturas convertiría un resultado lógico útil en una promesa de fiabilidad sin respaldo.
La razón del resultado: preservar un invariante
Un invariante es una propiedad que sigue siendo cierta tras cada paso permitido. Aquí es: antes del commit existe al menos una copia seleccionable, A. Inicialmente es cierta por construcción. Borrar y escribir B no cambia A porque las ranuras son independientes. Verificar B tampoco cambia A. Añadir un registro inactivo no hace seleccionable a B. Tenemos así una inducción sobre los pasos: si la propiedad vale antes de cada operación previa al commit, también vale después. Reiniciar en cualquiera de esos puntos devuelve A.
Cuando el marcador atómico se activa, B ya está completo y verificado; su registro más reciente lo hace preferible. Si las comprobaciones de arranque detectan un candidato inutilizable, A sigue disponible. El orden es esencial: activar el registro antes de completar los datos destruye el razonamiento. También lo es la independencia: si borrar B borra físicamente parte de A porque comparten sector, el invariante deja de cumplirse. No basta dibujar dos rectángulos en el mapa de memoria; hay que respetar la granularidad real de las operaciones destructivas.
Tres pruebas negativas, incluida una que debe fallar
El programa ejecuta además tres casos separados de la interrupción. Corrompe un byte del candidato ya activo: el resumen no coincide y arranca A. Cambia de 1 a 2 el runtime requerido en su registro: el contenido está íntegro pero es incompatible y arranca A. Finalmente, corrompe B y borra A: la selección devuelve None, ninguna copia utilizable. El último ensayo es tan importante como los dos primeros. Muestra el límite de la propiedad: dos ranuras no protegen contra perder ambas copias y el procedimiento no debe ocultarlo.
El resumen utilizado es SHA-256, una función que sintetiza los bytes en una secuencia de longitud fija. En el ejemplo detecta la modificación deliberada del dato de prueba, pero no demuestra quién proporcionó el modelo. Si un adversario puede sustituir tanto el archivo como su resumen esperado, la comparación puede aceptar un archivo no autorizado. La autenticidad del paquete, la protección de claves, las firmas y la autorización de actualizaciones requieren otro diseño. Aquí suponemos metadatos fiables y no evaluamos la seguridad del canal de distribución.
¿Cuánto espacio cuesta conservar una vuelta atrás?
Pasamos de cuatro bytes didácticos a un dimensionamiento hipotético, sin atribuirlo a una placa. El modelo ocupa W = 1.048.576 bytes, 1 MiB; cada copia tiene H = 256 bytes de cabecera. El borrado usa bloques de E = 4.096 bytes y reservamos J = 8.192 bytes para el journal. Para impedir que un borrado atraviese la frontera entre copias, redondeamos cada ranura al siguiente múltiplo de E. La fórmula responde a una pregunta física precisa: ¿cuántos bytes flash debemos reservar?
S es el tamaño reservado a una ranura y F el total; ceil significa redondear al entero superior. KiB equivale a 1.024 bytes y MiB a 1.048.576. El cálculo ejecutado da 2.113.536 bytes: ya son 16.384 bytes más que una memoria de 2 MiB, antes de añadir runtime, arranque u otros datos. Decir «el modelo ocupa un megabyte, por tanto dos bastan» sería incorrecto incluso aquí. El total corresponde a almacenamiento persistente, no al pico de RAM de inferencia, que también depende de activaciones, búferes y operadores.
Integridad no significa compatibilidad completa ni calidad
Nuestra comprobación de compatibilidad utiliza un único entero para el runtime: es deliberadamente elemental. En una aplicación de IA, los pesos pueden exigir formas de entrada, orden de canales, unidades de sensores, normalización, operadores y etiquetas específicos. Cambiar la normalización conservando pesos antiguos puede producir un sistema que arranque normalmente pero decida mal. El manifiesto del paquete debe identificar un conjunto coherente de dependencias. Restaurar solo los pesos no es una reversión completa si el resto de la configuración ya ha cambiado de forma incompatible.
El commit del programa hace seleccionable a B; no implementa un periodo de prueba con confirmación posterior. Una extensión práctica podría distinguir candidato, arranque de prueba y versión confirmada, con comprobación de funcionamiento y límite de intentos. También habría que definir qué ocurre si se corta la corriente durante la confirmación. Superar una prueba de arranque no demuestra precisión sobre datos futuros. Continuidad de arranque, corrección de la interfaz y validez predictiva son propiedades diferentes: ninguna se deduce automáticamente de las otras dos.
Sistemas reales y alternativas
La documentación oficial de MCUboot describe actualizaciones de firmware con prueba, confirmación y reversión, además de información persistente para recuperar intercambios interrumpidos. Ayuda a entender la distancia entre una regla abstracta y un cargador configurable. Nuestro código no ejecuta MCUboot, reproduce su algoritmo ni actualiza firmware.
Las alternativas dependen de la restricción dominante. Una ranura con recuperación por red ahorra espacio local, pero acepta un intervalo sin inferencia y depende de que la recuperación esté disponible. Dos ranuras conservan una copia lista a cambio de memoria adicional. Una actualización diferencial transmite solo cambios, pero menos bytes no garantizan una aplicación interrumpible sin daños: todavía hace falta preservar el estado anterior. Un archivo temporal seguido de sustitución del nombre traslada parte del problema al contrato del sistema de archivos, incluida la persistencia tras perder alimentación.
El journal también necesita un diseño que considere su duración. En el código es una lista Python ordenada al arrancar y el contador no se desborda. La memoria real es finita: eliminar registros, reciclar sectores y gestionar desgaste introduce transiciones adicionales. Con R registros, la ordenación didáctica cuesta O(R log R); verificar W bytes exige O(W) trabajo de hash y recurrir a A puede requerir leer dos copias. Estas complejidades no dan milisegundos ni milijulios: hacen falta medidas de la plataforma para obtener latencia y energía.
Cómo reproducirlo y qué comprobar en el dispositivo
El archivo adjunto contiene experiment.py, plot.py, resultados JSON e instrucciones en cuatro idiomas. El experimento es determinista y no utiliza semilla aleatoria. experiment.py enumera estados y comprueba automáticamente las propiedades; plot.py dibuja resultados, no mide hardware. El código breve del final invoca el mismo cálculo e imprime pares punto/resultado, los tres casos negativos y los bytes totales. En el archivo completo, la condición decisiva acepta un registro solo después de commit, compatibilidad y verificación: ahí la explicación se convierte en regla ejecutable.
En hardware propondríamos interrupciones controladas durante borrado, programación, actualización de metadatos y confirmación, no solo entre funciones. Habría que especificar placa, flash, versiones, configuración, tensiones y protocolo, comprobando candidatos corruptos, dependencias incorrectas, pérdida del journal y fallos del arranque de prueba. Es un plan de verificación no ejecutado. No tenemos medidas de reinicio, energía, desgaste ni probabilidad de fallo. Tampoco atribuimos el experimento a un producto embedded de EL-AI: es un análisis didáctico, no documentación de una instalación empresarial.
La respuesta: proteger la copia antes de elegir la nueva
Si se corta la corriente, el protocolo arranca A hasta el commit y B después, siempre que el candidato supere los controles; en caso contrario vuelve a A si sigue válida. La razón no es una capacidad especial de la inteligencia artificial: conservamos una copia íntegra y aplazamos la elección de otra hasta tener sus datos listos. Esto aclara qué exigir a una actualización embedded: una regla verificable, un estado anterior preservado e hipótesis realizables sobre la memoria. La simulación demuestra el razonamiento bajo esas hipótesis; el dispositivo aún debe demostrar que las cumple.
Fuente técnica y material reproducible
from experiment import run
r = run()
for name in ['naive', 'dual']:
print(name, [(x['cut'], x['outcome']) for x in r[name]])
print(r['negative_cases'])
print(r['memory']['total_flash_bytes'])
Código, datos e instrucciones · JSON. Cálculos didácticos ejecutados con Python 3.14.0; figuras con Matplotlib 3.11.2. Análisis asistido por IA, sin afirmar revisión por pares ni humana. Portada original ImageGen ilustrativa: no documenta personas, sedes ni instalaciones de EL-AI. Fuentes consultadas el 6 de octubre de 2026.

