Витрина 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.
Запуск
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 использует по умолчанию).
Пересборка оригинала
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 не публикуется: мы поставляем сборки.