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
23 KiB
Matcher v1 et Match Store v1
Statut opérationnel
INTERNAL_PARALLELISM_COMPUTE_RESOURCES_V1 — PASS / FROZEN. COMPUTE_GOVERNOR_V2 — PASS / FROZEN. ORB_VULKAN_ASYNC_EXECUTION — PASS / FROZEN.
Contrat
Le pipeline v1 est ORB → BFMatcher Hamming ou SIFT/RootSIFT → BFMatcher L2,
puis KNN k=2, Lowe ratio, tri canonique, Match File content-addressed et Match
Result. Il s'arrête avant toute vérification géométrique.
Pour chaque descripteur A, OpenCV fournit zéro, un ou deux voisins. Zéro ne
produit rien. Un seul voisin valide est accepté. Avec deux voisins, le premier
est accepté exactement si d1 < threshold * d2; une seconde distance nulle,
une égalité, NaN, Inf ou une distance négative est rejetée. -0.0 est accepté
comme zéro conformément à IEEE-754. Indices invalides et
distances du premier voisin non finies ou négatives sont rejetés.
Un feature_index_a produit donc au maximum une correspondance acceptée.
Sans cross-check, plusieurs indices A peuvent viser le même index B. L'ordre
final est (feature_index_a, distance, feature_index_b) croissant. BFMatcher
fournit une liste KNN par query et le filtre ne conserve que son premier voisin :
la déduplication est donc mathématiquement inutile. Le Matcher rejette toute
sortie anormale contenant deux fois le même index A.
Match File v1
Header fixe de 32 octets, tous les entiers en little-endian :
0..3 octets ASCII exacts "L3DM"
4 format_version = 1
5 descriptor_type (1=U8, 2=F32)
6..7 reserved = 0
8..11 match_count uint32
12..15 descriptor_dimension uint32 (32 ou 128)
16..23 feature_set_id_a uint64
24..31 feature_set_id_b uint64
Chaque entrée fait 12 octets : index A uint32, index B uint32, distance float32.
Le format accepte zéro entrée pour tester reader/writer, mais le Matcher ne
publie pas d'asset vide. La borne est 8192 entrées et la taille maximale exacte
est 32 + 8192 * 12 = 98336 octets, environ 96 Kio.
Le reader valide avant toute allocation : magic physique, version, reserved, type/dimension, borne du compte, calcul de taille, taille physique exacte, Feature Set IDs A/B sans permutation, indices contre les comptes attendus et distance finie non négative. Fichiers tronqués, trailing bytes et versions futures sont rejetés.
Publication et reuse
Pour NO_MATCH, le fichier temporaire est supprimé et seule une ligne sans
asset est créée. Pour MATCHED : temp dans assets/matches, écriture complète,
fsync du fichier, SHA-256, chemin assets/matches/<préfixe>/<sha256>, puis
publication atomique par link. Une race de même contenu est validée; un
contenu différent déjà présent n'est jamais écrasé lors d'une publication
fraîche. Lorsqu'une ligne existante prouve que son asset content-addressed est
corrompu, le Matcher recalcule puis remplace atomiquement cet asset par rename
et répare les métadonnées de la même ligne sous transaction. Le temp est nettoyé
et le répertoire final synchronisé.
Le reuse MATCHED exige métadonnées complètes, fichier régulier non symbolique,
taille, SHA-256, header, type/dimension, IDs A/B, compte et entrées valides. Il
n'existe aucun fallback faisant confiance au chemin ou à l'existence seuls.
La validation de reuse lit le fichier borné une seule fois et calcule le SHA-256
sur ce même buffer.
Bornes mémoire et performance
Les deux buffers de descripteurs contigus occupent au maximum 512 Kio pour ORB
ou 8 Mio pour SIFT/RootSIFT. Les résultats KNN ne contiennent que deux DMatch
par query, les matches filtrés et le Match File sont chacun bornés à environ
96 Kio. Le working set contrôlé du Matcher est donc inférieur à environ 10 Mio,
hors scratch interne borné par OpenCV; aucune structure A × B n'est
matérialisée.
Le Match File complet est sérialisé dans un buffer heap borné à 98336 octets et
écrit par un unique write_exact, puis synchronisé une fois. Les mesures locales
restent dans le rapport de session, pas dans ce contrat canonique.
À 8192 features, le coût CPU dominant reste l'évaluation exacte des distances
dans cv::BFMatcher::knnMatch. ORB peut remplacer ce seul hot path par Vulkan.
Une feasibility réelle a rejeté Vulkan pour SIFT et RootSIFT : leur accumulation
flottante ne garantit pas le top-2 OpenCV sur les égalités adversariales et le
gain n'est présent que sur les grandes paires carrées. Ils restent intégralement
sur BFMatcher CPU.
INTERNAL_PARALLELISM_COMPUTE_RESOURCES_V1 — PASS / FROZEN. matcher.run v1
orchestre le Matcher sans connaître son backend interne. La
tâche persiste uniquement la configuration, l'identité des Feature Sets à
sélectionner et un curseur Candidate Pair. Une paire est atomique et publiée
immédiatement. Les lots bornés sont séparés par checkpoint et
task_sequence_break(). La tâche utilise l'unique Resource Governor avec une
estimation couvrant ce working set ; aucune seconde logique de budget n'est
introduite. Dans un lot, des fenêtres bornées calculent au plus deux paires par
thread CPU effectivement admis, avec un plafond opérationnel validé de douze.
Le callback Queue
compte comme un participant, joint au plus cpu_threads - 1 enfants, puis
publie seul les stages en ordre candidate_pair_id. OpenCV reste à un thread
interne pendant cette séquence, ce qui interdit un fan-out BFMatcher imbriqué.
Le curseur ne suit que le préfixe contigu durable ; le premier échec scelle tout
le suffixe calculé. Sa ligne durable matcher_tasks appartient au schéma
Project DB v11 ; le Match Result reste le contrat publié en v10.
Compute Governor v2 fait évoluer seulement le choix opérationnel et la
télémétrie de cette exécution gelée. Un contrat de séquence admis est immutable
jusqu'à libération ; seuls la séquence suivante et son lot peuvent être
adaptés. Admission CPU et admission de lot sont indépendantes : CPU12 avec un
lot de 1 est valide et ne permet pas d'emprunter une paire à la séquence
suivante. Sous un signal de pression qui autorise encore une admission, la
capacité CPU réductible abandonne l'essai et réserve CPU1/lot minimum dans cette
même décision ; un seuil Gate G qui produit WAIT reste sans réservation.
Project DB v12 ajoute un enfant Geometric Verification Result référencé par
match_result_id. Le Matcher ne calcule, ne stocke et ne valide aucun inlier
géométrique ; seul son Match File canonique définit l'ordre indexé par le masque.
Backend Vulkan ORB
La frontière évaluée remplace uniquement KNN Hamming par un compute top-2 : un thread GPU par feature A parcourt B, conserve deux indices/distances et applique le tie-break du plus petit index. Elle ne matérialise jamais A×B. Lowe, canonicalisation et persistance restent communs. Sur Radeon 780M, la parité top-2 avec OpenCV est exacte et le gain warm est supérieur à 90 % à 4096/8192. Le backend de production conserve exactement cette frontière. Sa parité entière permet au CPU et à Vulkan de partager l'identité persistante. L'évidence directe de la tranche v2 établit ORB Vulkan comme déterministe, exact et mesurément supérieur pour ce hot path : il est le workload GPU primaire de la politique canonique GPU-first. Le CPU reste le fallback portable et de panne.
Les API normales créent AUTO. Pour ORB, elles exposent Vulkan depuis les faits
build, objet backend, GPU et enveloppe UMA sûre ; création et reprise ne sondent
ni n'initialisent le driver sur le caller/main. Le Governor essaie cette
capacité avant le CPU complet, puis le premier begin initialise Vulkan sur le
worker Queue déjà contraint. Avant ce worker, le démarrage a établi la politique
sûre MESA_SHADER_CACHE_DISABLE=true; elle supprime sur la 780M validée les
helpers de cache Mesa qui élargissaient leur masque. Une paire sous le seuil
n'initialise toujours pas Vulkan. Aucun sweep/latch Matcher ni appel
sched_*affinity(tid) auxiliaire n'existe ; le creator/main/TUI reste
inchangé. La frontière d'initialisation du backend vérifie elle-même, sans
setenv, que MESA_SHADER_CACHE_DISABLE vaut exactement true ou 1 avant
tout appel Mesa. Un consumer public tardif avec une valeur absente, fausse ou
malformée reçoit UNAVAILABLE, contexte mémorisé indisponible et aucune sortie
partielle ; la metadata non initialisante reste lisible. Les variantes *_with_mode
demandent encore explicitement CPU ou ORB Vulkan et conservent leurs erreurs
historiques. Le mode Vulkan demande CPU1, GPU1 et 640 Kio GPU/UMA, débités
exactement une fois de la RAM hôte sur la 780M. Un échec Vulkan pendant cette
séquence reprend la paire top-2 CPU complète sans changer fingerprint ni
résultat. Une panne d'initialisation retire Vulkan des admissions AUTO
suivantes ; une paire sous le seuil validé reste seulement inéligible. Le
rolling conserve explicitement l'état de soumission de chaque paire : une paire
sans handle exécute directement son stage CPU complet et n'appelle jamais
finish(NULL). Toute panne backend est publiée au Governor dès son observation,
avant le fallback, l'annulation ou la publication qui peuvent encore échouer ;
l'inéligibilité locale ne modifie jamais cette santé partagée et n'est jamais
réétiquetée par l'échec d'une soumission ou d'une finition voisine. Le
contrat détaillé est décrit dans
vulkan_matcher.md.
Les coutures privées renvoient séparément le résultat Matcher et la faute
backend. Lecture/allocation locale avant submit, puis filtrage/allocation ou
staging Match File après un finish réussi, produisent un fallback CPU complet
classé other. Elles ne désactivent pas Vulkan et ne jettent pas un successeur
soumis sain. Seul l'échec de la transaction backend exacte produit
backend-failure et invalide les slots partagés.
La production normale est AUTO, choisi par l'unique Governor avant chaque
séquence depuis une enveloppe privée CPU/Vulkan. Les modes explicites
CPU/Vulkan sont des overrides de debug, benchmark et reproductibilité. Le
contrat installé est immutable jusqu'au prochain sequence_break. Cette
couture et ses dimensions retenues sont PASS / FROZEN.
Le CPU Matcher compte ses participants uniquement dans cpu_threads=1..12;
helpers=0 tant qu'aucun helper GPU distinct n'est réellement admis. Le
diagnostic borné conserve le backend de capacité sélectionné et le backend réel
de la séquence (CPU, ORB_VULKAN ou plusieurs paires complètes des deux),
avec la raison d'inéligibilité ou de fallback. Ce retour opérationnel ne change
jamais l'identité ou le contenu scientifique.
La couture backend privée expose un snapshot cumulatif thread-safe :
soumissions, complétions, temps CPU de submit/dispatch, attente de fence,
readback, temps GPU issu des timestamps lorsqu'ils existent, intervalle entre
une complétion observée et la soumission suivante, pannes, discards et nombre
de slots actifs. Les compteurs saturent à UINT64_MAX au lieu de boucler. Matcher en
dérive par séquence les soumissions/complétions, temps GPU/fence/starvation,
travail CPU de fallback ou de staging et durée de publication durable. Ces
chronométrages n'ajoutent ni worker, ni attente artificielle, ni changement
d'ordre. Une séquence réellement fallback ne forme pas un échantillon de débit
Vulkan pur.
Une nouvelle Task ORB normale persiste une signature MIXED avec les champs
CPU12/GPU0 réels ; elle exprime honnêtement que la politique AUTO peut choisir
CPU ou Vulkan par séquence, sans réserver les deux et sans coder un backend
factice. L'override CPU conserve la classe CPU. La forme Vulkan explicite est
CPU1/GPU1/640 Kio. Toutes sont à lot 1..12 et 10 Mio par paire. Les signatures historiques
CPU8/GPU0 et CPU1/GPU1 à lot maximal 8, ainsi que les formes CPU12
pré-estimation-par-paire, restent reconnues uniquement pour la reprise et sont
normalisées éphémèrement sans checkpoint d'estimation seule. Une forme voisine
est rejetée ; aucun champ isolé ne sert à deviner le backend. Seule la nouvelle
classe MIXED reconstruit AUTO. Toute forme CPU ORB historique ou courante
reconstruit CPU fixe par compatibilité/sûreté des overrides ; Vulkan reste fixe.
Le build portable conserve la signature AUTO mais son enveloppe n'expose que CPU.
Seule une reconstruction MIXED/AUTO établit la disponibilité runtime partagée ;
la co-restauration ultérieure de formes CPU, Vulkan ou historiques fixes ne la
modifie pas. L'ordre de restauration ne peut donc pas déclasser une AUTO avant
son admission.
L'évolution Compute Governor v2 n'ajoute aucune dimension de ressources au
fingerprint. begin/finish/discard sont privés à
src/orb_vulkan_backend_internal.h et absents du header/ABI public. begin
transfère au backend les comptes exacts de la requête et un handle
slot+generation; finish utilise ces comptes, borne FEATURE_MAX et ne
consomme que ce slot sur tout résultat, même une capacité de sortie invalide.
discard attend sa fence ; une attente en échec
condamne/détruit la session avant une soumission suivante. Le wrapper public
synchrone top-2 reste stable et s'implémente par cette couture. Les objets
pending, fds et temporaires utilisent un nettoyage structuré, les chemins
tronqués sont refusés et aucune exception C++ ne franchit C.
Le propriétaire Queue conserve seul Lowe, canonicalisation, publication et
avancement du curseur. Device/pipeline/layout/cache sont partagés; deux slots
maximum dupliquent seulement command/fence/descriptors/buffers/query. La trace
déterministe prouve à depth 2, pour deux paires successives 769×769,
GPU_SUBMIT(i) < GPU_SUBMIT(i+1) < GPU_FINISH(i) < PUBLICATION_START(i) < PUBLICATION_FINISH(i), avec cardinalité/sortie intactes.
Le payload mappé suit le contrat de séquence plutôt que le maximum de
l'enveloppe. Un backend frais retient zéro, depth 1 retient exactement 640 Kio
et depth 2 retient 1,25 Mio jusqu'au nettoyage de la séquence; le second slot est
libéré avant la prochaine admission depth 1. Le redimensionnement est interdit
avec une requête pending et une croissance échouée conserve la capacité
antérieure. backend_info rapporte la rétention réelle. Une génération arrivée
à UINT64_MAX retire définitivement son slot avant tout nouveau submit; elle ne
boucle jamais vers un ancien handle.
Elle couvre aussi échec du begin successeur, faute locale avant/après backend,
échec de publication, annulation et réutilisation du slot. Le lifecycle est
ORB_VULKAN_ASYNC_EXECUTION — PASS / FROZEN et helpers reste 0.
Le runner réel opt-in utilise désormais AUTO par défaut ; cpu et vulkan
restent des overrides explicites de debug/benchmark/reproductibilité. Son
pipeline normal est le rolling Governor-owned inflight 1. Un
contrôle synchronous force depth 1 et est
compilé uniquement dans ce runner et le test Matcher dédié : pour une capacité
GPU sélectionnée, chaque paire passe par le wrapper public top-2 synchrone,
puis par la même canonicalisation et la même publication avant la paire
suivante. Ce contrôle historique de mesure n'existe pas dans le binaire
lardon3d, ne répond à aucun environnement en production et ne devient ni
identité, ni checkpoint, ni capacité Governor. Il est refusé pour CPU explicite
et pour la reprise d'une Task Matcher pendante, car son choix n'est pas
persisté. Les fixtures Vulkan comparent rolling depth 1 forcé, rolling depth 2
forcé et synchrone depth 1 sur des Projects neufs et obtiennent les mêmes champs
scientifiques, SHA et tailles d'assets.
Pour isoler la profondeur du reste de la boucle adaptative, ce même runner
accepte en contexte --resume-pre-gv-existing neuf le contrôle privé
--matcher-inflight 1|2. Il est limité à AUTO; rolling force une capacité
Vulkan unique et fixe min=max à la valeur demandée, avec un lot fixe de deux
par défaut. Le contrôle additionnel --matcher-batch 2|4|8|12 exige inflight,
AUTO et rolling, puis fixe aussi min=max batch pour une matrice reproductible.
Le Governor choisit et réserve toujours cette capacité depuis son snapshot
courant, y compris 640 Kio par slot et la charge UMA exacte; le contrôle ne
court-circuite aucune admission et n'expose aucune alternative CPU. GPU budget
zéro est refusé avant accès au Project; une capacité GPU/backend/mémoire non
admissible invalide l'expérience au lieu d'exécuter AUTO CPU. Synchronous reste
depth 1 et refuse la valeur 2. La valeur est absente du binaire lardon3d, de l'ABI, du payload, du
checkpoint et de l'identité scientifique; le runner la restaure dans
l'environnement sur toute sortie, la refuse sur une Task pendante et l'émet
comme 1, 2 ou null dans le JSON d'évidence; batch est pareillement émis
comme 2, 4, 8, 12 ou null. L'agrégat exige uniquement des contrats
sélectionnés Vulkan batch/depth demandés, réservation hôte et payload exacts,
zéro panne,
discard ou slot pending. Il accepte seulement les fallbacks CPU complets dus à
l'inéligibilité locale. Matcher compte chaque item une seule fois, après sa
publication CPU durable, dans les classes local-ineligible/backend-failure/other;
une séquence peut en contenir plusieurs et le CPU sélectionné normalement
n'entre dans aucune classe. Le commit par item est séparé du feedback de débit
de fin de séquence : une annulation ou panne ultérieure conserve le préfixe
durable, mais n'entraîne jamais la séquence avortée. La déduplication bornée est
éphémère à la Task et n'ajoute aucun état de reprise. L'agrégat conserve aussi
les comptes de séquences
pour diagnostic, mais l'égalité inter-cohortes porte exclusivement sur les
items localement inéligibles; tout item backend-failure/other donne
experiment_valid=false. Une panne backend tardive peut conserver la preuve
CPU déjà publiée, mais la Task de benchmark échoue après son checkpoint et la
cohorte ne peut pas être déclarée réussie. La mesure forcée ABBA donne
54,661652238 paires/s à depth 1 et 55,797311953 à depth 2 (+2,077617 %), sous
le deadband 5 %. Pour 4113 paires durables par run, le débit de cohorte est
(2 * 4113 * 1e9) / somme(wall_ns), pas la moyenne des débits par run. Les
walls bruts 75326831673/75162582080 et 73662096698/73764360098 ns donnent les
moyennes 75,244706877/73,713228398 s. Fence vaut 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 %.
Chaque bras A/B garde le digest
7a9dbc38a23a600379167d55e24836b7acbb22eea25573e7440bdc9e4602b3b3, quatre
séquences de fallback local par exécution et zéro panne/discard. Depth 2 reste donc validé pour la
sûreté privée (DEPTH_MAX_VALIDATED_SAFETY=2) mais est
REJECTED_WITH_MEASURED_REASON pour la politique normale
(DEPTH_MAX_USEFUL=1). Le corpus établit par ailleurs +10,27 % pour rolling
depth 1 face au contrôle synchrone, avec le même digest. La matrice de batch
runner-only item-valide donne, pour batch 2/4/8/12, les walls A/B bruts
76150272845/75674818393, 61319835797/63138565155,
55148130595/54847191200 et 53446248173/53724786321 ns. La même formule de débit
combiné donne 54,180767704, 66,094373197, 74,784998723 et 76,755814095 paires/s,
soit +21,988624373 %, +13,148812987 % puis +2,635308425 %. Chaque run conserve
4113 paires durables, six items locaux, zéro item backend-failure/other et le
même digest ci-dessus. Batch 12 est donc
REJECTED_WITH_MEASURED_REASON sous le deadband 5 % : la sûreté privée reste
BATCH_MAX_VALIDATED_SAFETY=12, mais AUTO normal suit
BATCH_MAX_USEFUL=8. Les fichiers sont
forced-batch{2,4,8,12}-items{,-b}.stdout.jsonl. Les anciens
forced-batch2-current.stdout.jsonl et forced-batch4.stdout.jsonl restent
historiques : leur comparateur par séquences était invalide et ne fonde pas la
décision.
Le run normal sans option backend/lot/inflight
short-auto-batch8-governor-v2.stdout.jsonl suit effectivement
1 → 2 → 4 → 8, conserve batch 8 et publie 4113/4113 résultats en
54,066973393 s (76,072 paires/s). Il garde le même digest L3DMRD1, six items
locaux, zéro panne/discard, inflight 1 et helpers 0. Ce run est la preuve du
contrôle AUTO; les cohortes forcées ci-dessus restent la preuve de sélection de
l'enveloppe utile.
Le run S21 final normal
final-s21-auto.stdout.jsonl publie 172 741 Match Results pour 172 741
Candidate Pairs en 2 345,444485079 s, avec zéro mapping dupliqué, curseur
contigu/complet et digest L3DMRD1
e5128a2e599ff593c4f79850e067254b1f249d19e8480a44973306b1af250f70.
AUTO reste Vulkan/rolling; 172 507 soumissions ont 172 507 complétions, zéro
panne/discard/pending, tandis que 234 items localement inéligibles sont
recalculés comme paires CPU complètes. L'agrégat compte 21 550 séquences
Vulkan pures, 77 mixtes et 3 CPU, sans jamais publier d'évidence partielle.
La Task 2831 finit COMPLETE, progression 100 et sequence_count=21629;
le checkpoint final SHA-256 vaut
636f4f4a20f27308d90142c495c9f6ffc04b4c0dfcca0fdc75cfeb5366ab50b1.
Les quatre sources retenues sous
/home/fy59/Documents/Lardon/.real-pre-sfm-2026-08-30/governor-v2-evidence/
sont forced-depth1-a.stdout.jsonl, forced-depth1-b.stdout.jsonl,
forced-depth2-a.stdout.jsonl et forced-depth2-b.stdout.jsonl.
AUTO Vulkan normal adapte le lot seulement dans 1..8. Il exige huit
observations pures consécutives pour la référence et huit au palier d'essai,
puis accepte seulement un gain moyen d'au moins 5 %. Pression, fallback ou
travail durable nul abandonnent la fenêtre; un ou deux échantillons ne décident
rien. La durée entraînant AUTO couvre la réadmission réussie, le calcul, les
publications ordonnées et le checkpoint générique durable; la seule durée du
noyau Matcher reste diagnostique et ne décide pas le lot. Une séquence dont le
checkpoint échoue ne peut donc pas entraîner le contrat suivant. CPU Matcher
garde sa rampe et son maximum 12. Helpers reste 0 : la
publication owner-only vaut environ 29,5 s dans ces cohortes, et ni le gain
depth 2 sous 5 % ni une autre mesure ne prouve qu'un helper supplémentaire
surmonterait cette frontière de durabilité ordonnée.
Le même runner audite la bijection ordonnée Candidate Pair/Match Result, le
curseur final contigu et chaque Match File par SHA, taille, header et entrées.
Son digest L3DMRD1 sérialise explicitement en largeurs fixes les IDs
scientifiques, kind/version/fingerprint, status/cardinalité et SHA/taille
d'asset ; Task IDs, timestamps, chemins et sélection opérationnelle sont
exclus. Ce digest est une preuve de comparaison du harness, pas une nouvelle
identité Project DB ou scientifique.
Déterminisme et fingerprint
Le fingerprint couvre kind, version, k=2, threshold float32 et absence de
cross-check. À Feature Files, configuration, implémentation/version OpenCV et
environnement numérique compatibles identiques, SIFT/RootSIFT garantissent les
mêmes paires attendues, l'ordre et la sérialisation stables. Aucune promesse
bit-à-bit cross-platform n'est faite pour les distances L2 flottantes.
ORB/Hamming bénéficie d'une garantie plus forte grâce à sa distance entière.
NEXT: GEOMETRIC VERIFIER v3