lardon3d/docs/architecture/internal_parallelism.md

19 KiB
Raw Blame History

Parallélisme interne borné

Statut

PASS / FROZEN — INTERNAL_PARALLELISM_COMPUTE_RESOURCES_V1. Ce document décrit le contrat validé pour features.extract, features.extract.sift, visual_index.update, candidate_pair.generate et matcher.run. Ce gel porte sur le parallélisme interne borné, les réservations Governor et les sorties canoniques ; il ne transforme pas les résultats de microbenchmarks GPU en une preuve de débit durable sur corpus sous pression hôte.

Frontière runtime

La Task Queue conserve un seul callback actif. Une Task peut exploiter à l'intérieur de ce callback les cpu_threads effectivement admis par le Resource Governor. Ce parallélisme interne ne crée ni pool global, ni deuxième scheduler, ni file de travail persistante. L'affinité CPU éventuelle appartient au lanceur ou à l'hôte : la bibliothèque ne choisit et ne persiste aucun ID de CPU.

Audit GPU — 29 août 2026

Cet audit est une constatation de capacité et une recommandation de périmètre ; il n'ajoute aucun backend ni aucune politique du Governor. La machine contrôlée expose AMD Radeon 780M Graphics (RADV PHOENIX), iGPU Vulkan API 1.4.354 sous Mesa 26.2.1. OpenCV 5.0.0 y indique le chargement dynamique de Vulkan et d'OpenCL, avec TBB comme framework parallèle ; ses modules CUDA ne sont pas disponibles. L'outil clinfo n'était pas installé : cette absence ne prouve ni ne valide une exécution OpenCL. Elle ne constitue donc pas une couture GPU utilisable par Lardon3D.

La 780M est UMA. Toute mémoire de travail GPU, toute double résidence CPU/GPU et tout staging host-visible consomment la RAM hôte et doivent être comptés une seule fois par le Resource Governor, conformément à la règle UMA du Resource Boundary ; ils ne créent pas un budget VRAM indépendant. L'admission, la durée de réservation et la libération restent du ressort du Governor et de la Task admise. Les copies ou synchronisations hôte↔GPU, y compris sur mémoire partagée, restent un coût de transfert/synchronisation à mesurer : l'UMA ne les rend pas gratuites.

| Étape | Backend courant / couture GPU existante | Bibliothèque ou API requise si étude ultérieure | Compatibilité, sortie et coût | Recommandation de l'audit | | --- | --- | --- | --- | | features.extract (ORB/SIFT) | Extraction OpenCV CPU ; aucune couture GPU de production. | Aucune API d'extraction GPU n'est validée. Les indicateurs OpenCV Vulkan/OpenCL dynamiques ne fournissent pas à eux seuls un backend d'extraction ; CUDA est indisponible dans ce build. | Il faudrait prouver l'algorithme, les keypoints, descripteurs, ordre et Feature File produits. Les descripteurs et leurs buffers devraient être résidents ou stagés en RAM UMA ; le transfert et la synchronisation risquent de dominer les images bornées. Bénéfice non mesuré, complexité élevée. | Rester CPU. Une étude ne peut commencer qu'avec une API/backend concret et une preuve d'équivalence. | | visual_index.update | Construction et publication CPU ; aucune couture GPU. | Aucun backend/API GPU validé. | Les postings, leur compaction, tri total et publication déterministe sont aujourd'hui CPU. Un backend devrait préserver exactement table_id,key24,feature_set_id,feature_index, le segment, SHA-256 et les memberships ; il ajouterait double résidence UMA et synchronisation pour un bénéfice non mesuré. Complexité élevée. | Rester CPU ; aucun chantier GPU n'est justifié par cet audit. | | candidate_pair.generate | Requête Visual Index, top-K et publication CPU ; aucune couture GPU. | Aucun backend/API GPU validé. | Les scores, top-K, tie-breaks, normalisation et ordre de publication sont canoniques. Une accélération devrait rendre ces résultats identiques malgré les accès DB et les petits résultats bornés ; copies/synchronisations UMA et l'accès persistant réduisent le bénéfice attendu. Bénéfice non mesuré, complexité élevée. | Rester CPU ; ne pas introduire un backend GPU ou une seconde politique de sélection. | | matcher.run | BFMatcher CPU ; couture existante limitée au backend Vulkan ORB sériel. Le mode parallèle CPU ne réserve ni n'utilise le GPU. | Vulkan compute existant pour ORB/Hamming top-2 uniquement. Aucun chemin Vulkan/OpenCL/CUDA validé pour SIFT/RootSIFT. | ORB Vulkan déjà prouve une parité top-2 et Match File complète avec le CPU, ce qui permet l'identité persistante commune. Les dispatchs restent soumis aux buffers descriptors et à la synchronisation UMA. SIFT/RootSIFT Vulkan n'est pas équivalent sur égalités adversariales et reste CPU. | Conserver le chemin ORB Vulkan sériel explicitement admis et le CPU parallèle séparé. Ne pas étendre à SIFT/RootSIFT ni fusionner les modes sans nouvelle validation. |

L'attente « sortie identique » est une exigence de preuve, non une présomption attachée au GPU. Un futur backend ne peut partager une identité, un fingerprint ou une version scientifique existants que si l'équivalence complète des sorties canoniques est démontrée à la frontière concernée. Si ses sorties diffèrent, il doit recevoir un backend/version et une identité scientifique versionnés, avec une décision et une validation dédiées ; il ne doit jamais être choisi comme un fallback transparent. Le mode ORB Vulkan sériel existant est l'exception déjà prouvée à cette règle d'identité commune.

Décision GPU finale — Radeon 780M

La validation de production de matcher.run distingue l'identité scientifique de son mode opérationnel. Les API historiques créent toujours le Matcher CPU parallèle (CPU=8, GPU=0). L'API additive de mode explicite peut créer ORB Vulkan seulement avant admission, avec CPU=1, un slot GPU et 640 Kio UMA. Cette réservation couvre les buffers persistants A (256 Kio), B (256 Kio) et top-2 (128 Kio); les 10 Mio de stage Matcher restent la mémoire CPU par paire. Une panne de dispatch reprend entièrement le top-2 CPU avant toute publication; elle ne publie jamais une sortie GPU partielle ni ne change fingerprint, identité Match Result ou SHA du Match File.

Sur l'hôte contrôlé (AMD Radeon 780M Graphics (RADV PHOENIX), Vulkan 1.4.354, Mesa 26.2.1), le backend a créé le device et exécuté les dispatchs réels. Les mesures de kernel chaud, distinctes du coût Task/SQLite/checkpoint, sont :

Paire ORB CPU BFMatcher Vulkan Accélération Vulkan
768 × 768 0,67 ms 0,34 ms 1,97×
4096 × 4096 17,00 ms 1,62 ms 10,49×
8192 × 8192 67,54 ms 3,91 ms 17,27×

La parité top-2, Match File et publication durable est couverte par une Task Queue réelle à 769 × 769 descriptors, par une répétition déterministe et par le fallback forcé. La reprise accepte seulement les signatures entières CPU8, Vulkan CPU1 et les deux signatures CPU12 historiques; celles-ci sont normalisées éphémèrement vers leur forme courante avant admission. Une signature voisine est rejetée. La configuration portable -Dvulkan_orb=disabled refuse le mode Vulkan avant allocation de Task ID et conserve le Matcher CPU.

HOST_PRESSURE_CONTAMINATED=TRUE pour une comparaison de débit de corpus : au moment de l'audit, MemAvailable était ~7,6 Gio, 7,6 Gio de swap étaient en usage, même si la PSI mémoire courante était nulle. Il n'existe donc pas de mesure défendable CPU t1/t6/t12 contre corpus Vulkan dans cette tranche. Le Matcher CPU durable actuel plafonne en outre à huit participants utiles; un CPU t12 ne serait pas une comparaison de Task valide. Les chiffres ci-dessus sont volontairement limités au kernel commun et ne sélectionnent pas une politique AUTO ou le backend par défaut.

Les classifications finales sont :

Domaine Classification Évidence déterminante
candidate_pair.generate CANDIDATE_GPU=REJECTED_WITH_MEASURED_REASON Le coût est Visual Index, filtrage, branchement et publication SQLite ordonnée; aucune primitive GPU existante ne préserve ces identités et le CPU parallèle Candidate a déjà démontré 7,114× à t12.
features.extract FEATURE_GPU=REJECTED_WITH_MEASURED_OR_IMPLEMENTATION_EVIDENCE OpenCV 5.0.0 installé n'expose aucun ORB/SIFT Vulkan/OpenCL utilisable, CUDA est absent, et aucun seam existant ne peut prouver les Feature Files byte-identiques.
visual_index.update VISUAL_INDEX_GPU=REJECTED_WITH_MEASURED_REASON Postings, tri total, SHA et publication déterministe sont CPU; aucun kernel GPU borné existant ne couvre cette frontière.
matcher.run ORB MATCHER_GPU=EXISTING_BACKEND_VALIDATED_BUT_CPU_PREFERRED Le backend est réellement admis, dispatché et exact, mais la pression hôte et l'absence d'une comparaison de débit Task/corpus saine interdisent de préférer Vulkan au CPU parallèle comme défaut.

Cette dernière classification ne rejette pas le backend : ORB Vulkan reste un mode explicite validé. Elle interdit seulement de présenter les microbenchmarks comme une preuve que le mode sériel GPU bat le Matcher CPU parallèle dans un corpus durable complet.

Extraction OpenCV

Le processus configure une seule fois la limite de threads interne OpenCV, avant la création de la Queue, au budget interactif audité min(12, logical_cpu_count - system_cpu_reserve). Les Tasks ORB et SIFT demandent le plafond douze au Governor, qui réduit l'admission à ce même budget ; la réservation vit pendant l'exécution bornée d'une image et est libérée avec la Task. OpenCV possède son fan-out interne, tandis que la Queue reste propriétaire de l'unique callback actif.

Le nombre admis est une politique de ressources, pas une identité scientifique. L'audit OpenCV 5.0.0 contrôlé à 1/2/4/8/12 threads obtient des Feature Files ORB v1 et SIFT v2 byte-identiques. Aucun fingerprint, format ou schéma n'est modifié. Les utilisateurs imbriqués d'OpenCV qui réduisent temporairement cette limite doivent restaurer sa valeur avant de libérer leur réservation ; ils ne peuvent pas faire varier la configuration pendant une extraction concurrente.

Pour Candidate Generation, l'estimation demande jusqu'à douze threads CPU et un slot I/O. Le callback de Queue compte comme un de ces threads ; il crée donc au plus cpu_threads - 1 threads enfants. Tous les enfants sont joints avant la fin de la séquence, la libération de réservation ou lardon3d_task_sequence_break().

Calcul et publication Visual Index

Une séquence sélectionne le même préfixe durable d'au plus seize Feature Sets qu'en mode sériel. Jusqu'à douze participants effectivement admis lisent les Feature Files immuables ; chaque reader, son descripteur de fichier et sa tranche de 256 descripteurs appartiennent à un seul participant. Aucun enfant n'utilise le handle Project DB partagé.

Chaque Feature Set écrit dans une tranche disjointe du buffer de postings borné du segment. Après jointure, le callback compacte seul ces tranches dans l'ordre de sélection, puis le tri total existant impose table_id,key24,feature_set_id,feature_index. Le callback est l'unique propriétaire de la sérialisation, du hash, de la publication asset et de la transaction segment + memberships. Le nombre de participants ne change donc ni octet, SHA-256, chemin, membership, génération, fingerprint, ni résultat de requête.

Une erreur de lecture interdit toute publication. Si la création d'un enfant échoue, le callback calcule après les jointures uniquement les tranches restées sans producteur. Le curseur n'avance qu'après commit du segment ; le checkpoint Task suit ce commit, et tous les enfants sont joints avant sequence_break. Le buffer de postings n'est pas multiplié : ses tranches privées partitionnent la capacité fixe existante. Les coûts supplémentaires sont au plus une pile et un reader/FD Feature File par participant admis.

Calcul et publication Candidate

CANDIDATE_PARALLELISM_IMPLEMENTATION=PASS. Le parallélisme interne Candidate est validé lorsque le Governor admet à la fois plusieurs CPUs et un lot contenant plusieurs memberships indépendants. Les benchmarks CPU 1/6/12 antérieurs restent valides pour leurs intervalles mesurés ; une preuve de débit durable sur machine réelle exige en plus que chaque frontière observée admette un lot supérieur à un.

L'admission CPU et l'admission de lot sont deux contrats opérationnels différents. desired_cpu_threads borne le fan-out disponible, tandis que batch_size borne strictement le nombre de memberships que la Task peut prendre dans la séquence courante. Ainsi, cpu_threads=12 avec batch_size=1 est architecturalement valide mais ne contient qu'une source indépendante : Candidate calcule un seul participant utile. Il ne doit jamais emprunter du travail à une séquence ultérieure pour remplir les CPUs admis.

Le test Candidate couvre désormais six Feature Sets et trois fenêtres productives saines : l'admission initiale, puis deux réadmissions productives après sequence_break, admettent chacune deux sources et obtiennent deux participants utiles. Une troisième réadmission après sequence_break observe le suffixe vide requis pour terminer. La sortie reste canonique, le curseur durable est contigu et aucune paire n'est dupliquée. Le même test vérifie le complément sûr : un lot admis d'un seul item, même avec douze CPUs disponibles, produit exactement un participant par frontière productive. Cette réduction est EXPECTED_RESOURCE_SAFETY_BEHAVIOR, pas une perte de parallélisme Candidate.

REAL_HOST_CURRENT_STATE=GOVERNOR_PRESSURE_THROTTLING_ACTIVE. Le 29 août 2026, un run S21 réel a conservé son estimate Candidate CPU-12 et son format de checkpoint courant (256 KiB + 64 KiB/item, lot 1..64) après 237 séquences, mais le Governor a admis des lots d'un sous pression globale. Les faits retenus sont : zram 6059936 / 6291452 KiB (~96,3 %), swapfile ~1,92 GiB, tandis que Candidate restait ~55 MiB RSS, VmSwap=0 et MemAvailable ~7,6 GiB. La disparition ultérieure du swap /dev/sdb1 vers 14:00 a modifié la configuration de swap de l'hôte, mais elle est postérieure à l'effondrement initial du lot et n'en est pas la cause initiale prouvée. Cette décision reflète des signaux de swap/PSI globaux, non la réservation Candidate elle-même. Les seuils Gate G ne sont pas modifiés pour améliorer un benchmark ; une preuve réelle soutenue doit attendre un état hôte qui admet répétitivement batch_size > 1.

Les sources sont les memberships durables du Visual Index, pagés en ordre croissant de feature_set_id. Les IDs peuvent être clairsemés. Une fenêtre contient au plus 2 * cpu_threads sources et jamais plus de 24. Chaque résultat conserve au plus le top-K existant de 256 propositions ; l'estimation de Task reste conservatrice à 256 Kio fixes et 64 Kio par item admis.

Chaque participant possède son propre handle project.db pour les lectures et le ferme avant de terminer. Le calcul parallèle charge le Feature Set source, exécute exactement la requête Visual Index existante et forme les paires canoniques en mémoire. Il ne publie rien.

La reprise normalise uniquement l'estimation Candidate v1 sérielle historique de forme exacte vers la demande courante de douze participants. Cette estimation effective reste privée et n'est pas checkpointée avant admission ; le Governor peut toujours la réduire à son budget disponible. Le Task ID, le curseur typé, le lot 164 et les paramètres scientifiques restent inchangés. Les autres kinds ne passent pas par cette normalisation Candidate. Matcher possède sa propre normalisation bornée des deux formes CPU12 historiques, décrite ci-dessous.

Après la jointure complète, le thread propriétaire de la Task publie seul, dans l'ordre croissant des sources puis dans l'ordre des résultats de requête. Il emploie le comportement existant find_candidate_pair puis create_candidate_pair. Les scores, top-K, tie-breaks, normalisation de paire, fingerprints et identités scientifiques ne changent pas. Le nombre de threads ne peut donc modifier ni les identités persistées, ni leur ordre de publication, ni leur cardinalité.

Échec, reprise et progression

Une erreur de calcul à la source S interdit toute publication de S et de toute source suivante déjà calculée dans la fenêtre. Les sources antérieures entièrement publiées forment le seul préfixe susceptible d'avancer le curseur. Une reprise revoit éventuellement un préfixe publié avant un crash mais non checkpointé ; l'idempotence existante fait converger ce rejeu sans deviner d'identité.

La progression est le rang du curseur parmi les memberships durables ordonnés, divisé par leur nombre total. Elle n'utilise ni la valeur numérique du feature_set_id, ni un compteur process-local, ni le nombre de séquences. Elle est donc monotone pendant l'exécution et reconstruite à l'identique après reprise pour un même état de memberships. Les valeurs incomplètes sont bornées à 099 ; 100 n'est publié qu'après observation d'un suffixe vide et achèvement de la Task.

Calcul et publication Matcher

Les sources Matcher sont les Candidate Pairs durables, pagées en ordre croissant de candidate_pair_id. Une fenêtre contient au plus 2 * cpu_threads paires et jamais plus de huit. Le callback Queue compte comme un participant et crée donc au plus cpu_threads - 1 enfants ; tous sont joints avant publication, libération de réservation ou sequence_break.

Chaque participant calcule un Match File temporaire privé. OpenCV est configuré à un thread interne pendant la séquence afin que BFMatcher ne crée pas un second fan-out sous les participants admis. Le backend Vulkan ORB partagé reste utilisé uniquement par un mode déclaré sériel avant admission, dont l'estimation réserve un thread CPU et le GPU. Le mode parallèle reste CPU-only même si le Governor ne lui accorde finalement qu'un participant ; il ne sélectionne donc jamais tardivement une ressource non réservée. Ce choix runtime ne modifie ni le fingerprint, ni Lowe ratio, ni les correspondances brutes, ni l'identité Match Result.

Les APIs historiques sélectionnent toujours le mode CPU parallèle ; le mode ORB Vulkan est demandé par une API additive explicite et n'est jamais activé par variable d'environnement ou réduction Governor. Sa création exige ORB, un GPU sélectionné et un backend présent. Après reprise, la forme immutable GPU peut conserver le fallback CPU exact si le backend runtime n'est plus disponible. Les signatures courantes complètes sont CPU8/GPU0 et CPU1/GPU1/640 Kio ; les signatures historiques CPU12 correspondantes sont normalisées éphémèrement comme un tout avant admission. Toute forme voisine est rejetée afin de ne pas inférer un mode depuis un champ isolé.

Après jointure, seul le callback propriétaire publie les stages, dans l'ordre croissant de candidate_pair_id, par le chemin atomique Match Store existant. Une erreur de calcul ou de publication interdit la paire fautive et tout son suffixe déjà calculé. Le curseur avance exclusivement avec le préfixe contigu durablement publié. Les temporaires non consommés sont supprimés sur succès, échec et annulation ; les assets partagés ne sont jamais supprimés par ce nettoyage. Le rejeu conserve l'idempotence Match Result existante.

Hors périmètre

  • parallélisme entre Tasks ou plusieurs callbacks Queue actifs ;
  • pool de workers global ou persistant ;
  • modification du schéma Project DB ;
  • affinité, pinning ou IDs de CPU dans l'API ;
  • modification de la sélection/scoring/fingerprint Candidate ;
  • changement des décisions FROZEN du Resource Governor.