📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

← Retour aux articles
July 7, 2026
5 min de lecture

Quand le GPU devient rentable : la roofline du sweep de paramètres, où un 167x d'affiche est en réalité 27x d'algorithme fois 6.2x de matériel

Quand le GPU devient rentable : la roofline du sweep de paramètres, où un 167x d'affiche est en réalité 27x d'algorithme fois 6.2x de matériel
#algotrading
#backtest
#performance
#gpu
#roofline
#optimisation
Part 9 of 10 · Collection
High-Performance Backtest Engines

Fait partie de la série "Backtests sans illusions".

L'échelle de vitesse s'était terminée sur une note délibérément insatisfaisante. Nous avions fait descendre un sweep de paramètres de 80 combos de 69.9 secondes de pandas à 0.23 seconde de numba parallèle sur un CPU de laptop — un 298x mesuré — puis soutenu qu'un GPU n'était pas le barreau manquant. La section des commentaires n'a pas accepté cela sans broncher, et elle a eu raison. "Pas le barreau manquant" est une affirmation sur une charge de travail donnée, à une taille donnée. Ce n'est pas une loi de la nature. Cet article fait donc la chose honnête et met le GPU sur le banc d'essai.

Le résultat n'est pas un verdict, c'est une courbe. L'avance du GPU sur le CPU n'est pas un nombre unique que l'on peut imprimer sur une slide ; c'est une fonction de la quantité de travail qu'on lui confie par appel. Sur notre précalcul d'indicateurs multi-timeframe, l'accélération du GPU par rapport au moteur CPU va de 54.5x quand on lui demande une combinaison de paramètres à la fois jusqu'à 359.6x quand on en demande 61 d'un coup. Même kernel, mêmes données, même matériel — la seule chose qui a changé, c'est le batch. Un benchmark qui rapporte l'un de ces nombres et cache l'autre ne mesure pas le GPU : il choisit un gros titre.

Et même 359.6x n'est pas ce qu'il paraît. Décortiquez-le et un gros titre multi-timeframe de 167x se décompose en 27x d'algorithme — une réécriture qui rend aussi le CPU 27x plus rapide — fois 6.2x de matériel réel. Ce n'est pas le GPU qui a produit le 27x. Ce sont les maths. Cet article consiste à apprendre à distinguer les deux, parce que les confondre est la manière dont une carte graphique à $2,000 finit vendue pour résoudre un problème qu'un git commit aurait résolu gratuitement.

Provenance : tous les chiffres ci-dessous sont mesurés sur un Apple M2 Max, fp32 sur le GPU Metal via MLX contre numba en fp64 sur douze threads CPU, à partir de nos scripts de benchmark engine_multitf_gpu.py et bench_param_sweep.py, chacun verrouillé par un contrôle d'équivalence qui confirme que les chemins GPU et CPU produisent les mêmes trades. Pas de paper compagnon public pour celui-ci — les scripts font foi.

La question est une roofline, pas un benchmark

Un graphique roofline : un plafond de calcul plat à droite, une pente raide de bande passante mémoire à gauche, et un point de batch qui grimpe la pente depuis le coin dominé par le surcoût vers le point de crête

La raison pour laquelle "de combien le GPU est-il plus rapide" n'a pas de réponse unique est le roofline model (Williams, Waterman & Patterson, 2009). La performance atteignable d'un processeur est plafonnée par deux limites à la fois : un plafond plat fixé par la puissance de calcul crête (FLOP/s), et un plafond incliné fixé par la bande passante mémoire multipliée par l'intensité arithmétique — le nombre d'opérations utiles effectuées par octet déplacé. Le travail pauvre en opérations par octet vit sous le plafond incliné et est limité par la bande passante ; le travail riche par octet atteint le plafond plat et est limité par le calcul. Le plafond plat du GPU surplombe de très haut celui du CPU, mais son plafond incliné et, surtout, son coût fixe par appel ne descendent pas en échelle avec la même élégance.

Pour un sweep de paramètres, un troisième terme domine le côté gauche du graphique : le surcoût de lancement et de transfert. Chaque appel GPU paie un prix à peu près fixe — dispatcher le kernel, faire passer les entrées à travers la frontière mémoire (unifiée sur Apple Silicon, mais pas gratuite), rapatrier les résultats. Modélisez le temps d'exécution pour un batch de BB combinaisons par

Tgpu(B)=O+bB,Tcpu(B)=aBT_{\text{gpu}}(B) = O + b\,B, \qquad T_{\text{cpu}}(B) = a\,B

OO est ce surcoût fixe, bb le coût marginal par combo du GPU, et aa celui du CPU. L'accélération vaut alors

S(B)=aBO+bB  B  ab.S(B) = \frac{a\,B}{O + b\,B} \;\xrightarrow{B \to \infty}\; \frac{a}{b}.

Cette seule fraction explique l'article entier. À petit BB, le OO du dénominateur écrase le ratio — vous avez payé pour réveiller le GPU et vous l'avez à peine utilisé. À mesure que BB grandit, vous amortissez OO sur davantage de combos et l'accélération grimpe vers son asymptote a/ba/b, le vrai ratio matériel. Le point de mi-parcours se situe à B=O/bB = O/b : un "point de crête" dans l'espace des batchs, qui vous dit quelle largeur votre sweep doit atteindre avant que le GPU ne soit ne serait-ce qu'à la moitié de ce qu'il peut être. Un sweep de quelques dizaines de combos se trouve loin à gauche de cette crête. Un sweep de plusieurs milliers se trouve sur la partie plate, là où le GPU gagne enfin son plafond.

La bonne question n'est donc jamais "le GPU est-il plus rapide". C'est "mon sweep est-il à droite de la crête, et mon travail par combo est-il assez limité par le calcul pour atteindre le plafond plat une fois arrivé là". Les deux conditions doivent être vraies. Le reste de cet article mesure où ces seuils tombent réellement.

Le verdict single-timeframe : le GPU gagne à peine

Deux barres pour le même sweep single-timeframe de 80 combos : le pool CPU à douze cœurs à 246 combos par seconde à côté du GPU à 779, un écart modeste de 3.2x plutôt qu'un gouffre

Commençons par la charge de travail que l'échelle de vitesse utilisait : un sweep HMA/HMA3 single-timeframe, 80 combinaisons sur 150,000 barres. Nous avons ajouté un sixième barreau à cette échelle — M5, les indicateurs sur le GPU Apple via MLX, les trades toujours extraits sur le CPU. Préchauffé, meilleur de trois runs, le gate d'équivalence au vert :

Méthode Temps vs pandas Combos/s
M0 pandas + boucle 287.08s 1.0x 0.3
M2 numba (1 cœur) 2.02s 142x 39.7
M4 mp + numba (12 cœurs) 0.33s 883x 245.9
M5 MLX GPU (fp32) 0.10s 2796x 779.2

Face à la baseline série naïve, le GPU a l'air héroïque — 2,796x. Mais c'est une comparaison qu'aucune personne honnête ne devrait faire : elle oppose une bonne implémentation GPU à la pire implémentation CPU. Alignez le GPU contre le CPU que vous déploieriez réellement — le même kernel sur les douze cœurs, M4 — et la victoire s'effondre à un sobre 3.2x (779 contre 246 combos par seconde). Une carte graphique entière, exécutant tout le sweep, bat un pool CPU à douze cœurs d'un facteur trois.

Trois virgule deux, ce n'est pas rien. Ce n'est pas non plus la raison pour laquelle on achète un GPU. Et c'est exactement ce que la roofline prédit pour un sweep aussi étroit : 80 combos, c'est à gauche de la crête. Le surcoût fixe de lancement et de transfert OO reste une part significative d'un job de 0.10 seconde, donc on n'atteint jamais l'asymptote a/ba/b. Pire, une partie du coût par combo est la passe O(n) d'extraction des trades, que nous avons délibérément laissée sur le CPU — un terme que le GPU ne peut pas accélérer du tout (le pourquoi arrive dans la section suivante). Pour une boucle de recherche single-timeframe de cette taille, le verdict initial de l'échelle de vitesse tient : le GPU n'est pas le barreau manquant. Le numba parallèle vous mettait déjà à 0.23–0.33 secondes, et raboter cela à 0.10 n'est pas ce qui débloque un chercheur. Ce qui le débloque, c'est l'orchestration autour du sweep.

Le mot intéressant dans ce verdict est cette taille. Déplacez-vous vers la droite le long de l'axe des batchs et l'histoire change.

Où vit réellement le coût

Avant de faire grossir le batch, regardons ce que nous payons réellement, parce que la roofline ne vous récompense que si la partie chère est limitée par le calcul. Profilez le sweep et presque tout se résume à une seule chose : des convolutions de moyennes mobiles pondérées. Une HMA, c'est trois WMA ; une HMA3, quatre ; chaque combinaison les relance sur toute la série. L'extraction des trades — parcourir les deux tableaux d'indicateurs, trouver les changements de signe de hma - hma3, comptabiliser les fills — est une unique passe O(n) bon marché. Le sweep est une charge de convolutions déguisée en stratégie de trading.

Cette séparation correspond exactement aux deux régimes de la roofline :

  • Les convolutions sont limitées par le calcul. Chaque prix est lu dans de nombreuses sommes de fenêtres qui se chevauchent, donc l'intensité arithmétique — opérations par octet déplacé — est élevée. Ce travail tend vers le plafond plat de calcul, et le plafond plat du GPU est celui qui domine. Mieux encore, les fenêtres se chevauchent entre combinaisons : une WMA de longueur 40 sert à des dizaines de combos, donc un précalcul en batch la partage une fois au lieu de la recalculer par combo. Le batching n'amortit pas seulement le surcoût de lancement ; il augmente l'intensité arithmétique en réutilisant les chargements. C'est la partie qui a sa place sur le GPU.
  • L'extraction des trades est limitée par la bande passante et pleine de branchements. Une passe séquentielle, des branchements dépendants des données à chaque croisement, essentiellement aucune réutilisation. Son intensité arithmétique frôle le plancher, et son flot de contrôle est hostile à un périphérique SIMD. La pousser sur le GPU rapporterait peu et coûterait cher ; elle reste sur le CPU. Ce qui en fait la queue série d'un découpage selon la loi d'Amdahl — un plancher fixe que l'accélération GPU ne peut jamais percer, et une partie de la raison pour laquelle le chiffre single-TF a saturé à 3.2x.

Une deuxième leçon, plus tranchante, se cache dans la version multi-timeframe de ce kernel, et c'est la source du 27x que nous promettons d'expliquer depuis le début. Le moteur multi-TF aligne une HMA de timeframe supérieur sur l'index de base à 1 minute sans look-ahead. Écrit de la manière évidente, c'est un travail par barre en O(length^1.5) — recalculer les moyennes mobiles du timeframe supérieur à chaque barre de base. Mais la HMA alignée est linéaire en un court buffer des dernières bougies clôturées du timeframe supérieur plus le close courant, donc tout le calcul par barre s'effondre en un vecteur de poids fixe : un conv1d sur la série des bougies clôturées suivi d'un gather en O(n). Des centaines de millions d'opérations redondantes deviennent une convolution sur une série bien plus courte.

Cet effondrement est un gain algorithmique, pas matériel. C'est une meilleure formule. Elle tourne sur le GPU, et elle tourne tout aussi bien sur le CPU — np.correlate plus un gather, en fp64. Gardez cela fermement à l'esprit : le facteur le plus important du gros titre multi-TF est une réécriture accessible à une machine sans le moindre GPU. Quand nous décomposerons enfin le 167x, ce sera le 27x.

L'avance grandit avec la taille de batch

Une courbe croissante d'accélération GPU contre CPU en fonction de la taille de batch, qui monte de 54.5x à un combo jusqu'à dépasser 359.6x à soixante et un, avec un unique creux à trente-deux annoté comme un artefact de frontière de chunk

Voici maintenant la mesure que la roofline réclamait. Prenez l'axe coûteux — le précalcul de la HMA alignée du timeframe supérieur sur la série de base à 1 minute, le plus long flux de bougies dont nous disposons — et donnez au GPU un nombre croissant de combinaisons de longueurs par appel, B=1,2,4,,61B = 1, 2, 4, \dots, 61. La baseline CPU ici est l'honnête moteur de production : numba avec prange sur les douze cœurs. Pour chaque batch, nous mesurons les deux et prenons le ratio.

Batch BB (combos/appel) Accélération GPU vs moteur CPU 12 cœurs
1 54.5x
2 102.5x
4 129.5x
8 187.4x
16 267.4x
32 245.0x
61 359.6x

C'est S(B)=aB/(O+bB)S(B) = aB/(O + bB) tracé en mesures réelles. À B=1B = 1, le GPU a déjà 54.5x d'avance — parce que cette comparaison se fait contre le moteur naïf par barre, donc l'effondrement algorithmique est déjà inclus même à un seul combo — mais il est très loin de son plafond : le surcoût fixe domine encore un appel à un combo. Doublez le batch et l'accélération double presque, à 102.5x ; à B=16B = 16 elle est de 267.4x ; à B=61B = 61 elle est de 359.6x et grimpe visiblement encore. L'avance grandit avec la taille du problème. C'est la phrase la plus importante qui soit sur les GPU et les sweeps de paramètres, et c'est l'exact opposé de la manière dont les accélérations GPU sont habituellement citées — comme si elles étaient une propriété constante de la puce.

Deux notes d'honnêteté, parce que c'est la série Backtests sans illusions et qu'une table proprement monotone mérite toujours la suspicion.

D'abord, le creux : B=32B = 32 affiche 245.0x, en dessous des 267.4x à B=16B = 16. Ce n'est pas du bruit à balayer sous le tapis — c'est un artefact de frontière de chunk. Notre conv1d empaquette 32 longueurs dans un chunk de kernel, donc B=32B = 32 remplit exactement un chunk sans marge, tandis que B=61B = 61 déborde sur un second chunk qui se trouve mieux saturer le périphérique. L'intérêt de la roofline, c'est la tendance, et un périphérique réel comporte des paliers de quantification ; nous rapportons l'oscillation plutôt que de la contourner en choisissant les points qui arrangent.

Ensuite, et c'est plus important : 54.5x et 359.6x sont tous deux mesurés contre le moteur CPU naïf, et aucun des deux n'est le gain matériel. Les deux nombres contiennent encore l'effondrement algorithmique de 27x. Si vous remplaciez la baseline CPU par l'algorithme effondré tournant sur le CPU — même formule, fp64, les deux côtés optimisés — chaque ligne rétrécirait à peu près de ce facteur. Ce qui est précisément la décomposition que la section suivante rend exacte.

La décomposition honnête : 27x d'algorithme fois 6.2x de matériel

Une barre unique de 167x qui se scinde en deux facteurs empilés — un grand 27x étiqueté algorithme, accessible aussi au CPU, et un plus petit 6.2x étiqueté matériel, la seule part que le GPU apporte réellement

Pour séparer l'algorithme du silicium, il faut mesurer trois chemins sur la même grille multi-timeframe, pas deux. Le benchmark exécute donc :

  1. cpu-engine — le moteur numba de production, HMA alignée par barre sur tous les cœurs. La baseline naïve mais parallèle.
  2. cpu-collapsed — les vecteurs de poids effondrés, np.correlate plus gather, fp64, sur le CPU. Même matériel que (1), meilleur algorithme.
  3. gpu-mlx — les poids effondrés sous forme de conv1d en batch sur le GPU Metal, fp32. Même algorithme que (2), matériel différent.

Alignez-les et le gros titre multi-timeframe complet se factorise proprement :

167×gpu vs naive CPU  =  27×cpu-collapsed vs cpu-engine  ×  6.2×gpu vs cpu-collapsed\underbrace{167\times}_{\text{gpu vs naive CPU}} \;=\; \underbrace{27\times}_{\text{cpu-collapsed vs cpu-engine}} \;\times\; \underbrace{6.2\times}_{\text{gpu vs cpu-collapsed}}

Le facteur de gauche, 27x, c'est l'algorithme — l'effondrement par-barre-vers-convolution de la section précédente. Il n'a rien à voir avec le GPU. Implémentez-le en numpy et le CPU de votre laptop devient 27x plus rapide sur cette charge, pour le prix d'un refactoring. Le facteur de droite, 6.2x, c'est le matériel — le gain honnête, à armes égales, du GPU Metal sur le même algorithme optimisé tournant sur douze cœurs CPU. Ce 6.2x est la seule part pour laquelle vous aviez réellement besoin d'un GPU.

C'est toute la morale de l'article énoncée sous forme d'arithmétique. Quand un benchmark de vendeur, le README d'une bibliothèque ou un collègue enthousiaste vous montre "167x sur GPU", le réflexe devrait être une seule question : quelle était la baseline CPU ? Si la baseline était l'implémentation naïve — et c'est presque toujours le cas, parce qu'une baseline lente fait une meilleure slide — alors l'essentiel du gros titre est un gain algorithmique auquel le CPU avait droit lui aussi, et seul le résidu est du matériel. Ici, le résidu est 6.2x. Une citation à 167x surestime la contribution du matériel d'un facteur d'environ 27.

Et remarquez comment le facteur matériel lui-même a bougé avec la taille du problème. Sur le petit sweep single-timeframe, le vrai gain GPU contre meilleur CPU était de 3.2x. Sur le précalcul multi-timeframe plus grand, il était de 6.2x — les deux mêmes puces, un avantage matériel presque doublé, uniquement parce que la charge plus grosse pousse plus haut sur la roofline, vers le plafond plat de calcul du GPU, avant que le CPU puisse suivre. L'avance matérielle non plus n'est pas une constante. C'est un point sur la même courbe croissante, et la manière de se déplacer vers la droite sur cette courbe est de grossir le batch et d'enrichir le travail par combo.

Un guide de décision : quelle largeur pour le sweep ?

Un organigramme de décision simple : petit sweep ou travail par combo maigre orienté vers le CPU, large sweep de milliers de combos lourds en calcul orienté vers le GPU, avec un gate de parité fp32 posté sur la branche GPU

Replions la roofline en une décision que vous pouvez prendre avant de dépenser de l'argent. Le GPU devient rentable quand les deux conditions de la roofline tiennent en même temps : votre sweep est à droite de la crête de batch (BO/bB \gg O/b, de sorte que le surcoût fixe de lancement et de transfert est amorti), et votre travail par combo est limité par le calcul (une intensité arithmétique assez riche pour atteindre le plafond plat, pas une maigre passe O(n)). Concrètement, d'après ce que nous avons mesuré :

  • Quelques dizaines de combos d'une stratégie single-timeframe : passez-vous du GPU. Vous êtes à gauche de la crête ; le gain honnête sur numba parallèle est de ~3.2x sur un job qui prend déjà un dixième de seconde. Le goulot d'étranglement n'est pas le kernel, c'est tout ce qui l'entoure.
  • Des milliers de combos, ou un précalcul véritablement multi-timeframe / multi-indicateurs : le GPU gagne sa place. Le surcoût s'amortit, les convolutions partagées augmentent l'intensité arithmétique, et le gain matériel grimpe à 6.2x et continue de croître avec le batch. C'est le régime où un GPU transforme un sweep d'une nuit entière en pause café.
  • Grimpez d'abord l'échelle CPU — elle coûte moins cher et elle vient en premier. Le 298x sur le CPU et l'effondrement algorithmique de 27x sont gratuits ou presque, et ce sont des prérequis, pas des alternatives : le 6.2x du GPU vient par-dessus l'algorithme effondré, que vous deviez écrire de toute façon. Un GPU boulonné sur un pipeline naïf mesure surtout la naïveté.

Il y a aussi une taxe sur la branche GPU qui n'a rien à voir avec la vitesse, et vous devez l'intégrer au prix : le GPU Metal d'Apple n'a pas de fp64 du tout. Tout tourne en fp32, ~1.2e-7 de précision relative. Cela tue l'astuce classique des manuels pour les moyennes mobiles rapides — la WMA par sommes préfixes en O(n) — parce qu'à une échelle de prix proche de 30,000 sur 150k barres, les sommes courantes atteignent ~1e14, sept ordres de grandeur au-delà de la plage entière sûre du fp32 ; nous avons mesuré des erreurs relatives allant jusqu'à ~2e2 (un facteur de deux cents, pas deux pour cent). La formulation qui marche est la convolution fenêtrée directe, où chaque somme de fenêtre est un nombre borné de termes de tailles comparables et où le fp32 reste précis à ~8e-7. Même là, une stratégie qui décide sur le signe de hma - hma3 inversera de temps en temps un croisement sur une barre limite où les deux courbes se touchent presque, parce que l'arrondi fp32 fait basculer une quasi-égalité. C'est pourquoi le chemin GPU est livré avec un gate d'équivalence qui mesure à quel point les trades ont divergé — delta de PnL en points de base, décalage relatif du nombre de trades — plutôt que d'affirmer une sortie identique au bit près qu'il ne pourra jamais fournir. Sur notre run, cette divergence était de 90 fills décalés sur 479,016 (0.019%), largement dans la tolérance, mais la charge est réelle : passer au GPU, c'est assumer une histoire de parité numérique, pas seulement une horloge plus rapide. Ce coût d'ingénierie fait lui aussi partie du break-even.

Les chiffres sont estampillés Apple ; la courbe, non

Chaque chiffre ci-dessus vient d'un Apple M2 Max : une machine à mémoire unifiée où le GPU et le CPU partagent un seul pool, et un GPU fp32 uniquement, sans double précision. Une carte NVIDIA ou AMD dédiée change les constantes, et il vaut la peine d'être explicite sur le sens dans lequel chacune bouge, parce que la forme de l'argument survit même quand les chiffres, eux, ne survivent pas.

  • Le surcoût de transfert OO devient pire, pas meilleur. Une carte dédiée se trouve derrière le PCIe, donc les entrées et les résultats font une vraie copie à travers le bus, copie que la mémoire unifiée évite. Cela pousse la crête de batch O/bO/b vers la droite — il faut un sweep encore plus large avant qu'un GPU dédié amortisse son lancement. Le bord gauche de la roofline est plus raide sur un périphérique PCIe, pas plus doux.
  • Le plafond plat a/ba/b devient plus haut. Un GPU de datacenter a bien plus de FLOP/s et de bande passante qu'un GPU intégré, donc le gain matériel asymptotique sur un sweep saturant est plus grand que notre 6.2x. La récompense pour atteindre le côté droit de la courbe grandit ; le péage pour rester à gauche grandit aussi.
  • Le fp64 revient, et avec lui l'astuce des sommes préfixes. Sur une carte avec une vraie double précision, la WMA par sommes préfixes en O(n) redevient viable et le gate de parité peut être resserré vers l'exactitude au bit près. La taxe fp32 spécifique que nous avons payée — convolution directe au lieu de sommes préfixes, un gate qui mesure la divergence au lieu d'un assert — est un détail d'Apple Silicon, pas une loi.

Rien de tout cela ne change la thèse. Sur n'importe quel périphérique, S(B)=aB/(O+bB)S(B) = aB/(O + bB) : un surcoût fixe qu'il faut amortir, une asymptote qu'on n'approche que par la droite. Les constantes, c'est le matériel ; la courbe, c'est de l'arithmétique. Mesurez vos propres OO, aa et bb avec un sweep de batch de cinq lignes avant de faire confiance au gros titre de qui que ce soit — y compris le nôtre.

Où tout cela se raccorde

C'est la quatrième mesure d'une petite sous-série sur l'origine réelle de la vitesse des backtests, et les pièces s'assemblent en un seul argument sur ce qu'il faut optimiser avant de dépenser :

  • L'échelle de vitesse a grimpé de pandas à numba parallèle pour 298x sur le CPU seul, et a laissé le GPU comme question ouverte. Cet article y répond : le GPU est un cinquième barreau réel mais conditionnel, qui vaut 3.2x–6.2x au-dessus du barreau CPU le plus haut, et seulement une fois que le sweep est assez large pour grimper la roofline.
  • La taxe IPC a fait le même mouvement dans l'autre direction — mesurer ce que coûte le fait de sortir du processus — et est arrivée à une conclusion de la même forme : la frontière (un socket, un lancement GPU) est bon marché ; la taxe réside dans la fréquence et le bavardage avec lesquels vous la traversez. Mettez vos appels GPU en batch pour la même raison que vous mettez votre IPC en batch : pour amortir un coût fixe par traversée.
  • Le cache parquet agrégé est la version côté CPU de ce que fait le précalcul GPU — calculer une fois les indicateurs partagés, les réutiliser pour chaque combo. Le GPU ne fait qu'emmener ce principe réutiliser-et-batcher jusqu'au silicium.
  • Et le gate de parité fp32 est un problème de parité backtest-live en miniature : dès que votre chemin rapide calcule quelque chose de légèrement différent de votre référence, vous devez un compte rendu quantifié de la divergence, pas un geste vague de la main.

La discipline qui relie tout cela est la même que celle que toute cette série défend : mesurez la chose qu'on est réellement en train de vous vendre. Une accélération est un ratio, et un ratio a un numérateur et un dénominateur. La plupart des déceptions GPU viennent d'un dénominateur — la baseline CPU — choisi pour flatter, et la plupart du gaspillage GPU vient de l'exécution d'un sweep trop petit pour quitter le bord gauche de la roofline.

À retenir

  1. Une accélération GPU est une courbe, pas un nombre. Sur notre précalcul multi-timeframe, l'avance sur le CPU allait de 54.5x à un combo par appel à 359.6x à soixante et un — même puce, mêmes données. Tout chiffre isolé est un point sur cette courbe ; demandez à quelle taille de batch il a été mesuré.
  2. Interrogez toujours la baseline CPU. Le gros titre multi-timeframe de 167x se factorise proprement en 27x d'algorithme (un effondrement par-barre-vers-convolution, qui accélère tout autant le CPU) fois 6.2x de matériel réel. Le gain équitable GPU contre meilleur CPU est ici de 3.2x en single-timeframe, 6.2x en multi — pas 167x.
  3. L'avance grandit avec la taille du problème, et le facteur matériel aussi. Un batch plus gros et un travail par combo plus riche vous poussent vers le haut de la roofline : le gain matériel honnête lui-même est passé de 3.2x à 6.2x simplement en agrandissant la charge de travail. Les petits sweeps restent à gauche de la crête et n'en profitent presque pas.
  4. Corrigez l'algorithme et grimpez d'abord l'échelle CPU — le gain GPU vient par-dessus, pas à la place. Le 6.2x est mesuré contre l'algorithme effondré que vous deviez écrire de toute façon. Boulonnez un GPU sur un pipeline naïf et l'essentiel de ce que vous mesurez est la naïveté, pas le silicium.
  5. Passer au GPU, c'est assumer une histoire de parité numérique. Pas de fp64 sur Metal, l'astuce de la WMA par sommes préfixes meurt à l'échelle des prix (erreur relative ~2e2), et les stratégies à signe de croisement basculent sur les barres limites. Livrez un gate d'équivalence qui quantifie la divergence en points de base ; comptez ce coût d'ingénierie dans votre break-even.

Quand quelqu'un vous dit que le GPU a rendu son backtest cent fois plus rapide, il ne vous a presque rien dit. Demandez-lui la taille de batch et la baseline CPU, et le cent se résout généralement en un gain matériel à un chiffre enroulé autour d'un gain algorithmique qu'il aurait pu avoir gratuitement — un gain qui vaut la peine, sur un sweep assez large, pour exactement les raisons que donne la roofline et pas une de plus.

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.