← 쇼케이스로 돌아가기

TAKT 쇼케이스: Rust 시계열 예측 (ETT 데이터셋의 DLinear와 PatchTST)

표준 장기 예측 모델인 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 측정 장비)

2026-10-03 측정. 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: 시간 기준 7.1–7.3배, 사이클 기준 7.2–7.4배이며, 단일 스레드와 8스레드 모두 마찬가지입니다. 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(및 targets)를 생성
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                 # 윈도우, targets, 예측값이 저희 측정과 같은지 확인
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 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는 시스템 C 라이브러리의 erff를 호출합니다. erff가 glibc 2.39(Ubuntu 24.04)와 다른 glibc에서는 예측값이 expected.sha256과 다를 수 있지만, 원본 빌드와 TAKT 빌드는 여전히 바이트 단위로 일치합니다.

동등성

원본 다시 빌드하기

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-02에 같은 머신에서 다른 조건으로 같은 모델을 측정했습니다. 측정 장비 예약 없음(논리 CPU 39개를 다른 작업과 공유), 두 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 빌드는 선택된 32스레드 기준으로 원본 Rust보다 5.6–5.9배(DLinear), 2.2–2.5배(PatchTST) 빨랐고, 단일 스레드 기준으로는 각각 7.2–7.3배와 2.3배 빨랐습니다. 그 실행의 모든 수치는 measurements.json(earlier_measurement_2026_10_02)에 있습니다.

구성

ETT 데이터는 포함되어 있지 않습니다(CC BY-ND 4.0, tools/make_windows.py가 내려받음). TAKT 빌드의 소스는 공개하지 않습니다. 저희는 빌드를 제공합니다.

원문: README.ko.md

Telegram