Khi Nào GPU Đáng Đồng Tiền: Roofline Của Sweep Tham Số, Nơi Con Số Tiêu Đề 167x Thực Ra Là 27x Thuật Toán Nhân 6.2x Phần Cứng
Bài viết thuộc series "Backtest Không Ảo Tưởng".
Thang tốc độ đã kết thúc bằng một nốt lặng cố ý gây hụt hẫng. Chúng tôi đã đưa một sweep tham số 80 combo từ 69.9 giây của pandas xuống còn 0.23 giây của numba song song trên một CPU laptop — mức đo được 298x — rồi lập luận rằng một GPU không phải là bậc thang còn thiếu. Phần bình luận đã không chấp nhận điều đó trong im lặng, và họ đã đúng khi không làm vậy. "Không phải bậc thang còn thiếu" là một tuyên bố về một khối lượng công việc, ở một kích thước. Đó không phải là một định luật tự nhiên. Vậy nên bài viết này làm điều trung thực và đưa GPU lên bàn cân.
Kết quả không phải là một phán quyết, mà là một đường cong. Lợi thế của GPU so với CPU không phải là một con số duy nhất bạn có thể in lên một slide; nó là một hàm của lượng công việc bạn giao cho nó ở mỗi lời gọi. Trên phép precompute chỉ báo đa khung thời gian của chúng tôi, mức tăng tốc của GPU so với engine CPU chạy từ 54.5x khi chúng tôi yêu cầu nó xử lý một tổ hợp tham số mỗi lần cho đến 359.6x khi chúng tôi yêu cầu 61 tổ hợp cùng lúc. Cùng kernel, cùng dữ liệu, cùng phần cứng — điều duy nhất thay đổi là kích thước lô. Một benchmark báo cáo một trong những con số đó và giấu đi con số kia thì không phải đang đo GPU, mà là đang chọn một tiêu đề giật gân.
Và ngay cả 359.6x cũng không phải như vẻ ngoài của nó. Bóc tách nó ra và một con số tiêu đề đa khung thời gian lớn 167x phân rã thành 27x của thuật toán — một phép viết lại cũng khiến CPU nhanh hơn 27x — nhân với 6.2x của phần cứng thực sự. GPU không tạo ra 27x đó. Toán học mới làm điều đó. Bài viết này nói về việc phân biệt hai thứ đó, bởi vì gộp chúng lại làm một chính là cách mà một card đồ họa $2,000 được bán để giải quyết một vấn đề mà một git commit lẽ ra đã giải quyết miễn phí.
Nguồn gốc: tất cả các con số bên dưới đều được đo trên một Apple M2 Max, fp32 trên Metal GPU thông qua MLX so với fp64 numba trên mười hai luồng CPU, từ các script benchmark engine_multitf_gpu.py và bench_param_sweep.py của chúng tôi, mỗi cái được gác cổng bởi một kiểm tra tương đương xác nhận rằng các đường GPU và CPU cho ra cùng những giao dịch. Không có bài báo đồng hành công khai cho bài này — các script chính là bản ghi.
Câu hỏi là một roofline, chứ không phải một benchmark

Lý do vì sao "GPU nhanh hơn bao nhiêu" không có một câu trả lời duy nhất chính là mô hình roofline (Williams, Waterman & Patterson, 2009). Hiệu năng có thể đạt được của một bộ xử lý bị giới hạn bởi hai trần cùng một lúc: một trần phẳng được đặt bởi compute đỉnh (FLOP/s), và một trần dốc được đặt bởi băng thông bộ nhớ nhân với cường độ số học — số phép toán hữu ích bạn thực hiện trên mỗi byte bạn di chuyển. Công việc rẻ-trên-mỗi-byte nằm dưới trần dốc và bị giới hạn bởi băng thông; công việc giàu-trên-mỗi-byte chạm tới trần phẳng và bị giới hạn bởi compute. Trần phẳng của GPU vươn cao vượt trội so với của CPU, nhưng trần dốc của nó và, quan trọng hơn cả, chi phí cố định cho mỗi lời gọi của nó lại không thu nhỏ xuống một cách duyên dáng như vậy.
Với một sweep tham số, một số hạng thứ ba chi phối phía bên trái của biểu đồ: chi phí khởi chạy và truyền dữ liệu. Mỗi lời gọi GPU đều phải trả một cái giá gần như cố định — dispatch kernel, di chuyển đầu vào qua ranh giới bộ nhớ (hợp nhất, trên Apple Silicon, nhưng không miễn phí), di chuyển kết quả về. Mô hình hóa wall time cho một lô gồm tổ hợp như sau:
trong đó là chi phí cố định đó, là chi phí biên của GPU cho mỗi combo, và là của CPU. Khi đó mức tăng tốc là
Một phân số duy nhất này giải thích toàn bộ bài viết. Ở nhỏ, ở mẫu số đè bẹp tỷ lệ — bạn đã trả tiền để đánh thức GPU mà gần như không dùng đến nó. Khi tăng lên, bạn khấu hao trên nhiều combo hơn và mức tăng tốc leo lên về phía tiệm cận của nó, tỷ lệ phần cứng thực sự. Điểm giữa nằm ở : một "điểm gờ" trong không gian lô cho bạn biết sweep của bạn phải rộng đến mức nào trước khi GPU đạt được thậm chí chỉ một nửa những gì nó có thể làm. Một sweep vài chục combo nằm xa về phía trái của điểm gờ đó. Một sweep hàng nghìn combo nằm trên phần phẳng nơi GPU cuối cùng cũng xứng đáng với cái trần của nó.
Vậy nên câu hỏi đúng không bao giờ là "GPU có nhanh hơn không." Mà là "sweep của tôi có nằm bên phải điểm gờ không, và công việc trên mỗi combo của tôi có bị giới hạn bởi compute đủ để chạm tới trần phẳng khi đến được đó không." Cả hai đều phải đúng. Phần còn lại của bài viết này đo xem những ngưỡng đó thực sự rơi vào đâu.
Phán quyết cho khung thời gian đơn: GPU thắng sát nút

Bắt đầu với khối lượng công việc mà thang tốc độ đã dùng: một sweep HMA/HMA3 khung thời gian đơn, 80 tổ hợp trên 150,000 nến. Chúng tôi đã thêm một bậc thứ sáu vào thang đó — M5, các chỉ báo trên GPU của Apple thông qua MLX, giao dịch vẫn được trích xuất trên CPU. Đã warm-up, tốt nhất trong ba lần, cổng tương đương báo xanh:
| Phương pháp | Wall time | vs pandas | Combo/giây |
|---|---|---|---|
| M0 pandas + vòng lặp | 287.08s | 1.0x | 0.3 |
| M2 numba (1 lõi) | 2.02s | 142x | 39.7 |
| M4 mp + numba (12 lõi) | 0.33s | 883x | 245.9 |
| M5 MLX GPU (fp32) | 0.10s | 2796x | 779.2 |
So với baseline tuần tự ngây thơ, GPU trông thật anh hùng — 2,796x. Nhưng đó là một phép so sánh mà không ai trung thực nên thực hiện: nó đặt một triển khai GPU tốt đối đầu với triển khai CPU tệ nhất. Xếp GPU đối đầu với CPU mà bạn thực sự sẽ triển khai — cùng kernel trên cả mười hai lõi, M4 — và chiến thắng sụp đổ xuống mức tỉnh táo 3.2x (779 so với 246 combo mỗi giây). Nguyên một card đồ họa, chạy toàn bộ sweep, đánh bại một pool CPU mười hai lõi với hệ số ba.
Ba-phẩy-hai không phải là con số vô nghĩa. Nó cũng không phải là lý do khiến ai đó mua một GPU. Và nó chính xác là những gì roofline dự đoán cho một sweep hẹp đến mức này: 80 combo nằm bên trái điểm gờ. Chi phí khởi chạy và truyền dữ liệu cố định vẫn là một lát cắt đáng kể của một công việc 0.10 giây, vậy nên chúng ta không bao giờ đạt tới tiệm cận . Tệ hơn nữa, một phần của chi phí trên mỗi combo là lượt trích xuất giao dịch O(n), thứ mà chúng tôi cố ý để lại trên CPU — một số hạng mà GPU hoàn toàn không thể tăng tốc được (chi tiết vì sao ở phần tiếp theo). Với một vòng lặp nghiên cứu khung thời gian đơn ở kích thước này, phán quyết ban đầu của thang tốc độ vẫn đứng vững: GPU không phải là bậc thang còn thiếu. numba song song vốn đã đưa bạn về mức 0.23–0.33 giây, và việc cạo mức đó xuống 0.10 không phải là thứ gỡ nút thắt cho một nhà nghiên cứu. Việc điều phối xung quanh sweep mới là thứ đó.
Từ đáng chú ý trong phán quyết đó là kích thước này. Di chuyển sang phải dọc theo trục lô và câu chuyện thay đổi.
Chi phí thực sự nằm ở đâu
Trước khi chúng ta phóng to kích thước lô, hãy nhìn xem chúng ta thực sự đang trả tiền cho cái gì, bởi vì roofline chỉ tưởng thưởng cho bạn nếu phần đắt đỏ bị giới hạn bởi compute. Profile sweep và gần như toàn bộ nó là một thứ: các phép convolution của weighted-moving-average. Một HMA là ba WMA; một HMA3 là bốn; mọi tổ hợp đều chạy lại chúng trên toàn bộ chuỗi. Trích xuất giao dịch — đi qua hai mảng chỉ báo, tìm các điểm đảo dấu của hma - hma3, ghi nhận các fill — là một lượt O(n) rẻ tiền duy nhất. Sweep là một khối lượng công việc convolution khoác lên mình bộ trang phục của một chiến lược giao dịch.
Sự phân chia đó chính xác là hai chế độ của roofline:
- Các phép convolution bị giới hạn bởi compute. Mỗi mức giá được đọc vào nhiều tổng cửa sổ chồng lấn, vậy nên cường độ số học — số phép toán trên mỗi byte được di chuyển — là cao. Công việc này vươn tới trần compute phẳng, và trần phẳng của GPU chính là cái vươn cao vượt trội. Còn tốt hơn nữa, các cửa sổ chồng lấn xuyên qua các tổ hợp: một WMA độ dài 40 hữu ích cho hàng chục combo, vậy nên một phép precompute theo lô chia sẻ nó một lần thay vì tính lại nó cho mỗi combo. Việc gộp lô không chỉ khấu hao chi phí khởi chạy; nó nâng cao cường độ số học bằng cách tái sử dụng các lần nạp. Đây là phần thuộc về GPU.
- Trích xuất giao dịch bị giới hạn bởi băng thông và nhiều nhánh. Một lượt tuần tự duy nhất, các nhánh phụ thuộc dữ liệu ở mỗi điểm giao cắt, về cơ bản không có sự tái sử dụng. Cường độ số học của nó gần với sàn, và luồng điều khiển của nó thù địch với một thiết bị SIMD. Đẩy nó lên GPU sẽ được lợi rất ít mà tốn rất nhiều; nó ở lại trên CPU. Điều đó có nghĩa nó là đuôi tuần tự của một phép chia theo định luật Amdahl — một sàn cố định mà mức tăng tốc GPU không bao giờ có thể xuyên thủng, và một phần lý do vì sao con số đơn khung (single-TF) bão hòa ở mức 3.2x.
Có một bài học thứ hai sắc bén hơn ẩn trong phiên bản đa khung thời gian của kernel này, và nó là nguồn gốc của con số 27x mà chúng tôi cứ hứa hẹn sẽ giải thích. Engine đa khung (multi-TF) căn chỉnh một HMA khung thời gian cao hơn lên chỉ mục 1 phút cơ sở mà không có look-ahead. Viết theo cách hiển nhiên, đó là công việc O(length^1.5) theo từng nến — tính lại các moving average khung cao hơn ở mỗi nến cơ sở. Nhưng HMA đã căn chỉnh lại tuyến tính theo một buffer ngắn gồm vài nến khung cao hơn đã đóng gần nhất cộng với giá đóng đang chạy, vậy nên toàn bộ phép tính theo từng nến thu gọn thành một vector trọng số cố định: một conv1d trên chuỗi các nến đã đóng theo sau bởi một phép gather O(n). Hàng trăm triệu phép toán dư thừa trở thành một phép convolution trên một chuỗi ngắn hơn nhiều.
Sự thu gọn đó là một chiến thắng thuật toán, không phải phần cứng. Đó là một công thức tốt hơn. Nó chạy trên GPU, và nó chạy tốt không kém trên CPU — np.correlate cộng với một phép gather, ở fp64. Hãy khắc ghi điều này: yếu tố lớn nhất duy nhất trong con số tiêu đề đa khung là một phép viết lại có sẵn cho một cỗ máy hoàn toàn không có GPU. Khi cuối cùng chúng ta phân rã 167x, đây chính là 27x.
Lợi thế tăng lên theo kích thước lô

Giờ đến phép đo mà roofline đã bảo chúng ta thực hiện. Lấy trục đắt đỏ — phép precompute HMA khung thời gian cao hơn đã căn chỉnh trên chuỗi cơ sở 1 phút, luồng nến dài nhất mà chúng tôi có — và cấp cho GPU một số lượng tổ hợp độ dài ngày càng tăng cho mỗi lời gọi, . Baseline CPU ở đây là engine production trung thực: numba với prange trên cả mười hai lõi. Với mỗi lô, chúng tôi đo cả hai và lấy tỷ lệ.
| Lô (combo/lời gọi) | Mức tăng tốc GPU so với engine CPU 12 lõi |
|---|---|
| 1 | 54.5x |
| 2 | 102.5x |
| 4 | 129.5x |
| 8 | 187.4x |
| 16 | 267.4x |
| 32 | 245.0x |
| 61 | 359.6x |
Đây chính là được vẽ bằng các phép đo thực. Ở , GPU đã dẫn trước 54.5x — bởi vì phép so sánh này là với engine ngây thơ theo từng nến, nên sự thu gọn thuật toán đã nằm sẵn trong đó ngay cả ở một combo — nhưng nó còn xa mới chạm tới cái trần của mình: chi phí cố định vẫn chi phối một lời gọi một-combo. Tăng gấp đôi kích thước lô và mức tăng tốc gần như tăng gấp đôi lên 102.5x; đến nó là 267.4x; đến nó là 359.6x và rõ ràng vẫn đang leo lên. Lợi thế tăng lên theo kích thước của bài toán. Đó là câu quan trọng nhất duy nhất về GPU và các sweep tham số, và nó hoàn toàn trái ngược với cách mà các mức tăng tốc GPU thường được trích dẫn — như thể chúng là một thuộc tính cố định của con chip.
Hai lưu ý trung thực, bởi vì đây là series Backtest Không Ảo Tưởng và một bảng đơn điệu sạch sẽ luôn đáng để nghi ngờ.
Thứ nhất, điểm sụt: đọc ra 245.0x, thấp hơn mức 267.4x ở . Đó không phải là nhiễu để quét vào dưới thảm — mà là một hiện vật do ranh giới chunk. conv1d của chúng tôi đóng gói 32 độ dài vào một chunk kernel, vậy nên lấp đầy đúng một chunk không còn chỗ trống, trong khi tràn sang một chunk thứ hai tình cờ làm bão hòa thiết bị tốt hơn. Điểm mấu chốt của roofline là xu hướng, và một thiết bị thực có các bước lượng tử hóa bên trong nó; chúng tôi báo cáo sự dao động thay vì lựa chọn thiên vị xung quanh nó.
Thứ hai, và quan trọng hơn: 54.5x và 359.6x đều là so với engine CPU ngây thơ, và không con số nào là chiến thắng phần cứng. Cả hai con số vẫn chứa sự thu gọn thuật toán 27x. Nếu bạn thay baseline CPU bằng thuật toán đã thu gọn chạy trên CPU — cùng công thức, fp64, cả hai phía đều được tối ưu — thì mọi hàng sẽ co lại khoảng chừng hệ số đó. Đó chính xác là phép phân rã mà phần tiếp theo làm cho chính xác.
Phép phân rã trung thực: 27x thuật toán nhân 6.2x phần cứng

Để tách thuật toán khỏi silicon, bạn phải đo ba đường trên cùng một grid đa khung thời gian, chứ không phải hai. Vậy nên benchmark chạy:
- cpu-engine — engine numba production, HMA căn chỉnh theo từng nến trên tất cả các lõi. Baseline ngây-thơ-nhưng-song-song.
- cpu-collapsed — các vector trọng số đã thu gọn,
np.correlatecộng với gather, fp64, trên CPU. Cùng phần cứng như (1), thuật toán tốt hơn. - gpu-mlx — các trọng số đã thu gọn dưới dạng một
conv1dtheo lô trên Metal GPU, fp32. Cùng thuật toán như (2), phần cứng khác.
Xếp chúng cạnh nhau và toàn bộ con số tiêu đề đa khung thời gian phân tích thành thừa số một cách gọn gàng:
Thừa số bên trái, 27x, là thuật toán — sự thu gọn từ-tính-theo-từng-nến-thành-convolution từ phần trước. Nó chẳng liên quan gì đến GPU. Triển khai nó bằng numpy và CPU laptop của bạn nhanh hơn 27x trên khối lượng công việc này với cái giá của một lần refactor. Thừa số bên phải, 6.2x, là phần cứng — chiến thắng trung thực, so-sánh-tương-đương của Metal GPU so với cùng thuật toán đã tối ưu trên mười hai lõi CPU. 6.2x đó là phần duy nhất mà bạn thực sự cần một GPU để có được.
Đây là toàn bộ bài học đạo lý của bài viết được phát biểu dưới dạng số học. Khi một benchmark của nhà cung cấp, một README của thư viện, hay một đồng nghiệp hào hứng cho bạn xem "167x trên GPU," phản xạ nên là một câu hỏi duy nhất: baseline CPU là gì? Nếu baseline là triển khai ngây thơ — và gần như luôn là vậy, bởi vì một baseline chậm tạo nên một slide đẹp hơn — thì phần lớn con số tiêu đề là một chiến thắng thuật toán mà CPU cũng có quyền được hưởng, và chỉ phần dư mới là phần cứng. Ở đây phần dư là 6.2x. Một con số 167x được trích dẫn phóng đại đóng góp của phần cứng lên khoảng 27 lần.
Và hãy để ý cách mà bản thân thừa số phần cứng dịch chuyển theo kích thước bài toán. Trên sweep khung thời gian đơn nhỏ, chiến thắng thực sự của GPU-so-với-CPU-tốt-nhất là 3.2x. Trên phép precompute đa khung thời gian lớn hơn, nó là 6.2x — cùng hai con chip, lợi thế phần cứng gần như gấp đôi, thuần túy vì khối lượng công việc lớn hơn đẩy xa hơn lên roofline về phía trần compute phẳng của GPU trước khi CPU có thể theo kịp. Lợi thế phần cứng cũng không phải là một hằng số. Nó là một điểm trên cùng đường cong tăng dần đó, và cách bạn di chuyển sang phải trên đường cong đó là làm cho lô lớn hơn và công việc trên mỗi combo giàu hơn.
Một hướng dẫn quyết định: sweep phải rộng đến mức nào?

Gấp roofline trở lại thành một quyết định bạn có thể đưa ra trước khi tiêu tiền. GPU đáng đồng tiền khi cả hai điều kiện của roofline cùng đúng một lúc: sweep của bạn nằm bên phải điểm gờ của lô (, để chi phí khởi chạy và truyền dữ liệu cố định được khấu hao), và công việc trên mỗi combo của bạn bị giới hạn bởi compute (cường độ số học đủ giàu để chạm tới trần phẳng, chứ không phải một lượt O(n) mỏng). Cụ thể, từ những gì chúng tôi đo được:
- Vài chục combo của một chiến lược khung thời gian đơn: bỏ qua GPU. Bạn ở bên trái điểm gờ; chiến thắng trung thực so với numba song song là ~3.2x trên một công việc vốn đã chỉ mất một phần mười giây. Nút thắt cổ chai không phải là kernel, mà là mọi thứ xung quanh nó.
- Hàng nghìn combo, hoặc một phép precompute thực sự đa khung thời gian / đa chỉ báo: GPU xứng đáng có chỗ đứng. Chi phí cố định được khấu hao, các phép convolution dùng chung nâng cao cường độ số học, và chiến thắng phần cứng leo lên 6.2x và tiếp tục tăng theo lô. Đây là chế độ mà một GPU biến một sweep chạy qua đêm thành một lần nghỉ uống cà phê.
- Leo lên thang CPU trước — nó rẻ hơn và nó đến trước. 298x trên CPU và sự thu gọn thuật toán 27x là miễn phí hoặc gần như vậy, và chúng là điều kiện tiên quyết, không phải là các lựa chọn thay thế: 6.2x của GPU nằm bên trên thuật toán đã thu gọn, thứ mà đằng nào bạn cũng phải viết. Một GPU được lắp vào một pipeline ngây thơ thì phần lớn chỉ đo được sự ngây thơ đó.
Còn có một khoản thuế trên nhánh GPU chẳng liên quan gì đến tốc độ, và bạn phải tính nó vào giá: Metal GPU của Apple hoàn toàn không có fp64. Mọi thứ chạy ở fp32, độ chính xác tương đối ~1.2e-7. Điều đó giết chết thủ thuật kinh điển cho các moving average nhanh — WMA prefix-sum O(n) — bởi vì ở một thang giá gần 30,000 trên 150k nến, các tổng đang chạy đạt tới ~1e14, vượt bảy bậc độ lớn qua khỏi phạm vi số nguyên an toàn của fp32; chúng tôi đã đo được các sai số tương đối lên tới ~2e2 (một hệ số hai trăm, không phải hai phần trăm). Cách công thức hóa hoạt động được là phép convolution cửa sổ trực tiếp, trong đó mỗi tổng cửa sổ là một số lượng có giới hạn các số hạng có kích thước tương đương nhau và fp32 giữ được độ chính xác tới ~8e-7. Ngay cả khi đó, một chiến lược quyết định dựa trên dấu của hma - hma3 thỉnh thoảng sẽ lật một điểm giao cắt trên một nến ranh giới nơi hai đường cong gần như chạm nhau, bởi vì việc làm tròn fp32 nghiêng một thế cân bằng sát sao. Đó là lý do vì sao đường GPU đi kèm với một cổng tương đương đo xem các giao dịch đã lệch nhau bao nhiêu — delta PnL tính bằng điểm cơ bản (basis points), độ dịch chuyển tương đối trong số lượng giao dịch — thay vì khẳng định một đầu ra giống hệt từng bit mà nó không bao giờ có thể mang lại. Trong lần chạy của chúng tôi, độ lệch đó là 90 fill bị dịch chuyển trên tổng số 479,016 (0.019%), nằm hẳn trong dung sai, nhưng gánh nặng là có thật: chuyển sang GPU nghĩa là phải sở hữu một câu chuyện tương đương về số học, chứ không chỉ là một chiếc đồng hồ nhanh hơn. Chi phí kỹ thuật đó cũng là một phần của điểm hòa vốn.
Các con số mang hình dạng Apple; đường cong thì không
Mọi con số ở trên đều là một Apple M2 Max: một thiết bị bộ nhớ hợp nhất (unified-memory) nơi GPU và CPU dùng chung một pool, và một GPU chỉ-có-fp32 không có độ chính xác kép. Một card NVIDIA hay AMD rời làm thay đổi các hằng số, và đáng để nói rõ mỗi hằng số dịch chuyển theo hướng nào, bởi vì hình dạng của lập luận vẫn sống sót ngay cả khi các con số thì không.
- Chi phí truyền dữ liệu trở nên tệ hơn, không phải tốt hơn. Một card rời nằm sau PCIe, vậy nên đầu vào và kết quả phải tạo một bản sao thực qua bus mà bộ nhớ hợp nhất né được. Điều đó đẩy điểm gờ của lô sang phải — bạn cần một sweep còn rộng hơn nữa trước khi một GPU rời khấu hao được chi phí khởi chạy của nó. Cạnh trái của roofline dốc hơn trên một thiết bị PCIe, chứ không thoải hơn.
- Trần phẳng trở nên cao hơn. Một GPU trung tâm dữ liệu có nhiều FLOP/s và băng thông hơn hẳn một GPU tích hợp, vậy nên chiến thắng phần cứng tiệm cận trên một sweep bão hòa lớn hơn con số 6.2x của chúng tôi. Phần thưởng cho việc đạt tới phía bên phải của đường cong tăng lên; cái giá phải trả cho việc ngồi ở bên trái cũng tăng theo.
- fp64 quay trở lại, và cùng với nó là thủ thuật prefix-sum. Trên một card có độ chính xác kép thực sự, WMA prefix-sum O(n) lại khả thi và cổng tương đương có thể được siết chặt về phía chính xác từng bit. Khoản thuế fp32 cụ thể mà chúng tôi đã trả — convolution trực tiếp thay vì prefix sum, một cổng đo-độ-lệch thay vì một assert — là một chi tiết của Apple Silicon, không phải một định luật.
Không điều nào trong số này thay đổi luận điểm. Trên bất kỳ thiết bị nào, : một chi phí cố định bạn phải khấu hao, một tiệm cận bạn chỉ tiếp cận được từ bên phải. Các hằng số là phần cứng; đường cong là số học. Hãy đo , , và của chính bạn bằng một sweep lô năm dòng trước khi bạn tin vào con số tiêu đề của bất kỳ ai — kể cả của chúng tôi.
Bài viết này kết nối vào đâu
Đây là phép đo thứ tư trong một series con nhỏ về việc tốc độ backtest thực sự đến từ đâu, và các mảnh ghép khớp lại với nhau thành một lập luận duy nhất về việc tối ưu hóa cái gì trước khi bạn tiêu tiền:
- Thang tốc độ đã leo từ pandas lên numba song song để đạt 298x chỉ riêng trên CPU, và để lại GPU như một câu hỏi mở. Bài viết này trả lời nó: GPU là một bậc thang thứ năm có thật nhưng có điều kiện, đáng giá 3.2x–6.2x so với bậc CPU cao nhất, và chỉ khi sweep đủ rộng để leo lên roofline.
- Thuế IPC đã thực hiện cùng một nước đi theo hướng ngược lại — đo xem việc rời khỏi tiến trình tốn kém bao nhiêu — và đạt tới cùng một hình dạng kết luận: ranh giới (một socket, một lần khởi chạy GPU) thì rẻ; khoản thuế nằm ở bạn băng qua nó thường xuyên và chatty đến mức nào. Hãy gộp lô các lời gọi GPU của bạn vì cùng lý do bạn gộp lô IPC: để khấu hao một chi phí cố định cho mỗi lần băng qua.
- Cache parquet tổng hợp là phiên bản phía-CPU của những gì phép precompute GPU làm — tính các chỉ báo dùng chung một lần, tái sử dụng chúng trên mọi combo. GPU chỉ đơn giản đưa nguyên tắc tái-sử-dụng-và-gộp-lô đó xuống tận silicon.
- Và cổng tương đương fp32 là một bài toán tính tương đồng backtest-live thu nhỏ: khoảnh khắc đường nhanh của bạn tính ra thứ gì đó hơi khác so với tham chiếu của bạn, bạn nợ một bản giải trình được định lượng về độ lệch, chứ không phải một cái phẩy tay.
Kỷ luật kết nối chính là kỷ luật mà cả series này đều thúc đẩy: hãy đo chính cái thứ mà bạn thực sự đang được bán. Một mức tăng tốc là một tỷ lệ, và một tỷ lệ có tử số và mẫu số. Phần lớn sự thất vọng về GPU đến từ một mẫu số — baseline CPU — được chọn để tô hồng, và phần lớn sự lãng phí GPU đến từ việc chạy một sweep quá nhỏ để rời khỏi cạnh trái của roofline.
Điểm rút ra
- Mức tăng tốc GPU là một đường cong, không phải một con số. Trên phép precompute đa khung thời gian của chúng tôi, lợi thế so với CPU chạy từ 54.5x ở một combo mỗi lời gọi đến 359.6x ở sáu mươi mốt — cùng con chip, cùng dữ liệu. Bất kỳ con số đơn lẻ nào cũng là một điểm trên đường cong đó; hãy hỏi nó được đo ở kích thước lô nào.
- Luôn chất vấn baseline CPU. Con số tiêu đề đa khung thời gian 167x phân tích gọn gàng thành 27x của thuật toán (một sự thu gọn từ-tính-theo-từng-nến-thành-convolution, thứ cũng làm CPU nhanh lên y như vậy) nhân với 6.2x của phần cứng thực sự. Chiến thắng công bằng của GPU-so-với-CPU-tốt-nhất ở đây là 3.2x với khung thời gian đơn, 6.2x với đa khung — không phải 167x.
- Lợi thế tăng lên theo kích thước bài toán, và thừa số phần cứng cũng vậy. Lô lớn hơn và công việc trên mỗi combo giàu hơn đẩy bạn lên cao trên roofline: bản thân chiến thắng phần cứng trung thực đã tăng từ 3.2x lên 6.2x thuần túy nhờ làm cho khối lượng công việc lớn hơn. Các sweep nhỏ nằm bên trái điểm gờ và gần như không được lợi gì.
- Sửa thuật toán và leo lên thang CPU trước — chiến thắng GPU nằm bên trên chúng, chứ không phải thay thế chúng. 6.2x được đo so với thuật toán đã thu gọn mà đằng nào bạn cũng phải viết. Lắp một GPU vào một pipeline ngây thơ và phần lớn những gì bạn đo được là sự ngây thơ, không phải silicon.
- Chuyển sang GPU nghĩa là phải sở hữu một câu chuyện tương đương về số học. Không có fp64 trên Metal, thủ thuật WMA prefix-sum chết ở thang giá lớn (sai số tương đối ~2e2), và các chiến lược dựa-trên-dấu-giao-cắt lật trên các nến ranh giới. Hãy trang bị một cổng tương đương định lượng độ lệch bằng điểm cơ bản; hãy tính chi phí kỹ thuật đó vào điểm hòa vốn của bạn.
Khi ai đó nói với bạn rằng GPU đã làm backtest của họ nhanh hơn một trăm lần, họ gần như chẳng nói cho bạn điều gì cả. Hãy hỏi họ kích thước lô và baseline CPU, và con số một trăm đó thường phân giải thành một chiến thắng phần cứng một chữ số bao quanh một chiến thắng thuật toán mà lẽ ra họ đã có được miễn phí — đáng để có, trên một sweep đủ lớn, chính xác vì những lý do mà roofline nói ra và không thêm một lý do nào nữa.
Tác Giả
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.