Skip to content

QF Solver 0.2.2 alpha : chantier backend numerique

Objet

La version 0.2.2 alpha est consacree au renforcement du backend numerique de QF Solver. Elle ne revendique pas de nouvelle physique majeure : elle vise la scalabilite, la memoire, la robustesse des solveurs et la tracabilite des diagnostics, sans modifier les formulations d'elements deja couvertes.

Architecture avant

Le chemin standard est analysis -> assembler -> reduction -> solveur -> resultat. Le solveur lineaire commun existait deja dans solveur.core.linear_methods, tandis que la politique de choix etait dans linear_policy. Les chemins Newmark et harmonique reutilisaient toutefois partiellement cette logique, et le modal possedait une chaine specifique eigsh/eigh.

Architecture introduite

La selection de backend est maintenant centralisee dans solveur.core.solver_backend : SciPy est toujours disponible, PETSc et SLEPc sont importes uniquement a la demande. La politique accepte backend=auto, backend=scipy et backend=petsc. Une demande PETSc explicite sans petsc4py produit une erreur explicite ; elle ne bascule jamais silencieusement vers un autre backend.

L'adaptateur KSP transmet maintenant explicitement les preconditionneurs PETSc jacobi, gamg/amg, hypre, ilu, sor, asm et bjacobi. Les alias et les noms non supportes sont testes sans importer PETSc ; un nom invalide est rejete avant toute allocation de matrice. Les tests de ce contrat ne constituent pas une execution HPC et ne ferment donc pas le gate multi-rang.

Le chemin statique accepte method=auto. Pour un petit systeme, il choisit un solveur direct sparse ; pour un systeme plus grand, il utilise les preuves de symetrie/SPD disponibles pour recommander CG, MINRES ou GMRES. Le choix reste visible dans le resultat et peut toujours etre surcharge par une methode explicite.

Les diagnostics lineaires enregistrent desormais le backend, le residu initial, le residu final, le residu relatif, la tolerance et la raison de terminaison. L'estimation de memoire distingue le stockage sparse, une factorisation directe et une representation dense potentielle.

Pour l'assemblage, solveur.core.sparse_accumulator est partage par le chemin standard et le chemin SciPy grand modele. Les chunks sont fusionnes pairwise, et les resultats grands modeles conservent le nombre de chunks, le pic de NNZ par chunk, les niveaux d'accumulation et le NNZ final. Le chemin grand modele ventile maintenant le temps de construction des chunks, de fusion et de finalisation CSR. Le chemin standard expose en plus les phases du kernel local, de conversion COO/CSR, de fusion et de reduction des discretes ; la campagne avant/apres les utilise maintenant pour distinguer le gain de la paire K/M du cout propre du kernel elementaire.

La chaine modale peut utiliser SLEPc lorsqu'il est disponible et demande, avec fallback trace vers SciPy pour backend=auto. Le chemin dense reste borne par dense_modal_max_dofs et n'est jamais choisi implicitement pour un grand systeme.

Tests et non-regression

Le baseline ciblé du chantier a produit 35 passed. Apres la premiere tranche, les tests lineaires, modaux, dynamiques et de comparaison ont produit 63 passed, puis les nouveaux tests de backend et de benchmark ont ete ajoutes. La regression pertinente de reference a produit 1327 passed, 107 deselected avec une couverture branchee de 90,195 % pour un seuil de 80 %. Les lots incrementaux ajoutes ensuite couvrent les helpers d'assemblage sparse, les controles multi-million, les metriques de scaling MPI simulees et les sorties file-backed ; la mesure cumulee locale atteint donc la cible de 90 % dans les artefacts coverage.json/coverage.xml. Cette verification locale ne remplace pas la campagne V&V complete et ne constitue pas une decision Owner. Les branches PETSc/MPI multi-rangs restent explicitement dependantes d'un runtime HPC ; elles ne sont pas simulees lorsque les dependances ne sont pas installees sur l'hote. Une campagne reelle est toutefois archivee dans le runtime Docker epingle decrite plus bas. Les contrats documentaires locaux ont produit 42 passed, 6 skipped et le controle public a conserve le statut PASS. Les tests ajoutés aux branches de partitionnement et de comparaison des préconditionneurs portent ces modules à 92 % et 91 %. Les contrats CLI grand modèle et le contrat KSP mono-rang sont maintenant couverts dans tests/unit/test_large_cli_commands.py et tests/unit/test_large_petsc_contract.py; ils vérifient le câblage et les métriques sans être comptés comme une preuve HPC. Le déficit global reste principalement concentré dans les chemins PETSc/MPI multi-rangs et certaines interfaces natives qui nécessitent leurs dépendances réelles.

Benchmark de scaling

Le script reproductible suivant est separe de la CI longue :

python scripts/benchmark_sparse_scaling.py --output results/scaling_0_2_2/summary.json

Il couvre par defaut environ 1k, 10k et 100k DDL et enregistre le nombre de non-zero, les temps d'assemblage et de resolution, les iterations, les residus, le backend et les estimations de memoire. Un cas 1M DDL peut etre lance manuellement avec --sizes 1000000 si la machine le permet.

Un echantillon numerique reproductible est archive dans qualification/benchmarks/qf_solver_0_2_2_sparse_scaling_reference.json. Sur cet echantillon, la resolution reste sous 0,04 s a 100k DDL, le residu relatif reste sous 4,0e-9, le stockage sparse est d'environ 4,0 MB et une representation dense equivalente est estimee a 80 GB. Ces chiffres sont un point de comparaison local, pas une garantie de performance universelle.

Le benchmark d'assemblage TET4 est separe dans qualification/benchmarks/qf_solver_0_2_2_assembly_scaling_reference.json. Avec un chunk de 4096 et trois répétitions, la médiane instrumentée donne environ 0,013 s pour 1029 DDL, 0,138 s pour 10125 DDL et 1,731 s pour 107811 DDL. Le cas 100k contient 196608 éléments et 3813789 non-zero finaux. Sur ce dernier cas, le kernel élémentaire représente environ 0,860 s, la conversion COO/CSR 0,714 s, la fusion pairwise 0,093 s et la finalisation CSR 0,024 s. Le temps chunk_build est la somme du kernel et de la conversion sparse. Le cache matériau est réutilisé entre les chunks, et AssemblyPlan sépare la carte DDL du kernel élémentaire. Le plan porte une empreinte SHA-256 déterministe du modèle, des DDL, des contraintes et du chunk_size; toute modification détectée invalide la réutilisation sans conserver de matrice locale variable. Les analyses modal/dynamiques mutualisent le motif temporaire K/M par chunk. Aucun motif global ni matrice locale variable n'est conservé. La conversion COO/CSR et le kernel élémentaire restent les deux leviers prioritaires. La comparaison K/M archivée ci-dessous quantifie le gain réel de la paire sans le confondre avec un gain mémoire.

Un sweep de taille de chunk sur le même cas 100000 DDL est archivé dans qualification/benchmarks/qf_solver_0_2_2_assembly_chunk_sweep_reference.json. Les médianes locales vont de 1,937 s avec 1024 à 1,673 s avec 16384, mais le pic de triplets augmente de 37323 à 448551. Le gain d'environ 2 % ne justifie donc pas de modifier la valeur par défaut. Le runner produit désormais une recommandation advisory bornée par budget : sur la référence 100k et 4 000 000 octets, il retient 4096 avec un pic estimé de 3 433 320 octets ; aucune politique automatique globale n'est activée.

La conversion directe csr_matrix((data, (rows, cols))) a été essayée comme remplacement local du passage COO intermédiaire. La comparaison archivée dans qualification/benchmarks/qf_solver_0_2_2_assembly_scaling_comparison.json confirme l'identité des NNZ et des valeurs, mais ne montre pas encore de gain de temps global robuste ; elle ne doit donc pas être présentée comme une accélération validée.

Le kernel géométrique TET4 a ensuite remplacé l'inverse batched de la matrice 4 x 4 d'interpolation par les gradients barycentriques construits à partir des produits vectoriels des trois arêtes et du déterminant orienté. Le contrat de volume signé, la somme nulle des quatre gradients et l'identité avec l'élément TET4 de référence sont testés. La campagne archivée dans qualification/benchmarks/qf_solver_0_2_2_assembly_scaling_geometry_comparison.json confirme l'identité numérique, les mêmes NNZ et une variation médiane du temps total d'environ -3,4 % ; le kernel élémentaire diminue d'environ 7,6 % à 100k DDL. Ce résultat est conservé comme amélioration locale à confirmer sur une seconde configuration, pas comme garantie universelle de performance.

Cette confirmation a été exécutée avec la décomposition centrée à douze TET4 par cellule et un matériau isotrope E=70 GPa, nu=0,27, rho=2700 kg/m3. Le résultat est archivé dans qualification/benchmarks/qf_solver_0_2_2_assembly_scaling_centered_reference.json. À la taille cible, il produit 206115 DDL, 393216 éléments et 7715381 NNZ, avec un kernel de 1,588 s et une conversion sparse de 1,553 s sur la machine de mesure. Cette campagne confirme la stabilité du calcul sur une seconde topologie et un second matériau ; ses temps ne sont pas comparés au maillage historique, car le nombre d'éléments et de NNZ est différent.

Cette quantification est archivee dans qualification/benchmarks/qf_solver_0_2_2_standard_km_pair_reference.json. Sur 1k, 10k et 100k DDL, le ratio temps paire/separe mesure apres le compactage des indices est respectivement 0,916, 0,888 et 0,890. Les matrices K et M ont les memes dimensions, NNZ et differences numeriques nulles dans les trois cas. Le resultat montre un gain de temps local d'environ 8,4 % a 11,2 %, mais pas de gain memoire revendique : l'estimation temporaire de la paire reste plus conservative.

La reutilisation de la matrice effective Newmark est mesuree separement dans qualification/benchmarks/qf_solver_0_2_2_newmark_factorization_reference.json. Le runner est scripts/benchmark_newmark_factorization.py. Sur environ 1029 DDL et 10125 DDL, avec 8 pas, le chemin direct utilise une seule factorisation (factorization_count=1) pour 8 resolutions (solve_count=8), soit un ratio de reutilisation de 8. Les residus dynamiques relatifs maximum mesures sont respectivement 1,28e-11 et 2,21e-11. Les temps de factorisation mesures sont d'environ 0,006 s et 0,572 s, contre 0,002 s et 0,063 s pour l'ensemble des huit resolutions. Le temps d'assemblage mesure est respectivement 0,180 s et 2,258 s. Cela confirme le contrat de reutilisation de la matrice effective ; cela ne constitue pas encore une comparaison avant/apres avec une ancienne implementation qui refactoriserait a chaque pas.

Limites restantes

Le chemin harmonique complexe conserve pour l'instant sa resolution sparse SciPy dediee par frequence. Le backend PETSc n'est pas obligatoire et son benefice doit etre mesure sur une configuration MPI tracee avant de modifier la politique par defaut. La factorisation re-utilisee de Newmark reste le chemin prefere pour les matrices effectives constantes ; les solveurs iteratifs sont disponibles lorsque la memoire ou la taille le justifie. Le benchmark Newmark est limite a 10k DDL dans cette tranche ; une campagne directe a 100k DDL reste manuelle car la factorisation LU peut devenir le goulot d'etranglement memoire.

La promotion de la release reste reservee a l'Owner apres execution de la regression complete, des benchmarks de scaling et de la revue des ecarts numeriques.

Le runner scripts/compare_large_backends.py a été ajouté pour comparer une observable mécanique identique entre SciPy, matrix-free et PETSc. La campagne Docker archivée sur 1 029 DDL termine avec les trois backends et des écarts relatifs de déplacement de 1,087e-13 (matrix-free) et 1,417e-13 (PETSc) par rapport à SciPy. Cette comparaison reste une preuve de cohérence sur un petit cas, pas une qualification HPC universelle.

Gate multi-million de DDL

La readiness distingue désormais les campagnes à partir de 2 000 000 DDL avec le contrôle MULTI-MILLION-GATE. Ce contrôle exige petsc ou matrix_free et accepte un budget mémoire explicite en octets, transmis par l'API ou par large-readiness --memory-budget-mb. Sans budget, le statut est WARNING; avec un budget inférieur à l'estimation PETSc indicative, il est FAIL. Le gate ne lance pas le solveur : il protège la campagne contre une allocation prématurée et laisse la qualification multi-million hors CI rapide.

Une tranche Docker réelle a été exécutée avec l'image épinglée qf-solver-large:0.2.0@sha256:f2a7931d0543ee142ce67847bb91bf59350a947d5d4874bfe7be43b6848a49c8. Les quatre cas 2M/4M DDL x 2/4 rangs passent l'audit, la convergence CG, le résidu relatif 1e-8, la sortie file-backed et le budget RSS de 32 GiB. Les efficacités fortes observées sont 0,651 à 2M et 0,615 à 4M. Cette preuve est bornée au statique linéaire TET4, à une machine, à une image et au partitionnement contigu ; elle ne ferme ni le modal/dynamique multi-million, ni SLEPc, ni le partitionnement graphe.

Campagne backend agregee 0.2.2 alpha

Le dossier qualification/benchmarks/qf_solver_0_2_2_backend_campaign/campaign.json est genere par scripts/build_backend_campaign_report.py. Son statut PASS_BOUNDED_BACKEND_CAMPAIGN ferme le gate technique dans le perimetre suivant, sans promotion de maturite :

Chemin Preuve Resultat
PETSc statique contigu 2M/4M DDL, 2/4 rangs efficacites fortes 0,651 et 0,615
PETSc graphe/PT-Scotch 2M DDL, 2/4 rangs efficacite forte 0,621
matrix-free 107 811 DDL residu relatif 1,104e-12
comparaison backends SciPy/matrix-free/PETSc, 1 029 DDL ecarts < 1,5e-13
modal SLEPc 107 811 DDL, 3 modes residu modal maximal 2,789e-12
Newmark PETSc/GAMG 2 044 416 DDL, 10 pas residu relatif maximal 1,968e-6

Les sept manifestes d'evidence de cette campagne sont verifies PASS. Les sorties modales et Newmark utilisent une masse TET4 coherente et ne forment pas inv(M) @ K.

La tentative modale SLEPc a 2 044 416 DDL n'a pas ete declaree PASS : le shift-invert direct a ete tue par la limite de ressources du conteneur apres environ 33,5 GiB observes. Cette limite est archivee dans qualification/benchmarks/qf_solver_0_2_2_backend_campaign/modal_2m_resource_limit/. La modalisation a plusieurs millions, une seconde configuration materielle et la revue Owner restent donc hors de ce gate technique borne.