# Витрина TAKT: прогнозирование временных рядов на Rust (DLinear и PatchTST на ETT) Две стандартные модели долгосрочного прогнозирования, DLinear (LTSF-Linear) и PatchTST, обученные официальными скриптами на наборах данных ETT, переведены с PyTorch на чистый Rust (без BLAS и без ML-рантайма) и собраны дважды: исходная сборка этого кода на Rust и сборка TAKT того же кода. Прогнозы обеих сборок совпадают до бита. Здесь — готовые программы обеих сборок, исходный код исходной версии, скрипт, который воссоздаёт входные окна из официальных данных, скрипт бенчмарка и наши замеры. ## Что вычисляют программы Каждое окно тестовой части набора данных ETT (официальные границы, features M, StandardScaler, обученный на обучающей части): на входе 336 прошлых шагов x 7 каналов, на выходе следующие 96 шагов x 7 каналов, float32. ETTh1/ETTh2: 2 785 окон; ETTm1/ETTm2: 11 425 окон. Веса чекпойнта для выбранного набора данных вкомпилированы в программу (`--data`). | Модель | Набор данных | MSE / MAE на тесте (все окна, `tools/score.py`) | Официальный прогон обучения, тест | |---|---|---|---| | 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 (первые 2 688: 0,381618 / 0,405088) | 0,381618 / 0,405088 | | PatchTST | ETTh2 | 0,274616 / 0,337234 (первые 2 688: 0,274118 / 0,336000) | 0,274117 / 0,336000 | Официальный тестовый цикл PatchTST отбрасывает последний неполный батч из 128 окон, отсюда значения «первые 2 688». Перевод на Rust не совпадает с PyTorch бит в бит (другой порядок суммирования; наибольшая абсолютная разница с PyTorch eager — от 2e-6 до 2e-5); сборка TAKT совпадает с исходной версией на Rust бит в бит. ## Замер (стенд TAKT) Дата замера: 3 октября 2026 г. AMD Threadripper PRO 5975WX (Zen 3), Linux. Зарезервирован один CCX из 8 ядер, их SMT-соседи простаивают; однопоточные прогоны привязаны к одному ядру, восьмипоточные — к 8 ядрам этого CCX. Для каждого набора данных: одна прогревочная пара прогонов, затем 9 раундов с чередованием порядка, медиана. Время: цикл прогнозирования, засекаемый самой программой (чтение окон и запись прогнозов не учитываются: в обеих сборках они стоят одинаково). Такты: такты пользовательского режима того же цикла (`perf stat`, сумма по всем потокам). Обе сборки используют один и тот же драйвер, который, как долгоживущий сервис, оставляет освобождённую память кучи в процессе (glibc `mallopt`), чтобы временные буферы на каждое окно не превращались в page faults. Замерены ровно файлы из `bin/`, скриптом `tools/run_bench.sh`. | Модель | Набор данных | Окна | Потоки | Исходная, мс | TAKT, мс | Ускорение | Исходная, млн тактов | TAKT, млн тактов | Ускорение по тактам | Совпавшие раунды | |---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:| | 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× по времени и 7,2–7,4× по тактам, на одном и на восьми потоках. PatchTST: 2,3× по времени и по тактам. На восьми потоках обе сборки тратят в сумме примерно столько же тактов, сколько на одном. Процесс целиком, включая чтение 26–107 МБ окон и запись прогнозов, быстрее в 5,7–6,0 раза для DLinear на одном потоке, в 3,0–3,1 раза на восьми и в 2,3 раза для PatchTST. Подробности, сырые отсчёты и время всего процесса — в `measurements.json`. ## Запуск ```sh python3 tools/make_windows.py --out data --targets # скачивает официальные CSV ETT (с проверкой sha256), # пишет data/windows_*_test.f32 (и целевые значения) 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 # окна, целевые значения и прогнозы — как в нашем замере python3 tools/score.py data/targets_ETTh1_test.f32 out/dlinear_ETTh1.f32 tools/run_bench.sh -m dlinear -n 9 -c 2 # обе сборки, с чередованием, побайтная сверка, ядро 2 tools/run_bench.sh -m dlinear -n 9 -t 8 -c 2-9 # восемь потоков на ядрах 2-9 tools/run_bench.sh -m patchtst -n 9 -c 2 ``` `--threads T` принимает любое T >= 1; прогнозы от T не зависят. Входные и выходные файлы — сырые little-endian float32 (n x 336 x 7 и n x 96 x 7); `tools/make_windows.py` нужен только numpy. Linux x86-64 (glibc). Программы собраны для x86-64-v3 (AVX2, FMA, BMI2: Intel Haswell и новее, AMD Zen и новее); инструкции FMA не используются. PatchTST вызывает `erff` из системной библиотеки C: на glibc, где `erff` отличается от glibc 2.39 (Ubuntu 24.04), его прогнозы могут не совпасть с `expected.sha256`, при этом исходная сборка и сборка TAKT по-прежнему совпадают побайтно. ## Эквивалентность - В каждом замеренном раунде прогнозы двух сборок совпадали побайтно (число раундов — в таблице). - Обе сборки побайтно воспроизводят прогнозы, сохранённые в нашем более раннем прогоне: DLinear на четырёх наборах данных и PatchTST на двух, тестовая и валидационная части, 1, 8 и 32 потока: 72 файла из 72. - `tools/make_windows.py` побайтно воспроизводит окна официальных загрузчиков данных LTSF-Linear / PatchTST (все четыре набора данных, тест и валидация; для этого заново реализован парсер чисел с плавающей точкой, который pandas использует по умолчанию). ## Пересборка оригинала ```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 ``` С Rust 1.96.0 из rustup (с компонентом `rust-src`) это на нашей машине воспроизводит `bin/ltsf-dlinear-original` и `bin/ltsf-patchtst-original` бит в бит; при другом окружении различаются встроенные пути, но не прогнозы. ## Более раннее сравнение с PyTorch (этим пакетом не воспроизводится) 2 октября 2026 г. мы замерили те же модели на той же машине в других условиях: без резервирования стенда (39 логических CPU, общих с другими задачами); обе версии на Rust собраны как разделяемые библиотеки под родной CPU (native) и вызывались внутри процесса; PyTorch 2.14 на CPU, eager и `torch.compile` (Inductor), батчи по 64 или 512. Для каждой реализации число потоков (1, 8 или 32) и размер батча выбирались по наименьшей задержке на валидационной части, затем замерялось время на тестовой части (медиана из 5). | Модель | Набор данных | Исходный Rust | Сборка TAKT | Самый быстрый PyTorch (Inductor) | PyTorch / TAKT | |---|---|---:|---:|---:|---:| | DLinear | ETTh1 | 28,7 мс (32 потока) | 5,1 мс (32) | 18,5 мс (32, батч 512) | 3,63× | | DLinear | ETTh2 | 30,1 мс (32) | 5,3 мс (32) | 12,5 мс (32, батч 512) | 2,34× | | DLinear | ETTm1 | 115,4 мс (32) | 19,7 мс (32) | 65,1 мс (8, батч 512) | 3,31× | | DLinear | ETTm2 | 114,9 мс (32) | 20,4 мс (32) | 49,5 мс (32, батч 512) | 2,43× | | PatchTST | ETTh1 | 593 мс (32) | 236 мс (32) | 634 мс (32, батч 64) | 2,69× | | PatchTST | ETTh2 | 519 мс (32) | 234 мс (32) | 551 мс (32, батч 64) | 2,35× | На одном потоке расклад был другим: PyTorch с Inductor и батчами работал быстрее сборки TAKT (DLinear ETTh1: 37,9 мс против 111,4 мс; PatchTST ETTh1: 2,03 с против 5,78 с). В том прогоне сборка TAKT была быстрее исходной версии на Rust в 5,6–5,9 раза (DLinear) и в 2,2–2,5 раза (PatchTST) на выбранных 32 потоках и в 7,2–7,3 и 2,3 раза соответственно на одном потоке. Все числа того прогона — в `measurements.json` (`earlier_measurement_2026_10_02`). ## Состав - `bin/`: `ltsf-dlinear-original`, `ltsf-dlinear-takt`, `ltsf-patchtst-original`, `ltsf-patchtst-takt` (статически скомпонованный код на Rust, веса вкомпилированы, символы удалены); - `source/`: исходная версия: драйвер командной строки, сгенерированный код моделей и веса; - `tools/`: `make_windows.py` (входные окна), `score.py` (MSE/MAE), `run_bench.sh` (бенчмарк); - `expected.sha256`, `measurements.json`, `SHA256SUMS`, `LICENSE`. Данные ETT в пакет не входят (CC BY-ND 4.0, их скачивает `tools/make_windows.py`). Исходный код сборки TAKT не публикуется: мы поставляем сборки.