# Demostración de TAKT: predicción de series temporales en Rust (DLinear y PatchTST sobre ETT) Dos modelos estándar de predicción a largo plazo, DLinear (LTSF-Linear) y PatchTST, entrenados con los scripts oficiales sobre los conjuntos de datos ETT, traducidos de PyTorch a Rust puro (sin BLAS ni runtime de ML) y compilados dos veces: la compilación original de ese código Rust y la compilación de TAKT del mismo código. Las predicciones de ambas compilaciones son idénticas bit a bit. Aquí encontrará programas listos para ambas compilaciones, el código fuente de la versión original, un script que recrea las ventanas de entrada a partir de los datos oficiales, un script de benchmark y nuestras mediciones. ## Qué calculan los programas Todas las ventanas de la partición de prueba de un conjunto ETT (límites oficiales, features M, StandardScaler ajustado sobre la partición de entrenamiento): de entrada, 336 pasos anteriores x 7 canales; de salida, los 96 pasos siguientes x 7 canales; float32. ETTh1/ETTh2: 2.785 ventanas; ETTm1/ETTm2: 11.425 ventanas. Los pesos del checkpoint del conjunto seleccionado están compilados dentro del programa (`--data`). | Modelo | Conjunto | MSE / MAE de prueba (todas las ventanas, `tools/score.py`) | Ejecución oficial de entrenamiento, prueba | |---|---|---|---| | DLinear | ETTh1 | 0,384144 / 0,404713 | 0,384144 / 0,404713 | | DLinear | ETTh2 | 0,290098 / 0,353328 | 0,290098 / 0,353328 | | DLinear | ETTm1 | 0,301222 / 0,344619 | 0,301222 / 0,344619 | | DLinear | ETTm2 | 0,171852 / 0,267122 | 0,171852 / 0,267122 | | PatchTST | ETTh1 | 0,385126 / 0,405953 (primeras 2.688: 0,381618 / 0,405088) | 0,381618 / 0,405088 | | PatchTST | ETTh2 | 0,274616 / 0,337234 (primeras 2.688: 0,274118 / 0,336000) | 0,274117 / 0,336000 | El bucle de prueba oficial de PatchTST descarta el último lote incompleto de 128 ventanas; de ahí la columna «primeras 2.688». La traducción a Rust no es idéntica bit a bit a PyTorch (otro orden de suma; la mayor diferencia absoluta respecto a PyTorch eager es de 2e-6 a 2e-5); la compilación de TAKT es idéntica bit a bit al original en Rust. ## Medición (banco de TAKT) Medido el 3 de octubre de 2026. AMD Threadripper PRO 5975WX (Zen 3), Linux. Un CCX de 8 núcleos reservado, con sus hilos SMT gemelos inactivos; las ejecuciones de un hilo se fijan a un núcleo, y las de ocho hilos, a los 8 núcleos del CCX. Para cada conjunto: un par de calentamiento y luego 9 rondas en orden alterno, mediana. Tiempo: el bucle de predicción, cronometrado por el propio programa (se excluyen la lectura de las ventanas y la escritura de las predicciones, que cuestan lo mismo en ambas compilaciones). Ciclos: ciclos en modo usuario del mismo bucle (`perf stat`, sumados en todos los hilos). Ambas compilaciones comparten el mismo programa de control, que conserva en el proceso la memoria de heap liberada (`mallopt` de glibc), como haría un servicio de larga duración, para que los búferes temporales de cada ventana no se conviertan en fallos de página. Se midieron exactamente los archivos de `bin/`, con `tools/run_bench.sh`. | Modelo | Conjunto | Ventanas | Hilos | Original, ms | TAKT, ms | Aceleración | Original, M ciclos | TAKT, M ciclos | Aceleración en ciclos | Rondas idénticas | |---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| | DLinear | ETTh1 | 2.785 | 1 | 788,4 | 111,0 | **7,10×** | 3.518 | 486 | 7,23× | 9/9 | | DLinear | ETTh2 | 2.785 | 1 | 785,0 | 108,4 | **7,24×** | 3.512 | 476 | 7,38× | 9/9 | | DLinear | ETTm1 | 11.425 | 1 | 3.223,9 | 447,8 | **7,20×** | 14.421 | 1.978 | 7,29× | 9/9 | | DLinear | ETTm2 | 11.425 | 1 | 3.228,4 | 442,6 | **7,29×** | 14.405 | 1.984 | 7,26× | 9/9 | | DLinear | ETTh1 | 2.785 | 8 | 103,3 | 14,5 | **7,11×** | 3.515 | 486 | 7,23× | 9/9 | | DLinear | ETTh2 | 2.785 | 8 | 103,3 | 14,2 | **7,26×** | 3.514 | 477 | 7,37× | 9/9 | | DLinear | ETTm1 | 11.425 | 8 | 423,3 | 58,6 | **7,22×** | 14.426 | 1.988 | 7,26× | 9/9 | | DLinear | ETTm2 | 11.425 | 8 | 422,1 | 58,9 | **7,17×** | 14.407 | 1.998 | 7,21× | 9/9 | | PatchTST | ETTh1 | 2.785 | 1 | 13.440,8 | 5.733,4 | **2,34×** | 59.973 | 25.507 | 2,35× | 9/9 | | PatchTST | ETTh2 | 2.785 | 1 | 13.484,3 | 5.749,2 | **2,35×** | 60.068 | 25.645 | 2,34× | 9/9 | | PatchTST | ETTh1 | 2.785 | 8 | 1.777,5 | 765,7 | **2,32×** | 60.424 | 25.955 | 2,33× | 9/9 | | PatchTST | ETTh2 | 2.785 | 8 | 1.785,3 | 769,3 | **2,32×** | 60.677 | 26.070 | 2,33× | 9/9 | DLinear: 7,1–7,3× en tiempo y 7,2–7,4× en ciclos, con uno y con ocho hilos. PatchTST: 2,3× en tiempo y en ciclos. Con ocho hilos, ambas compilaciones gastan en total aproximadamente los mismos ciclos que con uno. El proceso completo, incluidas la lectura de 26–107 MB de ventanas y la escritura de las predicciones, es 5,7–6,0× más rápido para DLinear con un hilo, 3,0–3,1× con ocho y 2,3× para PatchTST. Los detalles, las muestras sin procesar y los tiempos del proceso completo están en `measurements.json`. ## Ejecución ```sh python3 tools/make_windows.py --out data --targets # descarga los CSV oficiales de ETT (verificados con sha256), # escribe data/windows_*_test.f32 (y los valores objetivo) mkdir -p out for d in ETTh1 ETTh2 ETTm1 ETTm2; do bin/ltsf-dlinear-takt --data $d --windows data/windows_${d}_test.f32 --threads 1 --out out/dlinear_$d.f32 done for d in ETTh1 ETTh2; do bin/ltsf-patchtst-takt --data $d --windows data/windows_${d}_test.f32 --threads 1 --out out/patchtst_$d.f32 done sha256sum -c expected.sha256 # ventanas, valores objetivo y predicciones como en nuestra medición python3 tools/score.py data/targets_ETTh1_test.f32 out/dlinear_ETTh1.f32 tools/run_bench.sh -m dlinear -n 9 -c 2 # ambas compilaciones, intercaladas, verificación byte a byte, núcleo 2 tools/run_bench.sh -m dlinear -n 9 -t 8 -c 2-9 # ocho hilos en los núcleos 2-9 tools/run_bench.sh -m patchtst -n 9 -c 2 ``` `--threads T` admite cualquier T >= 1; las predicciones no dependen de T. Los archivos de entrada y salida son float32 little-endian sin formato (n x 336 x 7 y n x 96 x 7); `tools/make_windows.py` solo necesita numpy. Linux x86-64 (glibc). Los programas están compilados para x86-64-v3 (AVX2, FMA, BMI2: Intel Haswell y posteriores, AMD Zen y posteriores); no se usan instrucciones FMA. PatchTST llama a `erff` de la biblioteca C del sistema: con una glibc cuya `erff` difiera de la de glibc 2.39 (Ubuntu 24.04), sus predicciones pueden diferir de `expected.sha256`, mientras que el original y la compilación de TAKT siguen coincidiendo byte a byte. ## Equivalencia - En cada ronda medida, las predicciones de las dos compilaciones fueron idénticas byte a byte (recuentos en la tabla). - Ambas compilaciones reproducen, byte a byte, las predicciones guardadas en nuestra ejecución anterior: DLinear en cuatro conjuntos y PatchTST en dos, particiones de prueba y de validación, 1, 8 y 32 hilos: 72 de 72 archivos. - `tools/make_windows.py` reproduce byte a byte las ventanas de los cargadores de datos oficiales de LTSF-Linear / PatchTST (los cuatro conjuntos, prueba y validación; para ello se reimplementó el analizador de números de coma flotante predeterminado de pandas). ## Recompilar el original ```sh cd source RUSTFLAGS="-C target-cpu=x86-64-v3 --remap-path-prefix=$HOME/.rustup=. \ --remap-path-prefix=$HOME/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f=." \ cargo +1.96.0 build --release --locked strip --strip-all -o ltsf-dlinear-original target/release/ltsf-dlinear ``` Con Rust 1.96.0 de rustup (con el componente `rust-src`), esto reproduce bit a bit `bin/ltsf-dlinear-original` y `bin/ltsf-patchtst-original` en nuestra máquina; con otra configuración cambian las rutas incrustadas, pero no las predicciones. ## Comparación anterior con PyTorch (no reproducible con este paquete) El 2 de octubre de 2026 medimos los mismos modelos en la misma máquina en otras condiciones: sin reserva del banco (39 CPU lógicas compartidas con otras tareas); ambas versiones en Rust compiladas como bibliotecas compartidas para la CPU nativa y llamadas dentro del proceso; PyTorch 2.14 en la CPU, eager y `torch.compile` (Inductor), lotes de 64 o 512. Para cada implementación, el número de hilos (1, 8 o 32) y el tamaño de lote se eligieron por la menor latencia en la partición de validación, y después se cronometró la partición de prueba (mediana de 5). | Modelo | Conjunto | Rust original | Compilación de TAKT | PyTorch más rápido (Inductor) | PyTorch / TAKT | |---|---|---:|---:|---:|---:| | DLinear | ETTh1 | 28,7 ms (32 hilos) | 5,1 ms (32) | 18,5 ms (32, lote 512) | 3,63× | | DLinear | ETTh2 | 30,1 ms (32) | 5,3 ms (32) | 12,5 ms (32, lote 512) | 2,34× | | DLinear | ETTm1 | 115,4 ms (32) | 19,7 ms (32) | 65,1 ms (8, lote 512) | 3,31× | | DLinear | ETTm2 | 114,9 ms (32) | 20,4 ms (32) | 49,5 ms (32, lote 512) | 2,43× | | PatchTST | ETTh1 | 593 ms (32) | 236 ms (32) | 634 ms (32, lote 64) | 2,69× | | PatchTST | ETTh2 | 519 ms (32) | 234 ms (32) | 551 ms (32, lote 64) | 2,35× | Con un hilo, el orden fue distinto: PyTorch con Inductor y lotes fue más rápido que la compilación de TAKT (DLinear ETTh1: 37,9 ms frente a 111,4 ms; PatchTST ETTh1: 2,03 s frente a 5,78 s). En esa ejecución, la compilación de TAKT fue 5,6–5,9× (DLinear) y 2,2–2,5× (PatchTST) más rápida que el Rust original con los 32 hilos seleccionados, y 7,2–7,3× y 2,3× más rápida con un hilo. Todas las cifras de esa ejecución están en `measurements.json` (`earlier_measurement_2026_10_02`). ## Contenido - `bin/`: `ltsf-dlinear-original`, `ltsf-dlinear-takt`, `ltsf-patchtst-original`, `ltsf-patchtst-takt` (código Rust con enlace estático, pesos compilados dentro, sin símbolos); - `source/`: la versión original: programa de control de línea de comandos, código del modelo generado y pesos; - `tools/`: `make_windows.py` (ventanas de entrada), `score.py` (MSE/MAE), `run_bench.sh` (benchmark); - `expected.sha256`, `measurements.json`, `SHA256SUMS`, `LICENSE`. Los datos ETT no se incluyen (CC BY-ND 4.0; los descarga `tools/make_windows.py`). El código fuente de la compilación de TAKT no se publica: entregamos compilaciones.