@piecemaker-legal/piecemaker 1.0.6 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{abnfDiagram-VCTEODGH-DF7LFtT-.js → abnfDiagram-VCTEODGH-CR3_ADBu.js} +1 -1
- package/dist/assets/{arc-xOmjWeVH.js → arc-b92imMmc.js} +1 -1
- package/dist/assets/{architectureDiagram-5GKGNRK7-CESzNPwa.js → architectureDiagram-5GKGNRK7-Dfm5Epud.js} +1 -1
- package/dist/assets/{blockDiagram-I7D4REHJ-BjdN78Ad.js → blockDiagram-I7D4REHJ-B8kLLyAv.js} +1 -1
- package/dist/assets/{c4Diagram-7LVT6UL2-BcK9EDWT.js → c4Diagram-7LVT6UL2-BWSyAAWX.js} +1 -1
- package/dist/assets/channel-BbcW_Tkh.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-DGUN1JFP.js → chunk-2Q5K7J3B-Ci743mcC.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-BG3BBcFr.js → chunk-5VM5RSS4-SdXpal6h.js} +1 -1
- package/dist/assets/{chunk-F27PBJKO-CLIOfrF5.js → chunk-F27PBJKO-Rof94ueX.js} +1 -1
- package/dist/assets/{chunk-IMKFNOWR-D6lGkqij.js → chunk-IMKFNOWR-B3st6ECc.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BmF9vLBT.js → chunk-JWPE2WC7-alXmu5xU.js} +1 -1
- package/dist/assets/{chunk-POPQ4Y6H-C_0SR3ak.js → chunk-POPQ4Y6H-dMs1OCce.js} +1 -1
- package/dist/assets/{chunk-SVP7TREG-rT20Wfpp.js → chunk-SVP7TREG-BmM8FsmF.js} +1 -1
- package/dist/assets/{chunk-TICWLB2K-Cn2hkmcf.js → chunk-TICWLB2K-CL1LytYG.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Dm_eP1_v.js → chunk-XXDRQBXY-7U9iRPT1.js} +1 -1
- package/dist/assets/classDiagram-ZZMXUADV-D9_YRZ7i.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-D9_YRZ7i.js +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-cJeeJsep.js → cose-bilkent-JH36ORCC-DFOMlhj2.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DX2ePIXe.js → cynefin-OW5HDTMX-CDdJFd9d.js} +1 -1
- package/dist/assets/{cynefinDiagram-5FMLGOSQ-BvWXN-RR.js → cynefinDiagram-5FMLGOSQ-DPyVNrMW.js} +1 -1
- package/dist/assets/{dagre-GXQ25YYZ-CkwPRhGR.js → dagre-GXQ25YYZ-B3bpHa9D.js} +1 -1
- package/dist/assets/{diagram-S7CK7UJ4-CBGNzYyv.js → diagram-S7CK7UJ4-DeFO2rXB.js} +1 -1
- package/dist/assets/{diagram-UQ7AKVKN-CiwCU0SJ.js → diagram-UQ7AKVKN-CbdGTOVf.js} +1 -1
- package/dist/assets/{diagram-VSXAHHWV-Aekg4nTk.js → diagram-VSXAHHWV-DFfNQyMd.js} +1 -1
- package/dist/assets/{diagram-VX7I27RA-DPFP5K7r.js → diagram-VX7I27RA-DbNZ6_X3.js} +1 -1
- package/dist/assets/{diagram-Z3DM3KII-BHJkxoey.js → diagram-Z3DM3KII-B8F-ZkNJ.js} +1 -1
- package/dist/assets/{ebnfDiagram-PWID7BFC-2rV1P32T.js → ebnfDiagram-PWID7BFC-BfCCPPAP.js} +1 -1
- package/dist/assets/{erDiagram-RLTQ6QDP-C4BjyS_H.js → erDiagram-RLTQ6QDP-DcV-3Kzv.js} +1 -1
- package/dist/assets/{flowDiagram-HODETNUW-DL1TBlAG.js → flowDiagram-HODETNUW-BwSkJXCo.js} +1 -1
- package/dist/assets/{ganttDiagram-EL5Y4UJY-MKjvzCWz.js → ganttDiagram-EL5Y4UJY-BffHk77T.js} +1 -1
- package/dist/assets/{gitGraphDiagram-WWUBYQGX-CS_oTGK5.js → gitGraphDiagram-WWUBYQGX-CQ1EWa74.js} +1 -1
- package/dist/assets/{index-XOGQDqU3.js → index-BqK33gaZ.js} +120 -120
- package/dist/assets/index-D_Dc_nQv.css +1 -0
- package/dist/assets/{infoDiagram-27XIBGKW-ihUr2GjN.js → infoDiagram-27XIBGKW-DqIaBXQn.js} +1 -1
- package/dist/assets/{ishikawaDiagram-5VMMS53U-D2YAnuir.js → ishikawaDiagram-5VMMS53U-HlhsaZgv.js} +1 -1
- package/dist/assets/{journeyDiagram-3NMN7TZE-Bc2hSZGD.js → journeyDiagram-3NMN7TZE-BYO8-7rN.js} +1 -1
- package/dist/assets/{kanban-definition-UXKFOSKX-LRMGNonX.js → kanban-definition-UXKFOSKX-DXb8zHGT.js} +1 -1
- package/dist/assets/{layout-CcfWOlPV.js → layout-CREOVuEZ.js} +1 -1
- package/dist/assets/{linear-7JoS2gQO.js → linear-CVCrIRIY.js} +1 -1
- package/dist/assets/{mermaid.core-ijh_SsuN.js → mermaid.core-DFqeWtQR.js} +6 -6
- package/dist/assets/{mindmap-definition-YA3MSWOX-DCE4nzfF.js → mindmap-definition-YA3MSWOX-SUpXz333.js} +1 -1
- package/dist/assets/{pegDiagram-XKGWAZYB-SjaGa7lE.js → pegDiagram-XKGWAZYB-D-yWpve3.js} +1 -1
- package/dist/assets/{pieDiagram-E7YTZNPT-1xcRidSZ.js → pieDiagram-E7YTZNPT-BJX4Zp4Z.js} +1 -1
- package/dist/assets/{quadrantDiagram-AXDQQJYC-obHAHnYV.js → quadrantDiagram-AXDQQJYC-CHajheTu.js} +1 -1
- package/dist/assets/{railroadDiagram-O6MQD6OU-BsCVcRZb.js → railroadDiagram-O6MQD6OU-DHJWzW5A.js} +1 -1
- package/dist/assets/{requirementDiagram-BXWQKSXE-CUNkhvqb.js → requirementDiagram-BXWQKSXE-DigUIdso.js} +1 -1
- package/dist/assets/{sankeyDiagram-P5KCCOFB-DhezXMKm.js → sankeyDiagram-P5KCCOFB-CFUDDbA5.js} +1 -1
- package/dist/assets/{sequenceDiagram-WJ2MYXX4-CL9VswXH.js → sequenceDiagram-WJ2MYXX4-6tSaXfYD.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-t0g9jiEz.js → sizeCapture-INFHLROL-CXASEDKe.js} +1 -1
- package/dist/assets/{stateDiagram-D77RDMKH-BW-g-IOz.js → stateDiagram-D77RDMKH-DWTCAQfZ.js} +1 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-HDQF4MGA.js +1 -0
- package/dist/assets/{swimlanes-42K2YHIH-LsuyeBte.js → swimlanes-42K2YHIH-7-oz5r-e.js} +1 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-CJbgQFt8.js +8 -0
- package/dist/assets/{timeline-definition-24CTP7MA-B0cFka1R.js → timeline-definition-24CTP7MA-DVH5g4ty.js} +1 -1
- package/dist/assets/{vendor-codemirror-C5qqu4R7.js → vendor-codemirror-CjW0lHR5.js} +5 -5
- package/dist/assets/{vennDiagram-4TSXK5OY-D3HNCDR3.js → vennDiagram-4TSXK5OY-CQvY_ALZ.js} +1 -1
- package/dist/assets/{wardleyDiagram-VM6X3IG4-DJ3Co8nb.js → wardleyDiagram-VM6X3IG4-BdlTJgEc.js} +1 -1
- package/dist/assets/{xychartDiagram-S5SC5T6Z-Cz5E9New.js → xychartDiagram-S5SC5T6Z-CYn2sV77.js} +1 -1
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +1 -9
- package/dist/generate-icons.js +47 -46
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +1 -12
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +1 -12
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +1 -12
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +1 -12
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +1 -12
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +1 -12
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +1 -12
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +1 -12
- package/dist/index.html +3 -3
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo-sources/logo-black.png +0 -0
- package/dist/logo-sources/logo-white.png +0 -0
- package/dist/logo.svg +1 -17
- package/dist/sw.js +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js +91 -0
- package/dist-server/plugins/piecemaker-dossier/src/institutional-terms.js.map +1 -0
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js +112 -10
- package/dist-server/plugins/piecemaker-dossier/src/knowledge.js.map +1 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js +5 -1
- package/dist-server/plugins/piecemaker-dossier/src/scan-result.js.map +1 -1
- package/dist-server/server/index.js +60 -26
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +28 -6
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js +58 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/plugins/plugins.routes.js +8 -1
- package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +2 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/piecemaker/bodacc-search.js +191 -0
- package/dist-server/server/piecemaker/bodacc-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.js +145 -0
- package/dist-server/server/piecemaker/company-search.js.map +1 -0
- package/dist-server/server/piecemaker/company-search.test.js +18 -0
- package/dist-server/server/piecemaker/company-search.test.js.map +1 -0
- package/dist-server/server/piecemaker/index.js +6 -2
- package/dist-server/server/piecemaker/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/index.js +9 -4
- package/dist-server/server/piecemaker/knowledge/index.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/pipeline.js +68 -58
- package/dist-server/server/piecemaker/knowledge/pipeline.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/routes.js +2 -4
- package/dist-server/server/piecemaker/knowledge/routes.js.map +1 -1
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js +91 -0
- package/dist-server/server/piecemaker/knowledge/scan-jobs.js.map +1 -0
- package/dist-server/server/piecemaker/knowledge/service.js +18 -36
- package/dist-server/server/piecemaker/knowledge/service.js.map +1 -1
- package/dist-server/server/piecemaker/library/connector-installation.js +224 -0
- package/dist-server/server/piecemaker/library/connector-installation.js.map +1 -0
- package/dist-server/server/piecemaker/library/index.js +11 -1
- package/dist-server/server/piecemaker/library/index.js.map +1 -1
- package/dist-server/server/piecemaker/library/marketplace.js +102 -43
- package/dist-server/server/piecemaker/library/marketplace.js.map +1 -1
- package/dist-server/server/piecemaker/library/migrate.js +8 -1
- package/dist-server/server/piecemaker/library/migrate.js.map +1 -1
- package/dist-server/server/piecemaker/library/provider-agents.js +50 -0
- package/dist-server/server/piecemaker/library/provider-agents.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js +80 -0
- package/dist-server/server/piecemaker/library/provider-connectors.js.map +1 -0
- package/dist-server/server/piecemaker/library/provider-skills.js +11 -2
- package/dist-server/server/piecemaker/library/provider-skills.js.map +1 -1
- package/dist-server/server/piecemaker/library/store.js +125 -35
- package/dist-server/server/piecemaker/library/store.js.map +1 -1
- package/dist-server/server/piecemaker/library/workspace-installation.js +53 -29
- package/dist-server/server/piecemaker/library/workspace-installation.js.map +1 -1
- package/dist-server/server/piecemaker/timesheet/service.js +4 -21
- package/dist-server/server/piecemaker/timesheet/service.js.map +1 -1
- package/package.json +3 -2
- package/scripts/piecemaker/cli/install-command.mjs +35 -9
- package/scripts/piecemaker/cli/lib/config.mjs +1 -0
- package/scripts/piecemaker/cli/lib/piecemaker-anonymizer.mjs +78 -0
- package/scripts/piecemaker/cli/lib/plugins.mjs +2 -0
- package/scripts/piecemaker/cli/lib/pwa.mjs +46 -7
- package/scripts/piecemaker/cli/lib/services.mjs +65 -4
- package/scripts/piecemaker/cli/piecemaker-command.test.mjs +25 -0
- package/scripts/piecemaker/cli/piecemaker.mjs +45 -44
- package/scripts/piecemaker/cli/piecemaker.ps1 +8 -0
- package/scripts/piecemaker/cli/piecemaker.sh +6 -1
- package/server/index.ts +52 -23
- package/server/modules/database/repositories/projects.db.ts +28 -6
- package/server/modules/database/tests/projects.db.integration.test.ts +63 -1
- package/server/modules/plugins/plugins.routes.ts +8 -1
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +5 -0
- package/server/piecemaker/anonymizer/client-config.cjs +10 -2
- package/server/piecemaker/anonymizer/dictionary.cjs +6 -72
- package/server/piecemaker/bodacc-search.ts +205 -0
- package/server/piecemaker/company-search.test.ts +26 -0
- package/server/piecemaker/company-search.ts +183 -0
- package/server/piecemaker/index.ts +8 -2
- package/server/piecemaker/knowledge/index.ts +9 -4
- package/server/piecemaker/knowledge/pipeline.ts +89 -57
- package/server/piecemaker/knowledge/routes.ts +2 -4
- package/server/piecemaker/knowledge/scan-jobs.ts +111 -0
- package/server/piecemaker/knowledge/service.ts +18 -33
- package/server/piecemaker/library/connector-installation.ts +248 -0
- package/server/piecemaker/library/index.ts +5 -1
- package/server/piecemaker/library/marketplace.ts +104 -39
- package/server/piecemaker/library/migrate.ts +8 -1
- package/server/piecemaker/library/provider-agents.ts +49 -0
- package/server/piecemaker/library/provider-connectors.ts +89 -0
- package/server/piecemaker/library/provider-skills.ts +10 -1
- package/server/piecemaker/library/store.ts +129 -36
- package/server/piecemaker/library/workspace-installation.ts +54 -29
- package/server/piecemaker/router.cjs +2 -1
- package/server/piecemaker/timesheet/service.ts +4 -16
- package/server/piecemaker/vendor/installer/steps/03-python-gliner.mjs +0 -27
- package/server/piecemaker/vendor/installer/steps/14-mxc-sandbox.mjs +1 -1
- package/server/piecemaker/vendor/piecemaker-plugin/scripts/lib/substitution.cjs +2 -3
- package/server/piecemaker/vendor/piecemaker-plugin/skills/conversion-md/SKILL.md +3 -3
- package/server/piecemaker/vendor/websocket-server/admin-routes.cjs +3 -10
- package/server/piecemaker/vendor/websocket-server/originals-pipeline.cjs +135 -4
- package/server/piecemaker/vendor/websocket-server/process-group.cjs +95 -0
- package/server/piecemaker/vendor/websocket-server/scripts/__pycache__/scan_utils.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/convert_and_scan_pipeline.py +280 -149
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/model_config.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/scanner_worker.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/scanner_worker.py +35 -29
- package/server/piecemaker/vendor/websocket-server/scripts/requirements.txt +0 -5
- package/server/shared/types.ts +1 -0
- package/dist/assets/channel-Dz3eE5vU.js +0 -1
- package/dist/assets/classDiagram-ZZMXUADV-BC1eKV2j.js +0 -1
- package/dist/assets/classDiagram-v2-VYDZK3BY-BC1eKV2j.js +0 -1
- package/dist/assets/index-DhwJpHCq.css +0 -1
- package/dist/assets/stateDiagram-v2-MP3YSRHH-BXuT4zDG.js +0 -1
- package/dist/assets/swimlanesDiagram-VR7AAH4N-B0eYrB4U.js +0 -8
- package/server/piecemaker/vendor/websocket-server/case-instructions.cjs +0 -111
- package/server/piecemaker/vendor/websocket-server/scripts/ADDRESS_ANONYMIZATION.md +0 -216
- package/server/piecemaker/vendor/websocket-server/scripts/DEDUPLICATION_CHANGES.md +0 -134
- package/server/piecemaker/vendor/websocket-server/scripts/GLINER2_README.md +0 -138
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md +0 -246
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/MODELE_COREML.md +0 -62
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/__pycache__/coreml_runtime.cpython-312.pyc +0 -0
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/build_coreml.py +0 -118
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/coreml_runtime.py +0 -164
- package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/presidio-gliner.py +0 -479
package/server/piecemaker/vendor/websocket-server/scripts/presidio-gliner/AUDIT_PERF_QUALITE.md
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
# PieceMaker — plan v3 (archive historique)
|
|
2
|
-
|
|
3
|
-
> **Statut — archive, non-feuille de route active.** Ce plan est conservé comme trace de l'analyse
|
|
4
|
-
> publiée dans le commit `fb4bca1` du **6 août 2026**. Il fige les mesures, hypothèses et travaux
|
|
5
|
-
> « à faire » tels qu'ils étaient alors envisagés ; il ne décrit pas l'état courant du code et ne
|
|
6
|
-
> constitue pas un compte rendu d'exécution ultérieure. Ne pas interpréter ses chiffres ou ses
|
|
7
|
-
> étapes comme des résultats actuels sans consulter les documents de vérification ci-dessous.
|
|
8
|
-
>
|
|
9
|
-
> État et résultats disponibles depuis ce plan :
|
|
10
|
-
> - [`eval/CRITERES_QUALITE.md`](eval/CRITERES_QUALITE.md) vérifie les étapes 1 à 3 et documente
|
|
11
|
-
> la correction de la fuite liée aux tirets Unicode ;
|
|
12
|
-
> - [`eval/BACKENDS_MESURES.md`](eval/BACKENDS_MESURES.md) rassemble les mesures comparatives des
|
|
13
|
-
> backends ;
|
|
14
|
-
> - les scripts [`eval/verify_substitution.cjs`](eval/verify_substitution.cjs),
|
|
15
|
-
> [`eval/verify_whitespace.py`](eval/verify_whitespace.py), [`eval/evaluate.py`](eval/evaluate.py)
|
|
16
|
-
> et [`eval/bench_backend.py`](eval/bench_backend.py) indiquent les vérifications
|
|
17
|
-
> effectivement rejouables. Leurs JSON de sortie sont régénérables localement et ne sont
|
|
18
|
-
> volontairement pas conservés dans le dépôt.
|
|
19
|
-
>
|
|
20
|
-
> Les sections historiques ci-dessous sont volontairement laissées inchangées : leurs mentions
|
|
21
|
-
> « à faire », estimations et résultats partiels ne donnent pas le statut courant et ne prouvent
|
|
22
|
-
> pas des travaux réalisés aujourd'hui.
|
|
23
|
-
|
|
24
|
-
> Remplace la v2. Contient **deux corrections de mes propres conclusions** et un renversement de
|
|
25
|
-
> l'objectif qualité, provoqué par la lecture du code de substitution que je n'avais pas encore lu.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## 0. État du run et chiffres, tous mesurés
|
|
30
|
-
|
|
31
|
-
Le run end-to-end a été **interrompu à 592/972 chunks (61 %)**, encore en phase d'extraction — il n'a
|
|
32
|
-
jamais atteint la phase de classification. Ce qu'il a donné avant de s'arrêter :
|
|
33
|
-
|
|
34
|
-
| | valeur | base de mesure |
|
|
35
|
-
|---|---|---|
|
|
36
|
-
| Démarrage (modèle + 2 analyseurs spaCy) | **64 s** | mesuré |
|
|
37
|
-
| Extraction | **1,22 s/chunk** | **n = 592 chunks**, 61 % du document |
|
|
38
|
-
| → extraction document complet | **19,7 min** | 1,22 × 972 |
|
|
39
|
-
| RSS du worker | 869 Mo | mesuré |
|
|
40
|
-
| `classify_text` sur contextes réels | **2 890 ms/appel** | n = 25, tirés au sort dans les **1 689 occurrences réelles** |
|
|
41
|
-
| → phase classification | **81,3 min** | 2,89 × 1 689 |
|
|
42
|
-
| spaCy (pipeline complet, jeté) | ~1,5 min | mesuré |
|
|
43
|
-
| **Total attendu** | **≈ 102 min** | |
|
|
44
|
-
|
|
45
|
-
Ce n'est toujours pas un chrono end-to-end complet. Les deux phases sont maintenant mesurées sur des
|
|
46
|
-
échantillons larges et réels, mais la somme reste une somme. **Dis-moi si tu veux que je relance le
|
|
47
|
-
run complet** — il sature ton M1 pendant ~1 h 40, je ne le relance pas sans ton feu vert.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## 1. ⚠️ Correction n° 1 — j'avais tort sur `classify_text`
|
|
52
|
-
|
|
53
|
-
En v2 j'ai écrit : « 12 runs, 0 type `ORGANIZATION_*` produit → l'étape ne produit rien ».
|
|
54
|
-
Le fait était exact, **l'inférence était fausse**. Mesuré sur 25 contextes réels avec le schéma à
|
|
55
|
-
221 labels verbatim de `scanner_worker.py` : `classify_text` renvoie une forme ≠ `other`
|
|
56
|
-
**25 fois sur 25**. L'absence de `ORGANIZATION_*` dans les 12 sorties vient donc de ces runs-là
|
|
57
|
-
(fichier à 30 labels / version antérieure), pas d'un échec de la fonction.
|
|
58
|
-
|
|
59
|
-
**Mais ce que la mesure montre est pire.** Voici ce que la fonction produit sur un URD biotech
|
|
60
|
-
franco-américain :
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
SA 11/25 · AG 2 · AS 2 · No Liability 1 · Osakeyhtiö 1 · Ltd 1
|
|
64
|
-
EIRELI 1 · Srl 1 · Aktiengesellschaft 1 · GP 1 · ANS 1 · Inc 1 · CC 1
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
`Osakeyhtiö` (finlandais), `EIRELI` (brésilien), `No Liability` (australien), `ANS` (norvégien),
|
|
68
|
-
`CC` (Close Corporation sud-africaine) — pour des sociétés comme AlphaBio, Novartis, Investis.
|
|
69
|
-
44 % de « SA » par défaut. Ce ne sont pas des abstentions, ce sont des **réponses fausses et
|
|
70
|
-
confiantes, à 2,9 s pièce**.
|
|
71
|
-
|
|
72
|
-
L'argument de suppression est donc plus fort qu'en v2, mais pour une autre raison : l'étape ne coûte
|
|
73
|
-
pas 81 min pour rien, elle coûte 81 min pour **injecter des codes faux** (`SOCIETE_OSAKEYHTIÖ_04`)
|
|
74
|
-
dans le document anonymisé.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 2. 🔴 Correction n° 2 — j'avais tort sur l'objectif qualité
|
|
79
|
-
|
|
80
|
-
En v2 j'ai proposé « maximiser le rappel sous plancher de précision, car un faux positif n'est qu'un
|
|
81
|
-
mot caviardé pour rien ». **C'est faux**, et je l'ai écrit avant d'avoir lu le code de substitution.
|
|
82
|
-
|
|
83
|
-
`websocket-server/lib/anonymization-server.cjs:394` :
|
|
84
|
-
|
|
85
|
-
```js
|
|
86
|
-
const regex = new RegExp(escapeRegex(original), 'gi');
|
|
87
|
-
result = result.replace(regex, code);
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
Substitution **globale**, **insensible à la casse**, et **sans limite de mot**. Trois conséquences,
|
|
91
|
-
toutes vérifiées sur le document réel :
|
|
92
|
-
|
|
93
|
-
### 2.1 Ta seconde intuition est juste — et déjà implémentée
|
|
94
|
-
|
|
95
|
-
Détecter une entité **une seule fois suffit** : le `replace` global remplace ensuite toutes ses
|
|
96
|
-
occurrences. J'ai mesuré que 74,6 % des occurrences d'entités déjà connues ne sont jamais re-détectées
|
|
97
|
-
par GLiNER — j'ai failli te l'annoncer comme une fuite. **Ce n'en est pas une**, précisément à cause
|
|
98
|
-
de ce `replace` global. Vérifié avant de te l'écrire.
|
|
99
|
-
|
|
100
|
-
### 2.2 En revanche chaque faux positif est amplifié à l'échelle du document
|
|
101
|
-
|
|
102
|
-
Blast radius mesuré, entité par entité, sur le ALPHABIO :
|
|
103
|
-
|
|
104
|
-
| entité détectée | substitutions déclenchées | dont **à l'intérieur d'un autre mot** |
|
|
105
|
-
|---|---|---|
|
|
106
|
-
| `CA` | **4 725** | 4 724 — `capital`, `case`, `cash`, `indicators`, `carbon` |
|
|
107
|
-
| `us` / `US` | 2 099 ×2 | 1 918 — `business`, `used`, `adjustments` |
|
|
108
|
-
| `AU` | 1 030 | 1 028 — `Faubourg`, `authority`, `Audit`, `auditors` |
|
|
109
|
-
| `EU` | 726 | 644 — `European` |
|
|
110
|
-
| `RU` | 702 | 701 — `rue`, `structure`, `rules`, `instruments` |
|
|
111
|
-
| `ZA` | 477 | 476 — `organization`, `commercialization`, `authorization` |
|
|
112
|
-
| `IND` | 454 | 432 — `indicators`, `indirect`, `kind` |
|
|
113
|
-
| `RP` | 441 | 343 — `Corporation`, `purpose`, `corporate` |
|
|
114
|
-
| `Company` | 895 | — (vrai mot, mais générique) |
|
|
115
|
-
|
|
116
|
-
**Total : 600 entités → 28 956 substitutions sur un document de 194 391 mots.**
|
|
117
|
-
Soit environ **un mot sur sept réécrit**. Le document anonymisé n'est pas « sur-caviardé », il est
|
|
118
|
-
**détruit** — et de façon irréversible, puisque `reverse_mapping` ne peut pas reconstituer `capital`
|
|
119
|
-
à partir de `ADRESSE_07pital`.
|
|
120
|
-
|
|
121
|
-
### 2.3 Donc l'objectif s'inverse
|
|
122
|
-
|
|
123
|
-
Ce n'est pas « rappel maximal sous plancher de précision ». Avec cette substitution, **la précision
|
|
124
|
-
est le terme dominant** : une entité fausse coûte toutes ses occurrences, et une entité fausse et
|
|
125
|
-
courte corrompt le texte. Le rappel *par occurrence*, lui, est presque gratuit grâce au `replace`
|
|
126
|
-
global — il suffit d'un seul repérage.
|
|
127
|
-
|
|
128
|
-
**Objectif corrigé** : *maximiser le rappel sur les entités **distinctes**, sous contrainte de
|
|
129
|
-
précision élevée, avec une pénalité spécifique aux entités courtes et fréquentes.*
|
|
130
|
-
Et surtout : **corriger la substitution avant de toucher au modèle** — c'est là que se joue le ratio.
|
|
131
|
-
|
|
132
|
-
---
|
|
133
|
-
|
|
134
|
-
## 3. Tes deux propositions
|
|
135
|
-
|
|
136
|
-
### ✅ « Les `\n` doivent être supprimés des .md (ou ne pas être produits) » — oui, et c'est plus grave que je ne l'avais dit
|
|
137
|
-
|
|
138
|
-
Je peux maintenant le prouver par le code. La substitution utilise `escapeRegex(original)` : une
|
|
139
|
-
entité contenant `\n`, un double espace ou un NBSP produit une regex qui n'accepte **que cette
|
|
140
|
-
séquence exacte de blancs**. Ces entités-là ne sont donc **jamais substituées nulle part** :
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
ORGANIZATION : 217 entités contiennent un \n · 18 un NBSP
|
|
144
|
-
PERSON : 41 · 21
|
|
145
|
-
LOCATION : 19 · 6
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**277 entités = fuite de PII réelle**, pas théorique. C'est le seul vrai cas de fuite que j'aie trouvé.
|
|
149
|
-
|
|
150
|
-
Sur le **où** corriger : à la conversion, comme tu le dis — c'est le seul endroit où les offsets du
|
|
151
|
-
`sensitive_map` restent cohérents avec le fichier que tout le monde lit ensuite. Mais pas un
|
|
152
|
-
`replace(/\n/g,' ')` brutal : il faut **préserver les sauts de paragraphe (`\n\n`), les listes et les
|
|
153
|
-
tableaux**, et ne réduire que les retours à la ligne *intra-paragraphe* (retours de justification PDF),
|
|
154
|
-
les espaces multiples et les NBSP. Règle à appliquer dans `smart_converter.py` en post-traitement de
|
|
155
|
-
markitdown, avant écriture du `.md`.
|
|
156
|
-
|
|
157
|
-
### ⚠️ « Quand une entité est repérée, supprimer les occurrences suivantes pour éviter à GLiNER de recommencer » — juste sur la bonne phase, sans effet sur l'autre
|
|
158
|
-
|
|
159
|
-
- **Phase d'extraction : aucun gain.** Le coût est **par chunk**, pas par entité — 1,22 s que le chunk
|
|
160
|
-
contienne 0 ou 20 entités. GLiNER fait une passe d'encodeur sur le chunk entier quoi qu'il arrive ;
|
|
161
|
-
on ne peut pas lui dire « ignore Dupont ». Et supprimer le texte décalerait tous les offsets, en plus
|
|
162
|
-
d'exiger de savoir que c'est de la PII *avant* de l'avoir cherchée.
|
|
163
|
-
- **Phase de classification : exactement ça, et c'est le gros morceau.** Celle-là est bien
|
|
164
|
-
*par occurrence* : **1 689 appels pour 576 noms distincts**. Mémoïser par nom normalisé, c'est
|
|
165
|
-
**−66 %** sur les 81 min. Ton intuition tombe juste sur la seule phase où elle s'applique.
|
|
166
|
-
- **Et un troisième usage, meilleur** : une fois l'ensemble des noms distincts connu, une passe
|
|
167
|
-
« gazetteer » (recherche de chaîne de chaque nom connu sur tout le document) rattrape gratuitement
|
|
168
|
-
ce que GLiNER a manqué. C'est précisément ce que fait déjà le `replace` global — d'où le §2.1.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## 4. Plan révisé, par ordre de priorité
|
|
173
|
-
|
|
174
|
-
### Étape 1 — Réparer la substitution 🔴 *avant tout le reste*
|
|
175
|
-
`anonymization-server.cjs:377-404`. Rien ne sert de régler le modèle tant qu'une détection correcte
|
|
176
|
-
produit un document détruit.
|
|
177
|
-
1. **Limites de mot** : `new RegExp("(?<!\\w)"+escapeRegex(original)+"(?!\\w)", "g")`.
|
|
178
|
-
→ supprime à elle seule ~11 000 des 28 956 substitutions, toutes fausses.
|
|
179
|
-
2. **Supprimer le flag `i`** (ou ne le garder que pour la première lettre). `us` ≠ `US`.
|
|
180
|
-
3. **Longueur minimale** : refuser toute entité < 4 caractères hors liste blanche explicite.
|
|
181
|
-
→ neutralise `CA`, `us`, `AU`, `EU`, `RU`, `ZA`, `HK`, `MX`, `JP`, `KR`, `CN`.
|
|
182
|
-
4. **Ordonner les substitutions par longueur décroissante** et remplacer par jeton non re-substituable,
|
|
183
|
-
pour que `French` ne mange pas `French Monetary and Financial Code`.
|
|
184
|
-
- **Critère d'acceptation** : sur le ALPHABIO, 0 substitution à l'intérieur d'un mot. Vérifiable exactement.
|
|
185
|
-
|
|
186
|
-
### Étape 2 — Normalisation des blancs à la conversion 🔴
|
|
187
|
-
Règle décrite en §3. Cible : **0 entité contenant `\n` ou double espace** dans le `sensitive_map`,
|
|
188
|
-
et les 1 273 « distincts » qui retombent à 971 (−24 % de faux distincts, cf. v2).
|
|
189
|
-
|
|
190
|
-
### Étape 3 — Arbitrage de spans 🔴
|
|
191
|
-
Supprimer tout span chevauchant ou inclus **quel que soit le type** (presidio ne le fait qu'à type
|
|
192
|
-
égal). → corrige les 81 double-typages et 179 chevauchements. Départage : longueur, puis score,
|
|
193
|
-
puis priorité PERSON > ORG > LOC — à valider sur corpus.
|
|
194
|
-
|
|
195
|
-
### Étape 4 — Corpus de référence annoté ⭐
|
|
196
|
-
Inchangé depuis la v2, et toujours le prérequis de toute décision « qualité » :
|
|
197
|
-
3 documents du Dossier AVION + un extrait ALPHABIO, outil d'annotation HTML local, ~1 h de ton temps.
|
|
198
|
-
Métrique révisée par le §2.3 : **rappel sur entités distinctes / précision, avec pénalité sur les
|
|
199
|
-
entités courtes et fréquentes**.
|
|
200
|
-
|
|
201
|
-
### Étape 5 — Faux positifs des pattern recognizers
|
|
202
|
-
`IpRecognizer` : 31/31 FP (numéros de section `3.7.2.2`). `UrlRecognizer` : `2023.Th`, `occur.Th`.
|
|
203
|
-
Vérifiable exactement, sans annotation.
|
|
204
|
-
|
|
205
|
-
### Étape 6 — Calibration du seuil, par type
|
|
206
|
-
Rappel : le seuil actuel de **0.3 ne filtre rien** (score minimum observé 0.401). Balayage
|
|
207
|
-
0.30→0.90 sur le corpus. Tester aussi `0.1` : s'il apparaît des entités sous 0.4, il y a du rappel
|
|
208
|
-
gratuit ; sinon c'est un plancher interne du modèle. Mesure, pas hypothèse.
|
|
209
|
-
|
|
210
|
-
### Étape 7 — Supprimer `classify_text`
|
|
211
|
-
Regex de forme juridique sur le nom normalisé ; absence de forme → `ORGANIZATION`.
|
|
212
|
-
Gain : **81 min**, et surtout suppression des `Osakeyhtiö`/`EIRELI` du §1.
|
|
213
|
-
Couverture mesurée **sur ce document** : 11 % des noms portent une forme littérale (63/576).
|
|
214
|
-
**À re-mesurer sur le Dossier AVION** (Kbis « URGOT SA », « CAITLYN SA ») où elle sera bien plus haute.
|
|
215
|
-
|
|
216
|
-
### Étape 8 — Choix du checkpoint (A/B sur corpus)
|
|
217
|
-
`gliner2-privacy-filter-PII-multi` — même backbone, donc **même vitesse**, spécialisé PII.
|
|
218
|
-
Décision sur les chiffres du corpus uniquement.
|
|
219
|
-
|
|
220
|
-
### Étape 9 — Vitesse, chaque levier conditionné à un score corpus inchangé ou meilleur
|
|
221
|
-
| levier | gain mesuré | risque qualité |
|
|
222
|
-
|---|---|---|
|
|
223
|
-
| spaCy en tokenizer seul | −1,5 min, −1 Go RSS | nul (le NER spaCy est déjà jeté) |
|
|
224
|
-
| `torch.set_num_threads(6)` | −18 % sur l'extraction | nul |
|
|
225
|
-
| mémoïsation classification par nom distinct | −66 % *(sans objet si étape 7 faite)* | nul |
|
|
226
|
-
| recouvrement 50 → ~15 mots | −15 à −20 % de chunks | à mesurer sur corpus |
|
|
227
|
-
| taille de chunk | — | ⚠️ ne pas augmenter : p90 = 485 tokens / 512, 1 % des chunks débordent déjà |
|
|
228
|
-
| MPS · int8 | ⛔ écartés, mesurés | MPS 29,6 vs 23,0 min ; int8 indisponible (`NoQEngine`) |
|
|
229
|
-
|
|
230
|
-
### Étape 10 — Fuite PII dans les logs
|
|
231
|
-
`scanner_worker.py:495` — chaque entité en clair sur stderr, capturé par Electron.
|
|
232
|
-
Flag `PIECEMAKER_DEBUG_ENTITIES`, off par défaut.
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## 5. Bilan
|
|
237
|
-
|
|
238
|
-
**Qualité** — l'essentiel du ratio ne se joue pas dans GLiNER : il se joue dans la substitution
|
|
239
|
-
(28 956 remplacements dont ~11 000 à l'intérieur de mots), la normalisation des blancs (277 entités
|
|
240
|
-
jamais substituées = la seule vraie fuite trouvée), et l'arbitrage de spans. Ces trois corrections
|
|
241
|
-
coûtent **zéro milliseconde d'inférence**.
|
|
242
|
-
|
|
243
|
-
**Vitesse** — ~102 min → ~20 min, dont 81 min gagnées en supprimant une étape qui produit des
|
|
244
|
-
réponses fausses.
|
|
245
|
-
|
|
246
|
-
**Ce qui reste inconnu** — précision et rappel réels. Étape 4.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Encodeur CoreML — génération et déploiement
|
|
2
|
-
|
|
3
|
-
`coreml_runtime.py` fait tourner l'encodeur mdeberta sur le GPU du Mac : **2,1× sur le document
|
|
4
|
-
complet, sortie identique** (mesures et méthode dans `eval/BACKENDS_MESURES.md`). Si le modèle
|
|
5
|
-
compilé est absent, le worker retombe sur torch sans erreur — cette étape est donc **optionnelle**,
|
|
6
|
-
elle n'achète que la vitesse.
|
|
7
|
-
|
|
8
|
-
## Générer le modèle (~4 min, une fois)
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
cd eval
|
|
12
|
-
PIECEMAKER_COREML_SEQ=832 python3 coreml_encoder.py convert # ~2 min -> .mlpackage
|
|
13
|
-
python3 - <<'PY'
|
|
14
|
-
import shutil, coremltools as ct
|
|
15
|
-
m = ct.models.MLModel('artifacts/encoder_b1_832.mlpackage',
|
|
16
|
-
compute_units=ct.ComputeUnit.CPU_AND_GPU) # ~2 min de compilation
|
|
17
|
-
shutil.copytree(m.get_compiled_model_path(), 'artifacts/encoder_b1_832.mlmodelc')
|
|
18
|
-
PY
|
|
19
|
-
mkdir -p ../models && cp -R artifacts/encoder_b1_832.mlmodelc ../models/
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Prérequis : `pip install coremltools` (n'a rétrogradé aucune dépendance sur cette machine).
|
|
23
|
-
|
|
24
|
-
## Pourquoi un `.mlmodelc` et pas le `.mlpackage`
|
|
25
|
-
|
|
26
|
-
Charger le `.mlpackage` **recompile à chaque démarrage** : 97 s, mesuré deux fois de suite, parce
|
|
27
|
-
que chaque chargement compile dans un répertoire temporaire que le cache système ne reconnaît pas.
|
|
28
|
-
Un `.mlmodelc` persisté touche ce cache : **97 s au tout premier chargement, puis ~6 s**. C'est la
|
|
29
|
-
différence entre inutilisable et livrable.
|
|
30
|
-
|
|
31
|
-
## Pourquoi 832 tokens
|
|
32
|
-
|
|
33
|
-
Le padding se fait sur la séquence la plus longue du lot. Longueurs réelles mesurées :
|
|
34
|
-
|
|
35
|
-
| document | longueurs des lots |
|
|
36
|
-
|---|---|
|
|
37
|
-
| `Assignation_URGOT_vs_CAITLYN` | 741 · 781 · 811 |
|
|
38
|
-
| `Conclusions_Assignation_URGOT` | 770 · 811 |
|
|
39
|
-
|
|
40
|
-
À 772 (première tentative), 2 lots sur 3 de l'Assignation retombaient sur torch et le gain tombait
|
|
41
|
-
à 1,13×. À 832 : **0 repli en français**, 11 lots sur 122 sur le ALPHABIO anglais.
|
|
42
|
-
|
|
43
|
-
## Pourquoi le GPU et pas le Neural Engine
|
|
44
|
-
|
|
45
|
-
`CPU_AND_NE` a été mesuré **3,3× plus lent que le CPU** sur ce modèle (3,60 s contre 1,10 s).
|
|
46
|
-
L'attention désenchevêtrée de deberta-v3 est le motif que l'ANE traite le plus mal. `CPU_AND_GPU`
|
|
47
|
-
est un choix mesuré.
|
|
48
|
-
|
|
49
|
-
## Désactiver
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
PIECEMAKER_COREML=0 # repli torch explicite
|
|
53
|
-
PIECEMAKER_COREML_MODEL=/chemin/vers/encoder_b1_<seq>.mlmodelc
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
La longueur de séquence est lue dans le nom du fichier (`encoder_b1_832.mlmodelc` → 832).
|
|
57
|
-
|
|
58
|
-
## Déploiement
|
|
59
|
-
|
|
60
|
-
Le `.mlmodelc` fait 620 Mo et n'est **pas** versionné (`.gitignore`). Deux options :
|
|
61
|
-
soit l'empaqueter avec l'app, soit le générer à la première exécution. Tant qu'il est absent, le
|
|
62
|
-
worker tourne en torch — correct, seulement plus lent.
|
|
Binary file
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Génère une fois l'encodeur CoreML, pour que les scans GLiNER tournent sur le GPU.
|
|
3
|
-
|
|
4
|
-
Le runtime (`coreml_runtime.py`) accélère l'encodeur mdeberta sur le GPU du Mac
|
|
5
|
-
et libère surtout les cœurs CPU pour que la machine reste utilisable pendant un
|
|
6
|
-
scan. Les mesures historiques sont documentées dans `eval/BACKENDS_MESURES.md` ;
|
|
7
|
-
GLiNER2.5 reçoit son propre artefact compilé à partir de ses poids exacts.
|
|
8
|
-
|
|
9
|
-
Ce script est **best-effort et idempotent** :
|
|
10
|
-
- il ne refait rien si le `.mlmodelc` est déjà là ;
|
|
11
|
-
- il sort 0 quoi qu'il arrive (dépendance manquante, conversion refusée…), car
|
|
12
|
-
le runtime retombe seul sur torch — un scan ne doit jamais échouer à cause
|
|
13
|
-
d'une optimisation.
|
|
14
|
-
|
|
15
|
-
Il réutilise `Wrap` et `_patch_deberta_scale` de `eval/coreml_encoder.py` pour ne
|
|
16
|
-
pas dupliquer les correctifs de traçage deberta, mais synthétise ses propres
|
|
17
|
-
entrées (la conversion ne dépend que de leur forme, pas de leurs valeurs).
|
|
18
|
-
|
|
19
|
-
python3 build_coreml.py # génère l'encodeur GLiNER2.5 dans models/
|
|
20
|
-
PIECEMAKER_COREML_SEQ=832 # longueur de séquence (défaut 832)
|
|
21
|
-
"""
|
|
22
|
-
import os
|
|
23
|
-
import shutil
|
|
24
|
-
import sys
|
|
25
|
-
import tempfile
|
|
26
|
-
|
|
27
|
-
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
28
|
-
SEQ = int(os.environ.get("PIECEMAKER_COREML_SEQ", "832"))
|
|
29
|
-
MODELS_DIR = os.path.join(HERE, "models")
|
|
30
|
-
|
|
31
|
-
from model_config import PREFERRED_GLINER_MODEL # noqa: E402
|
|
32
|
-
from coreml_runtime import default_model_path # noqa: E402
|
|
33
|
-
|
|
34
|
-
GLINER_MODEL = PREFERRED_GLINER_MODEL
|
|
35
|
-
TARGET = default_model_path(GLINER_MODEL, SEQ)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _log(msg):
|
|
39
|
-
print(msg, flush=True)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def main():
|
|
43
|
-
if os.path.exists(TARGET):
|
|
44
|
-
_log(f"Encodeur CoreML déjà présent : {TARGET}")
|
|
45
|
-
return 0
|
|
46
|
-
|
|
47
|
-
try:
|
|
48
|
-
import numpy as np
|
|
49
|
-
import torch
|
|
50
|
-
import coremltools as ct
|
|
51
|
-
from gliner2 import AutoExtractor
|
|
52
|
-
except Exception as exc: # noqa: BLE001
|
|
53
|
-
_log(f"CoreML non généré (dépendance manquante : {type(exc).__name__}: {exc}) — les scans resteront sur CPU torch")
|
|
54
|
-
return 0
|
|
55
|
-
|
|
56
|
-
# Correctifs de traçage deberta partagés avec l'outil d'évaluation.
|
|
57
|
-
sys.path.insert(0, os.path.join(HERE, "eval"))
|
|
58
|
-
try:
|
|
59
|
-
from coreml_encoder import Wrap, _patch_deberta_scale
|
|
60
|
-
except Exception as exc: # noqa: BLE001
|
|
61
|
-
_log(f"CoreML non généré (helpers de conversion introuvables : {exc}) — CPU torch")
|
|
62
|
-
return 0
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
torch.set_grad_enabled(False)
|
|
66
|
-
_patch_deberta_scale()
|
|
67
|
-
|
|
68
|
-
# Entrées factices de la bonne forme : seule la forme (1 x SEQ) est gravée
|
|
69
|
-
# dans le modèle converti, pas les valeurs.
|
|
70
|
-
ids = torch.zeros((1, SEQ), dtype=torch.long)
|
|
71
|
-
mask = torch.ones((1, SEQ), dtype=torch.long)
|
|
72
|
-
|
|
73
|
-
_log(f"Chargement de {GLINER_MODEL}...")
|
|
74
|
-
model = AutoExtractor.from_pretrained(
|
|
75
|
-
GLINER_MODEL,
|
|
76
|
-
local_files_only=True,
|
|
77
|
-
).eval()
|
|
78
|
-
wrap = Wrap(model.encoder).eval()
|
|
79
|
-
del model
|
|
80
|
-
|
|
81
|
-
_log("Traçage du modèle...")
|
|
82
|
-
traced = torch.jit.trace(wrap, (ids, mask), strict=False)
|
|
83
|
-
|
|
84
|
-
_log("Conversion en MLProgram (fp16)...")
|
|
85
|
-
mlmodel = ct.convert(
|
|
86
|
-
traced,
|
|
87
|
-
inputs=[ct.TensorType(name="input_ids", shape=ids.shape, dtype=np.int32),
|
|
88
|
-
ct.TensorType(name="attention_mask", shape=mask.shape, dtype=np.int32)],
|
|
89
|
-
outputs=[ct.TensorType(name="last_hidden_state")],
|
|
90
|
-
convert_to="mlprogram",
|
|
91
|
-
compute_precision=ct.precision.FLOAT16,
|
|
92
|
-
minimum_deployment_target=ct.target.macOS14,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
with tempfile.TemporaryDirectory() as tmp:
|
|
96
|
-
pkg = os.path.join(tmp, f"encoder_b1_{SEQ}.mlpackage")
|
|
97
|
-
mlmodel.save(pkg)
|
|
98
|
-
|
|
99
|
-
# Le .mlmodelc persisté touche le cache système : ~6 s au chargement
|
|
100
|
-
# ensuite, contre ~97 s si l'on rechargeait le .mlpackage à chaque fois
|
|
101
|
-
# (cf. MODELE_COREML.md). On garde une référence vivante le temps de la
|
|
102
|
-
# copie, sinon le répertoire compilé temporaire peut disparaître.
|
|
103
|
-
_log("Compilation en .mlmodelc (peut prendre ~2 min)...")
|
|
104
|
-
compiled_model = ct.models.MLModel(pkg, compute_units=ct.ComputeUnit.CPU_AND_GPU)
|
|
105
|
-
os.makedirs(MODELS_DIR, exist_ok=True)
|
|
106
|
-
if os.path.exists(TARGET):
|
|
107
|
-
shutil.rmtree(TARGET, ignore_errors=True)
|
|
108
|
-
shutil.copytree(compiled_model.get_compiled_model_path(), TARGET)
|
|
109
|
-
|
|
110
|
-
_log(f"Encodeur GPU CoreML prêt : {TARGET}")
|
|
111
|
-
return 0
|
|
112
|
-
except Exception as exc: # noqa: BLE001
|
|
113
|
-
_log(f"Génération CoreML échouée ({type(exc).__name__}: {exc}) — les scans resteront sur CPU torch, sans conséquence sur la qualité")
|
|
114
|
-
return 0
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if __name__ == "__main__":
|
|
118
|
-
sys.exit(main())
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Run mdeberta's encoder on the Mac GPU through CoreML instead of the CPU.
|
|
3
|
-
|
|
4
|
-
The encoder dominates a scan's wall clock. Historical GLiNER2 measurements showed that a
|
|
5
|
-
CoreML/GPU MLProgram ran it about twice as fast as torch on this hardware (see
|
|
6
|
-
eval/BACKENDS_MESURES.md §6):
|
|
7
|
-
|
|
8
|
-
ALPHABIO_URD_2023 972 chunks 18,76 min -> 8,96 min 2,09x
|
|
9
|
-
Assignation URGOT 18 chunks 0,932 -> 0,403 s/chunk 2,31x
|
|
10
|
-
Conclusions 13 chunks 0,999 -> 0,441 s/chunk 2,27x
|
|
11
|
-
|
|
12
|
-
Those quality and speed measurements concern the historical checkpoint. GLiNER2.5 gets a
|
|
13
|
-
separate compiled artifact so old weights can never be injected into the new model.
|
|
14
|
-
|
|
15
|
-
NOT the Neural Engine: `CPU_AND_NE` measured **3,3x slower** than the CPU on this model.
|
|
16
|
-
deberta-v3's disentangled attention is the pattern the ANE handles worst. `CPU_AND_GPU` is
|
|
17
|
-
a measured choice, not a default.
|
|
18
|
-
|
|
19
|
-
Everything degrades to plain torch: coremltools missing, model file missing, a load failure,
|
|
20
|
-
a prediction failure, or a sequence longer than the converted shape — each falls back
|
|
21
|
-
correctly. A scan must never fail because of an optimisation.
|
|
22
|
-
|
|
23
|
-
Environment:
|
|
24
|
-
PIECEMAKER_COREML=0 disable entirely (default: enabled)
|
|
25
|
-
PIECEMAKER_COREML_MODEL=... path to the .mlmodelc (default: model-specific
|
|
26
|
-
file in models/ next to this file)
|
|
27
|
-
"""
|
|
28
|
-
import os
|
|
29
|
-
import sys
|
|
30
|
-
|
|
31
|
-
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
32
|
-
DEFAULT_SEQ = 832
|
|
33
|
-
LEGACY_MODEL_ID = "fastino/gliner2-multi-v1"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def default_model_path(model_id=None, seq_len=DEFAULT_SEQ):
|
|
37
|
-
"""Return a CoreML artifact tied to the exact checkpoint weights.
|
|
38
|
-
|
|
39
|
-
Reusing the historical GLiNER2 encoder with GLiNER2.5 would silently
|
|
40
|
-
produce invalid detections. Keep the old filename only for the old model;
|
|
41
|
-
every newer checkpoint gets its own explicit artifact.
|
|
42
|
-
"""
|
|
43
|
-
if not model_id or model_id == LEGACY_MODEL_ID:
|
|
44
|
-
filename = f"encoder_b1_{seq_len}.mlmodelc"
|
|
45
|
-
else:
|
|
46
|
-
slug = str(model_id).rsplit("/", 1)[-1].replace("/", "-")
|
|
47
|
-
filename = f"{slug}-encoder_b1_{seq_len}.mlmodelc"
|
|
48
|
-
return os.path.join(_HERE, "models", filename)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
DEFAULT_MODEL = default_model_path()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _log(msg):
|
|
55
|
-
print(msg, file=sys.stderr, flush=True)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class _Out:
|
|
59
|
-
"""Duck-types the HF model output — gliner2 only reads `.last_hidden_state`."""
|
|
60
|
-
|
|
61
|
-
__slots__ = ("last_hidden_state",)
|
|
62
|
-
|
|
63
|
-
def __init__(self, h):
|
|
64
|
-
self.last_hidden_state = h
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class CoreMLEncoder:
|
|
68
|
-
"""Callable replacement for `model.encoder.forward`.
|
|
69
|
-
|
|
70
|
-
The converted model has a fixed shape (1 x seq_len), so each sequence is padded to
|
|
71
|
-
seq_len and predicted on its own. Sequences longer than seq_len — 11 batches of 122 on
|
|
72
|
-
ALPHABIO, none on the French corpus — go to torch, which is why the original forward is
|
|
73
|
-
kept rather than replaced.
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
def __init__(self, mlmodel, torch_forward, seq_len):
|
|
77
|
-
import numpy as np
|
|
78
|
-
import torch
|
|
79
|
-
|
|
80
|
-
self._np = np
|
|
81
|
-
self._torch = torch
|
|
82
|
-
self.m = mlmodel
|
|
83
|
-
self.torch_forward = torch_forward
|
|
84
|
-
self.seq_len = seq_len
|
|
85
|
-
self.calls = 0
|
|
86
|
-
self.fallbacks = 0
|
|
87
|
-
|
|
88
|
-
def __call__(self, input_ids=None, attention_mask=None, **kw):
|
|
89
|
-
torch, np = self._torch, self._np
|
|
90
|
-
self.calls += 1
|
|
91
|
-
|
|
92
|
-
if input_ids is None or attention_mask is None:
|
|
93
|
-
return self.torch_forward(input_ids=input_ids, attention_mask=attention_mask, **kw)
|
|
94
|
-
|
|
95
|
-
_, s = input_ids.shape
|
|
96
|
-
if s > self.seq_len:
|
|
97
|
-
self.fallbacks += 1
|
|
98
|
-
return self.torch_forward(input_ids=input_ids, attention_mask=attention_mask, **kw)
|
|
99
|
-
|
|
100
|
-
pad = self.seq_len - s
|
|
101
|
-
ids = torch.nn.functional.pad(input_ids, (0, pad)).numpy().astype(np.int32)
|
|
102
|
-
msk = torch.nn.functional.pad(attention_mask, (0, pad)).numpy().astype(np.int32)
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
rows = [self.m.predict({"input_ids": ids[i:i + 1], "attention_mask": msk[i:i + 1]})
|
|
106
|
-
["last_hidden_state"] for i in range(ids.shape[0])]
|
|
107
|
-
except Exception as exc: # noqa: BLE001
|
|
108
|
-
self.fallbacks += 1
|
|
109
|
-
_log(f"CoreML predict failed ({type(exc).__name__}: {exc}) — falling back to torch")
|
|
110
|
-
return self.torch_forward(input_ids=input_ids, attention_mask=attention_mask, **kw)
|
|
111
|
-
|
|
112
|
-
h = np.concatenate(rows, axis=0)[:, :s, :].astype(np.float32)
|
|
113
|
-
return _Out(torch.from_numpy(h))
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# The worker builds one GLiNER2Recognizer per language and pre-loads a shared model, so
|
|
117
|
-
# maybe_accelerate is reached three times per process. Loading the .mlmodelc three times
|
|
118
|
-
# took startup from 63 s to 208 s (measured); the model is immutable, so cache it.
|
|
119
|
-
_MLMODEL_CACHE = {}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def _seq_len_from_path(path: str) -> int:
|
|
123
|
-
"""The sequence length is encoded in the filename (encoder_b1_832.mlmodelc).
|
|
124
|
-
|
|
125
|
-
Guessing wrong in the safe direction matters: too small only costs speed (more torch
|
|
126
|
-
fallbacks), while too large would feed the model a shape it was not converted for.
|
|
127
|
-
"""
|
|
128
|
-
base = os.path.basename(path)
|
|
129
|
-
tail = base.rsplit("_", 1)[-1].split(".")[0] if "_" in base else ""
|
|
130
|
-
return int(tail) if tail.isdigit() else DEFAULT_SEQ
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def maybe_accelerate(model, model_id=None) -> bool:
|
|
134
|
-
"""Point `model.encoder.forward` at CoreML when it is available and wanted.
|
|
135
|
-
|
|
136
|
-
Returns True if the encoder was swapped. Never raises.
|
|
137
|
-
"""
|
|
138
|
-
if os.environ.get("PIECEMAKER_COREML", "1").lower() in ("0", "false", "no"):
|
|
139
|
-
_log("CoreML disabled (PIECEMAKER_COREML=0) — torch CPU")
|
|
140
|
-
return False
|
|
141
|
-
|
|
142
|
-
path = os.environ.get("PIECEMAKER_COREML_MODEL", default_model_path(model_id))
|
|
143
|
-
if not os.path.exists(path):
|
|
144
|
-
_log(f"CoreML model absent ({path}) — torch CPU")
|
|
145
|
-
return False
|
|
146
|
-
|
|
147
|
-
if path in _MLMODEL_CACHE:
|
|
148
|
-
mlmodel = _MLMODEL_CACHE[path]
|
|
149
|
-
if mlmodel is None: # a previous attempt already failed; don't retry per recognizer
|
|
150
|
-
return False
|
|
151
|
-
else:
|
|
152
|
-
try:
|
|
153
|
-
import coremltools as ct
|
|
154
|
-
|
|
155
|
-
mlmodel = ct.models.CompiledMLModel(path, compute_units=ct.ComputeUnit.CPU_AND_GPU)
|
|
156
|
-
except Exception as exc: # noqa: BLE001
|
|
157
|
-
_MLMODEL_CACHE[path] = None
|
|
158
|
-
_log(f"CoreML unavailable ({type(exc).__name__}: {exc}) — torch CPU")
|
|
159
|
-
return False
|
|
160
|
-
_MLMODEL_CACHE[path] = mlmodel
|
|
161
|
-
_log(f"CoreML GPU encoder active (seq {_seq_len_from_path(path)}) — ~2x faster than torch CPU")
|
|
162
|
-
|
|
163
|
-
model.encoder.forward = CoreMLEncoder(mlmodel, model.encoder.forward, _seq_len_from_path(path))
|
|
164
|
-
return True
|