Vitrine TAKT: previsão de séries temporais em Rust (DLinear e PatchTST no ETT)
Dois modelos padrão de previsão de longo prazo, DLinear (LTSF-Linear) e PatchTST, treinados com os scripts oficiais nos conjuntos de dados ETT, traduzidos de PyTorch para Rust puro (sem BLAS, sem runtime de ML) e compilados duas vezes: o build original desse código Rust e o build TAKT do mesmo código. As previsões dos dois builds são idênticas até o último bit. Aqui você encontra os programas prontos dos dois builds, o código-fonte da versão original, um script que recria as janelas de entrada a partir dos dados oficiais, um script de benchmark e as nossas medições.
O que os programas calculam
Todas as janelas da partição de teste de um conjunto ETT (limites oficiais, features M,
StandardScaler ajustado na partição de treino): 336 passos passados x 7 canais na entrada, os
próximos 96 passos x 7 canais na saída, float32. ETTh1/ETTh2: 2.785 janelas; ETTm1/ETTm2: 11.425
janelas. Os pesos do checkpoint do conjunto selecionado são compilados no programa (--data).
| Modelo | Conjunto | MSE / MAE de teste (todas as janelas, tools/score.py) |
Execução oficial de treino, teste |
|---|---|---|---|
| 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 (primeiras 2.688: 0,381618 / 0,405088) | 0,381618 / 0,405088 |
| PatchTST | ETTh2 | 0,274616 / 0,337234 (primeiras 2.688: 0,274118 / 0,336000) | 0,274117 / 0,336000 |
O laço de teste oficial do PatchTST descarta o último lote incompleto de 128 janelas; daí os valores "primeiras 2.688". A tradução para Rust não é idêntica bit a bit ao PyTorch (outra ordem de soma; a maior diferença absoluta em relação ao PyTorch eager vai de 2e-6 a 2e-5); o build TAKT é idêntico bit a bit ao original em Rust.
Medição (bancada TAKT)
Medido em 3 de outubro de 2026. AMD Threadripper PRO 5975WX (Zen 3), Linux. Um CCX de 8 núcleos
reservado, com seus pares SMT ociosos; execuções com uma thread fixadas em um núcleo, execuções com
oito threads fixadas nos 8 núcleos do CCX. Para cada conjunto de dados: um par de aquecimento e
depois 9 rodadas em ordem alternada, mediana. Tempo: o laço de previsão cronometrado pelo próprio
programa (a leitura das janelas e a gravação das previsões ficam de fora; custam o mesmo nos dois
builds). Ciclos: ciclos em modo usuário do mesmo laço (perf stat, somados sobre todas as threads).
Os dois builds compartilham o mesmo driver, que mantém no processo a memória de heap liberada
(mallopt da glibc), como faria um serviço de longa duração, para que os buffers temporários de cada
janela não se transformem em page faults. Foram medidos exatamente os arquivos em bin/, com
tools/run_bench.sh.
| Modelo | Conjunto | Janelas | Threads | Original, ms | TAKT, ms | Aceleração | Original, mi de ciclos | TAKT, mi de ciclos | Aceleração em ciclos | Rodadas 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× em tempo e 7,2–7,4× em ciclos, com uma e com oito threads. PatchTST: 2,3× em tempo
e em ciclos. Com oito threads, os dois builds gastam aproximadamente o mesmo total de ciclos que com
uma. O processo inteiro, incluindo a leitura de 26–107 MB de janelas e a gravação das previsões, é
5,7–6,0× mais rápido para o DLinear com uma thread, 3,0–3,1× com oito e 2,3× para o PatchTST.
Detalhes, amostras brutas e tempos do processo inteiro estão em measurements.json.
Execução
python3 tools/make_windows.py --out data --targets # baixa os CSVs oficiais do ETT (com verificação sha256),
# grava data/windows_*_test.f32 (e os alvos)
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 # janelas, alvos e previsões como na nossa medição
python3 tools/score.py data/targets_ETTh1_test.f32 out/dlinear_ETTh1.f32
tools/run_bench.sh -m dlinear -n 9 -c 2 # os dois builds, intercalados, verificação byte a byte, núcleo 2
tools/run_bench.sh -m dlinear -n 9 -t 8 -c 2-9 # oito threads nos núcleos 2-9
tools/run_bench.sh -m patchtst -n 9 -c 2
--threads T aceita qualquer T >= 1; as previsões não dependem de T. Os arquivos de entrada e de
saída são float32 little-endian brutos (n x 336 x 7 e n x 96 x 7); tools/make_windows.py precisa
apenas do numpy.
Linux x86-64 (glibc). Os programas são compilados para x86-64-v3 (AVX2, FMA, BMI2: Intel Haswell e
mais recentes, AMD Zen e mais recentes); instruções FMA não são usadas. O PatchTST chama erff da
biblioteca C do sistema: em uma glibc cujo erff seja diferente do da glibc 2.39 (Ubuntu 24.04), as
previsões podem diferir de expected.sha256, enquanto o original e o build TAKT continuam
coincidindo byte a byte.
Equivalência
- Em cada rodada medida, as previsões dos dois builds foram idênticas byte a byte (contagens na tabela).
- Os dois builds reproduzem, byte a byte, as previsões salvas na nossa execução anterior: DLinear em quatro conjuntos e PatchTST em dois, partições de teste e de validação, 1, 8 e 32 threads: 72 de 72 arquivos.
tools/make_windows.pyreproduz byte a byte as janelas dos data loaders oficiais do LTSF-Linear / PatchTST (os quatro conjuntos, teste e validação; para isso, o parser de float padrão do pandas foi reimplementado).
Recompilação do original
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
Com o Rust 1.96.0 via rustup (com o componente rust-src), isso reproduz
bin/ltsf-dlinear-original e bin/ltsf-patchtst-original bit a bit na nossa máquina; em outra
configuração, os caminhos embutidos diferem, mas as previsões não.
Comparação anterior com o PyTorch (não reproduzível com este pacote)
Em 2 de outubro de 2026, medimos os mesmos modelos na mesma máquina em outras condições: sem reserva
da bancada (39 CPUs lógicas compartilhadas com outras tarefas); as duas versões em Rust compiladas
como bibliotecas compartilhadas para a CPU nativa e chamadas no mesmo processo; PyTorch 2.14 na CPU,
eager e torch.compile (Inductor), batches de 64 ou 512. Para cada implementação, o número de
threads (1, 8 ou 32) e o tamanho do batch foram escolhidos pela menor latência na partição de
validação; em seguida, a partição de teste foi cronometrada (mediana de 5).
| Modelo | Conjunto | Rust original | Build TAKT | PyTorch mais rápido (Inductor) | PyTorch / TAKT |
|---|---|---|---|---|---|
| DLinear | ETTh1 | 28,7 ms (32 threads) | 5,1 ms (32) | 18,5 ms (32, batch 512) | 3,63× |
| DLinear | ETTh2 | 30,1 ms (32) | 5,3 ms (32) | 12,5 ms (32, batch 512) | 2,34× |
| DLinear | ETTm1 | 115,4 ms (32) | 19,7 ms (32) | 65,1 ms (8, batch 512) | 3,31× |
| DLinear | ETTm2 | 114,9 ms (32) | 20,4 ms (32) | 49,5 ms (32, batch 512) | 2,43× |
| PatchTST | ETTh1 | 593 ms (32) | 236 ms (32) | 634 ms (32, batch 64) | 2,69× |
| PatchTST | ETTh2 | 519 ms (32) | 234 ms (32) | 551 ms (32, batch 64) | 2,35× |
Com uma thread, a ordem foi outra: o PyTorch com Inductor e batches foi mais rápido que o build TAKT
(DLinear ETTh1: 37,9 ms contra 111,4 ms; PatchTST ETTh1: 2,03 s contra 5,78 s). Naquela execução, o
build TAKT foi 5,6–5,9× (DLinear) e 2,2–2,5× (PatchTST) mais rápido que o Rust original com as 32
threads selecionadas, e 7,2–7,3× e 2,3× com uma thread. Todos os números daquela execução estão em
measurements.json (earlier_measurement_2026_10_02).
Conteúdo
bin/:ltsf-dlinear-original,ltsf-dlinear-takt,ltsf-patchtst-original,ltsf-patchtst-takt(código Rust com linkagem estática, pesos embutidos, sem símbolos);source/: a versão original: driver de linha de comando, código do modelo gerado e pesos;tools/:make_windows.py(janelas de entrada),score.py(MSE/MAE),run_bench.sh(benchmark);expected.sha256,measurements.json,SHA256SUMS,LICENSE.
Os dados do ETT não estão incluídos (CC BY-ND 4.0, baixados por tools/make_windows.py). O
código-fonte do build TAKT não é publicado: entregamos builds.