diff --git a/KNN-SCAN-flusso-shadow.md b/KNN-SCAN-flusso-shadow.md new file mode 100644 index 0000000..7640bbf --- /dev/null +++ b/KNN-SCAN-flusso-shadow.md @@ -0,0 +1,162 @@ +# Flusso KNN Shadow — progetto s190js + +## Panoramica + +Il confronto KNN in modalità **shadow** viene eseguito lato **server Node.js**, non nel browser. Il flusso parte dal worker che aggiorna i prototipi facciali e arriva al binario nativo `face_knn`, passando attraverso il modulo di confronto, il runtime KNN e uno script shell. + +```text +Server Node.js +└── tools/runPrototypeWorker.js + └── tools/runFaceKnnShadow.js + └── tools/faceKnnRuntime.js + └── native/face-knn/run-face-knn.sh + └── native/face-knn/bin/face_knn +``` + +Percorso del progetto usato negli esempi: + +```bash +/home/nvme/dockerdata/prove/s190js +``` + +## Ruolo dei componenti + +### 1. `tools/runPrototypeWorker.js` — worker Node.js + +È il processo server che gestisce i job di aggiornamento dei prototipi facciali. + +In sintesi: + +1. recupera un job in stato `pending`; +2. lo porta in elaborazione (`processing`); +3. esegue la procedura principale di selezione dei prototipi; +4. salva il risultato nel database; +5. dopo il salvataggio, avvia il confronto KNN shadow. + +Il confronto shadow viene quindi eseguito **dopo** il salvataggio del risultato principale. Non è il componente che decide quali prototipi selezionare nella procedura ordinaria. + +### 2. `tools/runFaceKnnShadow.js` — confronto KNN in modalità shadow + +Questo modulo coordina il confronto tra i risultati KNN nativi e quelli di riferimento prodotti dall'esperimento. + +Il suo compito è verificare e registrare eventuali differenze. In modalità `shadow`, il risultato del KNN nativo è usato per validazione e analisi: non sostituisce la selezione principale dei prototipi. + +Eventuali errori o discrepanze del confronto vengono registrati nei log; secondo il flusso esaminato, non annullano il salvataggio dei prototipi già completato. + +### 3. `tools/faceKnnRuntime.js` — runtime KNN + +Questo modulo prepara e avvia l'esecuzione del KNN nativo. + +In base al codice esaminato, si occupa di attività come: + +- leggere la configurazione KNN; +- recuperare gli embedding delle facce e i prototipi da confrontare; +- preparare i file di input temporanei richiesti dal programma nativo; +- invocare il launcher configurato; +- leggere e interpretare l'output JSON del binario. + +Il launcher è configurato nel file: + +```text +native/face-knn/config.json +``` + +Il valore atteso per `launcher` è: + +```text +native/face-knn/run-face-knn.sh +``` + +L'effettiva esecuzione dipende dalla configurazione: il KNN deve essere abilitato e la modalità shadow deve essere attiva. + +### 4. `native/face-knn/run-face-knn.sh` — script shell + +È il punto di ingresso shell usato per lanciare il programma nativo. + +Lo script cambia nella propria directory e poi esegue il binario: + +```text +./bin/face_knn +``` + +Questo passaggio permette al programma nativo di trovare correttamente i file relativi alla propria directory, come il kernel OpenCL, senza dipendere dalla directory da cui è stato avviato il server. + +Lo script è quindi un **launcher lato server**. Non viene eseguito dal browser. + +### 5. `native/face-knn/bin/face_knn` — binario nativo + +È il programma compilato che esegue il confronto KNN. + +Riceve dal runtime i percorsi dei file di input e i parametri necessari, come il numero di query, il numero di prototipi, la dimensione degli embedding, il valore di `top-k` e il backend richiesto. + +Il programma restituisce i risultati al processo Node.js, che li interpreta e li usa nel rapporto di confronto shadow. + +## Quando viene eseguito il flusso + +Il flusso non viene eseguito semplicemente perché un utente apre una pagina web. + +Viene avviato dal worker server durante l'elaborazione di un job di aggiornamento dei prototipi, dopo il completamento del salvataggio principale, se la configurazione KNN abilita il confronto shadow. + +L'assegnazione di un cluster a una persona accoda un job di aggiornamento dei prototipi. Il daemon/worker elabora quel job; il confronto KNN shadow è una fase successiva del lavoro server. + +## Cosa fa e cosa non fa la modalità shadow + +| Attività | Componente principale | +|---|---| +| Gestire il job di aggiornamento | `runPrototypeWorker.js` | +| Selezionare e salvare i prototipi principali | Worker e procedura di selezione | +| Coordinare il confronto di validazione | `runFaceKnnShadow.js` | +| Preparare input e invocare il programma nativo | `faceKnnRuntime.js` | +| Avviare il binario | `run-face-knn.sh` | +| Eseguire il calcolo KNN nativo | `bin/face_knn` | + +**Importante:** in modalità `shadow`, il KNN nativo è un controllo parallelo di validazione. Non è il motore che determina direttamente quali prototipi vengono salvati. Per cambiare questo comportamento servirebbe una modifica esplicita al flusso applicativo e una verifica separata. + +## Verifiche sul server + +Per controllare la configurazione effettiva e i punti di collegamento, eseguire sul server: + +```bash +cd /home/nvme/dockerdata/prove/s190js + +echo "=== Configurazione KNN ===" +cat native/face-knn/config.json + +echo +echo "=== Collegamenti nel codice ===" +grep -n -E \ + 'runFaceKnnShadow|runNativeKnn|launcher|spawnSync' \ + tools/runPrototypeWorker.js \ + tools/runFaceKnnShadow.js \ + tools/faceKnnRuntime.js +``` + +Per verificare che lo script shell lanci il binario previsto: + +```bash +cd /home/nvme/dockerdata/prove/s190js + +cat native/face-knn/run-face-knn.sh +ls -l native/face-knn/run-face-knn.sh \ + native/face-knn/bin/face_knn +``` + +## Nota sul fallback + +La configurazione può contenere un backend di fallback, ad esempio CPU. La sola presenza di un'impostazione come `fallbackBackend` non dimostra però che il codice esegua automaticamente un secondo tentativo in caso di errore. Per affermarlo con certezza occorre verificare nel runtime il percorso di gestione degli errori e l'eventuale seconda invocazione. + +## Conclusione + +La catena è interamente lato server: + +```text +runPrototypeWorker.js + → runFaceKnnShadow.js + → faceKnnRuntime.js + → run-face-knn.sh + → bin/face_knn +``` + +Il worker principale salva i prototipi; successivamente, se la configurazione lo consente, viene lanciato il confronto KNN shadow per confrontare e validare i risultati. Il browser non esegue né lo script shell né il binario nativo. + +Questo documento descrive il flusso ricostruito dal codice esaminato. Per confermare che una specifica esecuzione sia avvenuta, occorre controllare la configurazione attiva e i log del worker. diff --git a/KNN-SCAN.md b/KNN-SCAN.md new file mode 100644 index 0000000..4de8705 --- /dev/null +++ b/KNN-SCAN.md @@ -0,0 +1,164 @@ +# Gestione dei prototipi facciali: flusso di assegnazione e aggiornamento + +**Progetto:** server fotografico `s190js` +**Percorso di riferimento:** `/home/nvme/dockerdata/prove/s190js` +**Scopo:** descrivere il comportamento verificato nel codice, dalla nomina di un cluster al salvataggio dei prototipi usati per il riconoscimento facciale. + +> **Nota sul livello di verifica:** questo documento descrive il codice esaminato durante l'analisi. Non dimostra che uno specifico job sia stato completato nel database in produzione: per quello occorre controllare i record reali. + +## 1. Panoramica + +Quando si assegna una persona a un cluster dalla revisione dei volti, la route aggiorna le associazioni dei volti e richiede un aggiornamento dei prototipi per quella persona. + +La richiesta viene registrata nella tabella `face_prototype_jobs`. Un daemon controlla la coda e avvia `tools/runPrototypeWorker.js` in modalità persistente (`--persist`). Il worker esegue la selezione dei prototipi e, se l'elaborazione riesce, salva i risultati in una transazione SQLite. + +Il confronto KNN/OpenCL in modalità `shadow` viene eseguito **dopo** il salvataggio. Serve a confrontare i risultati e non decide quali prototipi vengono salvati. + +## 2. Componenti coinvolti + +| Componente | Responsabilità | +|---|---| +| `routes/faceReview.js` | Gestisce l'assegnazione della persona al cluster e richiede l'aggiornamento dei prototipi. | +| `queuePrototypeRefresh()` in `routes/faceReview.js` | Crea o aggiorna un job `pending` per la persona e l'utente. | +| Tabella `face_prototype_jobs` | Registra le richieste, lo stato, i tentativi, gli errori e il risultato. | +| `tools/runPrototypeDaemon.js` | Controlla periodicamente la coda, recupera alcuni job bloccati e avvia il worker. | +| `tools/runPrototypeWorker.js` | Calcola la selezione, salva i prototipi e le valutazioni e aggiorna lo stato del job. | +| `person_face_prototypes` | Contiene i prototipi facciali associati alle persone. | +| `person_face_recognition_evaluations` | Contiene le valutazioni di riconoscimento calcolate dal worker. | +| `face_recognition_index_state` | Indica che l'indice di riconoscimento deve essere aggiornato (`dirty`). | +| `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js` | Eseguono il confronto KNN in modalità shadow, se abilitato e configurato. | + +## 3. Dall'assegnazione del cluster alla coda + +Il frontend invia una richiesta alla route: + +```text +POST /face-review/clusters/:clusterId/assign-person +``` + +con i dati della persona e gli ID dei volti inclusi. Nel codice esaminato, la route: + +1. verifica che la persona e il cluster siano validi; +2. verifica che i volti indicati appartengano al cluster; +3. aggiorna le membership, segnando i volti inclusi come accettati e quelli esclusi come esclusi; +4. assegna `person_id` ai volti inclusi e rimuove eventuali esclusioni corrispondenti; +5. gestisce i volti esclusi e le relative righe in `person_face_exclusions`; +6. aggiorna `face_clusters.person_id`; +7. chiama `queuePrototypeRefresh()` con `reason: 'cluster_assigned'`. + +Questi aggiornamenti e l'accodamento sono eseguiti nel contesto della transazione della route. + +### Come viene accorpata la coda + +`queuePrototypeRefresh()` cerca un job con lo stesso `user`, lo stesso `person_id` e `status = 'pending'`. + +- **Se lo trova:** aggiorna il job esistente, compresi `reason`, `requested_by` e `requested_at`. +- **Se non lo trova:** crea un nuovo job con `status = 'pending'`. + +Il riutilizzo riguarda solo i job `pending`. Se esiste già un job `processing`, questa funzione non lo aggiorna: può creare un nuovo job `pending`. + +## 4. Come il daemon esegue i job + +Il `package.json` definisce: + +```json +"prototype:daemon": "node tools/runPrototypeDaemon.js", +"prototype:worker": "node tools/runPrototypeWorker.js --persist", +"prototype:once": "node tools/runPrototypeDaemon.js --once" +``` + +Il daemon: + +1. conta i job con `status = 'pending'`; +2. se non ne trova, attende l'intervallo configurato e ricontrolla (salvo la modalità `--once`); +3. se ne trova, avvia `tools/runPrototypeWorker.js --persist`; +4. dopo un successo, può avviare immediatamente un altro job pending; +5. se il worker fallisce, registra l'errore nei log e attende prima di ricontrollare la coda. + +### Recupero dei job bloccati + +`recoverStaleJobs()` può riportare da `processing` a `pending` i job che hanno `started_at` più vecchio della soglia `staleMinutes` configurata. Il job viene contrassegnato con il motivo `stale_processing_recovered`. + +Questa è una protezione per i job rimasti bloccati in elaborazione; non equivale a un nuovo tentativo automatico di tutti i job in stato `error`. + +## 5. Cosa fa il worker + +### 5.1 Selezione e acquisizione + +`loadPendingJob()` cerca un job `pending` unito alla tabella `persons`, ordinando per `requested_at` crescente. Quando inizia il salvataggio persistente, `claimJob()` tenta di cambiare lo stato da `pending` a `processing`, incrementando `attempts` e registrando la firma degli input. + +Se un altro worker ha già acquisito il job, l'aggiornamento condizionale non riesce e il worker segnala un errore. + +### 5.2 Calcolo e controlli + +Il worker carica i prototipi correnti, le esclusioni e una firma dei dati di input. Esegue l'esperimento di selezione e filtra i volti esclusi. Se non resta alcun prototipo selezionato, il job fallisce. + +Prima di salvare, `persistResult()` ricalcola la firma degli input. Se è cambiata durante l'elaborazione: + +- il job corrente diventa `superseded`; +- viene creato o riutilizzato un job sostitutivo `pending`; +- il risultato registra l'ID del job sostitutivo. + +Questo evita di applicare una selezione calcolata su dati che nel frattempo sono cambiati. + +### 5.3 Salvataggio dei prototipi + +Quando gli input sono ancora validi, `persistResult()`: + +1. carica le esclusioni di riferimento; +2. mantiene attivi i prototipi bloccati (`locked`) che non sono esclusi; +3. disattiva i prototipi non bloccati precedenti della persona; +4. inserisce o aggiorna i prototipi automatici selezionati in `person_face_prototypes`; +5. aggiorna l'ordine di selezione e i metadati; +6. ricalcola e salva le valutazioni in `person_face_recognition_evaluations`; +7. aggiorna `face_recognition_index_state` impostando `status = 'dirty'` e `dirty = 1`; +8. porta il job a `done`, registra `completed_at` e salva il riepilogo in `result_json`. + +Le operazioni descritte sono eseguite dentro una singola transazione SQLite. Se la transazione fallisce, le modifiche non vengono confermate. + +## 6. Errori e stato dei job + +| Stato | Significato nel flusso esaminato | +|---|---| +| `pending` | Richiesta in attesa di essere elaborata. | +| `processing` | Worker ha acquisito il job e lo sta elaborando. | +| `done` | Salvataggio completato e risultato registrato. | +| `error` | Il worker ha registrato un errore in `last_error`. | +| `superseded` | Gli input sono cambiati durante il calcolo; è stata richiesta una nuova elaborazione. | + +Se il worker fallisce, `markJobError()` imposta `status = 'error'`, registra `completed_at` e scrive il dettaglio dell'errore in `last_error`. Il daemon seleziona soltanto job `pending`: perciò un job `error` non viene ripreso automaticamente da quel normale ciclo di coda. Il recupero dei job `processing` scaduti è un meccanismo distinto. + +## 7. KNN/OpenCL in modalità shadow + +Dopo che `persistResult()` ha completato la transazione, il worker prova a eseguire `runFaceKnnShadow()`. + +- Il confronto avviene dopo il salvataggio dei prototipi. +- Non modifica i prototipi, le valutazioni o lo stato del job. +- Un risultato incoerente viene riportato nei log. +- Un errore dello shadow viene intercettato e registrato nei log: non annulla il salvataggio già completato. + +Pertanto, la modalità shadow è un controllo comparativo e non è il componente che decide la selezione persistita. + +## 8. Come verificare un caso reale + +Per concludere che l'assegnazione di un determinato cluster abbia prodotto prototipi, bisogna verificare nel database: + +1. il job della persona e dell'utente, con motivo `cluster_assigned` o un motivo successivo se il job è stato accorpato; +2. lo stato finale e, in caso di errore, il contenuto di `last_error`; +3. `result_json`, in particolare `selected_count` e `selected_face_ids`; +4. le righe attive di `person_face_prototypes` per quella persona; +5. le valutazioni in `person_face_recognition_evaluations`; +6. lo stato dell'indice in `face_recognition_index_state`. + +**Importante:** l'esistenza del codice di accodamento e del worker prova che il flusso è implementato, ma soltanto i dati reali del database confermano l'esito di una specifica assegnazione. + +## 9. File e punti del codice esaminati + +- `routes/faceReview.js`: route di assegnazione e `queuePrototypeRefresh()`. +- `tools/runPrototypeDaemon.js`: coda, ciclo del daemon e recupero dei job `processing` scaduti. +- `tools/runPrototypeWorker.js`: acquisizione del job, selezione, salvataggio transazionale, gestione degli errori e chiamata allo shadow KNN. +- `package.json`: script `prototype:daemon`, `prototype:worker` e `prototype:once`. +- `tools/faceKnnRuntime.js` e `tools/runFaceKnnShadow.js`: runtime e confronto KNN shadow. + +--- +Documento descrittivo basato sul codice ispezionato; verificare il codice e il database della versione in esecuzione prima di usarlo come specifica operativa definitiva. diff --git a/docs/face-knn-source/deploy/config.json b/docs/face-knn-source/deploy/config.json new file mode 100644 index 0000000..4ced0a0 --- /dev/null +++ b/docs/face-knn-source/deploy/config.json @@ -0,0 +1,14 @@ +{ + "enabled": true, + "mode": "shadow", + "backend": "opencl", + "fallbackBackend": "cpu", + "dimension": 128, + "topK": 5, + "recognitionThreshold": 0.5, + "scoreTolerance": 0.00001, + "autoBackendMinimumComparisons": 100000, + "launcher": "native/face-knn/run-face-knn.sh", + "indexDirectory": "api_v1/face-recognition-index", + "runtimeDirectory": "var/face-knn" +} diff --git a/docs/face-knn-source/deploy/run-face-knn.sh b/docs/face-knn-source/deploy/run-face-knn.sh new file mode 100755 index 0000000..ab9c340 --- /dev/null +++ b/docs/face-knn-source/deploy/run-face-knn.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +set -eu + +script_dir="$( + CDPATH= cd \ + -- "$(dirname -- "$0")" \ + && pwd +)" + +cd "$script_dir" + +exec ./bin/face_knn "$@" diff --git a/docs/face-scan-source/bin/face_scan b/docs/face-scan-source/bin/face_scan new file mode 100755 index 0000000..008171e Binary files /dev/null and b/docs/face-scan-source/bin/face_scan differ diff --git a/sync-face-knn.sh b/sync-face-knn.sh new file mode 100755 index 0000000..f283d7d --- /dev/null +++ b/sync-face-knn.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +cd "$(dirname "$0")" + +SRC="docs/face-knn-source" +DEPLOY="$SRC/deploy" +DST="native/face-knn" + +# Sorgente -> destinazione +declare -A FILES=( + ["bin/face_knn"]="$SRC/bin/face_knn" + ["kernels/knn_float4_topk.cl"]="$SRC/kernels/knn_float4_topk.cl" + ["config.json"]="$DEPLOY/config.json" + ["run-face-knn.sh"]="$DEPLOY/run-face-knn.sh" + ["SHA256SUMS"]="$SRC/SHA256SUMS" +) + +echo "=== Verifica dei file sorgente ===" + +for dest in "${!FILES[@]}"; do + src="${FILES[$dest]}" + + if [[ ! -f "$src" ]]; then + echo "ERRORE: file sorgente mancante: $src" >&2 + exit 1 + fi +done + +echo "Tutti i file sorgente sono presenti." +echo +echo "=== Copia ===" + +for dest in \ + "bin/face_knn" \ + "kernels/knn_float4_topk.cl" \ + "config.json" \ + "run-face-knn.sh" \ + "SHA256SUMS" +do + src="${FILES[$dest]}" + mkdir -p "$DST/$(dirname "$dest")" + cp -p "$src" "$DST/$dest" + echo "Copiato: $src -> $DST/$dest" +done + +echo +echo "=== Verifica SHA256SUMS ===" +if [[ -f "$DST/SHA256SUMS" ]]; then + ( + cd "$DST" + sha256sum -c SHA256SUMS + ) +fi + +echo +echo "=== File presenti nella destinazione ===" +find "$DST" -type f -printf '%P\n' | sort + +echo +echo "Copia completata." diff --git a/sync-face-scan-source.sh b/sync-face-scan-source.sh new file mode 100755 index 0000000..a974fbc --- /dev/null +++ b/sync-face-scan-source.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT="$(pwd)" + +echo "Sincronizzazione Face Scan..." + +mkdir -p \ + docs/face-scan-source/bin \ + docs/face-scan-source/models \ + docs/face-scan-source/lib + +cp -f bin/face_scan \ + docs/face-scan-source/bin/face_scan + +cp -f models/SCRFD_500M_KPS_640.rknn \ + docs/face-scan-source/models/ + +cp -f models/face_recognition_sface_2021dec.rknn \ + docs/face-scan-source/models/ + +cp -f lib/librknnrt.so \ + docs/face-scan-source/lib/ + +chmod 755 docs/face-scan-source/bin/face_scan + +echo +echo "Verifica file copiati:" +echo + +ls -lh docs/face-scan-source/bin/face_scan +ls -lh docs/face-scan-source/models/SCRFD_500M_KPS_640.rknn +ls -lh docs/face-scan-source/models/face_recognition_sface_2021dec.rknn +ls -lh docs/face-scan-source/lib/librknnrt.so + +echo +echo "SHA256:" +echo + +sha256sum \ + docs/face-scan-source/bin/face_scan \ + docs/face-scan-source/models/SCRFD_500M_KPS_640.rknn \ + docs/face-scan-source/models/face_recognition_sface_2021dec.rknn \ + docs/face-scan-source/lib/librknnrt.so + +echo +echo "Sincronizzazione completata."