Freeze Compute Governor v2 and asynchronous Vulkan Matcher execution. Governor now owns production task admission and live resource adaptation, with GPU-first AUTO selection for validated backends, CPU12 host capacity, desktop CPU/RAM reserves, UMA accounting, pressure throttling, hysteresis, and recovery. Validate and freeze the rolling Vulkan ORB Matcher path, host topology policy, task capability envelopes, runtime telemetry, restart/durability contracts, portable CPU fallback, and scientific equivalence. COMPUTE_GOVERNOR_V2=PASS/FROZEN ORB_VULKAN_ASYNC_EXECUTION=PASS/FROZEN MATCHER_GPU=EXISTING_BACKEND_VALIDATED_AND_PREFERRED
556 lines
37 KiB
Markdown
556 lines
37 KiB
Markdown
# Resource Governor Lardon3D
|
||
|
||
## COMPUTE_GOVERNOR_V2 — PASS / FROZEN
|
||
|
||
**COMPUTE_GOVERNOR_V2 — PASS / FROZEN.**
|
||
**ORB_VULKAN_ASYNC_EXECUTION — PASS / FROZEN.** Gate G core reste
|
||
**PASS / FROZEN**. Cette évolution opérationnelle ne change ni le contrat
|
||
scientifique Matcher gelé, ni les octets Match File, ni l'ordre du curseur, ni
|
||
la durabilité. Sa cible est une boucle fermée à `sequence_break()` : le
|
||
Governor observe une télémétrie hôte/Task bornée, choisit un contrat pour la
|
||
séquence suivante, puis le maintient immutable pendant toute son exécution.
|
||
Cette couture privée est gelée ; seule une séquence ultérieure peut recevoir
|
||
un autre contrat. Les dimensions retenues, le corpus S21 complet, les suites
|
||
portable/Vulkan, les sanitizers et l'audit XHIGH ferment le statut v2.
|
||
|
||
Sur le profil validé Ryzen 7 8845HS, le Governor lit le masque d'affinité permis
|
||
et la topologie package/core Linux. Il réserve des groupes de coeurs physiques
|
||
complets, frères SMT inclus, en préférant déterministiquement les plus grands
|
||
IDs package/core. Avec le masque unrestricted 0–15 et une réserve logique de
|
||
quatre, il dérive le pool lourd `0-5,8-13` et réserve `6,7,14,15` au desktop
|
||
Arch/Sway, à l'audio et à l'interaction ordinaire. Si le caller est déjà limité
|
||
à au plus `logical_total - reserve`, son masque permis devient directement le
|
||
pool de calcul sans seconde soustraction. Sans affinité/topologie exploitable,
|
||
le budget portable `logical_total - reserve` subsiste sans exclusion arbitraire
|
||
de frères SMT et l'affinité est diagnostiquée inactive. Cette politique privée
|
||
est **PASS / FROZEN** sur le profil validé ; aucun ID CPU n'est persisté ni ne
|
||
devient une identité scientifique.
|
||
|
||
Seul le worker lourd de l'unique Queue applique et relit son propre masque
|
||
(`pid=0`) avant les callbacks. Le caller/main/TUI reste unrestricted et aucun
|
||
autre processus n'est touché. Un pidfd de thread ne stabilise pas l'identifiant
|
||
numérique consommé par `sched_setaffinity(tid)` après la sortie du thread : le
|
||
Governor n'énumère ni ne mute donc jamais un TID auxiliaire. À la place,
|
||
Lardon3D établit `MESA_SHADER_CACHE_DISABLE=true` avant toute création de pthread
|
||
applicatif et avant toute initialisation Vulkan. L'absence de variable prend ce
|
||
défaut sûr ; les valeurs explicites exactes `true` et `1` sont conservées. Une
|
||
valeur explicite fausse ou malformée n'est pas écrasée et fait échouer le
|
||
démarrage, car elle permettrait à Mesa de créer ses helpers de cache disque
|
||
observés capables d'élargir leur affinité. La variable est inoffensive pour les
|
||
drivers non-Mesa, opérationnelle seulement, non persistée et absente de toute
|
||
identité scientifique. Sur le profil 780M contrôlé, les helpers `*:disk$0`
|
||
disparaissent ; tous les threads runtime restants observés héritent et gardent
|
||
`0-5,8-13`. Les diagnostics privés exposent l'activité de cette politique, la
|
||
valeur sûre et sa raison, sans prétendre recontraindre des auxiliaires.
|
||
Cette garantie est aussi défensive dans le backend public : toute requête
|
||
non vide qui devrait initialiser Vulkan vérifie sans mutation la valeur exacte
|
||
`true`/`1` avant le premier appel Mesa. Une valeur absente ou différente rend ce
|
||
contexte backend indisponible et retourne `UNAVAILABLE` sans sortie partielle.
|
||
La lecture metadata demeure non initialisante. Ainsi un consumer direct qui ne
|
||
passe ni par l'application ni par le runner ne contourne pas la réserve CPU.
|
||
Le nombre de CPUs du pool borne directement l'admission.
|
||
CPU Matcher reste validé dans `1..12`, indépendamment du lot :
|
||
`cpu_threads=12, batch_size=1` demeure un contrat valide.
|
||
|
||
La cible Compute Governor v2 conserve 3 GiB de `MemAvailable` et ne franchit
|
||
pas intentionnellement le plancher dur de 2 GiB sur cette classe d'hôte 16 GiB.
|
||
Les entrées de pression actives sont `MemAvailable`, les PSI CPU/mémoire/I/O et
|
||
les deltas swap-in/swap-out entre observations. L'occupation totale du swap est
|
||
un état historique, pas à elle seule une activité récente. Ces objectifs v2 ne
|
||
réécrivent pas rétroactivement les constantes Gate G core gelées ci-dessous.
|
||
Les observations saines autorisent une croissance lente. Pour une dimension
|
||
CPU réellement réductible, la rampe est exactement `1 → 2 → 4 → 8 → 12`,
|
||
bornée par l'enveloppe et le compute-pool. Deux séquences établissent d'abord
|
||
une référence de débit durable puis ouvrent un essai borné au palier supérieur.
|
||
Les dimensions CPU/génériques conservent deux observations d'essai et un gain
|
||
d'au moins 5 % avant une nouvelle croissance. Le lot ORB Vulkan normal exige
|
||
désormais huit observations pures consécutives pour sa référence puis huit pour
|
||
chaque essai; sa décision compare les moyennes bornées au même deadband de 5 %.
|
||
Pour Matcher, cette observation est la cadence durable complète de la séquence :
|
||
à partir de la deuxième séquence elle commence juste avant la rupture/réadmission
|
||
et se termine seulement après calcul, publication owner-only et checkpoint
|
||
générique durable. Le temps interne du calcul reste une métrique séparée, mais
|
||
ne peut pas masquer le coût de réadmission que le choix du lot doit amortir.
|
||
CPU et lot partagent un unique essai : deux dimensions ne changent jamais dans
|
||
la même mesure. L'infrastructure privée sait aussi
|
||
borner un essai inflight, mais l'enveloppe ORB AUTO normale le fixe maintenant
|
||
à 1 après la mesure A/B décrite ci-dessous. Un échantillon rapide isolé ne
|
||
décide rien ; sans gain, le plafond revient au dernier palier accepté et s'y
|
||
arrête. PSI ou delta swap actif abandonne immédiatement l'essai ; toute
|
||
admission encore permise prend lot minimum, inflight minimum et CPU1
|
||
avant réservation. Un `WAIT`/`REJECT` Gate G reste inchangé. Après
|
||
l'hystérésis `RED → YELLOW → GREEN`, une nouvelle référence
|
||
permet de reprendre des essais contrôlés. Les diagnostics distinguent les
|
||
essais/gains/refus CPU, inflight et lot, `backend-fallback-hold` et
|
||
`pressure-decrease`. En production normale, ORB est inflight 1 et helpers 0.
|
||
|
||
La télémétrie hôte privée lit, avec capacités fixes et parse strict, les deltas
|
||
`/proc/stat` limités au masque du compute-pool, `MemAvailable`, PSI mémoire et
|
||
I/O `some`/`full`, les deltas actifs `pswpin`/`pswpout`, RSS/HWM du processus et
|
||
le `gpu_busy_percent` de l'unique index DRM retenu par Hardware Profile, sans
|
||
scan ni fallback vers une autre carte. Utilisation CPU et GPU
|
||
sont exprimées en basis points avec un bit `known`; régression, overflow,
|
||
troncature, token malformé ou signal absent donnent `unknown`. La charge globale
|
||
n'est pas rebaptisée utilisation du pool et un Task qui utilise son pool admis
|
||
n'est pas réduit pour ce seul fait. RSS/HWM reste une observation du processus,
|
||
jamais une réservation mémoire Task. Aucun échec de cette capture optionnelle
|
||
ne fait échouer l'exécution scientifique.
|
||
|
||
Pour ORB Vulkan normal, seul le lot `1..8` reste essayable ; inflight est fixé à
|
||
1 et helpers à 0. Huit séquences pures saines construisent la référence, puis
|
||
huit séquences au palier d'essai sont nécessaires avant acceptation ou refus.
|
||
Fallback, travail durable nul et pression sont exclus et réinitialisent la
|
||
fenêtre pertinente. Un backend affamé ou un GPU peu occupé sous hôte sain peut
|
||
ouvrir cet essai ; `gpu_busy` inconnu ne l'interdit pas lorsque la starvation
|
||
backend est observable. Seul le gain moyen de débit durable accepte le palier.
|
||
Une occupation GPU plus haute sans gain le fait revenir au dernier contrat
|
||
accepté et arrête cette croissance. Un ou deux échantillons, hauts ou bas, ne
|
||
décident donc jamais le lot GPU.
|
||
|
||
La mesure forcée ABBA du même corpus et du même binaire donne 54,661652238
|
||
paires/s à depth 1 et 55,797311953 paires/s à depth 2, soit +2,077617 %, sous
|
||
le deadband matériel de 5 %. Ces débits combinés valent
|
||
`(2 * 4113 * 1e9) / (wall_ns_a + wall_ns_b)`, et non la moyenne des deux débits
|
||
par run. Les `wall_ns` bruts sont 75326831673/75162582080 à depth 1 et
|
||
73662096698/73764360098 à depth 2; les walls moyens sont donc
|
||
75,244706877/73,713228398 s. Les autres moyennes depth 1/depth 2 sont : fence
|
||
6,0684/3,6776 s, starvation 54,4534/50,1465 s,
|
||
publication 29,2582/30,0548 s, submit CPU 0,2655/0,3818 s, readback
|
||
0,0460/0,0873 s et GPU busy max 23/24 %. Les quatre exécutions ont le digest
|
||
`7a9dbc38a23a600379167d55e24836b7acbb22eea25573e7440bdc9e4602b3b3`, quatre
|
||
séquences de fallback local par exécution et zéro panne/discard. Conclusion opérationnelle :
|
||
`DEPTH_MAX_VALIDATED_SAFETY=2`, mais `DEPTH_MAX_USEFUL=1`; depth 2 est
|
||
**REJECTED_WITH_MEASURED_REASON** pour AUTO normal.
|
||
Les agrégats retenus sont
|
||
`/home/fy59/Documents/Lardon/.real-pre-sfm-2026-08-30/governor-v2-evidence/`
|
||
`forced-depth1-a.stdout.jsonl`, `forced-depth1-b.stdout.jsonl`,
|
||
`forced-depth2-a.stdout.jsonl` et `forced-depth2-b.stdout.jsonl`.
|
||
|
||
La Radeon 780M possède un slot GPU et utilise une mémoire UMA : buffers Vulkan,
|
||
staging, descripteurs/commandes et readback en vol sont débités exactement une
|
||
fois de la RAM hôte. Hardware Profile ne conclut plus « VRAM séparée » au seul
|
||
motif qu'amdgpu publie `mem_info_vram_total`. Il conserve la capacité de payload
|
||
rapportée, mais classe conservativement shared/UMA lorsqu'un petit aperture
|
||
VRAM volé/dédié est accompagné d'un GTT à l'échelle de la RAM système, ou
|
||
lorsque cette petite capacité reste incertaine. Sur l'hôte validé, les preuves
|
||
exactes sont 512 Mio de VRAM visible et 7 986 020 352 octets de GTT pour environ
|
||
16 Gio de RAM. Une classification UMA conservatrice d'un GPU à faible VRAM peut
|
||
refuser inutilement une admission ; classer à tort cet iGPU comme mémoire libre
|
||
séparée pourrait contourner les cibles 3 Gio/2 Gio et est interdit. La capacité
|
||
rapportée ne forme donc aucun second budget VRAM indépendant sur UMA.
|
||
|
||
La politique canonique v2 est GPU-first lorsqu'un backend est validé,
|
||
déterministe et mesurément plus rapide, sous réserve que son contrat GPU/UMA
|
||
soit admis. ORB Matcher est aujourd'hui le workload primaire qui satisfait ces
|
||
conditions : la frontière top-2 et la sortie complète sont exactes, et
|
||
l'évidence contrôlée le classe supérieure au CPU pour ce hot path. CPU demeure
|
||
le fallback portable et de panne. Les choix CPU/Vulkan explicites restent des
|
||
overrides de debug, benchmark et reproductibilité. La production normale cible
|
||
`AUTO`, choisi par le Governor et observable par séquence. Le code courant
|
||
implémente ce comportement pour le Matcher ORB normal : GPU validé d'abord,
|
||
puis capacité CPU complète si le build, le backend, le GPU ou l'admission UMA
|
||
ne sont pas sûrs. La création/reprise AUTO n'initialise ni ne sonde Vulkan sur
|
||
le caller : le premier `begin` initialise le driver sur le worker Queue déjà
|
||
contraint. Un échec produit une paire CPU complète sans preuve partielle et
|
||
désactive l'admission GPU ultérieure lorsqu'il s'agit réellement du backend ;
|
||
une paire sous le seuil Vulkan reste une simple inéligibilité, pas une panne.
|
||
Le Matcher représente séparément `SUBMITTED`, inéligibilité locale et panne :
|
||
il n'appelle `finish` qu'avec le handle soumis correspondant. Une panne réelle
|
||
ne reclasse que les requêtes effectivement soumises ou non encore tentées; la
|
||
cause locale déjà établie pour une paire voisine reste locale. Elle met la
|
||
disponibilité partagée à faux immédiatement, même si le fallback CPU,
|
||
l'annulation ou la publication échoue ensuite. Seule une reprise AUTO peut
|
||
réétablir l'éligibilité depuis les faits runtime ; les reprises fixes et
|
||
historiques ne l'écrasent pas.
|
||
Une faute locale avant soumission (lecture Feature, allocation, chemin) ou
|
||
après `finish` Vulkan réussi (filtrage, allocation, staging/fsync Match File)
|
||
appartient au bucket `other`, jamais à `backend-failure`. La paire est
|
||
recalculée entièrement sur CPU; le backend partagé et les successeurs déjà
|
||
soumis restent valides. Le booléen privé `backend_fault` n'est vrai que si la
|
||
transaction Vulkan `begin` ou `finish` elle-même échoue.
|
||
Les API CPU/Vulkan explicites restent fixes. La télémétrie bornée conserve un
|
||
dernier diagnostic sérialisé et un état de rampe par kind/backend ; elle
|
||
distingue backend sélectionné/réel, contrat complet, pression, mesures hôte,
|
||
débit durable et compteurs Matcher/Vulkan. Une couture privée permet un pull
|
||
`since(serial)` et un format texte borné ; rien n'est imprimé directement dans
|
||
ncurses, persisté ou accumulé en grande histoire. Les callbacks atomiques
|
||
Feature/SIFT/RootSIFT enregistrent un item seulement après extraction et
|
||
publication durable propre. READY/`ALREADY_PRESENT` réutilisé ou publication non
|
||
durable enregistre zéro et n'avance aucun essai. Visual Index applique la même règle par segment ;
|
||
Candidate enregistre chaque séquence durable. Un fallback CPU complet d'une
|
||
séquence sélectionnée Vulkan annule l'essai et reconstruit ultérieurement une
|
||
référence pure ; il n'entraîne jamais la baseline Vulkan.
|
||
|
||
Le runner opt-in `pre-sfm-real-execution` consomme maintenant cette couture
|
||
d'évidence. Ses échantillons JSON sont explicitement qualifiés
|
||
`latest-change-coalescing` : le polling peut fusionner des séquences rapides et
|
||
ne prétend donc pas les énumérer toutes. En parallèle, le Governor maintient par
|
||
kind/backend des compteurs cumulatifs saturants et des extrema de télémétrie de
|
||
taille fixe. Le résumé final rapporte ainsi les admissions et séquences
|
||
enregistrées, items durables, changements de contrat et sommes
|
||
CPU/Vulkan/publication sans histoire non bornée ni double comptage. Ces agrégats
|
||
vivent seulement avec l'instance Governor ; ils ne sont ni un budget RSS, ni un
|
||
nouveau payload, ni une persistance.
|
||
Ils conservent les classifications par séquence et ajoutent les comptes exacts
|
||
par item pour les fallbacks Matcher : inéligibilité locale, panne backend ou
|
||
raison autre/inconnue. La Task incrémente l'unique compteur de l'item seulement
|
||
après publication durable de son fallback CPU complet; une admission CPU
|
||
normale n'est pas un fallback. Cet incrément immédiat est séparé du feedback de
|
||
fin de séquence : un préfixe déjà durable reste compté si une paire suivante
|
||
échoue ou est annulée, sans créer une observation de débit pour la séquence
|
||
avortée. Un high-water mark Task borné évite le double comptage in-process; il
|
||
n'est ni persisté ni reconstruit au redémarrage. Ces compteurs fixes saturent
|
||
avec le drapeau
|
||
commun de l'agrégat et, contrairement au nombre de séquences, restent invariants
|
||
quand le lot change. Le contrôle matriciel forcé du runner n'expose qu'une
|
||
capacité Vulkan aux batch/depth demandés : zéro admission CPU, payload exact,
|
||
aucun changement de contrat, panne, discard ou slot pending sont nécessaires à
|
||
`experiment_valid=true`. Les items localement inéligibles ne rendent une
|
||
comparaison valide que si leurs comptes sont égaux entre cohortes; tout item de
|
||
panne ou autre cause échoue fermement.
|
||
|
||
La matrice forcée item-valide retenue est
|
||
`forced-batch{2,4,8,12}-items{,-b}.stdout.jsonl` sous le répertoire d'évidence
|
||
ci-dessus. Chaque run publie 4113 paires, six items localement inéligibles, zéro
|
||
item backend-failure/other et le digest
|
||
`7a9dbc38a23a600379167d55e24836b7acbb22eea25573e7440bdc9e4602b3b3`. Selon
|
||
`(2 * 4113 * 1e9) / (wall_ns_a + wall_ns_b)`, les walls bruts
|
||
76150272845/75674818393, 61319835797/63138565155,
|
||
55148130595/54847191200 et 53446248173/53724786321 ns donnent respectivement
|
||
54,180767704, 66,094373197, 74,784998723 et 76,755814095 paires/s. Les gains de
|
||
palier sont +21,988624373 %, +13,148812987 % et +2,635308425 %. Le dernier est
|
||
sous le deadband 5 % : `BATCH_MAX_VALIDATED_SAFETY=12` reste disponible au
|
||
benchmark privé, `BATCH_MAX_USEFUL=8` borne AUTO normal et batch 12 est
|
||
**REJECTED_WITH_MEASURED_REASON**. Les anciens
|
||
`forced-batch2-current.stdout.jsonl` et `forced-batch4.stdout.jsonl`, comparés
|
||
par nombre de séquences au lieu d'items, restent une preuve historique du
|
||
comparateur obsolète et ne participent pas à cette décision.
|
||
Le run de production sans override
|
||
`short-auto-batch8-governor-v2.stdout.jsonl` confirme ensuite la boucle réelle :
|
||
contrats `1 → 2 → 4 → 8`, dernier lot 8, inflight 1, helpers 0, 4113 résultats
|
||
durables en 54,066973393 s soit 76,072 paires/s, six fallbacks locaux, zéro
|
||
panne/discard et le même digest `L3DMRD1`. Le compute-pool contient 12 CPU,
|
||
les quatre CPU `6,7,14,15` restent réservés, l'UMA est comptée, le minimum
|
||
`MemAvailable` vaut 12 421 971 968 octets et aucun swap-in/out n'est observé.
|
||
La preuve S21 finale sans override,
|
||
`final-s21-auto.stdout.jsonl`, ferme la boucle sur 172 741 paires : AUTO
|
||
sélectionne Vulkan pour les 21 630 admissions, publie 172 741 résultats en
|
||
2 345,444485079 s (73,649 paires/s), termine avec batch 8, inflight 1 et
|
||
helpers 0, et ne compte aucune panne, aucun discard ni slot pending. La seule
|
||
admission classée YELLOW ramène batch 8 à 1; plusieurs séquences GREEN
|
||
reconstruisent ensuite la référence et remontent 1 → 2 → 4 → 8, sans
|
||
rebond. Le minimum `MemAvailable` est 10 927 390 720 octets, PSI mémoire
|
||
maximal 0, swap-in/out maximal 0, GPU busy moyen/médian/maximal 26/27/36 % et
|
||
HWM processus maximal 250 658 816 octets. L'UMA admise reste 655 360 octets;
|
||
les masques exacts sont compute `0-5,8-13` et reserve `6,7,14,15`. Cette preuve
|
||
est opérationnelle : le digest scientifique et la reprise sont documentés par
|
||
le contrat Matcher, pas redéfinis ici.
|
||
L'ensemble v2 est **PASS / FROZEN**. Le gel porte sur cette architecture et
|
||
ses bornes validées, sans rouvrir Gate G ni le Matcher scientifique.
|
||
|
||
Les décisions GPU négatives existantes restent inchangées : Candidate, Feature
|
||
et Visual Index restent CPU; SIFT/RootSIFT Matcher restent BFMatcher L2 CPU. Il
|
||
n'est introduit ni second scheduler, ni Queue, ni daemon, ni sous-système de
|
||
ressources.
|
||
|
||
### Audit des 14 kinds de production
|
||
|
||
Tous les kinds passent par l'unique Queue et l'unique Governor, y compris ceux
|
||
dont toutes les dimensions sont fixes. Dans le tableau, `CPU 1..N` décrit la
|
||
réduction possible par l'admission; `lot 1..N` décrit la dimension de lot
|
||
adaptable. La mémoire réservée vaut `fixe + par_item * batch_size`. Tous les
|
||
coûts GPU par item valent zéro; la forme ORB Vulkan normale réserve exactement
|
||
un slot inflight de 640 Kio, débité une fois de la RAM sur UMA. La capacité
|
||
privée de sûreté/benchmark peut retenir deux slots, soit 1,25 Mio, seulement
|
||
sous un contrat forcé depth 2. Device, pipeline, layouts et cache restent partagés; leurs
|
||
allocations driver opaques ne reçoivent pas un coût inventé.
|
||
|
||
| Kind v1 | Estimation courante | Dimensions réellement consommées / constat Phase 1 |
|
||
| --- | --- | --- |
|
||
| `raw.develop` | MIXED; CPU 1; lot 1; hôte `2 Gio + contexte`, 0/item; I/O 1; GPU 0 | Fixe et atomique. Un garde Queue applique CPU1 au pool OpenCV process-wide puis restaure la valeur précédente. |
|
||
| `photo_quality.triage` | IMPORT; CPU 1; lot 1; hôte `contexte retenu + 20 Mio`, 0/item; I/O 1; GPU 0 | Un groupe par séquence. Un garde Queue applique/restaure CPU1 ; lot et contexte sont honnêtes. |
|
||
| `acquisition_campaign.run` | IMPORT; CPU 1; lot 1; hôte `contexte retenu + 256 Kio` + 64 Kio/item; I/O 1; GPU 0 | Sources, confirmations, requête et plan sont chargés avant admission. La reprise remplace seulement l'enveloppe opérationnelle historique sous-estimée par ce coût exact ; le snapshot durable reste inchangé. |
|
||
| `import.images` | IMPORT; CPU 1; lot 1..32; hôte 128 Kio + `NAME_MAX+64`/item; I/O 1; GPU 0 | Le callback consomme exactement le lot admis et réadmet entre lots. |
|
||
| `features.extract` | CPU; CPU 1..12; lot 1; hôte 64 Mio + 512 Mio/item; I/O 1; GPU 0 | Une image. Le callback applique/restaure exactement le CPU admis dans OpenCV, y compris si la vérification échoue après mutation. Sortie égale à 1/2/4/8/12. |
|
||
| `features.extract.sift` | CPU; CPU 1..12; lot 1; hôte 64 Mio + 1 Gio/item; I/O 1; GPU 0 | Même enforcement/rollback adaptatif. La forme durable courante reste CPU12 et la forme historique CPU1 exacte est normalisée en mémoire. |
|
||
| `features.extract.rootsift` | CPU; CPU 1..12; lot 1; hôte 64 Mio + 1 Gio/item; I/O 1; GPU 0 | Même exécution adaptative que SIFT ; aucune couture GPU validée. |
|
||
| `visual_index.update` | CPU; CPU 1..12; lot 1..16; hôte 8 Mio + 2 Mio/item; I/O 1; GPU 0 | CPU et lot sont lus du contrat; au plus `cpu_threads-1` enfants joints avant publication. |
|
||
| `candidate_pair.generate` | CPU; CPU 1..12; lot 1..64; hôte 256 Kio + 64 Kio/item; I/O 1; GPU 0 | CPU et lot sont consommés; fenêtre `min(2*CPU, 24)`. La forme sérielle historique exacte est normalisée en mémoire. |
|
||
| `matcher.run` | CPU: CPU 1..12, lot 1..12, hôte 0 + 10 Mio/item, I/O 1, GPU 0. ORB Vulkan normal: CPU 1, lot 1..8, même hôte/item, I/O 1, GPU 1 + 640 Kio, inflight 1. | ORB AUTO adapte seulement le lot sur huit observations pures par palier; CPU complet en fallback. Le benchmark privé conserve batch 12 et depth 2 (1,25 Mio), pas la politique normale. Vulkan explicite et contrôle synchrone restent depth 1; SIFT/RootSIFT et CPU explicite restent fixes. Helpers=0. |
|
||
| `geometric_verifier.run` | CPU; CPU 1; lot 1..8; hôte 4 Mio, 0/item; I/O 1; GPU 0 | Le lot est consommé séquentiellement; USAC conserve `isParallel=false`. |
|
||
| `track_builder.run` | CPU; CPU 1; lot 1; fixe `(4 Mio + arêtes * (48 + 2*160)) * facteur`, facteur 2 jusqu'à 400k arêtes puis 8; 0/item; I/O 1; GPU 0 | Rebuild atomique. La reprise valide le scope mais ne recalcule pas l'estimation avant admission. |
|
||
| `sparse_sfm.run` | CPU; CPU 1; lot 1; fixe `ceil_Mio(128 Mio + 64 Kio/image + 2 Kio/track + 512 octets/observation)`; 0/item; I/O 1; GPU 0 | Exécution atomique; BA à un thread. La forme est redérivée à la reprise sans réconciliation de l'estimation persistée. |
|
||
| `incremental_reconstruction.run` | CPU; CPU 1; lot 1; fixe `ceil_Mio(256 Mio + 128 Kio*(caméras base + images extension) + 4 Kio*(landmarks base + tracks extension) + 1 Kio*(observations base + extension))`; 0/item; I/O 1; GPU 0 | Exécution atomique; la forme est redérivée à la reprise sans réconciliation de l'estimation persistée. |
|
||
|
||
Ces écarts sont des constats d'implémentation Compute Governor v2. Ils ne
|
||
créent ni nouvelle limite scientifique, ni nouvelle identité, ni modification
|
||
du schéma Project DB. Une enveloppe privée de capacités peut corriger la
|
||
sélection/réconciliation sans modifier l'ABI C public du Task Kind Registry.
|
||
|
||
## SIFT v1A
|
||
|
||
Une extraction SIFT demande jusqu'à douze threads CPU, un slot IO, aucun GPU,
|
||
pour une image. L'estimation structurelle conservatrice est environ 1,06 Gio
|
||
(décodage, pyramides, candidats et F32×128), lot 1, pic de record batch zéro.
|
||
Le callback applique exactement le contrat admis dans `1..12` au pool OpenCV
|
||
process-wide sous l'unique propriétaire Queue, puis restaure la valeur
|
||
précédente sur toutes les sorties. Les tests déterministes ORB, SIFT et
|
||
RootSIFT couvrent 1/2/4/8/12 et obtiennent les mêmes keypoints, descripteurs et
|
||
métriques. Cette dimension opérationnelle ne change ni fingerprint ni Feature
|
||
Set.
|
||
|
||
## Responsabilité
|
||
|
||
Le Resource Governor est l'unique propriétaire des budgets (RAM, GPU, CPU,
|
||
IO). Il arbitre les ressources disponibles et calcule les lots adaptatifs pour
|
||
chaque tâche.
|
||
|
||
Le profil interactif par défaut conserve un quart de la RAM détectée et un
|
||
quart des threads logiques pour le système hôte. Sur 16 Gio/16 threads, cela
|
||
donne environ 3,8 Gio et 4 threads de headroom. Une nouvelle admission attend
|
||
également lorsque PSI CPU `some avg10` atteint 20 %, ou PSI mémoire 1 %. Ces
|
||
signaux n'interrompent jamais le petit job déjà réservé. Ces valeurs décrivent
|
||
le profil Gate G core gelé; la cible opérationnelle v2 active est le couple
|
||
3 GiB/2 GiB et les signaux différentiels définis en tête de document.
|
||
|
||
La soft floor vaut un quart et la hard floor un huitième de la RAM détectée. La
|
||
soft floor place le Governor au minimum en YELLOW ; la hard floor le place
|
||
immédiatement en RED. Le premier delta swap entre deux snapshots produit
|
||
YELLOW. Un second delta consécutif produit RED. Le premier snapshot ne constitue
|
||
qu'une baseline et n'est jamais interprété comme une activité récente.
|
||
|
||
La récupération interdit `RED → GREEN` : trois observations saines produisent
|
||
RED vers YELLOW, puis trois autres YELLOW vers GREEN. Le plafond reste 1 pendant
|
||
ces phases. Une fois GREEN, chaque groupe de trois observations saines double
|
||
le plafond : 1, 2, 4, 8, puis les paliers supérieurs utiles aux autres kinds.
|
||
|
||
## Contrat Gate G gelé
|
||
|
||
**PASS / FROZEN.** Les constantes existantes ci-dessus
|
||
restent inchangées. La RAM disponible conserve le modèle conservateur
|
||
`min(MemAvailable, RAM physique) - réserve hôte - réservations actives`, borné à
|
||
zéro. Le double comptage conservateur possible d'une allocation déjà visible
|
||
dans `MemAvailable` est accepté : un faux `WAIT` est préféré à un overcommit.
|
||
|
||
Les snapshots de production emploient `CLOCK_MONOTONIC` et sont valides jusqu'à
|
||
un âge exact de 1000 ms inclus. Un snapshot plus ancien ou daté dans le futur
|
||
produit `WAIT`, sans réservation ni mutation de l'état de politique du
|
||
Governor. La capture synchrone complète impossible reste une erreur
|
||
opérationnelle qui fait échouer la tâche avant callback. Une télémétrie PSI ou
|
||
vmstat optionnelle absente reste inconnue et ne crée aucune pression fictive.
|
||
|
||
Gate G core cible un processus Linux natif non contraint et n'est pas cgroup,
|
||
systemd `MemoryMax` ou RLIMIT-aware. Il gouverne un seul GPU : le périphérique
|
||
DRM de plus petit numéro retenu par Hardware Profile. La capacité et l'usage
|
||
doivent provenir de ce même périphérique. La mémoire UMA est débitée exactement
|
||
une fois du budget RAM. Le multi-GPU est différé.
|
||
|
||
Le Governor ne garantit aucune allocation et ne transforme ni swap, ni zram,
|
||
ni stockage externe en RAM. Il ne modifie aucun paramètre scientifique. Aucun
|
||
scratch, historique RSS long terme, redimensionnement de réservation depuis le
|
||
RSS ou monitoring live n'appartient à Gate G core. L'observation courante
|
||
bornée RSS/HWM de Compute Governor v2 reste strictement diagnostique.
|
||
|
||
## API principale
|
||
|
||
### Création et destruction
|
||
- `lardon3d_resource_governor_create()` - Créer un gouverneur
|
||
- `lardon3d_resource_governor_destroy()` - Détruire un gouverneur
|
||
|
||
### Configuration
|
||
- `lardon3d_resource_governor_set_policy()` - Définir la politique
|
||
|
||
### Décision
|
||
- `lardon3d_resource_governor_decide()` - Décider de l'admission d'une tâche
|
||
|
||
### Réservation
|
||
- `lardon3d_resource_governor_reserve()` - Réserver des ressources
|
||
- `lardon3d_resource_governor_reserve_available()` - Réserver les ressources disponibles
|
||
- `lardon3d_resource_governor_release()` - Libérer une réservation
|
||
- `lardon3d_resource_governor_reservation_is_valid()` - Vérifier la validité
|
||
|
||
### Métriques
|
||
- `lardon3d_resource_governor_availability()` - Obtenir la disponibilité
|
||
- `lardon3d_resource_governor_record_batch()` - Enregistrer les métriques d'un lot
|
||
- `lardon3d_resource_governor_generation()` - Obtenir la génération actuelle
|
||
- `lardon3d_resource_governor_wait_for_change()` - Attendre un changement
|
||
- `lardon3d_resource_governor_pressure()` - Lire GREEN, YELLOW ou RED
|
||
|
||
## Invariants
|
||
|
||
1. Le scheduler ne décide jamais des ressources
|
||
2. Le Resource Governor est l'unique propriétaire des budgets
|
||
3. Les réservations sont obligatoires avant toute exécution
|
||
4. Les réservations sont libérées exactement une fois
|
||
5. Les estimations de ressources sont immuables
|
||
6. L'historique des métriques est strictement borné (8 entrées par classe)
|
||
7. Un contrat de séquence est immutable jusqu'à sa libération; seule la
|
||
séquence suivante peut être adaptée
|
||
|
||
## Cycle de vie
|
||
|
||
```text
|
||
1. Capture d'un snapshot de ressources
|
||
2. Décision d'admission
|
||
3. Réservation opaque
|
||
4. Exécution de la tâche
|
||
5. Enregistrement des métriques
|
||
6. Libération de la réservation
|
||
```
|
||
|
||
## Adaptation dynamique
|
||
|
||
### Calcul de lots adaptatifs
|
||
- Basé sur la consommation mémoire historique
|
||
- Mise à jour à chaque exécution
|
||
- Conservative (sous-estimation plutôt que sur-estimation)
|
||
|
||
### Historique borné
|
||
- 8 entrées par classe de tâche
|
||
- Buffer circulaire
|
||
- Mise à jour FIFO
|
||
|
||
## Réserves
|
||
|
||
- Sous-estimation temporaire possible avec des estimations statiques
|
||
- L'adaptation de débit v2 reste bornée au dernier état par kind/backend ; elle
|
||
ne persiste ni historique volumineux ni décision matérielle.
|
||
- L'import `import.images` est admis avec 128 Kio fixes, un coût borné par item,
|
||
un thread CPU, un slot I/O et des lots de 1 à 32. Il enregistre le nombre
|
||
d'images logiques nouvellement enregistrées dans le ScanSet et la durée
|
||
réelle du lot.
|
||
Cela inclut une copie orpheline identique adoptée, même si aucun octet n'est
|
||
recopié. `peak_memory_bytes == 0` signifie explicitement « mesure inconnue » :
|
||
l'échantillon peut conserver taille/durée mais n'alimente jamais l'adaptation
|
||
mémoire.
|
||
- Pas de communication inter-classes de tâches
|
||
- `features.extract` réserve un lot de 1, demande jusqu'à douze threads CPU et
|
||
un slot I/O, avec
|
||
64 Mio fixes et 512 Mio par image. Cette estimation conservatrice couvre le
|
||
chemin actuel sans prétendre mesurer les allocations internes d'OpenCV.
|
||
l'admission choisit 1..`min(12, compute_pool)` et le callback applique ce
|
||
nombre immutable au pool OpenCV. `record_batch` couvre la validation source,
|
||
le décodage, ORB, la publication
|
||
et la finalisation DB ; `peak_memory_bytes == 0` signifie « mesure inconnue ».
|
||
- `visual_index.update` demande jusqu'à douze threads CPU, un slot I/O, 8 Mio
|
||
fixes et 2 Mio par Feature Set, par lots de 1 à 16. Le GPU vaut zéro. Le
|
||
callback compte comme participant et crée au plus `cpu_threads - 1` enfants,
|
||
tous joints avant publication et rupture de séquence. Chaque participant
|
||
possède au plus un reader/FD Feature File ; les tranches de postings privées
|
||
partitionnent le buffer borné du segment. `record_batch` compte uniquement
|
||
les memberships commités et conserve la mémoire inconnue à zéro.
|
||
- `candidate_pair.generate` demande jusqu'à douze threads CPU, un slot I/O,
|
||
256 Kio fixes et 64 Kio par Feature Set, par lots de 1 à 64. Le GPU vaut
|
||
zéro. La reconstruction reconnaît uniquement l'ancienne estimation exacte
|
||
(128 Kio fixes, 64 Kio par item, un thread CPU, un slot I/O, aucun GPU, lots
|
||
1 à 64, classe CPU) et la normalise éphémèrement vers l'estimation courante
|
||
complète ; aucun checkpoint d'estimation seule n'est publié et une forme
|
||
voisine n'est jamais réinterprétée comme legacy.
|
||
`record_batch` compte le nombre de paires générées par séquence et la durée
|
||
réelle du lot ; `peak_memory_bytes == 0` signifie « mesure inconnue ».
|
||
Chaque séquence interroge le Visual Index pour jusqu'à 64 memberships. Le
|
||
calcul emploie des fenêtres internes d'au plus deux sources par thread admis
|
||
et 24 sources au total ; le propriétaire de Task persiste ensuite seul et en
|
||
ordre canonique. Cette estimation opérationnelle ne limite pas la taille
|
||
scientifique du dataset.
|
||
- `matcher.run` demande jusqu'à douze threads CPU, un slot IO et 10 Mio par
|
||
Candidate Pair admise, correspondant au working set contrôlé inférieur à
|
||
environ 10 Mio par paire au
|
||
maximum SIFT/RootSIFT (8 Mio de descripteurs contigus, KNN `k=2`, sorties et
|
||
fichier bornés), hors scratch interne OpenCV. Ses lots CPU restent bornés à
|
||
1..12 Candidate Pairs; ORB Vulkan AUTO normal est borné à 1..8.
|
||
**PASS / FROZEN — P4 / Governor v2 :** une fenêtre
|
||
contient au plus deux paires par thread CPU effectivement admis et douze
|
||
paires au total. Le callback Queue est un participant, crée au plus
|
||
`cpu_threads - 1` enfants et les joint avant publication et libération de la
|
||
réservation. Chaque paire conserve ses buffers dans un stage privé jusqu'à
|
||
sa publication ordonnée ou son nettoyage ; OpenCV reste à un thread interne
|
||
pour éviter une sursouscription imbriquée. Le Governor réserve donc au plus
|
||
120 Mio contrôlés pour un lot de douze ; cette borne opérationnelle ne limite
|
||
pas la cardinalité scientifique du dataset.
|
||
Une Task ORB normale nouvelle persiste la classe honnête `MIXED` avec les
|
||
champs opérationnels CPU12/GPU0, lot 1..12 et 10 Mio par paire : cette classe
|
||
signifie que la politique AUTO peut exécuter une séquence CPU ou Vulkan, pas
|
||
qu'elle réserve les deux simultanément et pas un tag de backend. L'override
|
||
CPU explicite persiste la même forme de ressources en classe `CPU`.
|
||
La signature durable ORB Vulkan courante demande CPU1/GPU1, lot 1..12 et
|
||
640 Kio; son maximum 12 reste une sûreté durable/benchmark, pas le plafond
|
||
utile AUTO. L'enveloppe AUTO normale borne le lot à 8 et fige inflight 1; sur
|
||
UMA ce payload est
|
||
débité une seule fois du budget RAM. Le backend est créé sans payload mappé,
|
||
initialise et conserve exactement 640 Kio pour depth 1. La couture privée de
|
||
sûreté/benchmark peut porter 1,25 Mio pendant un contrat forcé depth 2. Il ne
|
||
redimensionne jamais avec une
|
||
requête pending; la fin de séquence libère le second slot avant la prochaine
|
||
admission depth 1. Une croissance échouée restaure la capacité antérieure,
|
||
et `backend_info` rapporte le payload réellement retenu, non le maximum de
|
||
l'enveloppe. Les anciennes
|
||
formes CPU8/GPU0 et CPU1/GPU1 à lot maximal 8, puis les formes plus anciennes
|
||
CPU12 à 10 Mio fixes, sont seulement des signatures exactes de reprise. Elles
|
||
sont normalisées éphémèrement vers la forme courante correspondante; toute
|
||
forme voisine est rejetée.
|
||
La production normale ORB reconstruit désormais une enveloppe privée
|
||
CPU/Vulkan et demande au Governor un choix `AUTO` avant chaque séquence. Une
|
||
fois admis, ce choix ne varie jamais dans la séquence. La nouvelle signature
|
||
`MIXED` reconstruit AUTO, y compris dans un build portable où son enveloppe
|
||
n'expose que CPU. Pour la compatibilité et la sûreté des overrides, toutes les
|
||
signatures ORB historiques/courantes de classe `CPU` reconstruisent un CPU
|
||
fixe ; une signature Vulkan reste fixe Vulkan. Aucun champ de ressource ne
|
||
sert de faux tag et aucun backend ou matériel n'entre dans l'identité
|
||
scientifique ou le payload Project DB.
|
||
La création et la reconstruction AUTO exposent une capacité Vulkan depuis
|
||
les seules métadonnées build/backend/GPU, sans appeler le driver ni
|
||
pré-dimensionner la RAM. Le Governor évalue ensuite l'enveloppe exacte contre
|
||
son snapshot MemAvailable/PSI/swap et sa charge UMA. Le premier begin et
|
||
donc toute initialisation se déroulent sur le worker Queue après application
|
||
de son affinité. La politique de cache Mesa est déjà établie au démarrage,
|
||
avant ce worker : aucun balayage post-init, latch de Task ou appel d'affinité
|
||
par TID auxiliaire n'existe. Une paire localement inéligible n'initialise
|
||
toujours pas le backend. Le diagnostic de séquence conserve séparément le
|
||
backend
|
||
sélectionné et le backend réel (`CPU`, `ORB_VULKAN` ou mix de paires
|
||
complètes), avec une raison de fallback. Les participants Matcher CPU sont
|
||
comptés uniquement dans `cpu_threads`; `helpers` reste zéro.
|
||
La soumission asynchrone est une couture privée de
|
||
`src/orb_vulkan_backend_internal.h`, absente de l'ABI public. Le backend
|
||
conserve deux slots maximum et un handle exact `slot+generation` par requête;
|
||
`finish` ne peut consommer que ce handle et les comptes soumis. Une génération
|
||
arrivée à `UINT64_MAX` ne boucle jamais : le slot est retiré avant toute
|
||
nouvelle soumission, de sorte qu'un ancien handle ne peut redevenir courant.
|
||
Toute
|
||
sortie/capacité invalide libère son slot, et
|
||
un échec d'attente de fence détruit/met en échec la session avant toute
|
||
nouvelle soumission. Les temporaires et objets pending sont nettoyés sur
|
||
erreur, exception C++, annulation et échec de publication. Une trace de test
|
||
bornée prouve sans temporisation, à depth 2, `SUBMIT(i) < SUBMIT(i+1) <
|
||
FINISH(i) < PUBLICATION_START(i) < PUBLICATION_FINISH(i)` pour deux paires
|
||
769×769, sans modifier leur sortie.
|
||
- `track_builder.run` réserve un worker CPU, aucun GPU et aucun fan-out GVR.
|
||
L'estimation est `4 MiB + raw_inlier_edges * (48 + 2*160)` avec facteur 2
|
||
jusqu'à 400000 arêtes inclusivement et facteur 8 au-delà, après vérification
|
||
d'overflow. Le
|
||
facteur élevé protège la transition mémoire observée à grande échelle ; le
|
||
Governor reste l'unique propriétaire de l'admission et de la pression.
|
||
|
||
## Limites actuelles
|
||
|
||
- Worker unique (pas de pools multiples)
|
||
- Pas de priorités entre tâches
|
||
- Pas de persistance des métriques
|
||
- Inflight Vulkan normal est fixé à 1, helpers GPU reste 0 et le lot AUTO
|
||
maximal utile est 8. Batch 12 et depth 2 demeurent des capacités privées de
|
||
sûreté/benchmark, chacune rejetée comme politique normale faute du gain de
|
||
débit durable de 5 %. La publication canonique reste owner-only et représente
|
||
environ 29,5 s dans les cohortes contrôlées; avec depth 2 sous 5 %, aucune
|
||
preuve ne justifie un helper supplémentaire face à cette frontière durable.
|
||
- Pas de communication avec d'autres gouverneurs
|
||
|
||
Les corrections dérivables G-D01 (`UINT64_MAX` est le dernier ID valide et la
|
||
création suivante échoue sans réservation ni charge comptable),
|
||
G-D02 (saturation des compteurs de streak) et G-D03 (identité DRM identique
|
||
entre capacité et usage) sont implémentées. Les sept décisions G-B01 à G-B07
|
||
sont gelées ; il ne reste aucune décision humaine Gate G.
|
||
|
||
## Statut
|
||
|
||
**GATE G — PASS / FROZEN.**
|