TAKTショーケース:Rustによる時系列予測(ETTでのDLinearとPatchTST)
標準的な長期予測モデルであるDLinear(LTSF-Linear)とPatchTSTを、公式スクリプトでETTデータセットについて学習させ、PyTorchから素のRust(BLASもMLランタイムも不使用)に変換して、2通りにビルドしました。そのRustコードのオリジナルビルドと、同じコードのTAKTビルドです。両ビルドの予測値はビット単位で一致します。両ビルドのビルド済みプログラム、オリジナル版のソースコード、公式データから入力ウィンドウを再生成するスクリプト、ベンチマークスクリプト、当社の測定結果を収録しています。
プログラムの計算内容
ETTデータセットのテスト分割の全ウィンドウを処理します(公式の境界、features M、学習分割でfitしたStandardScaler)。入力は過去336ステップ × 7チャネル、出力は次の96ステップ × 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測定環境)
2026年10月3日に測定。AMD Threadripper PRO 5975WX(Zen 3)、Linux。8コアのCCXを1つ確保し、そのSMTの兄弟スレッドはアイドル状態にしました。シングルスレッドの実行は1コアに、8スレッドの実行はCCXの8コアに固定しています。各データセットについて、ウォームアップを1ペア実行した後、実行順を交互に入れ替えて9ラウンド測定し、中央値をとりました。時間:プログラム自身が計測した予測ループの時間です(ウィンドウの読み込みと予測値の書き出しは除外。どちらのビルドでもコストは同じです)。サイクル数:同じループのユーザーモードのサイクル数です(perf stat、全スレッドの合計)。両ビルドは同じドライバを共有しています。このドライバは長時間稼働するサービスと同様に、解放したヒープメモリをプロセス内に保持し(glibcのmallopt)、ウィンドウごとの一時バッファがページフォールトを起こさないようにしています。測定したのはbin/内のファイルそのもので、tools/run_bench.shを使用しました。
| モデル | データセット | ウィンドウ数 | スレッド数 | オリジナル(ms) | TAKT(ms) | 高速化 | オリジナル(百万サイクル) | 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:シングルスレッドでも8スレッドでも、時間で7.1〜7.3倍、サイクル数で7.2〜7.4倍。PatchTST:時間でもサイクル数でも2.3倍。8スレッドでも、両ビルドの総サイクル数はシングルスレッドの場合とほぼ同じです。26〜107 MBのウィンドウの読み込みと予測値の書き出しを含むプロセス全体では、DLinearがシングルスレッドで5.7〜6.0倍、8スレッドで3.0〜3.1倍、PatchTSTが2.3倍高速です。詳細、生のサンプル、プロセス全体の時間はmeasurements.jsonをご覧ください。
実行方法
python3 tools/make_windows.py --out data --targets # 公式のETT CSVをダウンロード(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で8スレッド
tools/run_bench.sh -m patchtst -n 9 -c 2
--threads Tには1以上の任意のTを指定でき、予測値はTに依存しません。入出力ファイルは、リトルエンディアンのfloat32の生データです(n × 336 × 7、n × 96 × 7)。tools/make_windows.pyに必要なのはnumpyだけです。
Linux x86-64(glibc)。プログラムはx86-64-v3向けにビルドしています(AVX2、FMA、BMI2:Intel Haswell以降、AMD Zen以降)。FMA命令は使用していません。PatchTSTはシステムのCライブラリのerffを呼び出します。erffの実装がglibc 2.39(Ubuntu 24.04)と異なるglibcでは、予測値がexpected.sha256と一致しない場合があります。その場合も、オリジナルとTAKTビルドの出力はバイト単位で一致します。
等価性
- 測定したすべてのラウンドで、2つのビルドの予測値はバイト単位で一致しました(回数は表を参照)。
- 両ビルドとも、以前の実行で保存した予測値をバイト単位で再現します。対象は4データセットのDLinearと2データセットのPatchTST、テスト分割と検証分割、1・8・32スレッドで、72ファイル中72ファイルが一致しました。
tools/make_windows.pyは、公式のLTSF-Linear / PatchTSTデータローダが生成するウィンドウをバイト単位で再現します(4データセットすべて、テストと検証。そのために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
rustupのRust 1.96.0(rust-srcコンポーネント付き)を使うと、当社のマシンではbin/ltsf-dlinear-originalとbin/ltsf-patchtst-originalがビット単位で再現されます。別の環境では埋め込まれるパスが異なりますが、予測値は変わりません。
PyTorchとの以前の比較(このパッケージでは再現できません)
2026年10月2日に、同じマシンで同じモデルを別の条件で測定しました。測定環境のリソースは確保せず(39論理CPUを他のジョブと共有)、両Rust版はネイティブCPU向けの共有ライブラリとしてビルドしてプロセス内から呼び出しました。PyTorch 2.14はCPU上でeagerとtorch.compile(Inductor)を使い、バッチサイズは64または512です。各実装について、スレッド数(1、8、32)とバッチサイズは検証分割でレイテンシが最小になるものを選び、そのうえでテスト分割の時間を計測しました(5回の中央値)。
| モデル | データセット | オリジナルのRust | TAKTビルド | 最速のPyTorch(Inductor) | PyTorch / TAKT |
|---|---|---|---|---|---|
| DLinear | ETTh1 | 28.7 ms(32スレッド) | 5.1 ms(32) | 18.5 ms(32、バッチ512) | 3.63× |
| DLinear | ETTh2 | 30.1 ms(32) | 5.3 ms(32) | 12.5 ms(32、バッチ512) | 2.34× |
| DLinear | ETTm1 | 115.4 ms(32) | 19.7 ms(32) | 65.1 ms(8、バッチ512) | 3.31× |
| DLinear | ETTm2 | 114.9 ms(32) | 20.4 ms(32) | 49.5 ms(32、バッチ512) | 2.43× |
| PatchTST | ETTh1 | 593 ms(32) | 236 ms(32) | 634 ms(32、バッチ64) | 2.69× |
| PatchTST | ETTh2 | 519 ms(32) | 234 ms(32) | 551 ms(32、バッチ64) | 2.35× |
シングルスレッドでは順位が逆で、Inductorとバッチ処理を使ったPyTorchの方がTAKTビルドより高速でした(DLinear ETTh1:37.9 ms対111.4 ms、PatchTST ETTh1:2.03秒対5.78秒)。この実行では、TAKTビルドはオリジナルのRustに対し、選択された32スレッドでDLinearが5.6〜5.9倍、PatchTSTが2.2〜2.5倍、シングルスレッドではそれぞれ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ビルドのソースコードは公開していません。当社が納品するのはビルドです。