Perangkap Ketepatan GPU: Bagaimana Backtest fp32 pada Apple Metal Diam-diam Memulangkan Sampah
Sebahagian daripada siri "Backtest Tanpa Ilusi".
Kami mengalihkan backtest sapuan-parameter kami ke GPU dan ia menjadi 2,796× lebih pantas. Angka yang dipulangkannya kelihatan munasabah. Ia juga, pada versi berfungsi pertama, sampah sepenuhnya — tersasar dengan faktor dua ratus — dan tiada apa yang ranap, tiada amaran, tiada apa yang kelihatan salah.
Inilah perangkap yang menjadikan backtesting GPU pada Apple Silicon berbahaya dengan cara yang kod CPU tidak: Metal tiada float64. Setiap nombor yang disentuh backtest anda pada GPU Apple ialah float 32-bit, sama ada anda memintanya atau tidak. Dan cara paling menggoda untuk mevektorkan purata bergerak — helah prefix-sum O(n) yang dicapai oleh setiap quant yang mementingkan prestasi — ialah tepat rumusan yang tidak dapat bertahan dalam fp32 pada skala harga. Ia tidak menghasilkan ralat. Ia berjalan pada kelajuan penuh dan menyerahkan kepada anda lengkung ekuiti yang kelihatan munasabah, dibina atas purata bergerak yang salah dengan faktor 211.
Penyelesaiannya ialah bahagian yang menarik, kerana ia bukan "guna lebih banyak bit" (anda tidak boleh) dan ia bukan "berhati-hati lebih" (kod naif itu sememangnya sudah berhati-hati). Penyelesaiannya adalah mengira kuantiti matematik yang sama dengan jumlah yang berbeza — konvolusi berjendela terus — yang mengekalkan setiap nilai perantaraan cukup kecil untuk fp32 mewakilinya dengan tepat. WMA yang sama, keputusan yang sama sehingga tujuh angka bererti, dan 55.9× lebih pantas daripada numba benang-tunggal pada CPU. Artikel ini ialah autopsinya: mengapa Metal memaksa fp32 ke atas anda, tepat di mana rumusan yang jelas itu melimpah, mengapa yang betul tidak, dan bagaimana kami membuktikan — dengan mengira dagangan, bukan dengan meneliti lengkung — bahawa versi pantas dan versi fp64 yang jujur bersetuju.
Semua angka di sini diukur pada Apple M2 Max, 150,000 bar × 80 gabungan parameter, terbaik-daripada-3, daripada scripts/bench_param_sweep.py repo (kaedah GPU M5, commit 97eadaf), scripts/engine_multitf_gpu.py (04d71e8), dan nota reka bentuk dalam scripts/GPU_NOTES.md.
Metal tiada float64

Pada CPU, backtest Python/numpy berjalan dalam ketepatan berganda secara lalai. float64 memberi anda mantissa 52-bit: integer diwakili dengan tepat sehingga 2⁵³ ≈ 9×10¹⁵, dan ketepatan relatif adalah kira-kira 1.1×10⁻¹⁶. Anda hampir tidak pernah memikirkannya, kerana pada skala harga — tutup BTC sekitar 30,000, jumlah kumulatif mencapai 10¹⁴ — fp64 mempunyai margin berlebihan.
GPU Apple tidak menawarkan margin itu, kerana ia tidak menawarkan float64 langsung. Metal Shading Language tiada jenis double; MLX, PyTorch-MPS, dan setiap rangka kerja lain yang menyasarkan Apple Silicon mewarisi ini. Tiada bendera untuk menghidupkannya dan tiada sandaran yang perlahan-tetapi-betul. Jika pengiraan anda menyentuh GPU, ia berlaku dalam fp32:
- Mantissa: 23 bit (24 dengan bit hadapan tersirat). Integer adalah tepat hanya sehingga 2²⁴ = 16,777,216 ≈ 1.6×10⁷.
- Ketepatan relatif: ~1.2×10⁻⁷. Lebih kurang tujuh angka perpuluhan bererti, dan tidak lebih.
Siling 1.6×10⁷ itu adalah keseluruhan ceritanya. Ia terdengar pemurah — enam belas juta — sehingga anda perasan bahawa backtest secara rutin membina kuantiti perantaraan yang jauh lebih besar daripada itu, dan saat perantaraan melepasi 1.6×10⁷, fp32 tidak lagi mampu walaupun mewakili integer berturutan, apatah lagi struktur pecahan yang anda perlukan. Ketepatan tidak merosot secara beransur-ansur; ia jatuh dari cenuram, dan cenuram itu berada pada nombor yang dilepasi data anda tanpa berfikir dua kali.
Perangkapnya ialah fp32 hampir selalu baik. Kebanyakan backtest — harga, pulangan, PnL, Sharpe — hidup dengan selesa dalam julat di mana tujuh angka sudah memadai. Jadi alih naif itu berfungsi, lulus ujian asas, menghasilkan output yang kelihatan waras. Kegagalannya terlokalisasi tepat kepada satu operasi, satu tempat di mana perantaraan meletup melepasi 10⁷, dan operasi itu ialah operasi yang divektorkan semua orang dahulu.
Rumusan yang menggoda: WMA dalam satu sapuan prefix-sum
Strategi kami bersandar pada purata bergerak Hull. HMA ialah tiga purata bergerak berwajaran yang dikarangkan bersama; varian HMA3 ialah empat. Purata bergerak berwajaran dengan kernel linear ke atas jendela panjang p ialah
Menyapu beribu-ribu gabungan parameter ke atas 150k bar, konvolusi WMA itulah kosnya. Jadi naluri — naluri yang betul, pada CPU — adalah menjadikan setiap WMA O(n) dan bukan O(n·p) menggunakan prefix sum. Anda prakira dua jumlah kumulatif sekali sahaja,
dan kemudian jumlah berwajaran-linear mana-mana jendela runtuh menjadi segelintir perbezaan dan anjakan indeks S1 dan S2. Tiada gelung per-jendela, tiada pengurangan per-jendela — dua laluan cumsum dan seluruh matriks WMA jatuh keluar daripada aritmetik tatasusunan. Ia mevektorkan dengan indah, ia memetakan dengan sempurna ke atas primitif imbasan-selari GPU, dan pada fp64 ia betul-betul tepat.
Ia juga perkara paling teruk yang boleh anda lakukan dalam fp32, dan sebabnya tersembunyi dalam S2.
S2 = mx.cumsum(j * price) # j is the global bar index: 0, 1, 2, ... , n-1
Sebutan j · price ialah masalahnya. Dengan j berjalan sehingga 150,000 dan price sekitar 30,000, sebutan terakhir sahaja ialah 4.5×10⁹, dan S2 ialah jumlah berjalan bagi 150,000 sebutan sebegitu. Ia tidak kekal pada skala harga. Ia mendaki ke wilayah di mana fp32 sudah berhenti mampu mengira.
Di mana ia melimpah: aritmetik perangkap

Mari letakkan tertib magnitud bersebelahan, kerana di situlah keseluruhan kegagalan itu hidup.
S2 = cumsum(j · price) mencapai kira-kira price · n²/2 ≈ 30,000 · (150,000)²/2 ≈ 3×10¹⁴. Panggilnya ~10¹⁴. Sekarang ingat semula siling integer-tepat fp32: ~1.6×10⁷. Jumlah berjalan itu melepasi integer terakhir yang boleh diwakili fp32 dengan tepat sebanyak tujuh tertib magnitud.
Apakah maksudnya secara konkrit? Berhampiran 10¹⁴, jurang antara dua nombor fp32 yang boleh diwakili — satu unit di tempat terakhir, ULP — adalah kira-kira 2²³ ≈ 8×10⁶. Jadi sebaik sahaja S2 berada dalam julat 10¹⁴, ia hanya diketahui dalam ±8 juta. Setiap nilai yang disimpannya telah dibundarkan kepada gandaan terdekat ~8×10⁶.
Sekarang perhatikan apa yang dilakukan pemulihan WMA dengan itu. Untuk mengekstrak jumlah berwajaran satu jendela, anda menolak dua nilai S2 berjiran (tambah pembetulan S1). Kedua-dua nilai S2 itu masing-masing ~10¹⁴, masing-masing membawa ±8×10⁶ hingar pembundaran. Perbezaan sebenar mereka — kuantiti berjendela yang sebenarnya anda mahu — sepadan, selepas penormalan, dengan WMA pada tertib harga itu sendiri, ~3×10⁴. Jadi aritmetiknya ialah:
Ini ialah pembatalan bencana dalam bentuk paling murni: ralat pembundaran setiap operand (±8×10⁶) adalah lebih besar daripada jawapan yang cuba anda pulihkan. Isyaratnya lebih kecil daripada aras hingar nombor yang darinya ia diekstrak. Bukan sahaja anda kehilangan beberapa angka — anda kehilangan kesemuanya, dan apa yang kembali dikuasai oleh pembundaran terkumpul cumsum itu.
Akibat yang diukur, daripada GPU_NOTES.md: untuk WMA yang dikira dengan cara ini pada 150k bar pada harga ~30,000, ralat relatif maksimum berbanding fp64 mencapai ~211. Bukan 211 peratus — 211×. Purata bergerak yang dikira boleh berada dua tertib magnitud daripada yang sebenar. Dan inilah bahagian yang menjadikannya perangkap dan bukan pepijat: ia berjalan sehingga selesai dan memulangkan nombor terhingga yang munasabah. Tiada limpahan ke infiniti, tiada NaN, tiada pengecualian. Purata bergerak yang salah dengan 211× masih kelihatan seperti purata bergerak — ia licin, ia terhingga, ia berada dalam lingkungan yang lebih kurang betul pada bar di mana pembatalan kebetulan lembut — jadi ia melepasi setiap semakan kewarasan yang bukan perbandingan langsung terhadap rujukan yang dipercayai. Anda mendapat backtest penuh, lengkung ekuiti penuh, satu set penuh parameter "optimum", semuanya dibina atas penunjuk yang fiksyen.
Penyelesaiannya bukan ketepatan lebih tinggi — ia adalah jumlah yang berbeza

Refleks, sebaik anda melihat ralat itu, adalah mencapai ketepatan lebih tinggi — kumpulkan dalam fp64, atau guna penjumlahan terpampas (Kahan). Pada Metal, yang pertama itu tidak tersedia langsung. Tetapi anda tidak memerlukan mana-mana, kerana masalahnya tidak pernah bilangan bit. Masalahnya ialah rumusan itu. Helah prefix-sum mengeluarkan perantaraan berskala 10¹⁴ dan kemudian menolaknya kembali ke bawah; magnitud yang diciptanya ialah artifak algoritma, bukan jawapannya. Pilih rumusan yang tidak pernah menciptanya dan fp32 baik-baik saja.
Rumusan itu ialah takrifnya sendiri: konvolusi berjendela terus. Bukannya dua jumlah kumulatif global, gelongsorkan kernel linear panjang-p merentas siri dan jumlahkannya di tempat. Setiap output ialah jumlah paling banyak p ≈ 200 sebutan, dan setiap sebutan ialah weight × price di mana pemberat dinormalkan supaya berjumlah 1 — jadi setiap sebutan berada pada tertib price / p, setiap jumlah separa kekal sekitar skala harga (~3×10⁴), dan tiada perantaraan pernah menghampiri enam tertib magnitud siling fp32. Tiada apa untuk dibatalkan kerana tiada apa yang pernah membengkak.
Dalam MLX ini ialah satu primitif — mx.conv1d — yang tepat merupakan apa yang GPU dibina untuk lakukan dengan pantas:
def _mx_wma_valid(x, period):
w = mx.arange(1, period + 1, dtype=mx.float32) / (period * (period + 1) / 2.0)
return mx.conv1d(x.reshape(1, -1, 1), w.reshape(1, period, 1), padding=0).reshape(-1)
WMA yang sama, setara secara matematik dengan versi prefix-sum dan dengan vec_wma/nb_wma fp64 CPU. Tetapi sekarang ralat relatif maksimum yang diukur berbanding fp64 ialah 8.2×10⁻⁷ — tepat pada aras hingar fp32 ~1.2×10⁻⁷, tujuh angka bererti persetujuan. Rumusan yang kelihatan lebih perlahan di atas kertas (O(n·p) dan bukan O(n)) ialah satu-satunya yang betul, dan — kerana ia konvolusi padat yang GPU selarikan merentas kedua-dua bar dan jendela serentak — ia juga sangat pantas. Kami beralih daripada ralat relatif 211 kepada 8×10⁻⁷ dengan menukar cara kami menjumlah, bukan berapa banyak bit kami menjumlah di dalamnya.
Dua nota praktikal yang timbul daripada melakukannya dengan cara ini. Pertama, MLX tidak akan menyebarkan NaN melalui conv1d seperti yang numpy lakukan, jadi rantau pemanasan (bar p−1 pertama, di mana purata berjendela tidak ditakrifkan) tidak boleh ditanda dengan NaN pada GPU. Kami tidak memerlukannya begitu: permulaan sah setiap siri diketahui secara analitik, awalan tidak sah diisi dengan sifar yang tidak pernah dibaca, dan padding NaN dipulihkan pada CPU selepasnya — topeng kesahihan yang sama bit-demi-bit seperti versi tervektor dan numba. Kedua, keseluruhan sapuan berkongsi satu siri cand_close dan menggunakan semula jendela dengan banyak merentas gabungan, jadi satu conv1d berkelompok tunggal dengan banyak saluran output mengira setiap WMA unik yang diperlukan sapuan dalam satu panggilan GPU, dimaterialkan dengan satu mx.eval().
Membuktikan anda tidak terjebak: kesetaraan melalui kiraan dagangan
Inilah soalan yang tidak selesa yang seksyen terakhir sepatutnya bangkitkan: jika WMA yang salah dengan 211× masih kelihatan seperti WMA, bagaimana anda tahu versi 8×10⁻⁷ itu sebenarnya betul dan bukan sekadar salah dengan lebih halus? Anda tidak boleh menelitinya. Anda memerlukan invarian yang didedahkan oleh bahagian hiliran yang diskret dalam saluran paip — dan backtest menyerahkan kepada anda satu yang sempurna: dagangan.
Kaedah GPU lain dalam tangga (M0–M4) berjalan sepenuhnya dalam fp64, jadi kami menegakkan mereka kepada assert kesetaraan yang ketat — kiraan dagangan yang sama, PnL sepadan kepada atol=1e-6. Kaedah GPU fp32 (M5) tidak boleh lulus itu secara pembinaan, dan diam-diam melonggarkan assert untuk semua orang bagi menampung ia adalah tepat jenis ketidakjujuran yang siri ini wujud untuk lawan. Jadi M5 mendapat laporan kesetaraan kuantitatifnya sendiri, report_equiv_fp32, membandingkan dagangan yang diekstraknya terhadap rujukan fp64.
Mekanisme sebarang ketidaksetujuan baki wajar dinamakan dengan tepat, kerana ia bukan bencana pembatalan — ia adalah pembundaran fp32 biasa yang kecil seperti yang anda jangka. Strategi menembak pada silangan dua purata Hull, h vs h3. Ralat relatif ~1×10⁻⁶ pada penunjuk pada harga ~30,000 ialah goyah mutlak ~0.03. Pada sebahagian besar bar, kedua-dua lengkung lebih jauh terpisah daripada itu dan silangan tidak samar. Tetapi pada bar sempadan — di mana h − h3 itu sendiri berada dalam 0.03 daripada sifar — goyah itu boleh membalikkan tanda perbandingan, mengalihkan satu silangan sebanyak satu bar, menambah atau membuang satu dagangan.
Inilah sebabnya "pecahan gabungan yang berbeza" ialah metrik kesihatan yang tidak berguna, dan semakan kesetaraan pertama kami memalukan diri sendiri dengan menggunakannya. Pada 150k bar setiap gabungan mempunyai beribu-ribu silangan, jadi sekurang-kurangnya satu bar sempadan muncul pada hampir setiap gabungan — 37 daripada 80 gabungan "berbeza," yang terdengar membimbangkan dan bermaksud tiada apa. Metrik yang penting ialah sebanyak mana:
- Delta PnL merentas semua 80 gabungan: maks |Δ| = 1.843 mata peratusan, maks relatif = 1.25×10⁻²; ambang ranap 5 m.p.
- Hanyutan kiraan-dagangan setiap gabungan: maks |Δn| = 4 dagangan daripada beribu-ribu, maks relatif = 2.5×10⁻³; ambang ranap 1%.
- Secara agregat: 90 dagangan teranjak daripada 479,016 — 0.019%.
Sembilan puluh dagangan daripada hampir setengah juta, setiap satunya silangan sempadan yang ditolak oleh goyah pembundaran lebih kecil daripada tik harga, tiada satu pun menghampiri ambang ranap. Itu ialah tandatangan kaedah fp32 yang betul — ketidaksetujuan yang kecil, terbatas, dan boleh dijelaskan — dan ia adalah haiwan yang sama sekali berbeza daripada ralat relatif 211. Ambang itu wujud untuk menangkap rumusan rosak yang menyamar sebagai "yalah, ia cuma fp32"; delta sebenar datang satu tertib magnitud di bawahnya. Kiraan dagangan ialah orakel yang lengkung ekuiti enggan menjadi.
Ganjarannya, dan di mana GPU berhenti membantu
Dengan kebetulan ditetapkan, kelajuan wajar dinyatakan — dan kemudian dilayakkan dengan jujur, kerana kelebihan GPU tidak seragam merentas saluran paip.
Pada konvolusi WMA tulen secara berasingan — operasi yang seluruh kaedah wujud untuk pecutkan — kelompok conv1d fp32 berjalan 55.9× lebih pantas daripada numba benang-tunggal, pada ralat relatif 8.2×10⁻⁷ itu. Itu ialah angka GPU-vs-CPU yang bersih dan setara-secara-adil: matematik yang sama, satu benang kod CPU terkompil terhadap GPU Metal.
Tetapi sapuan bukan hanya konvolusi. Sebaik matriks HMA/HMA3 dikira pada GPU, dagangan masih perlu diekstrak — jalanan O(n) merentas silangan setiap gabungan — dan itu kami lakukan pada CPU dalam fp64, menggunakan semula semantik dagangan tepat kaedah lain dan bukannya melaksanakannya semula pada GPU. Angka timed() hujung-ke-hujung merangkumi semuanya: pemanasan kernel dikecualikan (simetri dengan mengecualikan kompilasi numba), tetapi pemindahan GPU→CPU dan pengekstrakan dagangan CPU disertakan. Pada 150k bar × 80 gabungan, terbaik-daripada-3, M2 Max:
| Kaedah | Wall | Pecutan vs baseline | gabungan/s |
|---|---|---|---|
| M0 pandas + Python loop* | 287.08s | 1.0× | 0.3 |
| M1 vectorized numpy | 3.14s | 91.5× | 25.5 |
| M2 numba (serial) | 2.02s | 142.3× | 39.7 |
| M3 multiprocess + vectorized | 0.50s | 570.2× | 158.9 |
| M4 multiprocess + numba (12 teras) | 0.33s | 882.5× | 245.9 |
| M5 MLX GPU (fp32) | 0.10s | 2796.0× | 779.2 |
*M0 diekstrapolasi daripada sampel 5-gabungan seragam.
M5 enjin-penuh melakukan 779 gabungan/s — 2,796× berbanding baseline pandas, 19.6× berbanding numba serial, dan 3.2× berbanding seluruh kolam CPU 12-teras yang menjalankan numba (M4). Satu GPU mengalahkan setiap teras CPU yang dimiliki mesin, sebanyak tiga kali ganda.
Sekarang kelayakan yang jujur: perhatikan bahawa kelebihan GPU hujung-ke-hujung (19.6× berbanding M2) lebih kecil daripada kelebihan konvolusi-sahaja (55.9× berbanding numba). Jurang itu ialah hukum Amdahl tiba mengikut jadual. GPU memusnahkan konvolusi begitu sepenuhnya sehingga ia berhenti menjadi kesesakan; apa yang tinggal — pengekstrakan dagangan CPU O(n) yang GPU pecutkan langsung tidak — kini menguasai masa wall M5. Ini ialah pelajaran yang sama yang tangga-kelajuan dan cebisan cukai-IPC dalam siri ini terus mendarat: melepasi satu titik, kemenangan bukan "jadikan bahagian pantas lebih pantas," ia adalah orkestrasi — di mana data tinggal, peringkat mana yang kini serial, apa yang anda bayar untuk memindahkan antara peranti dan hos. Mengejar M6 hipotetikal yang menolak pengekstrakan dagangan ke dalam kernel Metal tersuai hanya akan mengaut kembali kepingan CPU yang mengecut, itulah sebabnya kami tidak membinanya.
Pelajaran umum: sampah numerik senyap ialah lalai
Berundur sedikit daripada purata Hull dan MLX, kerana perangkap ini menyeluruh jauh melepasi satu penunjuk ini.
Cadangan menggoda backtesting GPU ialah "satu matriks besar": susun setiap gabungan parameter ke dalam tensor, jalankan seluruh sapuan sebagai segelintir operasi tatasusunan padat, biar perkakasan memakannya. Cadangan itu nyata — pecutan di atas nyata. Tetapi ia diam-diam mengubah rejim numerik di bawah anda, dan perubahan itu tidak kelihatan dalam kod. Pada CPU, lalai anda melindungi anda: fp64, penyebaran NaN, cumsum yang boleh berjalan sehingga 10¹⁴ tanpa perasan. Alihkan ungkapan tatasusunan yang sama ke Metal dan anda berada dalam fp32 dengan siling integer keras 1.6×10⁷, dan baris kod yang sama — cumsum(j * price) — beralih daripada tepat kepada sampah. Tiada apa dalam sintaks yang memberi amaran kepada anda. Pengkompil gembira. Output terhingga dan munasabah. fp32 tidak gagal dengan kuat; ia gagal dengan sopan, dengan nombor.
Tiga tabiat yang sebenarnya melindungi anda adalah murah:
- Ketahui di mana perantaraan anda tinggal, bukan hanya input dan output anda. Input (harga ~10⁴) dan output (WMA ~10⁴) kedua-duanya berada dengan selesa dalam julat tepat fp32. Bencana itu sepenuhnya dalam perantaraan tersembunyi (
S2~10¹⁴) yang tidak dijadikan kelihatan oleh API mahupun jenis. Sebelum mempercayai sebarang pengurangan fp32, tanya apakah jumlah separa terbesar mencapainya — dan jika ia melepasi ~10⁷, tukar rumusannya. - Utamakan rumusan yang mengekalkan magnitud terbatas. Konvolusi terus berbanding prefix sum; jendela tempatan berbanding imbasan global; pemusatan/pembezaan sebelum penjumlahan dan bukan selepasnya. Besar-kemudian-batal ialah anti-corak. Algoritma yang betul selalunya yang kelihatan lebih teruk secara asimptotik di atas kertas tetapi tidak pernah mengeluarkan kuantiti yang perlu dibatalkannya.
- Sahkan terhadap orakel fp64 melalui invarian diskret. Jangan bandingkan lengkung; bandingkan sesuatu yang terkuantum dan hiliran — kiraan dagangan, bilangan silangan, peristiwa perubahan-kedudukan. Invarian diskret menukar ralat 211× yang senyap kepada kegagalan penegasan yang menjerit, dan menukar ralat 8×10⁻⁷ yang boleh diterima kepada delta yang kecil, terbatas, dan boleh dijelaskan. Ini disiplin yang sama seperti ujian anjakan satu-bar untuk bias pandang-hadapan: diagnostik murah yang menukar kegagalan tidak kelihatan kepada kegagalan yang kelihatan.
Tiada satu pun daripada ini analisis numerik eksotik. Ia ialah kebersihan biasa iaitu tidak mempercayai backtest pantas sehingga backtest perlahan yang dipercayai telah mengesahkannya — diperluas ke satu tempat di mana bahasa berhenti memberi amaran kepada anda bahawa ketepatan telah diam-diam jatuh sebanyak dua belas tertib magnitud.
Ringkasan Penting

- GPU Apple tiada float64 — setiap nombor GPU dalam backtest anda ialah fp32. Integer tepat hanya sehingga ~1.6×10⁷ dan ketepatan ialah ~1.2×10⁻⁷. Tiada bendera, tiada sandaran. Kebanyakan backtest bertahan dengan ini; tepat satu operasi biasanya tidak.
- WMA prefix-sum ialah perangkapnya.
cumsum(j · price)mendaki ke ~10¹⁴, tujuh tertib melepasi siling tepat fp32, dan memulihkan satu jendela memaksa anda menolak dua nombor sebegitu yang ralat pembundarannya (±8×10⁶) sudah mengecilkan jawapan. Ralat relatif maksimum yang diukur: 211×. Ia tidak pernah ranap — ia memulangkan sampah yang munasabah. - Penyelesaiannya ialah jumlah yang berbeza, bukan lebih banyak bit. Konvolusi berjendela terus (
mx.conv1d) mengekalkan setiap jumlah separa berhampiran skala harga, jadi fp32 memegang tujuh angka jujur: ralat relatif 8.2×10⁻⁷, dan 55.9× lebih pantas daripada numba benang-tunggal. Anda tidak boleh membeli fp64 pada Metal, dan anda tidak memerlukannya. - Sahkan dengan invarian diskret, jangan sekali-kali lengkung. Kesetaraan kiraan-dagangan menangkapnya: conv1d fp32 tidak bersetuju dengan fp64 pada 90 daripada 479,016 dagangan (0.019%), semuanya silangan sempadan, semuanya jauh di bawah ambang ranap — tandatangan kaedah yang betul, jelas berbeza daripada ralat 211×. "Pecahan gabungan yang berbeza" ialah metrik umpan; ukur sebanyak mana.
- Sapuan penuh melakukan 779 gabungan/s — 2,796× berbanding baseline pandas, 3.2× berbanding seluruh kolam CPU 12-teras — tetapi kemenangan hujung-ke-hujung (19.6× berbanding numba serial) lebih kecil daripada kemenangan konvolusi-sahaja (55.9×) kerana pengekstrakan dagangan CPU kini ialah kesesakan. Melepasi satu titik, kelajuan ialah orkestrasi, bukan aritmetik.
Alih GPU itu 2,796× lebih pantas dan, pada versi berfungsi pertamanya, salah sepenuhnya — dan kedua-dua fakta itu tiada kaitan antara satu sama lain. Kelajuannya nyata. Sampah itu ialah perantaraan 10¹⁴ tersembunyi yang fp32 tidak dapat pegang dan tiada mesej ralat akan sebut. Jika backtest menjadi jauh lebih pantas secara dramatik dan nombornya masih kelihatan baik, itu bukan pengesahan. Pada Metal, "kelihatan baik" ialah tepat rupa ralat relatif 211.
Ini ialah anak tangga GPU dalam tangga yang siri ini telah daki: tangga kelajuan enjin-backtest, cukai IPC pemprosesan berbilang, taksonomi pandang-hadapan kebocoran, dan reka bentuk fungsi-objektif yang menentukan apa itu "baik" pun bermaksud. Kelajuan tidak bernilai jika ia pantas mengira nombor yang salah.
Pengarang
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.