# 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 ```sh 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.py` reproduz 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 ```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 ``` 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.