GPU कब अपनी कीमत वसूलता है: पैरामीटर-स्वीप की roofline, जहाँ सुर्खियों वाला 167x असल में 27x एल्गोरिदम गुणा 6.2x हार्डवेयर है
"बिना भ्रम के बैकटेस्ट" श्रृंखला का एक हिस्सा।
स्पीड लैडर का अंत जानबूझकर एक अधूरे-से नोट पर हुआ था। हमने 80-combo पैरामीटर स्वीप को लैपटॉप CPU पर pandas के 69.9 सेकंड से घटाकर parallel numba के 0.23 सेकंड तक पहुँचाया था — मापा हुआ 298x — और फिर दलील दी थी कि GPU वह गायब पायदान नहीं है। कमेंट सेक्शन ने इसे चुपचाप कबूल नहीं किया, और न करने में वह सही था। "गायब पायदान नहीं" एक workload के बारे में, एक आकार पर, किया गया दावा है। यह प्रकृति का कोई नियम नहीं। इसलिए यह लेख वही करता है जो ईमानदारी कहती है, और GPU को बेंच पर बिठा देता है।
नतीजा कोई फ़ैसला नहीं, एक कर्व है। CPU पर GPU की बढ़त कोई एक अदद संख्या नहीं जिसे स्लाइड पर छाप दिया जाए; वह इस बात का फलन है कि हर कॉल में आप उसे कितना काम थमाते हैं। हमारे मल्टी-टाइमफ्रेम इंडिकेटर precompute पर CPU इंजन के मुकाबले GPU का स्पीडअप 54.5x से — जब हम एक बार में एक पैरामीटर combination माँगते हैं — 359.6x तक जाता है, जब हम एक साथ 61 माँगते हैं। वही kernel, वही डेटा, वही हार्डवेयर — बदला सिर्फ़ batch है। जो बेंचमार्क इनमें से एक संख्या बताता है और दूसरी छिपा लेता है, वह GPU को नहीं नाप रहा, वह सुर्खी चुन रहा है।
और 359.6x भी वह नहीं है जो दिखता है। परतें खोलिए तो मल्टी-टाइमफ्रेम की बड़ी सुर्खी 167x टूटकर बनती है 27x एल्गोरिदम की — एक ऐसा rewrite जो CPU को भी 27x तेज़ कर देता है — गुणा 6.2x असली हार्डवेयर की। वह 27x GPU ने नहीं किया। गणित ने किया। यह लेख इन दोनों को अलग-अलग पहचानने के बारे में है, क्योंकि इन्हें गड्डमड्ड करना ही वह तरीका है जिससे $2,000 का ग्राफिक्स कार्ड उस समस्या के इलाज के तौर पर बिक जाता है जिसे एक git commit मुफ़्त में हल कर देता।
स्रोत-नोट: नीचे की सभी संख्याएँ Apple M2 Max पर मापी गई हैं — MLX के ज़रिए Metal GPU पर fp32 बनाम बारह CPU थ्रेड्स पर fp64 numba — हमारी engine_multitf_gpu.py और bench_param_sweep.py बेंचमार्क स्क्रिप्ट्स से, हर एक उस equivalence जाँच से होकर, जो पुष्टि करती है कि GPU और CPU रास्ते एक जैसे trades निकालते हैं। इस लेख का कोई सार्वजनिक साथी-पेपर नहीं है — स्क्रिप्ट्स ही रिकॉर्ड हैं।
सवाल एक roofline है, बेंचमार्क नहीं

"GPU कितना तेज़ है" का कोई एक जवाब क्यों नहीं है, इसकी वजह है roofline model (Williams, Waterman & Patterson, 2009)। किसी प्रोसेसर का हासिल-करने-योग्य प्रदर्शन एक साथ दो छतों से बंधा है: एक सपाट छत, जिसे शिखर compute (FLOP/s) तय करता है, और एक ढलान वाली, जिसे मेमोरी बैंडविड्थ गुणा arithmetic intensity तय करती है — यानी हर हिलाए गए byte पर आप कितने उपयोगी ऑपरेशन करते हैं। प्रति-byte सस्ता काम ढलान वाली छत के नीचे रहता है और bandwidth-bound होता है; प्रति-byte समृद्ध काम सपाट छत तक पहुँचता है और compute-bound होता है। GPU की सपाट छत CPU की छत के ऊपर मीनार-सी खड़ी है, पर उसकी ढलान वाली छत और — सबसे अहम — उसकी प्रति कॉल फिक्स्ड लागत उतनी शालीनता से नीचे नहीं उतरती।
पैरामीटर स्वीप के लिए चार्ट के बाएँ हिस्से पर एक तीसरा पद हावी रहता है: launch और transfer overhead। हर GPU कॉल एक लगभग तयशुदा कीमत चुकाती है — kernel dispatch करो, इनपुट को (Apple Silicon पर unified, पर मुफ़्त नहीं) मेमोरी सीमा के पार ले जाओ, नतीजे वापस लाओ। combinations के एक batch का wall time यूँ मॉडल कीजिए
जहाँ वही फिक्स्ड overhead है, GPU की प्रति combo सीमांत लागत, और CPU की। तब स्पीडअप है
यही एक भिन्न पूरा लेख समझा देती है। छोटे पर हर में बैठा अनुपात को कुचल देता है — आपने GPU को जगाने की कीमत चुकाई और इस्तेमाल मुश्किल से किया। जैसे-जैसे बढ़ता है, ज़्यादा combos पर बँटता जाता है और स्पीडअप अपने asymptote — असली हार्डवेयर अनुपात — की ओर चढ़ता है। आधे रास्ते का बिंदु पर बैठा है: batch-space का एक "ridge point", जो बताता है कि GPU जो हो सकता है उसका आधा भी बनने से पहले आपका स्वीप कितना चौड़ा होना चाहिए। कुछ दर्जन combos का स्वीप उस ridge से बहुत बाईं ओर बैठता है। हज़ारों का स्वीप उस सपाट हिस्से पर, जहाँ GPU आखिरकार अपनी छत की कमाई करता है।
तो सही सवाल कभी "क्या GPU तेज़ है" नहीं होता। सवाल है "क्या मेरा स्वीप ridge के दाईं ओर है, और क्या मेरा प्रति-combo काम इतना compute-bound है कि वहाँ पहुँचकर सपाट छत छू सके।" दोनों सच होने चाहिए। बाकी लेख नापता है कि ये दहलीज़ें असल में कहाँ पड़ती हैं।
सिंगल-टाइमफ्रेम फ़ैसला: GPU बस बाल-बाल जीतता है

उसी workload से शुरू कीजिए जो स्पीड लैडर ने इस्तेमाल किया था: सिंगल-टाइमफ्रेम HMA/HMA3 स्वीप, 150,000 bars पर 80 combinations। हमने उस सीढ़ी में छठा पायदान जोड़ा — M5, MLX के ज़रिए Apple GPU पर indicators, trades अब भी CPU पर निकाले जाते हैं। Warm-up हो चुका, best of three, equivalence gate हरा:
| विधि | वास्तविक समय | बनाम pandas | Combos/s |
|---|---|---|---|
| M0 pandas + लूप | 287.08s | 1.0x | 0.3 |
| M2 numba (1 कोर) | 2.02s | 142x | 39.7 |
| M4 mp + numba (12 कोर) | 0.33s | 883x | 245.9 |
| M5 MLX GPU (fp32) | 0.10s | 2796x | 779.2 |
नादान serial बेसलाइन के मुकाबले GPU वीर दिखता है — 2,796x। पर वह तुलना कोई ईमानदार आदमी करेगा ही नहीं: वह एक अच्छे GPU implementation को सबसे बुरे CPU implementation से भिड़ाती है। GPU को उस CPU के सामने खड़ा कीजिए जिसे आप असल में तैनात करते — वही kernel बारहों कोर पर, M4 — और जीत सिकुड़कर एक संजीदा 3.2x रह जाती है (779 बनाम 246 combo प्रति सेकंड)। एक पूरा का पूरा ग्राफिक्स कार्ड, पूरा स्वीप चलाकर, बारह-कोर CPU पूल को तीन के गुणक से हराता है।
तीन दशमलव दो कुछ नहीं है, ऐसा नहीं। पर यह वह वजह भी नहीं जिसके लिए कोई GPU खरीदता है। और इतने सँकरे स्वीप के लिए roofline की भविष्यवाणी ठीक यही है: 80 combos ridge के बाईं ओर हैं। फिक्स्ड launch-और-transfer overhead अब भी 0.10-सेकंड के काम का अच्छा-खासा हिस्सा है, इसलिए हम asymptote तक कभी पहुँचते ही नहीं। इससे भी बुरा: प्रति-combo लागत का एक हिस्सा O(n) trade-extraction पास है, जिसे हमने जानबूझकर CPU पर छोड़ा — एक ऐसा पद जिसे GPU बिल्कुल भी तेज़ नहीं कर सकता (क्यों, यह अगले सेक्शन में)। इस आकार के सिंगल-टाइमफ्रेम रिसर्च लूप के लिए स्पीड लैडर का मूल फ़ैसला कायम है: GPU वह गायब पायदान नहीं है। Parallel numba ने आपको पहले ही 0.23–0.33 सेकंड पर पहुँचा दिया था, और उसे छीलकर 0.10 कर देना वह चीज़ नहीं जो किसी रिसर्चर को अनब्लॉक करती है। स्वीप के इर्द-गिर्द की orchestration करती है।
उस फ़ैसले का दिलचस्प शब्द है इस आकार के। batch की धुरी पर दाईं ओर बढ़िए, और कहानी बदल जाती है।
लागत असल में कहाँ रहती है
batch बढ़ाने से पहले देखिए कि हम असल में किस चीज़ की कीमत चुका रहे हैं, क्योंकि roofline इनाम तभी देती है जब महँगा हिस्सा compute-bound हो। स्वीप की profile निकालिए और उसका लगभग सारा हिस्सा एक ही चीज़ है: weighted-moving-average convolutions। एक HMA तीन WMA है; एक HMA3 चार; हर combination इन्हें पूरी series पर दोबारा चलाता है। Trade extraction — दोनों indicator arrays पर चलो, hma - hma3 के sign flips ढूँढो, fills दर्ज करो — एक अकेला सस्ता O(n) पास है। यह स्वीप ट्रेडिंग-स्ट्रैटेजी की पोशाक पहने एक convolution workload है।
वह बँटवारा ठीक roofline के दो शासन हैं:
- Convolutions compute-bound हैं। हर price कई अतिव्यापी window sums में पढ़ा जाता है, इसलिए arithmetic intensity — प्रति हिलाए गए byte ऑपरेशन — ऊँची है। यह काम सपाट compute छत की ओर हाथ बढ़ाता है, और मीनार-सी ऊँची तो GPU की सपाट छत ही है। और भी बेहतर: windows combinations के आर-पार भी अतिव्यापी हैं — लंबाई 40 की एक WMA दर्जनों combos के काम आती है, इसलिए batched precompute उसे प्रति combo दोबारा गिनने के बजाय एक बार साझा कर देता है। batching सिर्फ़ launch overhead नहीं बाँटती; loads के पुन:उपयोग से वह arithmetic intensity भी बढ़ाती है। यही वह हिस्सा है जिसकी जगह GPU पर है।
- Trade extraction bandwidth-bound और शाखाओं भरा है। एक sequential पास, हर crossing पर data-निर्भर branches, पुन:उपयोग लगभग शून्य। इसकी arithmetic intensity फ़र्श के पास है, और इसका control flow किसी SIMD डिवाइस के लिए दुश्मनी से भरा। इसे GPU पर धकेलने से मिलता थोड़ा, कीमत बहुत — यह CPU पर रहता है। जिसका मतलब है कि यह इस बँटवारे की Amdahl's law वाली serial पूँछ है — एक ऐसा स्थिर फ़र्श जिसे GPU का स्पीडअप कभी भेद नहीं सकता, और एक वजह कि सिंगल-TF संख्या 3.2x पर जाकर saturate हुई।
इस kernel के मल्टी-टाइमफ्रेम संस्करण में एक दूसरा, और तीखा सबक छिपा है, और वही उस 27x का स्रोत है जिसे समझाने का वादा हम बार-बार किए जा रहे हैं। मल्टी-TF इंजन एक ऊँचे-टाइमफ्रेम HMA को look-ahead के बिना आधार 1-मिनट index पर align करता है। सीधे-सीधे लिखें तो यह प्रति-bar O(length^1.5) काम है — हर आधार bar पर ऊँचे-TF के moving averages दोबारा गिनो। पर aligned HMA आखिरी कुछ बंद ऊँचे-TF candles के एक छोटे buffer और चालू close में रैखिक है, इसलिए पूरा प्रति-bar हिसाब एक तयशुदा weight vector में सिमट जाता है: बंद candles की series पर एक conv1d, उसके बाद एक O(n) gather। दसियों करोड़ फ़ालतू ऑपरेशन एक कहीं छोटी series पर एक convolution बन जाते हैं।
वह सिमटाव एक एल्गोरिदमिक जीत है, हार्डवेयर की नहीं। वह एक बेहतर फ़ॉर्मूला है। वह GPU पर चलता है, और उतने ही मज़े से CPU पर भी — np.correlate और एक gather, fp64 में। इसे मज़बूती से याद रखिए: मल्टी-TF सुर्खी का सबसे बड़ा गुणनखंड एक ऐसा rewrite है जो बिना किसी GPU वाली मशीन को भी उपलब्ध है। जब हम आखिर में 167x को तोड़ेंगे, यही वह 27x है।
बढ़त batch size के साथ बढ़ती है

अब वह माप, जो roofline ने हमें करने को कहा था। महँगी धुरी लीजिए — 1-मिनट आधार series पर aligned ऊँचे-टाइमफ्रेम HMA precompute, हमारी सबसे लंबी candle stream — और GPU को हर कॉल में बढ़ती संख्या में length combinations खिलाइए, । यहाँ CPU बेसलाइन ईमानदार production इंजन है: बारहों कोर पर prange वाला numba। हर batch पर हम दोनों नापते हैं और अनुपात लेते हैं।
| बैच (combo/कॉल) | 12-कोर CPU इंजन के मुकाबले GPU स्पीडअप |
|---|---|
| 1 | 54.5x |
| 2 | 102.5x |
| 4 | 129.5x |
| 8 | 187.4x |
| 16 | 267.4x |
| 32 | 245.0x |
| 61 | 359.6x |
यह असली मापों में खींचा गया है। पर GPU पहले से 54.5x आगे है — क्योंकि यह तुलना नादान प्रति-bar इंजन से है, इसलिए एल्गोरिदमिक सिमटाव एक combo पर भी पहले से भीतर बैठा है — पर वह अपनी छत के आसपास भी नहीं: एक-combo कॉल पर फिक्स्ड overhead अब भी हावी है। batch दोगुना कीजिए और स्पीडअप लगभग दोगुना होकर 102.5x हो जाता है; तक 267.4x; तक 359.6x, और साफ़ दिख रहा है कि अभी भी चढ़ रहा है। बढ़त समस्या के आकार के साथ बढ़ती है। GPU और पैरामीटर स्वीप के बारे में यही सबसे अहम वाक्य है, और यह ठीक उसके उलट है जैसे GPU स्पीडअप आमतौर पर बताए जाते हैं — मानो वे चिप की कोई स्थिर संपत्ति हों।
दो ईमानदारी-नोट, क्योंकि यह बिना भ्रम के बैकटेस्ट श्रृंखला है और एक साफ़-सुथरी monotone तालिका हमेशा शक की हकदार होती है।
पहला, गिरावट: पर 245.0x पढ़ा गया, के 267.4x से नीचे। यह गलीचे के नीचे बुहार देने वाला शोर नहीं है — यह एक chunk-boundary artifact है। हमारा conv1d एक kernel chunk में 32 lengths पैक करता है, तो ठीक एक chunk को बिना किसी ढील के भरता है, जबकि दूसरे chunk में छलक जाता है, जो संयोग से डिवाइस को बेहतर saturate करता है। roofline का मतलब रुझान है, और असली डिवाइस में quantization की सीढ़ियाँ होती हैं; हम डगमगाहट की रिपोर्ट करते हैं, उसके इर्द-गिर्द cherry-pick नहीं।
दूसरा, और ज़्यादा अहम: 54.5x और 359.6x दोनों नादान CPU इंजन के मुकाबले हैं, और दोनों में से कोई भी हार्डवेयर जीत नहीं है। दोनों संख्याओं के भीतर अब भी 27x का एल्गोरिदमिक सिमटाव बैठा है। अगर आप CPU बेसलाइन की जगह CPU पर चलता सिमटा हुआ एल्गोरिदम रख दें — वही फ़ॉर्मूला, fp64, दोनों पक्ष optimized — तो हर पंक्ति लगभग उसी गुणक से सिकुड़ जाएगी। और ठीक यही वह विघटन है जिसे अगला सेक्शन सटीक बनाता है।
ईमानदार विघटन: 27x एल्गोरिदम गुणा 6.2x हार्डवेयर

एल्गोरिदम को सिलिकॉन से अलग करने के लिए आपको एक ही मल्टी-टाइमफ्रेम grid पर तीन रास्ते नापने होते हैं, दो नहीं। तो बेंचमार्क चलाता है:
- cpu-engine — production numba इंजन, सभी कोर पर प्रति-bar aligned HMA। नादान-पर-parallel बेसलाइन।
- cpu-collapsed — सिमटे हुए weight vectors,
np.correlateऔर gather, fp64, CPU पर। (1) वाला ही हार्डवेयर, बेहतर एल्गोरिदम। - gpu-mlx — सिमटे हुए weights, Metal GPU पर batched
conv1dके रूप में, fp32। (2) वाला ही एल्गोरिदम, अलग हार्डवेयर।
तीनों को कतार में रखिए और पूरी मल्टी-टाइमफ्रेम सुर्खी साफ़-साफ़ गुणनखंडों में टूट जाती है:
बायाँ गुणनखंड, 27x, एल्गोरिदम है — पिछले सेक्शन वाला प्रति-bar-से-convolution सिमटाव। इसका GPU से कोई लेना-देना नहीं। इसे numpy में लागू कीजिए और आपका लैपटॉप CPU इस workload पर एक refactor की कीमत में 27x तेज़ हो जाता है। दायाँ गुणनखंड, 6.2x, हार्डवेयर है — बारह CPU कोर पर चल रहे उसी optimized एल्गोरिदम पर Metal GPU की ईमानदार, बराबरी-की-टक्कर वाली जीत। वह 6.2x इकलौता हिस्सा है जिसे पाने के लिए आपको सचमुच GPU चाहिए था।
यही पूरे लेख की नैतिक सीख है, अंकगणित की शक्ल में। जब कोई vendor बेंचमार्क, कोई library README या कोई उत्साहित सहकर्मी आपको "GPU पर 167x" दिखाए, तो पलटकर एक ही सवाल आना चाहिए: CPU बेसलाइन क्या थी? अगर बेसलाइन नादान implementation थी — और लगभग हमेशा होती है, क्योंकि धीमी बेसलाइन से स्लाइड बेहतर बनती है — तो सुर्खी का ज़्यादातर हिस्सा वह एल्गोरिदमिक जीत है जिस पर CPU का भी उतना ही हक था, और सिर्फ़ बचा-खुचा हिस्सा हार्डवेयर है। यहाँ वह बचा-खुचा 6.2x है। 167x का हवाला हार्डवेयर का योगदान करीब 27 गुना बढ़ा-चढ़ाकर बताता है।
और गौर कीजिए कि खुद हार्डवेयर गुणनखंड भी समस्या के आकार के साथ हिला। छोटे सिंगल-टाइमफ्रेम स्वीप पर असली GPU-बनाम-सर्वश्रेष्ठ-CPU जीत 3.2x थी। बड़े मल्टी-टाइमफ्रेम precompute पर वह 6.2x — वही दो चिप, लगभग दोगुना हार्डवेयर फ़ायदा, सिर्फ़ इसलिए कि बड़ा workload roofline पर GPU की सपाट compute छत की ओर उतना ऊपर धकेल देता है जहाँ CPU कदम मिलाकर नहीं चल पाता। हार्डवेयर बढ़त भी कोई स्थिरांक नहीं। वह उसी चढ़ते कर्व पर एक बिंदु है, और उस कर्व पर दाईं ओर बढ़ने का तरीका है batch को बड़ा और प्रति-combo काम को समृद्ध बनाना।
निर्णय-गाइड: स्वीप कितना चौड़ा होना चाहिए?

roofline को वापस एक ऐसे फ़ैसले में मोड़िए जो आप पैसा खर्च करने से पहले ले सकें। GPU तब कीमत वसूलता है जब roofline की दोनों शर्तें एक साथ पूरी हों: आपका स्वीप batch ridge के दाईं ओर हो (, ताकि फिक्स्ड launch-और-transfer overhead बँट जाए), और आपका प्रति-combo काम compute-bound हो (सपाट छत तक पहुँचने लायक भरपूर arithmetic intensity, न कि एक पतला O(n) पास)। ठोस रूप में, हमने जो नापा उसके हिसाब से:
- सिंगल-टाइमफ्रेम स्ट्रैटेजी के कुछ दर्जन combos: GPU रहने दीजिए। आप ridge के बाईं ओर हैं; parallel numba पर ईमानदार जीत ~3.2x है, उस काम पर जो पहले ही सेकंड का दसवाँ हिस्सा लेता है। अड़चन kernel नहीं है, उसके इर्द-गिर्द का बाकी सब कुछ है।
- हज़ारों combos, या सचमुच का मल्टी-टाइमफ्रेम / मल्टी-indicator precompute: GPU अपनी जगह कमा लेता है। overhead बँट जाता है, साझा convolutions arithmetic intensity बढ़ा देती हैं, और हार्डवेयर जीत 6.2x तक चढ़कर batch के साथ बढ़ती रहती है। यही वह शासन है जहाँ GPU रात भर चलने वाले स्वीप को कॉफ़ी-ब्रेक में बदल देता है।
- पहले CPU की सीढ़ी चढ़िए — वह सस्ती है और पहले आती है। CPU पर 298x और 27x का एल्गोरिदमिक सिमटाव मुफ़्त या लगभग मुफ़्त हैं, और वे पूर्व-शर्तें हैं, विकल्प नहीं: GPU का 6.2x सिमटे हुए एल्गोरिदम के ऊपर है, जिसे आपको वैसे भी लिखना ही था। नादान pipeline पर कस दिया गया GPU ज़्यादातर नादानी को ही नापता है।
GPU वाली शाखा पर एक ऐसा टैक्स भी है जिसका रफ़्तार से कोई रिश्ता नहीं, और आपको उसे कीमत में जोड़ना ही होगा: Apple के Metal GPU में fp64 है ही नहीं। सब कुछ fp32 में चलता है, ~1.2e-7 सापेक्ष परिशुद्धता। यह तेज़ moving averages की किताबों वाली तरकीब — O(n) prefix-sum WMA — को मार देता है, क्योंकि 150k bars पर करीब 30,000 के price scale पर चालू योग ~1e14 तक पहुँच जाते हैं, fp32 की सुरक्षित integer range से सात order of magnitude आगे; हमने ~2e2 तक की सापेक्ष त्रुटियाँ मापीं (दो सौ का गुणक, दो प्रतिशत नहीं)। काम करने वाला रूप सीधी windowed convolution है, जहाँ हर window sum तुलनीय आकार के सीमित पदों का जोड़ है और fp32 ~8e-7 तक सटीक रहता है। फिर भी, जो स्ट्रैटेजी hma - hma3 के sign पर फ़ैसला करती है, वह किसी सीमा-रेखा वाले bar पर — जहाँ दोनों कर्व लगभग छू रहे हों — कभी-कभार crossing पलट देगी, क्योंकि fp32 की rounding लगभग-बराबरी को एक ओर झुका देती है। इसीलिए GPU रास्ता एक ऐसे equivalence gate के साथ आता है जो नापता है कि trades कितने भटके — PnL delta basis points में, trade count में सापेक्ष खिसकाव — बजाय bit-identical आउटपुट का दावा ठोकने के, जो वह कभी दे ही नहीं सकता। हमारी run पर वह भटकाव 479,016 में से 90 खिसके हुए fills था (0.019%), सहनसीमा के भीतर आराम से — पर बोझ असली है: GPU पर जाने का मतलब सिर्फ़ तेज़ घड़ी नहीं, numerical-parity की एक कहानी का मालिक बनना है। वह engineering लागत भी break-even का हिस्सा है।
संख्याएँ Apple के साँचे की हैं; कर्व नहीं
ऊपर का हर आँकड़ा Apple M2 Max का है: एक unified-memory डिवाइस, जहाँ GPU और CPU एक ही पूल साझा करते हैं, और double precision के बिना एक fp32-only GPU। एक discrete NVIDIA या AMD कार्ड स्थिरांक बदल देता है, और साफ़-साफ़ कहना ज़रूरी है कि हर स्थिरांक किस दिशा में खिसकता है, क्योंकि दलील की शक्ल बच जाती है, भले संख्याएँ न बचें।
- Transfer overhead बदतर होता है, बेहतर नहीं। discrete कार्ड PCIe के पीछे बैठा है, तो इनपुट और नतीजे bus के आर-पार एक असली copy बनाते हैं, जिससे unified memory बचा लेती है। यह batch ridge को दाईं ओर धकेलता है — discrete GPU के launch की भरपाई के लिए आपको और भी चौड़ा स्वीप चाहिए। PCIe डिवाइस पर roofline का बायाँ किनारा और खड़ा होता है, नरम नहीं।
- सपाट छत ऊँची हो जाती है। एक data-center GPU में integrated वाले से कहीं ज़्यादा FLOP/s और bandwidth हैं, तो saturate करते स्वीप पर asymptotic हार्डवेयर जीत हमारे 6.2x से बड़ी होती है। कर्व के दाएँ छोर तक पहुँचने का इनाम बढ़ता है; बाईं ओर बैठे रहने का महसूल भी।
- fp64 लौट आता है, और साथ में prefix-sum तरकीब। असली double precision वाले कार्ड पर O(n) prefix-sum WMA फिर से चलने लायक हो जाता है और parity gate को bit-exactness की ओर कसा जा सकता है। जो खास fp32 टैक्स हमने चुकाया — prefix sums की जगह सीधी convolution, assert की जगह भटकाव नापने वाला gate — वह Apple-Silicon का ब्योरा है, कोई नियम नहीं।
इनमें से कुछ भी थीसिस नहीं बदलता। किसी भी डिवाइस पर, : एक फिक्स्ड overhead, जिसे बाँटना ही है, एक asymptote, जिसके पास आप सिर्फ़ दाईं ओर से पहुँचते हैं। स्थिरांक हार्डवेयर हैं; कर्व अंकगणित है। किसी की भी सुर्खी पर भरोसा करने से पहले — हमारी समेत — पाँच लाइन के batch sweep से अपने , और खुद नापिए।
यह कहाँ जुड़ता है
यह उस छोटी उप-श्रृंखला का चौथा माप है जो पूछती है कि बैकटेस्ट की रफ़्तार असल में आती कहाँ से है, और सारे टुकड़े मिलकर एक ही दलील बनाते हैं — खर्च करने से पहले क्या optimize किया जाए:
- स्पीड लैडर अकेले CPU पर pandas से parallel numba तक 298x चढ़ी, और GPU को एक खुला सवाल छोड़ गई। यह लेख उसका जवाब देता है: GPU एक असली पर सशर्त पाँचवाँ पायदान है, शीर्ष CPU पायदान पर 3.2x–6.2x के लायक, और वह भी तभी जब स्वीप roofline चढ़ने जितना चौड़ा हो।
- IPC टैक्स ने यही चाल उलटी दिशा में चली — process से बाहर निकलने की कीमत नापी — और उसी शक्ल के नतीजे पर पहुँचा: सीमा (एक socket, एक GPU launch) सस्ती है; टैक्स इसमें है कि आप उसे कितनी बार और कितनी बातूनी होकर पार करते हैं। अपनी GPU कॉलें उसी वजह से batch कीजिए जिस वजह से IPC को batch करते हैं: प्रति-पार-गमन की फिक्स्ड लागत बाँटने के लिए।
- aggregated parquet cache उसी चीज़ का CPU-पक्षीय संस्करण है जो GPU precompute करता है — साझा indicators एक बार गिनो, हर combo में दोबारा इस्तेमाल करो। GPU बस उसी reuse-और-batch सिद्धांत को सिलिकॉन तक ले जाता है।
- और fp32 parity gate लघु रूप में एक बैकटेस्ट-लाइव parity समस्या है: जिस पल आपका तेज़ रास्ता आपके reference से ज़रा-सा अलग कुछ गिनता है, आप पर भटकाव का परिमाणित हिसाब बकाया हो जाता है, हाथ हिलाकर टालना नहीं।
जोड़ने वाला अनुशासन वही है जिस पर यह पूरी श्रृंखला ज़ोर देती है: जो चीज़ आपको बेची जा रही है, उसी को नापिए। स्पीडअप एक अनुपात है, और अनुपात में अंश और हर होते हैं। ज़्यादातर GPU-निराशा एक ऐसे हर से आती है — CPU बेसलाइन — जो चापलूसी के लिए चुना गया, और ज़्यादातर GPU-बर्बादी इतने छोटे स्वीप चलाने से, जो roofline के बाएँ किनारे से निकल ही नहीं पाते।
निष्कर्ष
- GPU स्पीडअप एक कर्व है, संख्या नहीं। हमारे मल्टी-टाइमफ्रेम precompute पर CPU पर बढ़त प्रति कॉल एक combo पर 54.5x से इकसठ पर 359.6x तक गई — वही चिप, वही डेटा। कोई भी अकेला आँकड़ा उस कर्व पर एक बिंदु है; पूछिए कि वह किस batch size पर मापा गया।
- CPU बेसलाइन से हमेशा जिरह कीजिए। मल्टी-टाइमफ्रेम की 167x सुर्खी साफ़ गुणनखंडों में टूटती है: 27x एल्गोरिदम (प्रति-bar-से-convolution सिमटाव, जो CPU को भी उतना ही तेज़ करता है) गुणा 6.2x असली हार्डवेयर। यहाँ निष्पक्ष GPU-बनाम-सर्वश्रेष्ठ-CPU जीत सिंगल-टाइमफ्रेम पर 3.2x और मल्टी पर 6.2x है — 167x नहीं।
- बढ़त समस्या के आकार के साथ बढ़ती है, और हार्डवेयर गुणनखंड भी। बड़ा batch और समृद्ध प्रति-combo काम आपको roofline पर ऊपर धकेलते हैं: ईमानदार हार्डवेयर जीत खुद 3.2x से 6.2x हो गई, सिर्फ़ workload बड़ा करने से। छोटे स्वीप ridge के बाईं ओर बैठते हैं और उन्हें फ़ायदा नाम-मात्र का मिलता है।
- पहले एल्गोरिदम सुधारिए और CPU की सीढ़ी चढ़िए — GPU की जीत उनके ऊपर बैठती है, उनकी जगह नहीं। 6.2x उस सिमटे हुए एल्गोरिदम के मुकाबले मापा गया है जिसे आपको वैसे भी लिखना ही था। नादान pipeline पर GPU कस दीजिए, और जो नापेंगे उसका ज़्यादातर हिस्सा नादानी होगी, सिलिकॉन नहीं।
- GPU पर जाने का मतलब है numerical-parity की कहानी का मालिक बनना। Metal पर fp64 नहीं, prefix-sum WMA तरकीब price scale पर दम तोड़ देती है (सापेक्ष त्रुटि ~2e2), और sign-of-crossing स्ट्रैटेजियाँ सीमा-रेखा वाले bars पर पलट जाती हैं। भटकाव को basis points में परिमाणित करने वाला equivalence gate शिप कीजिए; वह engineering लागत अपने break-even में गिनिए।
जब कोई आपसे कहे कि GPU ने उसका बैकटेस्ट सौ गुना तेज़ कर दिया, तो उसने आपको लगभग कुछ नहीं बताया। उससे batch size और CPU बेसलाइन पूछिए, और वह सौ आमतौर पर घुलकर एक इकाई-अंक की हार्डवेयर जीत निकलती है, जो एक ऐसी एल्गोरिदमिक जीत के गिर्द लिपटी है जो उसे मुफ़्त में मिल सकती थी — पाने लायक, काफ़ी चौड़े स्वीप पर, ठीक उन्हीं वजहों से जो roofline गिनाती है, और एक भी वजह ज़्यादा नहीं।
Authors
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.