Aller au contenu

4 · Signal et image

Le domaine où les unités à fonction fixe comptent autant que les unités programmables — et où le transfert de données est presque toujours le vrai problème.


4.1 La FFT

La transformée de Fourier rapide est l'algorithme le plus utilisé du traitement du signal.

\[ X_k = \sum_{n=0}^{N-1} x_n \, e^{-2\pi i kn/N} \]

Complexité \(O(N \log N)\) par l'algorithme de Cooley-Tukey, qui décompose récursivement en FFT plus petites.

Le profil GPU :

  • beaucoup de parallélisme — chaque « papillon » est indépendant ;
  • accès à motif régulier mais non contigu — les papillons sautent d'un pas \(N/2\), puis \(N/4\), etc. ;
  • intensité arithmétique faible — pour \(N = 2^{20}\), environ 5 opérations par point et par étage, sur 20 étages, contre 8 octets lus et écrits.

Elle est donc limitée par la mémoire, et son optimisation consiste à garder les données en mémoire partagée pendant plusieurs étages.

#include <cufft.h>
cufftHandle plan;
cufftPlan1d(&plan, N, CUFFT_C2C, lots);
cufftExecC2C(plan, d_entree, d_sortie, CUFFT_FORWARD);

cuFFTDx : la FFT fusionnable

L'extension cuFFTDx permet d'exécuter une FFT à l'intérieur de votre noyau, sans repasser par la mémoire globale.

C'est décisif pour les pipelines du type FFT → multiplication → FFT inverse (convolution rapide, filtrage, corrélation), où l'implémentation naïve fait trois allers-retours en HBM. En fusionnant, on n'en fait qu'un.

C'est exactement le même raisonnement que la fusion de noyaux en IA — voir IA · Fusion.


4.2 Les convolutions et filtres

Une convolution 2D :

\[ y[i,j] = \sum_{m}\sum_{n} x[i+m,\ j+n] \cdot h[m,n] \]

C'est un stencil pondéré. Les techniques sont celles du chapitre 2 : pavage en mémoire partagée avec halo, et exploitation de la séparabilité quand elle existe.

La séparabilité est le levier majeur. Si \(h[m,n] = h_1[m] \cdot h_2[n]\) (cas du flou gaussien, de Sobel, de nombreux filtres), on remplace un filtre \(K \times K\) par deux passes 1D :

\[ O(K^2) \longrightarrow O(2K) \]

Pour \(K = 15\) : 225 opérations par pixel deviennent 30. Facteur 7,5, gratuit.

Pour les grands noyaux (\(K > 30\) environ), la convolution par FFT devient préférable : \(O(N \log N)\) au lieu de \(O(NK^2)\).


4.3 Les unités à fonction fixe

Un GPU contient bien plus que des SM. Pour le traitement d'images et de vidéo, les unités dédiées font une grande partie du travail.

Unité Fonction Accès
NVENC encodage vidéo (H.264, HEVC, AV1) NVIDIA Video Codec SDK
NVDEC décodage vidéo idem, DALI, PyNvVideoCodec
Unités de texture échantillonnage avec filtrage bilinéaire/trilinéaire tex2D() en CUDA
Moteurs de copie DMA indépendant des SM cudaMemcpyAsync
Optical Flow Accelerator flux optique en matériel NVIDIA Optical Flow SDK
Moteur de décompression LZ4, Zstd, Bitcomp (Blackwell) nvCOMP

Le point important sur NVENC/NVDEC

Ces unités sont indépendantes des SM. Un pipeline vidéo peut décoder, inférer et encoder simultanément, chaque étape sur du silicium distinct.

C'est la base des pipelines de vision en temps réel : NVDEC décode l'image \(n+2\) pendant que les SM traitent l'image \(n+1\) et que NVENC encode l'image \(n\).

Attention : le nombre de sessions NVENC simultanées est limité par segmentation commerciale sur les cartes grand public (souvent 3 à 5), et illimité sur les cartes professionnelles.

Les textures pour l'interpolation

Une astuce sous-utilisée : les unités de texture font l'interpolation bilinéaire en matériel, gratuitement.

texture<float, 2, cudaReadModeElementType> tex;
// ...
float v = tex2D(tex, x + 0.5f, y + 0.5f);   // interpolé, sans calcul

Utile pour : redimensionnement, rotation, correction de distorsion, échantillonnage d'un champ vectoriel. Le coût en précision est réel (l'unité utilise une interpolation en virgule fixe sur 8 bits de fraction), ce qui est sans conséquence pour de l'image et rédhibitoire pour du calcul scientifique.


4.4 Le vrai problème : les transferts

Pour la plupart des pipelines d'image, le calcul n'est pas le goulot.

Considérons un flux vidéo 4K à 60 images/s :

  • une image 4K en RGB 8 bits : \(3840 \times 2160 \times 3 = 24{,}9\) Mo ;
  • à 60 im/s : 1,49 Go/s de données brutes.

En PCIe 5.0 ×16 (64 Go/s), cela représente 2,3 % de la bande passante — donc ce n'est pas le PCIe le problème, mais :

  1. le décodage si la source est compressée (→ NVDEC) ;
  2. les copies inutiles entre CPU et GPU à chaque étape du pipeline ;
  3. la latence si le traitement doit être temps réel.

La règle d'or du pipeline d'image

Les données entrent sur le GPU une fois et n'en sortent qu'à la fin.

Un pipeline typique mal conçu :

décoder (CPU) → transférer → filtrer (GPU) → rapatrier →
redimensionner (CPU) → transférer → inférer (GPU) → rapatrier
Quatre transferts, deux étapes CPU.

Le même pipeline bien conçu :

décoder (NVDEC) → filtrer (GPU) → redimensionner (GPU) → inférer (GPU) → rapatrier
Un seul transfert, en sortie.

Les bibliothèques qui y aident : DALI (NVIDIA Data Loading Library) pour les pipelines d'entraînement, CV-CUDA pour la vision, VPI pour l'embarqué (Jetson).


4.5 La vision par ordinateur

L'écosystème actuel :

Bibliothèque Contenu
OpenCV CUDA portage GPU d'OpenCV — utile mais inégal
CV-CUDA opérateurs de vision conçus pour les pipelines d'IA, sans copie
DALI chargement et augmentation de données pour l'entraînement
VPI Vision Programming Interface, pour Jetson (CPU, GPU, PVA, VIC)
NPP primitives NVIDIA de traitement d'image
kornia vision différentiable dans PyTorch

DALI mérite d'être souligné : dans beaucoup d'entraînements de vision, le chargement et l'augmentation des données sur CPU sont le goulot, pas le modèle. DALI déplace tout le pipeline (décodage JPEG via nvJPEG, redimensionnement, recadrage, normalisation) sur le GPU.

Le symptôme à reconnaître, dans Nsight Systems : des trous réguliers dans la chronologie GPU, alignés sur les itérations. Le GPU attend les données.


4.6 Le traitement du signal radio et scientifique

Un domaine moins visible mais où le GPU domine.

Application Traitement
Radioastronomie (SKA, MeerKAT) corrélation, formation de faisceaux, dédispersion
Radar compression d'impulsion, Doppler, SAR
Radio logicielle filtrage, démodulation, décodage
Imagerie médicale (IRM, TDM) reconstruction itérative, transformée de Radon
Sismique migration en profondeur (RTM), inversion de forme d'onde

La migration temps-inverse (Reverse Time Migration) en sismique est un cas d'école : c'est un stencil 3D d'ordre élevé appliqué à des téraoctets de données. Elle est limitée par la bande passante et par la capacité mémoire, et c'est un des rares domaines industriels où le pavage temporel est systématiquement employé.

GNU Radio dispose de blocs GPU, et cuSignal (désormais intégré à cuPy) fournit les équivalents GPU de scipy.signal.


Résumé du chapitre

À retenir

  • La FFT est limitée par la mémoire ; cuFFTDx permet de la fusionner dans votre noyau, ce qui supprime les allers-retours HBM des pipelines FFT → multiplication → FFT⁻¹.
  • Pour les filtres, exploiter la séparabilité : \(O(K^2) \to O(2K)\). Au-delà de \(K \approx 30\), passer par la FFT.
  • NVENC, NVDEC, unités de texture, moteurs de copie sont indépendants des SM : un pipeline vidéo peut décoder, calculer et encoder simultanément.
  • Les unités de texture font l'interpolation bilinéaire gratuitement, avec une précision limitée (virgule fixe 8 bits de fraction).
  • Le vrai goulot est presque toujours les transferts. Les données entrent une fois et sortent une fois. DALI, CV-CUDA, VPI existent pour ça.
  • Symptôme d'un pipeline mal conçu : des trous réguliers dans la chronologie Nsight Systems.

Vérifiez que vous avez compris

Pourquoi la convolution par FFT devient-elle rentable au-delà d'un noyau de ~30×30 ?

Comparons les coûts pour une image \(N \times N\) et un noyau \(K \times K\) :

  • convolution directe : \(O(N^2 K^2)\) ;
  • par FFT : \(O(N^2 \log N)\), indépendant de \(K\) — deux FFT 2D, une multiplication point à point, une FFT inverse.

Le point d'équilibre est en \(K^2 \approx c \log N\), où \(c\) tient compte des constantes (la FFT a une constante bien plus élevée, et le padding pour éviter le repliement circulaire coûte).

En pratique, sur GPU, le seuil observé est autour de \(K = 25\) à \(40\) selon l'implémentation. En dessous, la convolution directe avec mémoire partagée gagne — d'autant qu'elle se fusionne facilement avec les opérations voisines.

Votre entraînement de vision utilise 40 % du GPU. Le modèle est-il en cause ?

Probablement pas. 40 % d'utilisation avec un modèle de vision signifie presque toujours que le chargement des données est le goulot.

Vérification dans Nsight Systems : des trous réguliers dans la ligne GPU, alignés sur les itérations, avec des threads CPU occupés à décoder du JPEG et à faire des transformations PIL.

Remèdes, par ordre :

  1. augmenter num_workers du DataLoader et activer pin_memory=True ;
  2. passer à DALI ou CV-CUDA pour décoder et augmenter sur GPU ;
  3. précalculer les augmentations déterministes ;
  4. utiliser un format de stockage adapté (WebDataset, TFRecord) plutôt que des millions de petits fichiers.
Vous devez traiter 16 flux vidéo 1080p simultanés avec un modèle de détection. Faisable sur un GPU ?

Calculons.

Décodage : 16 flux × 30 im/s = 480 images/s à décoder. Un NVDEC moderne décode plusieurs centaines d'images 1080p par seconde ; c'est jouable, mais il faut vérifier le nombre de sessions simultanées autorisées par votre carte (limité sur le grand public).

Inférence : si le modèle traite une image en 5 ms, 480 im/s exigent 2,4 s de calcul par seconde — impossible. Il faut soit un modèle plus léger, soit ne pas traiter toutes les images (détection à 5 im/s + suivi entre les deux), soit grouper les images en lots pour amortir.

Le regroupement est la clé : traiter 16 images d'un coup au lieu d'une par une multiplie le débit par 5 à 10× sur un modèle de détection, parce qu'on passe d'un régime limité par la mémoire à un régime limité par le calcul. C'est exactement le raisonnement du roofline.


Chapitre suivant : 5 · Finance et cryptographie


Sources de ce chapitre