← voltar à vitrine

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

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

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.

Texto original: README.pt.md

Telegram