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
22 KiB
Système de tâches (Task System)
Extraction précise v1A
features.extract.sift et features.extract.rootsift, version 1, possèdent
chacun leur lifecycle durable. Une tâche correspond à une image, est persistée
avant enqueue et reprend avec le même task_id. detectAndCompute n'est pas
interruptible : pause/cancel sont coopératifs à ses frontières et un crash
recommence uniquement cette image. ORB READY n'est jamais recalculé par SIFT.
Objectif et responsabilités
Le module task gère le cycle de vie complet des tâches de traitement dans
Lardon3D. Il définit les états, la progression, la pause coopérative,
l'annulation et les callbacks associés à chaque tâche.
Chaque tâche représente une unité de travail atomique : estimation des coûts, exécution sous réservation et publication d'un résultat validé.
Fichiers
include/lardon3d/task.h— types publics et APIsrc/task.c— implémentation
Types principaux
Les états réels sont TASK_PENDING, TASK_RUNNING, TASK_PAUSED,
TASK_CANCELLED, TASK_FAILED et TASK_COMPLETED. Une rupture de séquence est
une opération de réadmission, pas un état supplémentaire.
API publique
| Fonction | Description |
|---|---|
lardon3d_task_create() |
Alloue une tâche et copie son estimation |
lardon3d_task_destroy() |
Annule, attend puis libère la tâche |
lardon3d_task_start() |
Exécute sous réservation active |
lardon3d_task_pause() / resume() |
Contrôle la pause coopérative |
lardon3d_task_request_cancel() |
Demande l'annulation coopérative |
lardon3d_task_checkpoint() |
Frontière coopérative en mémoire |
lardon3d_task_sequence_break() |
Libère puis renouvelle la réservation |
lardon3d_task_snapshot() |
Copie l'état d'observation runtime |
API durable
| Fonction | Description |
|---|---|
lardon3d_task_durable_snapshot() |
Copie les champs durables sous mutex |
lardon3d_task_restore() |
Reconstruit une tâche sans état d'exécution vivant |
lardon3d_task_create_typed() |
Crée une tâche persistable avec kind/version immuables |
lardon3d_task_restore_typed() |
Restaure une tâche typée et transfère l'ownership du userdata |
lardon3d_task_checkpoint_save() |
Publie atomiquement un snapshot v1 |
lardon3d_task_checkpoint_load() |
Lit et valide un checkpoint borné |
lardon3d_task_checkpoint_stage() / promote_staged() |
Publie d'abord <path>.next, puis promeut sous le verrou par tâche |
Après rename, un échec du fsync du répertoire retourne
LARDON3D_TASK_CHECKPOINT_PUBLISHED_NOT_DURABLE : la publication est visible,
mais sa durabilité après crash n'est pas confirmée.
Publication et reprise projet
Le protocole générique par tâche emploie le chemin canonique
.lardon3d/checkpoints/<task_id>.chk, sa représentation staged .chk.next et
le verrou consultatif .chk.lock. Le verrou sérialise un writer et la reprise
sur le slot staged fixe ; il est détenu pour la séquence entière et sa fermeture
par le noyau après crash ne constitue pas un état durable.
L'ordre de publication est strictement : codec durable de .next, commit
SQLite du résumé de tâche et de la référence checkpoint, puis promotion de
.next vers le fichier canonique et durabilité du répertoire. DB et système de
fichiers ne forment pas une transaction unique : si la promotion échoue ou sa
durabilité est incertaine après le commit SQLite, .next reste la
représentation de récupération possible.
La reprise acquiert d'abord .lock, puis recharge la ligne SQLite : la page de
découverte peut être devenue périmée pendant l'attente. Elle valide le codec et
la version du canonique et compare seulement les champs effectivement stockés
dans le résumé DB — task_id, nom, états saved/recovery, progression et
compteur de séquences. Les timestamps et l'estimation complète ne sont pas
dupliqués par la DB et ne participent donc pas à ce test. Un canonique valide
dont ce résumé correspond est prioritaire ; une .next absente, périmée ou
corrompue est alors ignorée. Sinon, une .next valide qui correspond au même
résumé est promue sous le même verrou puis reprise. Un ancien projet v22 qui ne
possède que .chk reste ainsi récupérable. Toute autre absence, version non
supportée, corruption ou divergence interdit la reprise de cette tâche sans
affecter les autres entrées de l'inventaire.
Invariants
- Estimation durable immuable : l'estimation stockée dans la Task courante ne change jamais. Compute Governor v2 utilise une enveloppe privée de capacités, distincte de ce snapshot et non persistée comme identité. Le contrat choisi pour une séquence est lui aussi immutable jusqu'à sa libération ; seule une séquence suivante peut être adaptée.
- Transitions d'état validées : le cycle nominal est
PENDING → RUNNING → COMPLETED/FAILED/CANCELLED, avec pause coopérative. - Pause et annulation coopératives : le callback appelle périodiquement
lardon3d_task_checkpoint(). Aucun autre thread ne force son arrêt. - Progression bornée : la progression ne peut jamais dépasser la valeur maximale définie par l'estimation.
- Reprise réadmise : une tâche restaurée non terminale repasse par la file, le scheduler et le Resource Governor avec une nouvelle réservation.
- Snapshot court : seuls les champs durables sont copiés sous le mutex ; la sérialisation et les I/O ont lieu après déverrouillage.
Interactions
- task_queue : la file gère l'ordre d'exécution et invoque les callbacks.
- resource_governor : l'estimation est utilisée pour la réservation avant exécution.
- scheduler : ses responsabilités restent représentées par le runtime et l'unique Queue existants ; aucun second scheduler n'est introduit.
Frontière Compute Governor v2
COMPUTE_GOVERNOR_V2 — PASS / FROZEN. Chaque kind de production reste propriétaire d'une Task et passe par l'unique Queue à un worker, puis par l'unique Resource Governor, même lorsque CPU, lot, mémoire, I/O et GPU sont tous fixes. Une dimension fixe n'autorise jamais à contourner l'admission.
Le code conserve une Lardon3DResourceEstimate canonique immutable pour la
durabilité. Derrière les types opaques, chaque Task possède désormais une
enveloppe privée bornée : par défaut une capacité fixe exactement égale à cette
estimation ; seuls les kinds possédant des alternatives réelles en ajoutent.
Queue et sequence_break() demandent au Governor de choisir et réserver une
capacité depuis un seul snapshot courant. L'exécution reçoit ce contrat
immutable et ne le renégocie pas en cours de séquence. Cette couture n'étend ni
le descriptor public du Task Kind Registry, ni le checkpoint, ni le payload
Project DB et n'ajoute aucune dimension d'identité scientifique.
Le Governor possède aussi la politique CPU hôte privée. Depuis le masque
permis et la topologie package/core, il dérive un compute-pool par coeurs
physiques complets ; un caller déjà précontraint fournit directement son pool.
Sans topologie exploitable, le budget portable subsiste avec affinité inactive.
Seul le worker lourd de Queue applique et vérifie son propre masque (pid=0)
avant les callbacks, donc le creator/main/TUI reste libre. Le Governor ne mute
jamais un TID auxiliaire énuméré : un PIDFD_THREAD ne stabilise pas le numéro
TID consommé par sched_setaffinity(tid). Le démarrage établit plutôt
MESA_SHADER_CACHE_DISABLE=true avant tout pthread applicatif et toute
initialisation Vulkan. Une absence prend ce défaut sûr ; true/1 explicites
sont conservés, tandis qu'une valeur explicite fausse ou malformée est respectée
mais entraîne un refus de démarrage. Cette politique opérationnelle, sans état
Task durable ni identité scientifique, supprime les helpers de cache Mesa
observés qui élargissaient leur masque. Il n'existe plus de sweep post-init,
latch de Task ou retry de recontrainte auxiliaire ; les diagnostics indiquent
l'état et la raison de la politique sans prétendre une activité auxiliaire.
Le nombre du pool borne toute admission CPU. Cette couture
n'ajoute ni scheduler, ni worker, ni champ durable ou ABI publique.
Le comportement de production normal ORB est AUTO Governor-owned. Les modes
CPU/Vulkan explicites sont réservés au debug, benchmark et à la
reproductibilité. Une Task normale nouvelle persiste une classe MIXED
sémantiquement honnête ; cette signature reconstruit AUTO, tandis que toute
signature CPU ORB ancienne ou courante reconstruit un CPU fixe et que Vulkan
reste fixe. Création et reprise n'initialisent pas Vulkan sur le caller ; le
premier begin appartient au worker Queue contraint. Le choix matériel ne
devient ni payload ni identité. Seule une reprise AUTO établit la disponibilité
Vulkan partagée ; les reprises fixes et historiques sont sans effet global,
donc leur ordre ne dégrade pas AUTO. Une panne backend est publiée avant tout
fallback ou sortie précoce ; une inéligibilité de paire reste locale et ne
possède aucun handle à terminer. Le Governor conserve une télémétrie fixe et
bornée par kind/backend (dernière durée, travail durable, débit, backend
sélectionné/réel, contrat, pression et raison) et aucune grande histoire
persistante. L'adaptation générique utilise deux observations de référence et
deux du palier d'essai. Le lot ORB Vulkan, plus bruité, exige huit séquences
pures consécutives pour chacune de ces fenêtres avant toute décision ; une
observation Matcher mesure la cadence de bout en bout depuis la réadmission
réussie jusqu'au checkpoint générique durable, et non le seul callback de
calcul. Le temps de calcul seul reste diagnostique. Ainsi le Governor apprend
le coût de séquence amorti par le lot et un checkpoint échoué ne l'entraîne pas.
Une
admission adaptative encore permise sous pression installe
CPU1, lot minimum et inflight minimum avant sa réservation. Inflight ORB normal
reste fixé à 1; helpers reste 0. Les callbacks atomiques n'annoncent
un item que si extraction et publication propre sont durables ; READY,
ALREADY_PRESENT et PUBLISHED_NOT_DURABLE sont des observations zéro qui
n'avancent pas la rampe.
L'A/B forcé ABBA a mesuré seulement +2,077617 % à depth 2
(54,661652238 contre 55,797311953 paires/s), sous le deadband 5 %, avec digest identique,
quatre séquences de fallback local par exécution et zéro panne/discard. Depth 2 est donc
REJECTED_WITH_MEASURED_REASON pour AUTO normal. Compute Governor v2 reste
en cours jusqu'aux réconciliations restantes.
Pour Matcher Vulkan, le lease privé de capacité matérialise le contrat de la
séquence seulement après son admission. Il alloue un ou deux payloads de
640 Kio sans requête pending, les conserve jusqu'au nettoyage complet de la
séquence, puis rend depth 2 à depth 1 avant sequence_break(). Une allocation
depth 2 échouée ne modifie pas le contrat scientifique et conduit à la paire CPU
complète; elle ne laisse ni réservation suivante sous-facturée, ni résultat
Vulkan partiel.
Cette boucle est maintenant observe → choose → execute → measure → adapt next.
Les CPU réductibles progressent uniquement par 1/2/4/8/12, bornés par le
compute-pool ; CPU et lot ne sont jamais essayés ensemble. Les observations
hôte privées comprennent utilisation du pool, mémoire/PSI/swap actif, GPU busy
et RSS observé, sans confondre RSS et réservation. Le dernier diagnostic peut
être tiré par numéro de série ou formaté dans un buffer borné ; le runtime ne
l'imprime pas directement dans le TUI. Le contrat déjà installé demeure
immutable même si une nouvelle observation arrive pendant son exécution.
Un agrégat privé de taille fixe complète ce dernier diagnostic : il compte les
admissions et séquences effectivement enregistrées et somme leurs métriques en
saturant, afin qu'un poller lent ne transforme pas des changements coalescés en
fausse histoire exhaustive. Il vit uniquement avec le Governor courant.
Pour la matrice forcée du runner, cet agrégat sépare les fallbacks par
inéligibilité locale, panne backend et raison autre/inconnue, à la fois par
séquence diagnostique et par item exact. Le compte d'items avance une seule fois
après la publication durable du fallback CPU complet; le travail d'une
séquence sélectionnée CPU reste à zéro. Ce commit opérationnel immédiat survit à
l'échec ou l'annulation d'une paire suivante, mais ne crée ni séquence réussie,
ni débit durable, ni adaptation. Un high-water mark non persisté le rend
idempotent pendant la vie de la Task. La Task n'expose alors qu'une capacité
Vulkan aux batch/depth demandés : absence de GPU/backend/mémoire ne peut donc
pas devenir une admission CPU. Une panne backend tardive laisse la publication
CPU complète déjà durable mais fait échouer la Task de benchmark après
checkpoint; seul le compte d'items localement inéligibles, égal entre cohortes
comparées même lorsque leur batch diffère, reste admissible pour l'évidence.
Les anciens logs batch 2/4, à quatre contre trois séquences locales pour les
mêmes items/digest, sont préliminaires et ne déterminent aucun débit utile. La
matrice item-valide suivante mesure batch 2/4/8/12 à
54,180767704/66,094373197/74,784998723/76,755814095 paires/s. Les deux premiers
gains de palier dépassent le deadband, celui de 8 à 12 vaut seulement
+2,635308425 % : AUTO normal s'arrête à batch 8 et batch 12 reste un contrôle
privé sûr rejeté pour la politique normale.
Le run sans override short-auto-batch8-governor-v2.stdout.jsonl valide ensuite
la Task réelle : 1 → 2 → 4 → 8, 4113/4113 résultats, 76,072 paires/s, digest
identique, six items locaux et aucune panne/discard. La cadence de séquence
inclut réadmission et checkpoint durable; inflight reste 1 et helpers 0.
Le run S21 final crée ensuite la Task normale 2831 en mode AUTO, sans option
backend/lot/inflight. Ses 21 630 admissions sont toutes Vulkan; la Task publie
172 741/172 741 résultats, termine COMPLETE à 100 %, avec zéro doublon et
curseur complet. La seule admission YELLOW produit un contrat batch 1, puis
les admissions GREEN suivantes remontent de façon bornée jusqu'à batch 8. Le
contrat actif n'est jamais muté sous le callback : chaque changement appartient
à la séquence suivante.
Le runner d'évidence réel crée les nouvelles Tasks Matcher normales par l'API
AUTO. Son contrôle synchronous est un flag de contexte compilé seulement dans
le target benchmark/test : il ne modifie ni Task durable, ni envelope, ni
contrat installé, ni callback de production. Puisque ce flag n'est pas
checkpointé, le runner refuse expressément de l'appliquer à une reprise Matcher
pendante. Le chemin rolling/recovery normal reste inchangé.
Dans ce même target seulement, --matcher-inflight 1|2 reconstruit avant la
création d'une Task neuve une enveloppe AUTO Vulkan à profondeur fixe et batch
2 par défaut. --matcher-batch 2|4|8|12, valable seulement avec inflight et
rolling AUTO, fixe aussi le lot. Ces contrôles ne remplacent pas le Governor : l'admission, la mémoire
par slot, l'UMA et la réservation de séquence restent identiques. Le contrôle
est refusé pour les modes explicites, pour synchronous depth 2 et pour une Task
pendante. Une garde de processus restaure les tokens privés sur toute sortie;
aucun token, champ de contexte, payload ou comportement correspondant n'est
compilé dans lardon3d.
Statut
IMPLEMENTED — cycle de vie, pause/annulation coopératives, séquences adaptatives et fondation de checkpoints persistants isolés.
IMPLEMENTED — une registry statique peut reconstruire explicitement le callback et le userdata d'un kind connu. Le destructeur du userdata est détenu par la tâche restaurée et exécuté après arrêt de son exécution.
IMPLEMENTED — import.images utilise la pause, l'annulation, les ruptures
de séquence et les checkpoints génériques ; une tâche restaurée conserve son ID
lors de sa soumission explicite à la file.
IMPLEMENTED — resoumission automatique sélective des tâches production à l'ouverture du projet.
NOT_YET_WIRED — autosave générique et dépendances entre tâches.
IMPLEMENTED — visual_index.update traite jusqu'à seize Feature Sets par
séquence, checkpoint après commit de segment et repasse par le Governor.
IMPLEMENTED — candidate_pair.generate traite jusqu'à soixante-quatre
Feature Sets par séquence, interroge le Visual Index, persiste les paires
candidates avec idempotence, checkpoint après chaque lot et repasse par le
Governor via sequence_break. La reprise est idempotente avec le curseur
after_feature_set_id rechargé depuis la DB.
PASS / FROZEN — Compute Governor v2. matcher.run traite une
Candidate Pair atomique à la fois, par lots opérationnels bornés jusqu'à 12.
Le code courant consomme honnêtement CPU, lot et GPU du contrat choisi. Feature,
SIFT et RootSIFT appliquent l'admission OpenCV 1..12; RAW et Photo Quality
restent CPU1. Pour ORB normal, le Governor choisit GPU-first ou CPU complet pour la prochaine
séquence, sans mutation pendant son exécution. Ces dimensions ne changent ni
identité scientifique ni publication. Le callback persiste le curseur
after_candidate_pair_id, checkpoint après publication de chaque lot et
effectue une rupture de séquence avant le suivant. Une paire repassée après un
crash est réutilisée par son Match Result. La soumission Vulkan rolling est
privée et request-bound; le contrat normal de séquence fige inflight 1. Deux slots
maximum restent disponibles à la couture privée de sûreté/benchmark et portent
chacun command/fence/buffers/query et un handle exact tandis
que device/pipeline/layout/cache restent partagés. Le propriétaire soumet
jusqu'à la profondeur admise, finit le plus ancien et publie toujours le
préfixe canonique contigu; toute sortie, annulation ou exception nettoie les
handles encore privés. Les buffers du second slot ne sont mappés que pendant
une séquence depth 2 admise et sont libérés avant la rupture suivante. Helpers
reste 0 et le contrôle synchrone force depth 1.
IMPLEMENTED — geometric_verifier.run v1 traite un Match Result atomique à
la fois, par lots adaptatifs de 1, 2, 4 ou 8. Project DB v13 conserve sa
configuration scientifique et after_match_result_id. Chaque résultat est
publié avant le curseur ; pause, annulation, checkpoint et rupture de séquence
restent coopératifs aux frontières des parents et des lots.
IMPLEMENTED — track_builder.run v1 est une tâche durable de rebuild
complet. Le scope GVR est persistant et immuable ; la reprise rejoue depuis le
début avant publication et l'exact reuse Gate C absorbe un crash post-publication.
Pause et annulation sont observées avant/after l'unité Gate B non préemptible.
PASS / FROZEN — acquisition_campaign.run v1 est une
tâche durable reconstruite par la registry existante à partir de son Task ID et
de sa requête typée immuable Project DB v20. Elle matérialise un seul groupe
S3-E par séquence, vérifie pause/annulation aux frontières de groupe, retient
transactionnellement Capture et curseur avant progression/checkpoint, puis
appelle sequence_break avant le groupe suivant. Sa reprise passe par la Queue
et le Resource Governor existants ; aucune boucle d'exécution parallèle n'est
introduite.
PASS / FROZEN — le callback photo_quality.triage v1 réutilise ce
même Task/Queue/Resource Governor et une sequence_break entre groupes. Sa requête typée
est immuable ; le next_group_id canonique commence à 1, avance à k+1 après le résultat
k, puis vaut N+1 à terminaison. Résultat et curseur sont durables avant le checkpoint
générique, de sorte qu'une reprise ne devine ni ne réanalyse une identité déjà publiée. La
réservation charge le contexte retenu et 20 MiB de travail par groupe ; le
JPEG au-dessus de la limite opérationnelle de décodage 8192 pixels reste en attente
UNAVAILABLE + SUSPECT (ni erreur de décodage ni rejet) ; une entrée admise est réduite à
1024 pixels maximum avant analyse. Cette borne ne limite ni la campagne ni le dataset.
Le chemin de production de l'import ne possède plus de thread ni de drapeau d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de tâche, puis effectue une rupture de séquence afin d'obtenir un nouveau contrat et une nouvelle réservation.
Le callback terminal optionnel est notifié exactement une fois pour
COMPLETED, FAILED ou CANCELLED, jamais pour une pause ou une rupture de
séquence. L'état est fixé sous mutex, puis la réservation terminale est libérée
avant l'appel hors mutex. join() attend la fin du callback ; le userdata reste
donc valide pendant celui-ci et son destructeur n'est appelé qu'ensuite par la
destruction de la tâche.
Une tâche reconstruite mais refusée avant transfert à la queue est abandonnée localement : son userdata est détruit, sans callback terminal ni écriture durable d'une fausse annulation. Une annulation explicitement demandée conserve le contrat de notification terminale.
La Project Database v7 peut enregistrer transactionnellement un résumé
Lardon3DTaskDurableSnapshot et la référence de son checkpoint. Elle ne stocke
ni estimation sérialisée complète, ni callback, ni réservation, et ne remplace
pas la validation du fichier checkpoint avant task_restore().
lardon3d_project_checkpoint_task() est la frontière runtime : elle capture le
snapshot, stage le fichier hors mutex de tâche, met à jour la DB, puis promeut
le fichier canonique sous le verrou par tâche. L'API d'inventaire retourne des
snapshots dont le codec/version et le résumé DB ont été validés, mais ne peut
appeler task_restore() que via un descriptor connu ; aucun pointeur n'est
persistant.
Limites
- Aucune priorité interne : l'ordre est uniquement FIFO.
- Pas encore de DAG pour ordonner des reprises interdépendantes.
- Aucune dépendance inter-tâches (pas de DAG).
- Pas encore de références d'artefacts métier validés.