Initial validated RK3588 OpenCL face recognition engine
29
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Build output
|
||||||
|
/build/
|
||||||
|
/bin/
|
||||||
|
|
||||||
|
# Runtime libraries and models
|
||||||
|
/lib/*.so
|
||||||
|
/lib/*.so.*
|
||||||
|
/models/*.rknn
|
||||||
|
|
||||||
|
# Local backups
|
||||||
|
*.before_*
|
||||||
|
*.before-*
|
||||||
|
*.bak
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Runtime output
|
||||||
|
*.tmp
|
||||||
|
*.stderr
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Generated result files
|
||||||
|
*-cpu-raw.json
|
||||||
|
*-opencl-raw.json
|
||||||
|
*-cpp-cpu.json
|
||||||
|
*-cpp-opencl.json
|
||||||
|
|
||||||
|
# Editors
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
99
Makefile
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
CXX ?= g++
|
||||||
|
|
||||||
|
ROOT := $(abspath .)
|
||||||
|
|
||||||
|
CXXFLAGS := \
|
||||||
|
-std=c++17 \
|
||||||
|
-O2 \
|
||||||
|
-Wall \
|
||||||
|
-Wextra \
|
||||||
|
-Wpedantic \
|
||||||
|
-Iinclude \
|
||||||
|
-DFACE_CLUSTERING_ROOT=\"$(ROOT)\"
|
||||||
|
|
||||||
|
LDFLAGS := \
|
||||||
|
-L$(ROOT)/lib \
|
||||||
|
-Wl,-rpath,'$$ORIGIN/../lib'
|
||||||
|
|
||||||
|
LDLIBS := \
|
||||||
|
-lrknnrt \
|
||||||
|
-ldl \
|
||||||
|
-lpthread
|
||||||
|
|
||||||
|
BIN_DIR := bin
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
|
ENGINE_SOURCE := src/face_engine.cc
|
||||||
|
ENGINE_OBJECT := $(BUILD_DIR)/face_engine.o
|
||||||
|
|
||||||
|
FACE_RECOGNITION_SOURCE := src/face_recognition.cc
|
||||||
|
FACE_RECOGNITION_OBJECT := $(BUILD_DIR)/face_recognition.o
|
||||||
|
FACE_RECOGNITION_BINARY := $(BIN_DIR)/face_recognition
|
||||||
|
|
||||||
|
FACE_SCAN_SOURCE := src/face_scan.cc
|
||||||
|
FACE_SCAN_OBJECT := $(BUILD_DIR)/face_scan.o
|
||||||
|
FACE_SCAN_BINARY := $(BIN_DIR)/face_scan
|
||||||
|
|
||||||
|
.PHONY: all clean test info
|
||||||
|
|
||||||
|
all: \
|
||||||
|
$(FACE_RECOGNITION_BINARY) \
|
||||||
|
$(FACE_SCAN_BINARY)
|
||||||
|
|
||||||
|
$(BUILD_DIR):
|
||||||
|
mkdir -p $(BUILD_DIR)
|
||||||
|
|
||||||
|
$(BIN_DIR):
|
||||||
|
mkdir -p $(BIN_DIR)
|
||||||
|
|
||||||
|
$(ENGINE_OBJECT): $(ENGINE_SOURCE) include/face_engine.h | $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(FACE_RECOGNITION_OBJECT): \
|
||||||
|
$(FACE_RECOGNITION_SOURCE) \
|
||||||
|
include/face_engine.h \
|
||||||
|
include/face_config.h \
|
||||||
|
| $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(FACE_SCAN_OBJECT): \
|
||||||
|
$(FACE_SCAN_SOURCE) \
|
||||||
|
include/face_engine.h \
|
||||||
|
include/face_config.h \
|
||||||
|
| $(BUILD_DIR)
|
||||||
|
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(FACE_RECOGNITION_BINARY): \
|
||||||
|
$(ENGINE_OBJECT) \
|
||||||
|
$(FACE_RECOGNITION_OBJECT) \
|
||||||
|
| $(BIN_DIR)
|
||||||
|
$(CXX) $^ $(LDFLAGS) $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
$(FACE_SCAN_BINARY): \
|
||||||
|
$(ENGINE_OBJECT) \
|
||||||
|
$(FACE_SCAN_OBJECT) \
|
||||||
|
| $(BIN_DIR)
|
||||||
|
$(CXX) $^ $(LDFLAGS) $(LDLIBS) -o $@
|
||||||
|
|
||||||
|
test: all
|
||||||
|
./bin/face_recognition \
|
||||||
|
test/test.jpg \
|
||||||
|
test/test3f.jpg
|
||||||
|
./bin/face_scan \
|
||||||
|
test/test3f.jpg \
|
||||||
|
> /tmp/face_scan_test.json
|
||||||
|
python3 -m json.tool \
|
||||||
|
/tmp/face_scan_test.json \
|
||||||
|
> /dev/null
|
||||||
|
@echo "Tests completed successfully"
|
||||||
|
|
||||||
|
info:
|
||||||
|
@echo "ROOT=$(ROOT)"
|
||||||
|
@echo "CXX=$(CXX)"
|
||||||
|
@echo "CXXFLAGS=$(CXXFLAGS)"
|
||||||
|
@echo "LDFLAGS=$(LDFLAGS)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
rm -f $(FACE_RECOGNITION_BINARY)
|
||||||
|
rm -f $(FACE_SCAN_BINARY)
|
||||||
481
README.md
Normal file
|
|
@ -0,0 +1,481 @@
|
||||||
|
# Face Recognition RK3588 OpenCL
|
||||||
|
|
||||||
|
Motore C++ per rilevamento facciale, generazione di embedding e riconoscimento kNN accelerato su Rockchip RK3588.
|
||||||
|
|
||||||
|
Il progetto usa:
|
||||||
|
|
||||||
|
- NPU tramite RKNN per SCRFD e SFace;
|
||||||
|
- GPU Mali-G610 tramite OpenCL per kNN;
|
||||||
|
- CPU C++ come riferimento numerico e fallback.
|
||||||
|
|
||||||
|
## Funzionalità
|
||||||
|
|
||||||
|
- rilevamento volti con SCRFD;
|
||||||
|
- confidence, bounding box e cinque landmark;
|
||||||
|
- non-maximum suppression;
|
||||||
|
- allineamento volto 112 x 112;
|
||||||
|
- embedding SFace float32 a 128 dimensioni;
|
||||||
|
- normalizzazione L2;
|
||||||
|
- cosine similarity tramite prodotto scalare;
|
||||||
|
- kNN CPU e OpenCL;
|
||||||
|
- conteggi dinamici, TOPK configurabile e chunking;
|
||||||
|
- leave-one-out tramite self-map;
|
||||||
|
- JSON su stdout e diagnostica su stderr.
|
||||||
|
|
||||||
|
## Architettura
|
||||||
|
|
||||||
|
```text
|
||||||
|
Immagine
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SCRFD tramite RKNN / NPU
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Bounding box e landmark
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Allineamento volto 112 x 112
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SFace tramite RKNN / NPU
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Embedding float32[128] normalizzato L2
|
||||||
|
|
|
||||||
|
+-----------------------------+
|
||||||
|
| |
|
||||||
|
v v
|
||||||
|
kNN CPU C++ kNN OpenCL / Mali-G610
|
||||||
|
| |
|
||||||
|
+-------------+---------------+
|
||||||
|
|
|
||||||
|
v
|
||||||
|
TOPK prototype
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Leave-one-out opzionale
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Soglia di riconoscimento
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formato degli embedding
|
||||||
|
|
||||||
|
```text
|
||||||
|
Dimensione: 128
|
||||||
|
Tipo: float32
|
||||||
|
Dimensione binaria: 512 byte
|
||||||
|
Ordine byte: little-endian
|
||||||
|
Normalizzazione: L2
|
||||||
|
Norma attesa: circa 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Poiché gli embedding sono normalizzati:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cosine_similarity(a, b) = dot(a, b)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Componenti
|
||||||
|
|
||||||
|
### Face engine RKNN
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/face_config.h
|
||||||
|
include/face_engine.h
|
||||||
|
include/rknn_api.h
|
||||||
|
src/face_engine.cc
|
||||||
|
src/face_recognition.cc
|
||||||
|
src/face_scan.cc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Motore kNN
|
||||||
|
|
||||||
|
```text
|
||||||
|
include/knn_opencl.h
|
||||||
|
src/knn_opencl.cc
|
||||||
|
src/knn_opencl_backend.cc
|
||||||
|
src/face_knn.cc
|
||||||
|
kernels/knn_float4_topk.cl
|
||||||
|
kernels/knn_reference.cl
|
||||||
|
```
|
||||||
|
|
||||||
|
Il kernel produttivo viene compilato a runtime, ad esempio con:
|
||||||
|
|
||||||
|
```text
|
||||||
|
-cl-fast-relaxed-math
|
||||||
|
-D DIM=128
|
||||||
|
-D TOPK=5
|
||||||
|
-D QUERY_COUNT=21
|
||||||
|
-D LANES_PER_QUERY=8
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requisiti
|
||||||
|
|
||||||
|
- Linux AArch64;
|
||||||
|
- Rockchip RK3588 o piattaforma compatibile;
|
||||||
|
- compilatore C++17 e GNU Make;
|
||||||
|
- runtime RKNN;
|
||||||
|
- OpenCL ICD loader e driver Mali;
|
||||||
|
- modelli SCRFD e SFace in formato RKNN.
|
||||||
|
|
||||||
|
Su Debian:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install \
|
||||||
|
build-essential \
|
||||||
|
opencl-headers \
|
||||||
|
ocl-icd-opencl-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Verifica OpenCL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clinfo 2>/dev/null \
|
||||||
|
| grep -E 'Platform Name|Device Name|Device Type|Device Version'
|
||||||
|
```
|
||||||
|
|
||||||
|
## File locali richiesti
|
||||||
|
|
||||||
|
Non sono versionati:
|
||||||
|
|
||||||
|
```text
|
||||||
|
models/SCRFD_500M_KPS_640.rknn
|
||||||
|
models/face_recognition_sface_2021dec.rknn
|
||||||
|
lib/librknnrt.so
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compilazione
|
||||||
|
|
||||||
|
Dalla root del repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
make -j"$(nproc)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Binari previsti:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bin/face_recognition
|
||||||
|
bin/face_scan
|
||||||
|
bin/face_knn
|
||||||
|
```
|
||||||
|
|
||||||
|
Verifica del binario OpenCL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file bin/face_knn
|
||||||
|
ldd bin/face_knn | grep -Ei 'opencl|stdc|not found'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utilizzo dei binari
|
||||||
|
|
||||||
|
### face_recognition
|
||||||
|
|
||||||
|
Programma dimostrativo con output destinato alla lettura umana.
|
||||||
|
|
||||||
|
Analisi di una singola fotografia:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_recognition test/test.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Confronto tra due fotografie:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_recognition \
|
||||||
|
test/test.jpg \
|
||||||
|
test/test3f.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Usarlo per verificare modelli e runtime RKNN, detection, landmark, embedding e similarità. Non è il binario consigliato per integrazioni applicative.
|
||||||
|
|
||||||
|
### face_scan
|
||||||
|
|
||||||
|
Elabora una fotografia e produce JSON con immagine, volti, confidence, bounding box, landmark ed embedding.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_scan test/test3f.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Separazione di risultato e diagnostica:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_scan test/test3f.jpg \
|
||||||
|
> /tmp/face-scan.json \
|
||||||
|
2> /tmp/face-scan.stderr
|
||||||
|
```
|
||||||
|
|
||||||
|
Validazione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m json.tool /tmp/face-scan.json > /dev/null \
|
||||||
|
&& echo "OK: JSON valido"
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit code:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 scansione completata
|
||||||
|
2 argomenti non validi
|
||||||
|
3 inizializzazione FaceEngine fallita
|
||||||
|
4 caricamento o elaborazione immagine fallita
|
||||||
|
5 risultato facciale o embedding non valido
|
||||||
|
```
|
||||||
|
|
||||||
|
Usarlo per integrare il face engine con Node.js, Python o altri linguaggi e per costruire query destinate a `face_knn`.
|
||||||
|
|
||||||
|
### face_knn
|
||||||
|
|
||||||
|
Confronta embedding query con un database di prototype. Backend disponibili:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cpu
|
||||||
|
opencl
|
||||||
|
```
|
||||||
|
|
||||||
|
Il programma va normalmente eseguito dalla root del repository, perché il kernel viene caricato da:
|
||||||
|
|
||||||
|
```text
|
||||||
|
kernels/knn_float4_topk.cl
|
||||||
|
```
|
||||||
|
|
||||||
|
Help:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_knn --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend CPU:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_knn \
|
||||||
|
--queries queries.bin \
|
||||||
|
--database prototypes.bin \
|
||||||
|
--query-count 21 \
|
||||||
|
--database-count 5 \
|
||||||
|
--dimension 128 \
|
||||||
|
--top-k 5 \
|
||||||
|
--threshold 0.50 \
|
||||||
|
--backend cpu
|
||||||
|
```
|
||||||
|
|
||||||
|
Backend OpenCL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_knn \
|
||||||
|
--queries queries.bin \
|
||||||
|
--database prototypes.bin \
|
||||||
|
--query-count 21 \
|
||||||
|
--database-count 5 \
|
||||||
|
--dimension 128 \
|
||||||
|
--top-k 5 \
|
||||||
|
--threshold 0.50 \
|
||||||
|
--backend opencl
|
||||||
|
```
|
||||||
|
|
||||||
|
Argomenti:
|
||||||
|
|
||||||
|
```text
|
||||||
|
--queries file binario degli embedding query
|
||||||
|
--database file binario degli embedding prototype
|
||||||
|
--query-count numero di query
|
||||||
|
--database-count numero di prototype
|
||||||
|
--dimension componenti per embedding, 128 per SFace
|
||||||
|
--top-k numero massimo di vicini
|
||||||
|
--threshold soglia minima di riconoscimento
|
||||||
|
--self-map esclusioni leave-one-out opzionali
|
||||||
|
--backend cpu oppure opencl
|
||||||
|
```
|
||||||
|
|
||||||
|
Il TOPK effettivo è:
|
||||||
|
|
||||||
|
```text
|
||||||
|
effective_top_k = min(top_k, database_count)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Leave-one-out
|
||||||
|
|
||||||
|
Quando una query è anche un prototype, il confronto con sé stessa va escluso. La self-map contiene una riga per query:
|
||||||
|
|
||||||
|
```text
|
||||||
|
-1
|
||||||
|
1
|
||||||
|
3
|
||||||
|
0
|
||||||
|
4
|
||||||
|
-1
|
||||||
|
```
|
||||||
|
|
||||||
|
`-1` indica nessuna esclusione; un valore da zero in su indica l'indice del prototype da escludere.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_knn \
|
||||||
|
--queries queries.bin \
|
||||||
|
--database prototypes.bin \
|
||||||
|
--query-count 21 \
|
||||||
|
--database-count 5 \
|
||||||
|
--dimension 128 \
|
||||||
|
--top-k 5 \
|
||||||
|
--threshold 0.50 \
|
||||||
|
--self-map self-map.txt \
|
||||||
|
--backend opencl
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Output e diagnostica
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_knn \
|
||||||
|
--queries queries.bin \
|
||||||
|
--database prototypes.bin \
|
||||||
|
--query-count 21 \
|
||||||
|
--database-count 5 \
|
||||||
|
--dimension 128 \
|
||||||
|
--top-k 5 \
|
||||||
|
--threshold 0.50 \
|
||||||
|
--self-map self-map.txt \
|
||||||
|
--backend opencl \
|
||||||
|
> /tmp/face-knn.json \
|
||||||
|
2> /tmp/face-knn.stderr
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
stdout → JSON valido
|
||||||
|
stderr → diagnostica OpenCL e driver Mali
|
||||||
|
```
|
||||||
|
|
||||||
|
Validazione:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m json.tool /tmp/face-knn.json > /dev/null \
|
||||||
|
&& echo "OK: JSON valido"
|
||||||
|
```
|
||||||
|
|
||||||
|
Campi principali del JSON:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend, device_name, query_count, database_count, dimension, top_k,
|
||||||
|
threshold, leave_one_out, elapsed_ms, results, recognized_count,
|
||||||
|
below_threshold_count, coverage_percent
|
||||||
|
```
|
||||||
|
|
||||||
|
Per ogni query:
|
||||||
|
|
||||||
|
```text
|
||||||
|
query_index, excluded_index, recognized, best_index, best_score, neighbors
|
||||||
|
```
|
||||||
|
|
||||||
|
### test_knn_cpu
|
||||||
|
|
||||||
|
Sorgente:
|
||||||
|
|
||||||
|
```text
|
||||||
|
test/knn/test_knn_cpu.cc
|
||||||
|
```
|
||||||
|
|
||||||
|
Esecuzione, se compilato:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/test_knn_cpu
|
||||||
|
```
|
||||||
|
|
||||||
|
Output atteso:
|
||||||
|
|
||||||
|
```text
|
||||||
|
OK: backend CPU dinamico
|
||||||
|
query_count=2
|
||||||
|
database_count=3
|
||||||
|
top_k=2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scelta del backend
|
||||||
|
|
||||||
|
Usare CPU per database piccoli, riferimento numerico o fallback. Usare OpenCL per database grandi o molte query. Con cinque prototype l'overhead OpenCL può superare il costo del calcolo; con 350000 prototype e 256 query è stato misurato uno speedup di circa 7 volte.
|
||||||
|
|
||||||
|
## Flusso completo
|
||||||
|
|
||||||
|
```text
|
||||||
|
face_scan
|
||||||
|
→ estrae gli embedding
|
||||||
|
→ applicazione seleziona i prototype
|
||||||
|
→ face_knn calcola TOPK
|
||||||
|
→ applicazione associa gli indici alle identità
|
||||||
|
→ applicazione applica soglia e margine
|
||||||
|
→ applicazione salva il risultato
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prestazioni sintetiche validate
|
||||||
|
|
||||||
|
```text
|
||||||
|
Database: 350000
|
||||||
|
Query: 256
|
||||||
|
Dimensione: 128
|
||||||
|
TOPK: 32
|
||||||
|
Chunk: 50000
|
||||||
|
GPU: Mali-G610 r0p0
|
||||||
|
CPU: 9663.226 ms
|
||||||
|
OpenCL: 1373.797 ms
|
||||||
|
Speedup: 7.034x
|
||||||
|
TOPK identico: 8192 / 8192
|
||||||
|
```
|
||||||
|
|
||||||
|
Variante float4 min-replace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Kernel: 1244.576 ms
|
||||||
|
Throughput: 71.992 M vector/s
|
||||||
|
Dot rate: 18.430 GFLOP/s
|
||||||
|
Traffico effettivo: 36.860 GB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validazione reale
|
||||||
|
|
||||||
|
```text
|
||||||
|
Query: 21
|
||||||
|
Prototype: 5
|
||||||
|
Dimensione: 128
|
||||||
|
Soglia: 0.50
|
||||||
|
Leave-one-out: attivo
|
||||||
|
Riconosciuti: 20 / 21
|
||||||
|
Copertura: 95.2%
|
||||||
|
Caso sotto soglia: IMG_0183.JPG
|
||||||
|
Score: 0.466428
|
||||||
|
Miglior prototype: IMG_0143.JPG
|
||||||
|
```
|
||||||
|
|
||||||
|
Confronti:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Python vs C++ CPU: 21 / 21 query coerenti
|
||||||
|
CPU vs OpenCL dopo leave-one-out: 100 / 100 posizioni
|
||||||
|
CPU vs OpenCL TOPK grezzo: 105 / 105 posizioni
|
||||||
|
Errore massimo CPU/OpenCL: 3.58e-7
|
||||||
|
```
|
||||||
|
|
||||||
|
Dettagli:
|
||||||
|
|
||||||
|
```text
|
||||||
|
VALIDATION_JESSICA.md
|
||||||
|
README1.md
|
||||||
|
modules/knn-opencl/reference/VALIDATION.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integrazione applicativa
|
||||||
|
|
||||||
|
Un'applicazione deve leggere e validare i prototype, generare i file binari e i metadati, invocare `face_knn`, associare gli indici alle identità, applicare soglia e margine e salvare il risultato. La prima integrazione dovrebbe funzionare in modalità shadow, confrontando il risultato corrente con OpenCL prima dell'attivazione definitiva.
|
||||||
|
|
||||||
|
## Stato
|
||||||
|
|
||||||
|
```text
|
||||||
|
Face engine RKNN: validato
|
||||||
|
Backend CPU: validato
|
||||||
|
Backend OpenCL: validato
|
||||||
|
Mali-G610: validata
|
||||||
|
TOPK dinamico: validato
|
||||||
|
Chunking: validato
|
||||||
|
Leave-one-out: validato
|
||||||
|
Output JSON puro: validato
|
||||||
|
Dataset reale: validato
|
||||||
|
Integrazione applicativa: da completare
|
||||||
|
```
|
||||||
239
README1.md
Normal file
|
|
@ -0,0 +1,239 @@
|
||||||
|
# Note tecniche e cronologia del kNN OpenCL
|
||||||
|
|
||||||
|
Questo documento raccoglie il percorso sperimentale che ha portato al motore kNN OpenCL. Per installazione, compilazione e uso dei binari consultare `README.md`.
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
|
||||||
|
Input:
|
||||||
|
|
||||||
|
```text
|
||||||
|
float32[N][128]
|
||||||
|
```
|
||||||
|
|
||||||
|
Gli embedding sono normalizzati L2, quindi cosine similarity e prodotto scalare coincidono. Output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
indices[query_count][TOPK]
|
||||||
|
scores[query_count][TOPK]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Baseline
|
||||||
|
|
||||||
|
```text
|
||||||
|
DIM=128
|
||||||
|
TOPK=32
|
||||||
|
QUERIES=256
|
||||||
|
LANES_PER_QUERY=8
|
||||||
|
CHUNK_SIZE=50000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Risultati sperimentali principali
|
||||||
|
|
||||||
|
### Numero di lane
|
||||||
|
|
||||||
|
```text
|
||||||
|
LANES=4: 9.60 GOP/s
|
||||||
|
LANES=8: 10.57 GOP/s
|
||||||
|
LANES=16: 4.04 GOP/s
|
||||||
|
LANES=32: 1.49 GOP/s
|
||||||
|
```
|
||||||
|
|
||||||
|
Otto lane sono risultate la configurazione più equilibrata.
|
||||||
|
|
||||||
|
### Stage benchmark
|
||||||
|
|
||||||
|
```text
|
||||||
|
DOT: 72.79 M vector/s
|
||||||
|
DOT+threshold: 83.47 M vector/s
|
||||||
|
TOPK score: 77.94 M vector/s
|
||||||
|
TOPK+indici: 76.28 M vector/s
|
||||||
|
kNN completo: 75.22 M vector/s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vettorizzazione
|
||||||
|
|
||||||
|
```text
|
||||||
|
scalar: 74.37 M vector/s
|
||||||
|
float4: 84.14 M vector/s
|
||||||
|
float8: 83.49 M vector/s
|
||||||
|
```
|
||||||
|
|
||||||
|
Database e query entrambi float4 hanno raggiunto 84.13 M vector/s.
|
||||||
|
|
||||||
|
### Accumulatori
|
||||||
|
|
||||||
|
```text
|
||||||
|
F4 singolo accumulatore: 74.53 M vector/s
|
||||||
|
F4 quattro accumulatori: 83.47 M vector/s
|
||||||
|
F4 quattro acc + unroll2: 70.53 M vector/s
|
||||||
|
F8 quattro accumulatori: 82.76 M vector/s
|
||||||
|
```
|
||||||
|
|
||||||
|
L'unrolling esplicito ha peggiorato il code generation del compilatore Mali.
|
||||||
|
|
||||||
|
### Pressione sui registri
|
||||||
|
|
||||||
|
Una query mantenuta in `private float4 query[DIM4]` ha ridotto le prestazioni a 17.85 M vector/s. Rimuovendo l'array privato si è risaliti a circa 65 M vector/s.
|
||||||
|
|
||||||
|
### Strategie TOPK
|
||||||
|
|
||||||
|
```text
|
||||||
|
sorted insertion: 78.37 M vector/s
|
||||||
|
min-replace: 81.99 M vector/s
|
||||||
|
threshold + min-replace: 82.02 M vector/s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dimensione TOPK
|
||||||
|
|
||||||
|
```text
|
||||||
|
TOPK=8: 73.87 M vector/s
|
||||||
|
TOPK=16: 84.60 M vector/s
|
||||||
|
TOPK=32: 82.53 M vector/s
|
||||||
|
TOPK=64: 78.81 M vector/s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmark da 350000 embedding
|
||||||
|
|
||||||
|
```text
|
||||||
|
Database: 350000
|
||||||
|
Query: 256
|
||||||
|
Dimensione: 128
|
||||||
|
TOPK: 32
|
||||||
|
Chunk: 50000
|
||||||
|
Confronti: 89600000
|
||||||
|
```
|
||||||
|
|
||||||
|
Variante min-replace:
|
||||||
|
|
||||||
|
```text
|
||||||
|
KNN kernel: 1244.576 ms
|
||||||
|
Merge GPU: 8.022 ms
|
||||||
|
Merge CPU globale: 14.039 ms
|
||||||
|
Tempo complessivo: 1266.638 ms
|
||||||
|
Vector rate: 71.992 M vector/s
|
||||||
|
Dot rate: 18.430 GFLOP/s
|
||||||
|
Traffico: 36.860 GB/s
|
||||||
|
Indici non validi: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Benchmark parametrico:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CPU: 9663.226 ms
|
||||||
|
OpenCL: 1373.797 ms
|
||||||
|
Speedup: 7.034x
|
||||||
|
Posizioni TOPK identiche: 8192 / 8192
|
||||||
|
Exact ratio: 100%
|
||||||
|
```
|
||||||
|
|
||||||
|
## Trasformazione in motore dinamico
|
||||||
|
|
||||||
|
Il kernel produttivo usa definizioni configurabili:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#ifndef DIM
|
||||||
|
#define DIM 128
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TOPK
|
||||||
|
#define TOPK 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef QUERY_COUNT
|
||||||
|
#define QUERY_COUNT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LANES_PER_QUERY
|
||||||
|
#define LANES_PER_QUERY 8
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
Il limite fisso `q >= 256` è diventato `q >= QUERY_COUNT`. Le definizioni vengono passate a `clBuildProgram` insieme a `-cl-fast-relaxed-math`.
|
||||||
|
|
||||||
|
## Backend CPU
|
||||||
|
|
||||||
|
È stato generalizzato per conteggi e dimensione dinamici, TOPK sicuro, ordinamento deterministico, rifiuto di valori non finiti e layout identico a OpenCL.
|
||||||
|
|
||||||
|
## Backend OpenCL
|
||||||
|
|
||||||
|
Il backend:
|
||||||
|
|
||||||
|
1. enumera piattaforme e GPU;
|
||||||
|
2. seleziona preferibilmente Mali-G610;
|
||||||
|
3. carica il kernel da file;
|
||||||
|
4. compila con parametri runtime;
|
||||||
|
5. alloca buffer riutilizzabili;
|
||||||
|
6. copia il database a chunk;
|
||||||
|
7. esegue il kernel;
|
||||||
|
8. legge indici e score;
|
||||||
|
9. converte indici locali in globali;
|
||||||
|
10. effettua il merge TOPK;
|
||||||
|
11. rilascia le risorse tramite RAII.
|
||||||
|
|
||||||
|
## Diagnostica Mali
|
||||||
|
|
||||||
|
Il runtime Mali emette `arm_release_ver` direttamente su stdout. La CLI devia temporaneamente stdout verso stderr durante OpenCL e lo ripristina prima della scrittura JSON.
|
||||||
|
|
||||||
|
## Validazione reale
|
||||||
|
|
||||||
|
Prototype:
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 IMG_0145.JPG cluster_medoid
|
||||||
|
1 IMG_0141.JPG cluster_medoid
|
||||||
|
2 IMG_0193.JPG cluster_medoid
|
||||||
|
3 IMG_0143.JPG greedy_leave_one_out_gain
|
||||||
|
4 IMG_0146.JPG greedy_leave_one_out_gain
|
||||||
|
```
|
||||||
|
|
||||||
|
Risultato:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Query: 21
|
||||||
|
Prototype: 5
|
||||||
|
Riconosciuti: 20
|
||||||
|
Sotto soglia: 1
|
||||||
|
Copertura: 95.2%
|
||||||
|
Caso sotto soglia: IMG_0183.JPG
|
||||||
|
Score: 0.466428
|
||||||
|
Miglior prototype: IMG_0143.JPG
|
||||||
|
```
|
||||||
|
|
||||||
|
Python contro C++ CPU:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Query confrontate: 21
|
||||||
|
Mismatch top-1: 0
|
||||||
|
Mismatch decisione: 0
|
||||||
|
Errore massimo: 2.643e-7
|
||||||
|
```
|
||||||
|
|
||||||
|
C++ CPU contro OpenCL:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Dopo leave-one-out: 100 / 100 posizioni identiche
|
||||||
|
TOPK grezzo: 105 / 105 posizioni identiche
|
||||||
|
Errore massimo: 3.58e-7
|
||||||
|
```
|
||||||
|
|
||||||
|
## File di riferimento
|
||||||
|
|
||||||
|
```text
|
||||||
|
modules/knn-opencl/reference/knn_benchmark.cpp
|
||||||
|
modules/knn-opencl/reference/knn_float4_minreplace_validate_benchmark.cpp
|
||||||
|
modules/knn-opencl/reference/validation-opencl-350k.txt
|
||||||
|
modules/knn-opencl/reference/validation-minreplace-350k.txt
|
||||||
|
modules/knn-opencl/reference/VALIDATION.md
|
||||||
|
VALIDATION_JESSICA.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sviluppi futuri
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend auto
|
||||||
|
cache del programma OpenCL
|
||||||
|
indice persistente dei prototype
|
||||||
|
margine fra prima e seconda identità
|
||||||
|
modalità shadow applicativa
|
||||||
|
metriche operative
|
||||||
|
```
|
||||||
9
SHA256SUMS
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
9fb33ec13abd1714832d6bc81a3b3df553d80577195e54ff8bfdbc33220a6566 README.md
|
||||||
|
7f9373326a666d36dcd26fcf9d2326fa56198b5aab448d53651fe5aff379be57 README1.md
|
||||||
|
c6b144c487af817ea1578f3190e968d81a09dcee5d4cdc9f8f5e664b6cd922b5 VALIDATION_JESSICA.md
|
||||||
|
f0f057dda1dd0bb8d3e097698da7b8ea956a01217b47349a99936729aefd2f3d include/knn_opencl.h
|
||||||
|
ca211d9e68b4c15b1fcbf8a2607ba398b845f7fab7db417302642f5ed2e0413e src/knn_opencl.cc
|
||||||
|
918bed774c805cafb2645214ab239852b96ed4eb104fcdf78ac2488d64c19737 src/knn_opencl_backend.cc
|
||||||
|
4561c83dcec5015e09ee5e7990698c50eed9d2251dc56349fe51cbc579d011a3 src/face_knn.cc
|
||||||
|
2721831e9cb285c3ef12aedb59fb66ca35e067321f29e67582a39e58e47d283b kernels/knn_reference.cl
|
||||||
|
6c4f502438aad3dd1600a00cb7988e0e35e56b2e8e4620d44ffcb0f3610a7226 kernels/knn_float4_topk.cl
|
||||||
49
VALIDATION_JESSICA.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Validazione kNN reale
|
||||||
|
|
||||||
|
## Dataset
|
||||||
|
|
||||||
|
- Persona: Jessica
|
||||||
|
- Query: 21
|
||||||
|
- Prototype: 5
|
||||||
|
- Dimensione embedding: 128 float32
|
||||||
|
- Soglia: 0.50
|
||||||
|
- Leave-one-out: attivo
|
||||||
|
|
||||||
|
## Python vs C++ CPU
|
||||||
|
|
||||||
|
- Query confrontate: 21
|
||||||
|
- Mismatch top-1: 0
|
||||||
|
- Mismatch decisione: 0
|
||||||
|
- Errore massimo score: 2.643e-7
|
||||||
|
|
||||||
|
## C++ CPU vs OpenCL dopo leave-one-out
|
||||||
|
|
||||||
|
- Posizioni confrontate: 100
|
||||||
|
- Indici identici: 100
|
||||||
|
- Exact ratio: 100%
|
||||||
|
- Mismatch decisione: 0
|
||||||
|
- Errore massimo score: 3.58e-7
|
||||||
|
|
||||||
|
## C++ CPU vs OpenCL TOPK grezzo
|
||||||
|
|
||||||
|
- Posizioni confrontate: 105
|
||||||
|
- Indici identici: 105
|
||||||
|
- Exact ratio: 100%
|
||||||
|
- Mismatch score oltre 1e-5: 0
|
||||||
|
- Errore massimo score: 3.58e-7
|
||||||
|
|
||||||
|
## Risultato riconoscimento
|
||||||
|
|
||||||
|
- Riconosciuti: 20/21
|
||||||
|
- Copertura: 95.2%
|
||||||
|
- Unico caso sotto soglia: IMG_0183.JPG
|
||||||
|
- Best similarity: 0.466428
|
||||||
|
- Miglior prototype: IMG_0143.JPG
|
||||||
|
|
||||||
|
## Backend OpenCL
|
||||||
|
|
||||||
|
- Device: ARM / Mali-G610 r0p0
|
||||||
|
- DIM: 128
|
||||||
|
- TOPK: 5
|
||||||
|
- QUERY_COUNT: 21
|
||||||
|
- LANES_PER_QUERY: 8
|
||||||
144
docs/architecture.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Architettura
|
||||||
|
|
||||||
|
Il progetto separa il motore di elaborazione facciale dai programmi che lo
|
||||||
|
utilizzano.
|
||||||
|
|
||||||
|
## Componenti principali
|
||||||
|
|
||||||
|
### face_engine.h
|
||||||
|
|
||||||
|
Definisce l'interfaccia pubblica C++:
|
||||||
|
|
||||||
|
- `Point`;
|
||||||
|
- `Face`;
|
||||||
|
- `FaceEngine`;
|
||||||
|
- inizializzazione dei modelli;
|
||||||
|
- elaborazione di una singola immagine.
|
||||||
|
|
||||||
|
### face_engine.cc
|
||||||
|
|
||||||
|
Implementa:
|
||||||
|
|
||||||
|
- caricamento delle immagini tramite stb_image;
|
||||||
|
- resize bilineare;
|
||||||
|
- conversione float32 e float16;
|
||||||
|
- inferenza SCRFD tramite RKNN;
|
||||||
|
- decodifica di score, bounding box e landmark;
|
||||||
|
- non-maximum suppression;
|
||||||
|
- trasformazione similarity;
|
||||||
|
- allineamento facciale;
|
||||||
|
- inferenza SFace tramite RKNN;
|
||||||
|
- conversione dell'output;
|
||||||
|
- normalizzazione L2 dell'embedding.
|
||||||
|
|
||||||
|
### face_recognition.cc
|
||||||
|
|
||||||
|
Programma dimostrativo con output leggibile.
|
||||||
|
|
||||||
|
Può:
|
||||||
|
|
||||||
|
- elaborare una singola immagine;
|
||||||
|
- elaborare due immagini;
|
||||||
|
- confrontare tutti i volti rilevati tramite cosine similarity.
|
||||||
|
|
||||||
|
### face_scan.cc
|
||||||
|
|
||||||
|
Programma destinato all'integrazione con altri software.
|
||||||
|
|
||||||
|
Caratteristiche:
|
||||||
|
|
||||||
|
- elabora una singola immagine;
|
||||||
|
- scrive esclusivamente JSON su standard output;
|
||||||
|
- scrive diagnostica ed errori su standard error;
|
||||||
|
- valida la dimensione degli embedding;
|
||||||
|
- rifiuta valori NaN o infiniti;
|
||||||
|
- restituisce exit code documentati.
|
||||||
|
|
||||||
|
## Pipeline runtime
|
||||||
|
|
||||||
|
```text
|
||||||
|
File immagine
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Caricamento RGB
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Resize proporzionale
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Input SCRFD 640 x 640 FLOAT16 NHWC
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SCRFD su NPU
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Decodifica livelli stride 8, 16 e 32
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Score threshold
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Non-maximum suppression
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Bounding box e cinque landmark
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Similarity transform
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Volto allineato 112 x 112
|
||||||
|
|
|
||||||
|
v
|
||||||
|
SFace su NPU
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Embedding 128D
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Normalizzazione L2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Riutilizzo
|
||||||
|
|
||||||
|
Le applicazioni C++ possono utilizzare direttamente `FaceEngine`.
|
||||||
|
|
||||||
|
Applicazioni scritte in altri linguaggi possono eseguire
|
||||||
|
`face_scan` come processo figlio e analizzare il JSON prodotto.
|
||||||
|
|
||||||
|
Il motore non dipende:
|
||||||
|
|
||||||
|
- dal server della galleria;
|
||||||
|
- dal database;
|
||||||
|
- dall'interfaccia utente;
|
||||||
|
- da uno specifico framework applicativo.
|
||||||
|
|
||||||
|
## Estensione futura
|
||||||
|
|
||||||
|
Gli embedding prodotti da `FaceEngine` saranno utilizzati dalla
|
||||||
|
pipeline di clustering:
|
||||||
|
|
||||||
|
```text
|
||||||
|
FaceEngine
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Embedding float32[N][128]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
kNN OpenCL TOPK=32
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Core distance
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Mutual reachability
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Minimum spanning tree
|
||||||
|
|
|
||||||
|
v
|
||||||
|
HDBSCAN
|
||||||
|
|
|
||||||
|
v
|
||||||
|
Cluster ID per embedding
|
||||||
|
```
|
||||||
113
docs/json-format.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Formato JSON di face_scan
|
||||||
|
|
||||||
|
`face_scan` scrive un singolo oggetto JSON su standard output.
|
||||||
|
|
||||||
|
Gli errori e i messaggi diagnostici vengono scritti su standard error.
|
||||||
|
|
||||||
|
## Invocazione
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_scan /percorso/immagine.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Esempio
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"image": {
|
||||||
|
"path": "/percorso/immagine.jpg",
|
||||||
|
"width": 1500,
|
||||||
|
"height": 2000
|
||||||
|
},
|
||||||
|
"faces": [
|
||||||
|
{
|
||||||
|
"face_index": 0,
|
||||||
|
"score": 0.8671875,
|
||||||
|
"bbox": {
|
||||||
|
"x": 1079.3457,
|
||||||
|
"y": 489.648438,
|
||||||
|
"width": 131.591797,
|
||||||
|
"height": 182.275391
|
||||||
|
},
|
||||||
|
"landmarks": [
|
||||||
|
[1107.8125, 558.325195],
|
||||||
|
[1170.99609, 559.423828],
|
||||||
|
[1134.17969, 594.500732],
|
||||||
|
[1106.22559, 615.83252],
|
||||||
|
[1168.34717, 616.967773]
|
||||||
|
],
|
||||||
|
"embedding": [
|
||||||
|
-0.0426222458,
|
||||||
|
0.0678814054
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L'array `embedding` reale contiene sempre 128 valori.
|
||||||
|
|
||||||
|
## Oggetto image
|
||||||
|
|
||||||
|
Campi disponibili:
|
||||||
|
|
||||||
|
- `path`: percorso ricevuto dal programma;
|
||||||
|
- `width`: larghezza originale dell'immagine;
|
||||||
|
- `height`: altezza originale dell'immagine.
|
||||||
|
|
||||||
|
## Oggetto face
|
||||||
|
|
||||||
|
Campi disponibili:
|
||||||
|
|
||||||
|
- `face_index`: indice sequenziale del volto;
|
||||||
|
- `score`: confidence SCRFD;
|
||||||
|
- `bbox.x`: coordinata sinistra;
|
||||||
|
- `bbox.y`: coordinata superiore;
|
||||||
|
- `bbox.width`: larghezza;
|
||||||
|
- `bbox.height`: altezza;
|
||||||
|
- `landmarks`: cinque coppie di coordinate;
|
||||||
|
- `embedding`: vettore L2-normalizzato di 128 elementi.
|
||||||
|
|
||||||
|
## Immagini senza volti
|
||||||
|
|
||||||
|
Un'immagine elaborata correttamente ma senza volti produce:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"image": {
|
||||||
|
"path": "image.jpg",
|
||||||
|
"width": 1920,
|
||||||
|
"height": 1080
|
||||||
|
},
|
||||||
|
"faces": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L'exit code resta `0`.
|
||||||
|
|
||||||
|
## Exit code
|
||||||
|
|
||||||
|
```text
|
||||||
|
0 scansione completata
|
||||||
|
2 argomenti non validi
|
||||||
|
3 inizializzazione FaceEngine fallita
|
||||||
|
4 caricamento o elaborazione immagine fallita
|
||||||
|
5 risultato facciale o embedding non valido
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requisiti dell'embedding
|
||||||
|
|
||||||
|
Per ogni volto:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Dimensione 128
|
||||||
|
Tipo logico float32
|
||||||
|
Norma L2 circa 1.0
|
||||||
|
Valori tutti finiti
|
||||||
|
```
|
||||||
|
|
||||||
|
Quando viene salvato in formato binario float32:
|
||||||
|
|
||||||
|
```text
|
||||||
|
128 x 4 byte = 512 byte
|
||||||
|
```
|
||||||
5
include/face_config.h
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef FACE_CLUSTERING_ROOT
|
||||||
|
#define FACE_CLUSTERING_ROOT "."
|
||||||
|
#endif
|
||||||
47
include/face_engine.h
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Point {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Face {
|
||||||
|
float score = 0.0f;
|
||||||
|
|
||||||
|
float x1 = 0.0f;
|
||||||
|
float y1 = 0.0f;
|
||||||
|
float x2 = 0.0f;
|
||||||
|
float y2 = 0.0f;
|
||||||
|
|
||||||
|
Point kps[5]{};
|
||||||
|
|
||||||
|
// SFace: embedding L2-normalizzato da 128 float32.
|
||||||
|
std::vector<float> embedding;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FaceEngine {
|
||||||
|
public:
|
||||||
|
FaceEngine();
|
||||||
|
~FaceEngine();
|
||||||
|
|
||||||
|
FaceEngine(const FaceEngine&) = delete;
|
||||||
|
FaceEngine& operator=(const FaceEngine&) = delete;
|
||||||
|
|
||||||
|
bool init(
|
||||||
|
const std::string& scrfd_model,
|
||||||
|
const std::string& sface_model);
|
||||||
|
|
||||||
|
bool process_image(
|
||||||
|
const std::string& image_path,
|
||||||
|
std::vector<Face>& faces,
|
||||||
|
int& image_width,
|
||||||
|
int& image_height,
|
||||||
|
int rotation_degrees = 0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
Impl* impl_;
|
||||||
|
};
|
||||||
121
include/knn_opencl.h
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
#ifndef FACE_CLUSTERING_KNN_OPENCL_H
|
||||||
|
#define FACE_CLUSTERING_KNN_OPENCL_H
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace face_knn {
|
||||||
|
|
||||||
|
constexpr std::uint32_t invalid_index =
|
||||||
|
0xffffffffu;
|
||||||
|
|
||||||
|
|
||||||
|
struct Neighbor {
|
||||||
|
float score = 0.0f;
|
||||||
|
|
||||||
|
std::uint32_t index =
|
||||||
|
invalid_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct SearchOptions {
|
||||||
|
std::uint32_t dimension = 128;
|
||||||
|
|
||||||
|
std::uint32_t top_k = 1;
|
||||||
|
|
||||||
|
std::uint32_t lanes_per_query = 8;
|
||||||
|
|
||||||
|
std::size_t chunk_size = 50000;
|
||||||
|
|
||||||
|
std::string kernel_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct SearchOutput {
|
||||||
|
std::size_t query_count = 0;
|
||||||
|
|
||||||
|
std::size_t database_count = 0;
|
||||||
|
|
||||||
|
std::uint32_t dimension = 0;
|
||||||
|
|
||||||
|
std::uint32_t top_k = 0;
|
||||||
|
|
||||||
|
std::string backend;
|
||||||
|
|
||||||
|
std::string device_name;
|
||||||
|
|
||||||
|
double elapsed_ms = 0.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Layout:
|
||||||
|
*
|
||||||
|
* neighbors[
|
||||||
|
* query_index * top_k
|
||||||
|
* + neighbor_position
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
std::vector<Neighbor> neighbors;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct ValidationResult {
|
||||||
|
std::size_t positions = 0;
|
||||||
|
|
||||||
|
std::size_t exact_index_matches = 0;
|
||||||
|
|
||||||
|
std::size_t decision_matches = 0;
|
||||||
|
|
||||||
|
float maximum_score_error = 0.0f;
|
||||||
|
|
||||||
|
bool exact_indices = false;
|
||||||
|
|
||||||
|
bool decisions_equal = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void validate_input(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options);
|
||||||
|
|
||||||
|
|
||||||
|
SearchOutput search_cpu(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options);
|
||||||
|
|
||||||
|
|
||||||
|
SearchOutput search_opencl(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options);
|
||||||
|
|
||||||
|
|
||||||
|
ValidationResult compare_outputs(
|
||||||
|
const SearchOutput& expected,
|
||||||
|
const SearchOutput& actual,
|
||||||
|
float score_tolerance,
|
||||||
|
float recognition_threshold);
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Neighbor> query_neighbors(
|
||||||
|
const SearchOutput& output,
|
||||||
|
std::size_t query_index);
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Neighbor> exclude_neighbor(
|
||||||
|
const std::vector<Neighbor>& neighbors,
|
||||||
|
std::uint32_t excluded_index);
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace face_knn
|
||||||
|
|
||||||
|
#endif
|
||||||
804
include/rknn_api.h
Normal file
|
|
@ -0,0 +1,804 @@
|
||||||
|
/****************************************************************************
|
||||||
|
*
|
||||||
|
* Copyright (c) 2017 - 2022 by Rockchip Corp. All rights reserved.
|
||||||
|
*
|
||||||
|
* The material in this file is confidential and contains trade secrets
|
||||||
|
* of Rockchip Corporation. This is proprietary information owned by
|
||||||
|
* Rockchip Corporation. No part of this work may be disclosed,
|
||||||
|
* reproduced, copied, transmitted, or used in any way for any purpose,
|
||||||
|
* without the express written permission of Rockchip Corporation.
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _RKNN_API_H
|
||||||
|
#define _RKNN_API_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Definition of extended flag for rknn_init.
|
||||||
|
*/
|
||||||
|
/* set high priority context. */
|
||||||
|
#define RKNN_FLAG_PRIOR_HIGH 0x00000000
|
||||||
|
|
||||||
|
/* set medium priority context */
|
||||||
|
#define RKNN_FLAG_PRIOR_MEDIUM 0x00000001
|
||||||
|
|
||||||
|
/* set low priority context. */
|
||||||
|
#define RKNN_FLAG_PRIOR_LOW 0x00000002
|
||||||
|
|
||||||
|
/* asynchronous mode.
|
||||||
|
when enable, rknn_outputs_get will not block for too long because it directly retrieves the result of
|
||||||
|
the previous frame which can increase the frame rate on single-threaded mode, but at the cost of
|
||||||
|
rknn_outputs_get not retrieves the result of the current frame.
|
||||||
|
in multi-threaded mode you do not need to turn this mode on. */
|
||||||
|
#define RKNN_FLAG_ASYNC_MASK 0x00000004
|
||||||
|
|
||||||
|
/* collect performance mode.
|
||||||
|
when enable, you can get detailed performance reports via rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, ...),
|
||||||
|
but it will reduce the frame rate. */
|
||||||
|
#define RKNN_FLAG_COLLECT_PERF_MASK 0x00000008
|
||||||
|
|
||||||
|
/* allocate all memory in outside, includes weight/internal/inputs/outputs */
|
||||||
|
#define RKNN_FLAG_MEM_ALLOC_OUTSIDE 0x00000010
|
||||||
|
|
||||||
|
/* weight sharing with the same network structure */
|
||||||
|
#define RKNN_FLAG_SHARE_WEIGHT_MEM 0x00000020
|
||||||
|
|
||||||
|
/* send fence fd from outside */
|
||||||
|
#define RKNN_FLAG_FENCE_IN_OUTSIDE 0x00000040
|
||||||
|
|
||||||
|
/* get fence fd from inside */
|
||||||
|
#define RKNN_FLAG_FENCE_OUT_OUTSIDE 0x00000080
|
||||||
|
|
||||||
|
/* dummy init flag: could only get total_weight_size and total_internal_size by rknn_query*/
|
||||||
|
#define RKNN_FLAG_COLLECT_MODEL_INFO_ONLY 0x00000100
|
||||||
|
|
||||||
|
/* allocate internal memory in outside */
|
||||||
|
#define RKNN_FLAG_INTERNAL_ALLOC_OUTSIDE 0x00000200
|
||||||
|
|
||||||
|
/* set GPU as the preferred execution backend When the operator is not supported by the NPU */
|
||||||
|
#define RKNN_FLAG_EXECUTE_FALLBACK_PRIOR_DEVICE_GPU 0x00000400
|
||||||
|
|
||||||
|
/* enable allocate sram type buffers */
|
||||||
|
#define RKNN_FLAG_ENABLE_SRAM 0x00000800
|
||||||
|
|
||||||
|
/* sram type buffers are shared among different contexts */
|
||||||
|
#define RKNN_FLAG_SHARE_SRAM 0x00001000
|
||||||
|
|
||||||
|
/* default nice -19, this flag can disable default priority */
|
||||||
|
#define RKNN_FLAG_DISABLE_PROC_HIGH_PRIORITY 0x00002000
|
||||||
|
|
||||||
|
/* don't flush input buffer cache, the user must ensure that the input tensor has flushed the cache before calling rknn_run.
|
||||||
|
!!! Don't use this flags when you call rknn_inputs_set() to set input data. */
|
||||||
|
#define RKNN_FLAG_DISABLE_FLUSH_INPUT_MEM_CACHE 0x00004000
|
||||||
|
|
||||||
|
/* Don't invalid output buffer cache.
|
||||||
|
Users cannot directly access output_mem->virt_addr,
|
||||||
|
which will cause cache consistency problems.
|
||||||
|
If you want to use output_mem->virt_addr,
|
||||||
|
you must use rknn_mem_sync (ctx, mem, RKNN_MEMORY_SYNC_FROM_DEVICE) to flush the cache.
|
||||||
|
This flags is generally used when the output data of the NPU is not accessed by the CPU,
|
||||||
|
but is accessed by the GPU or RGA to reduce the time required to flush the cache.
|
||||||
|
!!! Don't use this flags when you call rknn_outputs_get() to get output data.*/
|
||||||
|
#define RKNN_FLAG_DISABLE_FLUSH_OUTPUT_MEM_CACHE 0x00008000
|
||||||
|
|
||||||
|
/* This flag is used when the model data buffer is allocated by NPU, and can be accessed by NPU directly. */
|
||||||
|
#define RKNN_FLAG_MODEL_BUFFER_ZERO_COPY 0x00010000
|
||||||
|
|
||||||
|
/* This flag is a memory allocation flag, which is used in rknn_create_mem2() when no context is available. */
|
||||||
|
#define RKNN_MEM_FLAG_ALLOC_NO_CONTEXT 0x00020000
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Error code returned by the RKNN API.
|
||||||
|
*/
|
||||||
|
#define RKNN_SUCC 0 /* execute succeed. */
|
||||||
|
#define RKNN_ERR_FAIL -1 /* execute failed. */
|
||||||
|
#define RKNN_ERR_TIMEOUT -2 /* execute timeout. */
|
||||||
|
#define RKNN_ERR_DEVICE_UNAVAILABLE -3 /* device is unavailable. */
|
||||||
|
#define RKNN_ERR_MALLOC_FAIL -4 /* memory malloc fail. */
|
||||||
|
#define RKNN_ERR_PARAM_INVALID -5 /* parameter is invalid. */
|
||||||
|
#define RKNN_ERR_MODEL_INVALID -6 /* model is invalid. */
|
||||||
|
#define RKNN_ERR_CTX_INVALID -7 /* context is invalid. */
|
||||||
|
#define RKNN_ERR_INPUT_INVALID -8 /* input is invalid. */
|
||||||
|
#define RKNN_ERR_OUTPUT_INVALID -9 /* output is invalid. */
|
||||||
|
#define RKNN_ERR_DEVICE_UNMATCH -10 /* the device is unmatch, please update rknn sdk
|
||||||
|
and npu driver/firmware. */
|
||||||
|
#define RKNN_ERR_INCOMPATILE_PRE_COMPILE_MODEL -11 /* This RKNN model use pre_compile mode, but not compatible with current driver. */
|
||||||
|
#define RKNN_ERR_INCOMPATILE_OPTIMIZATION_LEVEL_VERSION -12 /* This RKNN model set optimization level, but not compatible with current driver. */
|
||||||
|
#define RKNN_ERR_TARGET_PLATFORM_UNMATCH -13 /* This RKNN model set target platform, but not compatible with current platform. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Definition for tensor
|
||||||
|
*/
|
||||||
|
#define RKNN_MAX_DIMS 16 /* maximum dimension of tensor. */
|
||||||
|
#define RKNN_MAX_NUM_CHANNEL 15 /* maximum channel number of input tensor. */
|
||||||
|
#define RKNN_MAX_NAME_LEN 256 /* maximum name lenth of tensor. */
|
||||||
|
#define RKNN_MAX_DYNAMIC_SHAPE_NUM 512 /* maximum number of dynamic shape for each input. */
|
||||||
|
|
||||||
|
#ifdef __arm__
|
||||||
|
typedef uint32_t rknn_context;
|
||||||
|
#else
|
||||||
|
typedef uint64_t rknn_context;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
The query command for rknn_query
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_query_cmd {
|
||||||
|
RKNN_QUERY_IN_OUT_NUM = 0, /* query the number of input & output tensor. */
|
||||||
|
RKNN_QUERY_INPUT_ATTR = 1, /* query the attribute of input tensor. */
|
||||||
|
RKNN_QUERY_OUTPUT_ATTR = 2, /* query the attribute of output tensor. */
|
||||||
|
RKNN_QUERY_PERF_DETAIL = 3, /* query the detail performance, need set
|
||||||
|
RKNN_FLAG_COLLECT_PERF_MASK when call rknn_init,
|
||||||
|
this query needs to be valid after rknn_outputs_get. */
|
||||||
|
RKNN_QUERY_PERF_RUN = 4, /* query the time of run,
|
||||||
|
this query needs to be valid after rknn_outputs_get. */
|
||||||
|
RKNN_QUERY_SDK_VERSION = 5, /* query the sdk & driver version */
|
||||||
|
|
||||||
|
RKNN_QUERY_MEM_SIZE = 6, /* query the weight & internal memory size */
|
||||||
|
RKNN_QUERY_CUSTOM_STRING = 7, /* query the custom string */
|
||||||
|
|
||||||
|
RKNN_QUERY_NATIVE_INPUT_ATTR = 8, /* query the attribute of native input tensor. */
|
||||||
|
RKNN_QUERY_NATIVE_OUTPUT_ATTR = 9, /* query the attribute of native output tensor. */
|
||||||
|
|
||||||
|
RKNN_QUERY_NATIVE_NC1HWC2_INPUT_ATTR = 8, /* query the attribute of native input tensor. */
|
||||||
|
RKNN_QUERY_NATIVE_NC1HWC2_OUTPUT_ATTR = 9, /* query the attribute of native output tensor. */
|
||||||
|
|
||||||
|
RKNN_QUERY_NATIVE_NHWC_INPUT_ATTR = 10, /* query the attribute of native input tensor. */
|
||||||
|
RKNN_QUERY_NATIVE_NHWC_OUTPUT_ATTR = 11, /* query the attribute of native output tensor. */
|
||||||
|
|
||||||
|
RKNN_QUERY_DEVICE_MEM_INFO = 12, /* query the attribute of rknn memory information. */
|
||||||
|
|
||||||
|
RKNN_QUERY_INPUT_DYNAMIC_RANGE = 13, /* query the dynamic shape range of rknn input tensor. */
|
||||||
|
RKNN_QUERY_CURRENT_INPUT_ATTR = 14, /* query the current shape of rknn input tensor, only valid for dynamic rknn model*/
|
||||||
|
RKNN_QUERY_CURRENT_OUTPUT_ATTR = 15, /* query the current shape of rknn output tensor, only valid for dynamic rknn model*/
|
||||||
|
|
||||||
|
RKNN_QUERY_CURRENT_NATIVE_INPUT_ATTR = 16, /* query the current native shape of rknn input tensor, only valid for dynamic rknn model*/
|
||||||
|
RKNN_QUERY_CURRENT_NATIVE_OUTPUT_ATTR = 17, /* query the current native shape of rknn output tensor, only valid for dynamic rknn model*/
|
||||||
|
|
||||||
|
|
||||||
|
RKNN_QUERY_CMD_MAX
|
||||||
|
} rknn_query_cmd;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the tensor data type.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_tensor_type {
|
||||||
|
RKNN_TENSOR_FLOAT32 = 0, /* data type is float32. */
|
||||||
|
RKNN_TENSOR_FLOAT16, /* data type is float16. */
|
||||||
|
RKNN_TENSOR_INT8, /* data type is int8. */
|
||||||
|
RKNN_TENSOR_UINT8, /* data type is uint8. */
|
||||||
|
RKNN_TENSOR_INT16, /* data type is int16. */
|
||||||
|
RKNN_TENSOR_UINT16, /* data type is uint16. */
|
||||||
|
RKNN_TENSOR_INT32, /* data type is int32. */
|
||||||
|
RKNN_TENSOR_UINT32, /* data type is uint32. */
|
||||||
|
RKNN_TENSOR_INT64, /* data type is int64. */
|
||||||
|
RKNN_TENSOR_BOOL,
|
||||||
|
RKNN_TENSOR_INT4,
|
||||||
|
RKNN_TENSOR_BFLOAT16,
|
||||||
|
|
||||||
|
RKNN_TENSOR_TYPE_MAX
|
||||||
|
} rknn_tensor_type;
|
||||||
|
|
||||||
|
inline static const char* get_type_string(rknn_tensor_type type)
|
||||||
|
{
|
||||||
|
switch(type) {
|
||||||
|
case RKNN_TENSOR_FLOAT32: return "FP32";
|
||||||
|
case RKNN_TENSOR_FLOAT16: return "FP16";
|
||||||
|
case RKNN_TENSOR_INT8: return "INT8";
|
||||||
|
case RKNN_TENSOR_UINT8: return "UINT8";
|
||||||
|
case RKNN_TENSOR_INT16: return "INT16";
|
||||||
|
case RKNN_TENSOR_UINT16: return "UINT16";
|
||||||
|
case RKNN_TENSOR_INT32: return "INT32";
|
||||||
|
case RKNN_TENSOR_UINT32: return "UINT32";
|
||||||
|
case RKNN_TENSOR_INT64: return "INT64";
|
||||||
|
case RKNN_TENSOR_BOOL: return "BOOL";
|
||||||
|
case RKNN_TENSOR_INT4: return "INT4";
|
||||||
|
case RKNN_TENSOR_BFLOAT16: return "BF16";
|
||||||
|
default: return "UNKNOW";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
the quantitative type.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_tensor_qnt_type {
|
||||||
|
RKNN_TENSOR_QNT_NONE = 0, /* none. */
|
||||||
|
RKNN_TENSOR_QNT_DFP, /* dynamic fixed point. */
|
||||||
|
RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC, /* asymmetric affine. */
|
||||||
|
|
||||||
|
RKNN_TENSOR_QNT_MAX
|
||||||
|
} rknn_tensor_qnt_type;
|
||||||
|
|
||||||
|
inline static const char* get_qnt_type_string(rknn_tensor_qnt_type type)
|
||||||
|
{
|
||||||
|
switch(type) {
|
||||||
|
case RKNN_TENSOR_QNT_NONE: return "NONE";
|
||||||
|
case RKNN_TENSOR_QNT_DFP: return "DFP";
|
||||||
|
case RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC: return "AFFINE";
|
||||||
|
default: return "UNKNOW";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
the tensor data format.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_tensor_format {
|
||||||
|
RKNN_TENSOR_NCHW = 0, /* data format is NCHW. */
|
||||||
|
RKNN_TENSOR_NHWC, /* data format is NHWC. */
|
||||||
|
RKNN_TENSOR_NC1HWC2, /* data format is NC1HWC2. */
|
||||||
|
RKNN_TENSOR_UNDEFINED,
|
||||||
|
|
||||||
|
RKNN_TENSOR_FORMAT_MAX
|
||||||
|
} rknn_tensor_format;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the mode of running on target NPU core.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_core_mask {
|
||||||
|
RKNN_NPU_CORE_AUTO = 0, /* default, run on NPU core randomly. */
|
||||||
|
RKNN_NPU_CORE_0 = 1, /* run on NPU core 0. */
|
||||||
|
RKNN_NPU_CORE_1 = 2, /* run on NPU core 1. */
|
||||||
|
RKNN_NPU_CORE_2 = 4, /* run on NPU core 2. */
|
||||||
|
RKNN_NPU_CORE_0_1 = RKNN_NPU_CORE_0 | RKNN_NPU_CORE_1, /* run on NPU core 0 and core 1. */
|
||||||
|
RKNN_NPU_CORE_0_1_2 = RKNN_NPU_CORE_0_1 | RKNN_NPU_CORE_2, /* run on NPU core 0 and core 1 and core 2. */
|
||||||
|
RKNN_NPU_CORE_ALL = 0xffff, /* auto choice, run on NPU cores depending on platform */
|
||||||
|
|
||||||
|
RKNN_NPU_CORE_UNDEFINED,
|
||||||
|
} rknn_core_mask;
|
||||||
|
|
||||||
|
inline static const char* get_format_string(rknn_tensor_format fmt)
|
||||||
|
{
|
||||||
|
switch(fmt) {
|
||||||
|
case RKNN_TENSOR_NCHW: return "NCHW";
|
||||||
|
case RKNN_TENSOR_NHWC: return "NHWC";
|
||||||
|
case RKNN_TENSOR_NC1HWC2: return "NC1HWC2";
|
||||||
|
case RKNN_TENSOR_UNDEFINED: return "UNDEFINED";
|
||||||
|
default: return "UNKNOW";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_IN_OUT_NUM.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_input_output_num {
|
||||||
|
uint32_t n_input; /* the number of input. */
|
||||||
|
uint32_t n_output; /* the number of output. */
|
||||||
|
} rknn_input_output_num;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_INPUT_ATTR / RKNN_QUERY_OUTPUT_ATTR.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_tensor_attr {
|
||||||
|
uint32_t index; /* input parameter, the index of input/output tensor,
|
||||||
|
need set before call rknn_query. */
|
||||||
|
|
||||||
|
uint32_t n_dims; /* the number of dimensions. */
|
||||||
|
uint32_t dims[RKNN_MAX_DIMS]; /* the dimensions array. */
|
||||||
|
char name[RKNN_MAX_NAME_LEN]; /* the name of tensor. */
|
||||||
|
|
||||||
|
uint32_t n_elems; /* the number of elements. */
|
||||||
|
uint32_t size; /* the bytes size of tensor. */
|
||||||
|
|
||||||
|
rknn_tensor_format fmt; /* the data format of tensor. */
|
||||||
|
rknn_tensor_type type; /* the data type of tensor. */
|
||||||
|
rknn_tensor_qnt_type qnt_type; /* the quantitative type of tensor. */
|
||||||
|
int8_t fl; /* fractional length for RKNN_TENSOR_QNT_DFP. */
|
||||||
|
int32_t zp; /* zero point for RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC. */
|
||||||
|
float scale; /* scale for RKNN_TENSOR_QNT_AFFINE_ASYMMETRIC. */
|
||||||
|
|
||||||
|
uint32_t w_stride; /* the stride of tensor along the width dimention of input,
|
||||||
|
Note: it is read-only, 0 means equal to width. */
|
||||||
|
uint32_t size_with_stride; /* the bytes size of tensor with stride. */
|
||||||
|
|
||||||
|
uint8_t pass_through; /* pass through mode, for rknn_set_io_mem interface.
|
||||||
|
if TRUE, the buf data is passed directly to the input node of the rknn model
|
||||||
|
without any conversion. the following variables do not need to be set.
|
||||||
|
if FALSE, the buf data is converted into an input consistent with the model
|
||||||
|
according to the following type and fmt. so the following variables
|
||||||
|
need to be set.*/
|
||||||
|
uint32_t h_stride; /* the stride along the height dimention of input,
|
||||||
|
Note: it is write-only, if it was set to 0, h_stride = height. */
|
||||||
|
} rknn_tensor_attr;
|
||||||
|
|
||||||
|
typedef struct _rknn_input_range {
|
||||||
|
uint32_t index; /* input parameter, the index of input/output tensor,
|
||||||
|
need set before call rknn_query. */
|
||||||
|
uint32_t shape_number; /* the number of shape. */
|
||||||
|
rknn_tensor_format fmt; /* the data format of tensor. */
|
||||||
|
char name[RKNN_MAX_NAME_LEN]; /* the name of tensor. */
|
||||||
|
uint32_t dyn_range[RKNN_MAX_DYNAMIC_SHAPE_NUM][RKNN_MAX_DIMS]; /* the dynamic input dimensions range. */
|
||||||
|
uint32_t n_dims; /* the number of dimensions. */
|
||||||
|
|
||||||
|
} rknn_input_range;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_PERF_DETAIL.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_perf_detail {
|
||||||
|
char* perf_data; /* the string pointer of perf detail. don't need free it by user. */
|
||||||
|
uint64_t data_len; /* the string length. */
|
||||||
|
} rknn_perf_detail;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_PERF_RUN.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_perf_run {
|
||||||
|
int64_t run_duration; /* real inference time (us) */
|
||||||
|
} rknn_perf_run;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_SDK_VERSION.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_sdk_version {
|
||||||
|
char api_version[256]; /* the version of rknn api. */
|
||||||
|
char drv_version[256]; /* the version of rknn driver. */
|
||||||
|
} rknn_sdk_version;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_MEM_SIZE.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_mem_size {
|
||||||
|
uint32_t total_weight_size; /* the weight memory size */
|
||||||
|
uint32_t total_internal_size; /* the internal memory size, exclude inputs/outputs */
|
||||||
|
uint64_t total_dma_allocated_size; /* total dma memory allocated size */
|
||||||
|
uint32_t total_sram_size; /* total system sram size reserved for rknn */
|
||||||
|
uint32_t free_sram_size; /* free system sram size reserved for rknn */
|
||||||
|
uint32_t reserved[10]; /* reserved */
|
||||||
|
} rknn_mem_size;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the information for RKNN_QUERY_CUSTOM_STRING.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_custom_string {
|
||||||
|
char string[1024]; /* the string of custom, lengths max to 1024 bytes */
|
||||||
|
} rknn_custom_string;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The flags of rknn_tensor_mem.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_tensor_mem_flags {
|
||||||
|
RKNN_TENSOR_MEMORY_FLAGS_ALLOC_INSIDE = 1, /*Used to mark in rknn_destroy_mem() whether it is necessary to release the "mem" pointer itself.
|
||||||
|
If the flag RKNN_TENSOR_MEMORY_FLAGS_ALLOC_INSIDE is set, rknn_destroy_mem() will call free(mem).*/
|
||||||
|
RKNN_TENSOR_MEMORY_FLAGS_FROM_FD = 2, /*Used to mark in rknn_create_mem_from_fd() whether it is necessary to release the "mem" pointer itself.
|
||||||
|
If the flag RKNN_TENSOR_MEMORY_FLAGS_FROM_FD is set, rknn_destroy_mem() will call free(mem).*/
|
||||||
|
RKNN_TENSOR_MEMORY_FLAGS_FROM_PHYS = 3, /*Used to mark in rknn_create_mem_from_phys() whether it is necessary to release the "mem" pointer itself.
|
||||||
|
If the flag RKNN_TENSOR_MEMORY_FLAGS_FROM_PHYS is set, rknn_destroy_mem() will call free(mem).*/
|
||||||
|
RKNN_TENSOR_MEMORY_FLAGS_UNKNOWN
|
||||||
|
} rknn_tensor_mem_flags;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The mode to allocate rknn memory.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_mem_alloc_flags {
|
||||||
|
RKNN_FLAG_MEMORY_FLAGS_DEFAULT = 0 << 0, /* Same with RKNN_FLAG_MEMORY_CACHEABLE */
|
||||||
|
RKNN_FLAG_MEMORY_CACHEABLE = 1 << 0, /* Create Cacheable memory. */
|
||||||
|
RKNN_FLAG_MEMORY_NON_CACHEABLE = 1 << 1, /* Create NON-Cacheable memory. */
|
||||||
|
RKNN_FLAG_MEMORY_TRY_ALLOC_SRAM = 1 << 2, /* Try to allocate memory in SRAM if possible. if SRAM is not enough, allocate rest memory in DRAM. */
|
||||||
|
} rknn_mem_alloc_flags;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The mode to sync cacheable rknn memory.
|
||||||
|
*/
|
||||||
|
typedef enum _rknn_mem_sync_mode {
|
||||||
|
RKNN_MEMORY_SYNC_TO_DEVICE = 0x1, /* the mode used for consistency of device access after CPU accesses data. */
|
||||||
|
RKNN_MEMORY_SYNC_FROM_DEVICE = 0x2, /* the mode used for consistency of CPU access after device accesses data. */
|
||||||
|
RKNN_MEMORY_SYNC_BIDIRECTIONAL = RKNN_MEMORY_SYNC_TO_DEVICE | RKNN_MEMORY_SYNC_FROM_DEVICE, /* the mode used for consistency of data access
|
||||||
|
between device and CPU in both directions. */
|
||||||
|
} rknn_mem_sync_mode;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the memory information of tensor.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_tensor_memory {
|
||||||
|
void* virt_addr; /* the virtual address of tensor buffer. */
|
||||||
|
uint64_t phys_addr; /* the physical address of tensor buffer. */
|
||||||
|
int32_t fd; /* the fd of tensor buffer. */
|
||||||
|
int32_t offset; /* indicates the offset of the memory. */
|
||||||
|
uint32_t size; /* the size of tensor buffer. */
|
||||||
|
uint32_t flags; /* the flags of tensor buffer, reserved */
|
||||||
|
void * priv_data; /* the private data of tensor buffer. */
|
||||||
|
} rknn_tensor_mem;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the input information for rknn_input_set.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_input {
|
||||||
|
uint32_t index; /* the input index. */
|
||||||
|
void* buf; /* the input buf for index. */
|
||||||
|
uint32_t size; /* the size of input buf. */
|
||||||
|
uint8_t pass_through; /* pass through mode.
|
||||||
|
if TRUE, the buf data is passed directly to the input node of the rknn model
|
||||||
|
without any conversion. the following variables do not need to be set.
|
||||||
|
if FALSE, the buf data is converted into an input consistent with the model
|
||||||
|
according to the following type and fmt. so the following variables
|
||||||
|
need to be set.*/
|
||||||
|
rknn_tensor_type type; /* the data type of input buf. */
|
||||||
|
rknn_tensor_format fmt; /* the data format of input buf.
|
||||||
|
currently the internal input format of NPU is NCHW by default.
|
||||||
|
so entering NCHW data can avoid the format conversion in the driver. */
|
||||||
|
} rknn_input;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the output information for rknn_outputs_get.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_output {
|
||||||
|
uint8_t want_float; /* want transfer output data to float */
|
||||||
|
uint8_t is_prealloc; /* whether buf is pre-allocated.
|
||||||
|
if TRUE, the following variables need to be set.
|
||||||
|
if FALSE, the following variables do not need to be set. */
|
||||||
|
uint32_t index; /* the output index. */
|
||||||
|
void* buf; /* the output buf for index.
|
||||||
|
when is_prealloc = FALSE and rknn_outputs_release called,
|
||||||
|
this buf pointer will be free and don't use it anymore. */
|
||||||
|
uint32_t size; /* the size of output buf. */
|
||||||
|
} rknn_output;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the extend information for rknn_init.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_init_extend {
|
||||||
|
rknn_context ctx; /* rknn context */
|
||||||
|
int32_t real_model_offset; /* real rknn model file offset, only valid when init context with rknn file path and zero-copy model model */
|
||||||
|
uint32_t real_model_size; /* real rknn model file size, only valid when init context with rknn file path and zero-copy model model */
|
||||||
|
int32_t model_buffer_fd; /* the fd of model buffer. */
|
||||||
|
uint32_t model_buffer_flags; /* the flags of model_buffer */
|
||||||
|
uint8_t reserved[112]; /* reserved */
|
||||||
|
} rknn_init_extend;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the extend information for rknn_run.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_run_extend {
|
||||||
|
uint64_t frame_id; /* output parameter, indicate current frame id of run. */
|
||||||
|
int32_t non_block; /* block flag of run, 0 is block else 1 is non block */
|
||||||
|
int32_t timeout_ms; /* timeout for block mode, in milliseconds */
|
||||||
|
int32_t fence_fd; /* fence fd from other unit */
|
||||||
|
} rknn_run_extend;
|
||||||
|
|
||||||
|
/*
|
||||||
|
the extend information for rknn_outputs_get.
|
||||||
|
*/
|
||||||
|
typedef struct _rknn_output_extend {
|
||||||
|
uint64_t frame_id; /* output parameter, indicate the frame id of outputs, corresponds to
|
||||||
|
struct rknn_run_extend.frame_id.*/
|
||||||
|
} rknn_output_extend;
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_init
|
||||||
|
|
||||||
|
initial the context and load the rknn model.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context* context the pointer of context handle.
|
||||||
|
void* model if size > 0, pointer to the rknn model, if size = 0, filepath to the rknn model.
|
||||||
|
uint32_t size the size of rknn model.
|
||||||
|
uint32_t flag extend flag, see the define of RKNN_FLAG_XXX_XXX.
|
||||||
|
rknn_init_extend* extend the extend information of init.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_init(rknn_context* context, void* model, uint32_t size, uint32_t flag, rknn_init_extend* extend);
|
||||||
|
|
||||||
|
/* rknn_dup_context
|
||||||
|
|
||||||
|
initial the context and load the rknn model.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context* context_in the pointer of context in handle.
|
||||||
|
rknn_context* context_out the pointer of context out handle.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_dup_context(rknn_context* context_in, rknn_context* context_out);
|
||||||
|
|
||||||
|
/* rknn_destroy
|
||||||
|
|
||||||
|
unload the rknn model and destroy the context.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_destroy(rknn_context context);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_query
|
||||||
|
|
||||||
|
query the information about model or others. see rknn_query_cmd.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
rknn_query_cmd cmd the command of query.
|
||||||
|
void* info the buffer point of information.
|
||||||
|
uint32_t size the size of information.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_query(rknn_context context, rknn_query_cmd cmd, void* info, uint32_t size);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_inputs_set
|
||||||
|
|
||||||
|
set inputs information by input index of rknn model.
|
||||||
|
inputs information see rknn_input.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
uint32_t n_inputs the number of inputs.
|
||||||
|
rknn_input inputs[] the arrays of inputs information, see rknn_input.
|
||||||
|
return:
|
||||||
|
int error code
|
||||||
|
*/
|
||||||
|
int rknn_inputs_set(rknn_context context, uint32_t n_inputs, rknn_input inputs[]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
rknn_set_batch_core_num
|
||||||
|
|
||||||
|
set rknn batch core_num.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
int core_num the core number.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
|
||||||
|
*/
|
||||||
|
int rknn_set_batch_core_num(rknn_context context, int core_num);
|
||||||
|
|
||||||
|
/* rknn_set_core_mask
|
||||||
|
|
||||||
|
set the core mask for the model.(only supported on multi-core NPU platform)
|
||||||
|
|
||||||
|
RKNN_NPU_CORE_AUTO: auto mode, default value
|
||||||
|
RKNN_NPU_CORE_0: core 0 mode
|
||||||
|
RKNN_NPU_CORE_1: core 1 mode
|
||||||
|
RKNN_NPU_CORE_2: core 2 mode
|
||||||
|
RKNN_NPU_CORE_0_1: combine core 0/1 mode
|
||||||
|
RKNN_NPU_CORE_0_1_2: combine core 0/1/2 mode
|
||||||
|
RKNN_NPU_CORE_ALL: auto mode, select multiple npu cores to run depending on platform
|
||||||
|
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
rknn_core_mask core_mask the core mask.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_core_mask(rknn_context context, rknn_core_mask core_mask);
|
||||||
|
|
||||||
|
/* rknn_run
|
||||||
|
|
||||||
|
run the model to execute inference.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
rknn_run_extend* extend the extend information of run.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_run(rknn_context context, rknn_run_extend* extend);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_wait
|
||||||
|
|
||||||
|
wait the model after execute inference.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
rknn_run_extend* extend the extend information of run.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_wait(rknn_context context, rknn_run_extend* extend);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_outputs_get
|
||||||
|
|
||||||
|
wait the inference to finish and get the outputs.
|
||||||
|
this function will block until inference finish.
|
||||||
|
the results will set to outputs[].
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
uint32_t n_outputs the number of outputs.
|
||||||
|
rknn_output outputs[] the arrays of output, see rknn_output.
|
||||||
|
rknn_output_extend* the extend information of output.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_outputs_get(rknn_context context, uint32_t n_outputs, rknn_output outputs[], rknn_output_extend* extend);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_outputs_release
|
||||||
|
|
||||||
|
release the outputs that get by rknn_outputs_get.
|
||||||
|
after called, the rknn_output[x].buf get from rknn_outputs_get will
|
||||||
|
also be free when rknn_output[x].is_prealloc = FALSE.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
uint32_t n_ouputs the number of outputs.
|
||||||
|
rknn_output outputs[] the arrays of output.
|
||||||
|
return:
|
||||||
|
int error code
|
||||||
|
*/
|
||||||
|
int rknn_outputs_release(rknn_context context, uint32_t n_ouputs, rknn_output outputs[]);
|
||||||
|
|
||||||
|
|
||||||
|
/* new api for zero copy */
|
||||||
|
|
||||||
|
/* rknn_create_mem_from_phys (memory allocated outside)
|
||||||
|
|
||||||
|
initialize tensor memory from physical address.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
uint64_t phys_addr physical address.
|
||||||
|
void *virt_addr virtual address.
|
||||||
|
uint32_t size the size of tensor buffer.
|
||||||
|
return:
|
||||||
|
rknn_tensor_mem the pointer of tensor memory information.
|
||||||
|
*/
|
||||||
|
rknn_tensor_mem* rknn_create_mem_from_phys(rknn_context ctx, uint64_t phys_addr, void *virt_addr, uint32_t size);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_create_mem_from_fd (memory allocated outside)
|
||||||
|
|
||||||
|
initialize tensor memory from file description.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
int32_t fd file description.
|
||||||
|
void *virt_addr virtual address.
|
||||||
|
uint32_t size the size of tensor buffer.
|
||||||
|
int32_t offset indicates the offset of the memory (virt_addr without offset).
|
||||||
|
return:
|
||||||
|
rknn_tensor_mem the pointer of tensor memory information.
|
||||||
|
*/
|
||||||
|
rknn_tensor_mem* rknn_create_mem_from_fd(rknn_context ctx, int32_t fd, void *virt_addr, uint32_t size, int32_t offset);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_create_mem_from_mb_blk (memory allocated outside)
|
||||||
|
|
||||||
|
create tensor memory from mb_blk.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
void *mb_blk mb_blk allocate from system api.
|
||||||
|
int32_t offset indicates the offset of the memory.
|
||||||
|
return:
|
||||||
|
rknn_tensor_mem the pointer of tensor memory information.
|
||||||
|
*/
|
||||||
|
rknn_tensor_mem* rknn_create_mem_from_mb_blk(rknn_context ctx, void *mb_blk, int32_t offset);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_create_mem (memory allocated inside)
|
||||||
|
|
||||||
|
create tensor memory.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
uint32_t size the size of tensor buffer.
|
||||||
|
return:
|
||||||
|
rknn_tensor_mem the pointer of tensor memory information.
|
||||||
|
*/
|
||||||
|
rknn_tensor_mem* rknn_create_mem(rknn_context ctx, uint32_t size);
|
||||||
|
|
||||||
|
/* rknn_create_mem2 (memory allocated inside)
|
||||||
|
|
||||||
|
create tensor memory.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
uint64_t size the size of tensor buffer.
|
||||||
|
uint64_t alloc_flags memory allocation flags.
|
||||||
|
return:
|
||||||
|
rknn_tensor_mem the pointer of tensor memory information.
|
||||||
|
*/
|
||||||
|
rknn_tensor_mem* rknn_create_mem2(rknn_context ctx, uint64_t size, uint64_t alloc_flags);
|
||||||
|
|
||||||
|
/* rknn_destroy_mem (support allocate inside and outside)
|
||||||
|
|
||||||
|
destroy tensor memory.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
rknn_tensor_mem *mem the pointer of tensor memory information.
|
||||||
|
return:
|
||||||
|
int error code
|
||||||
|
*/
|
||||||
|
int rknn_destroy_mem(rknn_context ctx, rknn_tensor_mem *mem);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_set_weight_mem
|
||||||
|
|
||||||
|
set the weight memory.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
rknn_tensor_mem *mem the array of tensor memory information
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_weight_mem(rknn_context ctx, rknn_tensor_mem *mem);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_set_internal_mem
|
||||||
|
|
||||||
|
set the internal memory.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
rknn_tensor_mem *mem the array of tensor memory information
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_internal_mem(rknn_context ctx, rknn_tensor_mem *mem);
|
||||||
|
|
||||||
|
|
||||||
|
/* rknn_set_io_mem
|
||||||
|
|
||||||
|
set the input and output tensors buffer.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
rknn_tensor_mem *mem the array of tensor memory information.
|
||||||
|
rknn_tensor_attr *attr the attribute of input or output tensor buffer.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_io_mem(rknn_context ctx, rknn_tensor_mem *mem, rknn_tensor_attr *attr);
|
||||||
|
|
||||||
|
/* rknn_set_input_shape(deprecated)
|
||||||
|
|
||||||
|
set the input tensor shape (only valid for dynamic shape rknn model).
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
rknn_tensor_attr *attr the attribute of input or output tensor buffer.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_input_shape(rknn_context ctx, rknn_tensor_attr* attr);
|
||||||
|
|
||||||
|
/* rknn_set_input_shapes
|
||||||
|
|
||||||
|
set all the input tensor shapes. graph will run under current set of input shapes after rknn_set_input_shapes.(only valid for dynamic shape rknn model).
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context ctx the handle of context.
|
||||||
|
uint32_t n_inputs the number of inputs.
|
||||||
|
rknn_tensor_attr attr[] the attribute array of all input tensors.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_set_input_shapes(rknn_context ctx, uint32_t n_inputs, rknn_tensor_attr attr[]);
|
||||||
|
|
||||||
|
/* rknn_mem_sync
|
||||||
|
|
||||||
|
sync cacheable rknn memory when both cpu and device access data.
|
||||||
|
|
||||||
|
input:
|
||||||
|
rknn_context context the handle of context.
|
||||||
|
rknn_tensor_mem *mem the pointer of tensor memory information.
|
||||||
|
rknn_mem_sync_mode mode the mode of sync cache.
|
||||||
|
return:
|
||||||
|
int error code.
|
||||||
|
*/
|
||||||
|
int rknn_mem_sync(rknn_context context, rknn_tensor_mem* mem, rknn_mem_sync_mode mode);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} //extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //_RKNN_API_H
|
||||||
7762
include/stb_image.h
Normal file
191
kernels/knn_float4_topk.cl
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
#ifndef DIM
|
||||||
|
#define DIM 128
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TOPK
|
||||||
|
#define TOPK 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef QUERY_COUNT
|
||||||
|
#define QUERY_COUNT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Numero di work-item che collaborano per ogni query.
|
||||||
|
*/
|
||||||
|
#ifndef LANES_PER_QUERY
|
||||||
|
#define LANES_PER_QUERY 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni work-group contiene 8 work-item.
|
||||||
|
*
|
||||||
|
* Ogni work-item calcola un TOPK locale.
|
||||||
|
* Poi il work-item 0 fonde gli 8 TOPK locali.
|
||||||
|
*/
|
||||||
|
|
||||||
|
__kernel void knn(
|
||||||
|
__global const float *db,
|
||||||
|
__global const float *queries,
|
||||||
|
__global uint *indices,
|
||||||
|
__global float *values,
|
||||||
|
uint n)
|
||||||
|
{
|
||||||
|
uint lane = get_local_id(0);
|
||||||
|
uint q = get_group_id(0);
|
||||||
|
|
||||||
|
if (q >= QUERY_COUNT)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locale per ogni work-item.
|
||||||
|
*/
|
||||||
|
float best_score[TOPK];
|
||||||
|
uint best_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
best_score[k] = -3.402823466e+38f;
|
||||||
|
best_index[k] = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
__global const float *query =
|
||||||
|
&queries[q * DIM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni lane analizza una parte del database.
|
||||||
|
*
|
||||||
|
* Esempio con 8 lane:
|
||||||
|
*
|
||||||
|
* lane 0 -> 0, 8, 16, 24, ...
|
||||||
|
* lane 1 -> 1, 9, 17, 25, ...
|
||||||
|
* ...
|
||||||
|
* lane 7 -> 7, 15, 23, 31, ...
|
||||||
|
*/
|
||||||
|
for (uint i = lane; i < n; i += LANES_PER_QUERY) {
|
||||||
|
|
||||||
|
__global const float *v =
|
||||||
|
&db[i * DIM];
|
||||||
|
|
||||||
|
float s = 0.0f;
|
||||||
|
|
||||||
|
for (uint j = 0; j < DIM; ++j)
|
||||||
|
s += v[j] * query[j];
|
||||||
|
|
||||||
|
if (s <= best_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > best_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = TOPK - 1; k > pos; --k) {
|
||||||
|
best_score[k] =
|
||||||
|
best_score[k - 1];
|
||||||
|
|
||||||
|
best_index[k] =
|
||||||
|
best_index[k - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
best_score[pos] = s;
|
||||||
|
best_index[pos] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locali nella local memory.
|
||||||
|
*
|
||||||
|
* 8 lane × 32 elementi.
|
||||||
|
*/
|
||||||
|
__local float local_scores[LANES_PER_QUERY][TOPK];
|
||||||
|
__local uint local_indices[LANES_PER_QUERY][TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
local_scores[lane][k] = best_score[k];
|
||||||
|
local_indices[lane][k] = best_index[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tutti i lane devono aver scritto
|
||||||
|
* la local memory prima della riduzione.
|
||||||
|
*/
|
||||||
|
barrier(CLK_LOCAL_MEM_FENCE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solo lane 0 esegue il merge finale.
|
||||||
|
*/
|
||||||
|
if (lane == 0) {
|
||||||
|
|
||||||
|
float final_score[TOPK];
|
||||||
|
uint final_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
final_score[k] =
|
||||||
|
-3.402823466e+38f;
|
||||||
|
|
||||||
|
final_index[k] =
|
||||||
|
0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fonde gli 8 TOPK locali.
|
||||||
|
*
|
||||||
|
* Al massimo 8 × 32 = 256 candidati.
|
||||||
|
*/
|
||||||
|
for (uint l = 0;
|
||||||
|
l < LANES_PER_QUERY;
|
||||||
|
++l) {
|
||||||
|
|
||||||
|
for (uint k = 0;
|
||||||
|
k < TOPK;
|
||||||
|
++k) {
|
||||||
|
|
||||||
|
float s =
|
||||||
|
local_scores[l][k];
|
||||||
|
|
||||||
|
uint idx =
|
||||||
|
local_indices[l][k];
|
||||||
|
|
||||||
|
if (idx == 0xffffffff)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s <= final_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > final_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int p = TOPK - 1;
|
||||||
|
p > pos;
|
||||||
|
--p) {
|
||||||
|
|
||||||
|
final_score[p] =
|
||||||
|
final_score[p - 1];
|
||||||
|
|
||||||
|
final_index[p] =
|
||||||
|
final_index[p - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
final_score[pos] = s;
|
||||||
|
final_index[pos] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Risultato finale della query.
|
||||||
|
*/
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
indices[q * TOPK + k] =
|
||||||
|
final_index[k];
|
||||||
|
|
||||||
|
values[q * TOPK + k] =
|
||||||
|
final_score[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
kernels/knn_float4_topk.cl.sha256
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
6c4f502438aad3dd1600a00cb7988e0e35e56b2e8e4620d44ffcb0f3610a7226 knn_float4_topk.cl
|
||||||
180
kernels/knn_reference.cl
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
|
||||||
|
#define DIM 128
|
||||||
|
#define TOPK 32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Numero di work-item che collaborano per ogni query.
|
||||||
|
*/
|
||||||
|
#define LANES_PER_QUERY 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni work-group contiene 8 work-item.
|
||||||
|
*
|
||||||
|
* Ogni work-item calcola un TOPK locale.
|
||||||
|
* Poi il work-item 0 fonde gli 8 TOPK locali.
|
||||||
|
*/
|
||||||
|
|
||||||
|
__kernel void knn(
|
||||||
|
__global const float *db,
|
||||||
|
__global const float *queries,
|
||||||
|
__global uint *indices,
|
||||||
|
__global float *values,
|
||||||
|
uint n)
|
||||||
|
{
|
||||||
|
uint lane = get_local_id(0);
|
||||||
|
uint q = get_group_id(0);
|
||||||
|
|
||||||
|
if (q >= 256)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locale per ogni work-item.
|
||||||
|
*/
|
||||||
|
float best_score[TOPK];
|
||||||
|
uint best_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
best_score[k] = -3.402823466e+38f;
|
||||||
|
best_index[k] = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
__global const float *query =
|
||||||
|
&queries[q * DIM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni lane analizza una parte del database.
|
||||||
|
*
|
||||||
|
* Esempio con 8 lane:
|
||||||
|
*
|
||||||
|
* lane 0 -> 0, 8, 16, 24, ...
|
||||||
|
* lane 1 -> 1, 9, 17, 25, ...
|
||||||
|
* ...
|
||||||
|
* lane 7 -> 7, 15, 23, 31, ...
|
||||||
|
*/
|
||||||
|
for (uint i = lane; i < n; i += LANES_PER_QUERY) {
|
||||||
|
|
||||||
|
__global const float *v =
|
||||||
|
&db[i * DIM];
|
||||||
|
|
||||||
|
float s = 0.0f;
|
||||||
|
|
||||||
|
for (uint j = 0; j < DIM; ++j)
|
||||||
|
s += v[j] * query[j];
|
||||||
|
|
||||||
|
if (s <= best_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > best_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = TOPK - 1; k > pos; --k) {
|
||||||
|
best_score[k] =
|
||||||
|
best_score[k - 1];
|
||||||
|
|
||||||
|
best_index[k] =
|
||||||
|
best_index[k - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
best_score[pos] = s;
|
||||||
|
best_index[pos] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locali nella local memory.
|
||||||
|
*
|
||||||
|
* 8 lane × 32 elementi.
|
||||||
|
*/
|
||||||
|
__local float local_scores[LANES_PER_QUERY][TOPK];
|
||||||
|
__local uint local_indices[LANES_PER_QUERY][TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
local_scores[lane][k] = best_score[k];
|
||||||
|
local_indices[lane][k] = best_index[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tutti i lane devono aver scritto
|
||||||
|
* la local memory prima della riduzione.
|
||||||
|
*/
|
||||||
|
barrier(CLK_LOCAL_MEM_FENCE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solo lane 0 esegue il merge finale.
|
||||||
|
*/
|
||||||
|
if (lane == 0) {
|
||||||
|
|
||||||
|
float final_score[TOPK];
|
||||||
|
uint final_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
final_score[k] =
|
||||||
|
-3.402823466e+38f;
|
||||||
|
|
||||||
|
final_index[k] =
|
||||||
|
0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fonde gli 8 TOPK locali.
|
||||||
|
*
|
||||||
|
* Al massimo 8 × 32 = 256 candidati.
|
||||||
|
*/
|
||||||
|
for (uint l = 0;
|
||||||
|
l < LANES_PER_QUERY;
|
||||||
|
++l) {
|
||||||
|
|
||||||
|
for (uint k = 0;
|
||||||
|
k < TOPK;
|
||||||
|
++k) {
|
||||||
|
|
||||||
|
float s =
|
||||||
|
local_scores[l][k];
|
||||||
|
|
||||||
|
uint idx =
|
||||||
|
local_indices[l][k];
|
||||||
|
|
||||||
|
if (idx == 0xffffffff)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s <= final_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > final_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int p = TOPK - 1;
|
||||||
|
p > pos;
|
||||||
|
--p) {
|
||||||
|
|
||||||
|
final_score[p] =
|
||||||
|
final_score[p - 1];
|
||||||
|
|
||||||
|
final_index[p] =
|
||||||
|
final_index[p - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
final_score[pos] = s;
|
||||||
|
final_index[pos] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Risultato finale della query.
|
||||||
|
*/
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
indices[q * TOPK + k] =
|
||||||
|
final_index[k];
|
||||||
|
|
||||||
|
values[q * TOPK + k] =
|
||||||
|
final_score[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
lib/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Runtime RKNN
|
||||||
|
|
||||||
|
Copiare in questa directory la libreria runtime:
|
||||||
|
|
||||||
|
```text
|
||||||
|
lib/librknnrt.so
|
||||||
|
```
|
||||||
|
|
||||||
|
La libreria deve essere compatibile con:
|
||||||
|
|
||||||
|
- il dispositivo Rockchip utilizzato;
|
||||||
|
- la versione del driver RKNN;
|
||||||
|
- i modelli RKNN.
|
||||||
|
|
||||||
|
Il runtime è escluso dal repository tramite `.gitignore`.
|
||||||
|
|
||||||
|
Prima della distribuzione verificare le condizioni di licenza.
|
||||||
25
models/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Modelli RKNN
|
||||||
|
|
||||||
|
Copiare in questa directory i modelli richiesti:
|
||||||
|
|
||||||
|
```text
|
||||||
|
models/SCRFD_500M_KPS_640.rknn
|
||||||
|
models/face_recognition_sface_2021dec.rknn
|
||||||
|
```
|
||||||
|
|
||||||
|
## SCRFD
|
||||||
|
|
||||||
|
Il modello SCRFD rileva i volti e produce:
|
||||||
|
|
||||||
|
- confidence score;
|
||||||
|
- bounding box;
|
||||||
|
- cinque landmark.
|
||||||
|
|
||||||
|
## SFace
|
||||||
|
|
||||||
|
Il modello SFace riceve un volto allineato a 112 x 112 pixel e produce un
|
||||||
|
embedding di 128 elementi.
|
||||||
|
|
||||||
|
I modelli sono esclusi dal repository tramite `.gitignore`.
|
||||||
|
|
||||||
|
Prima della distribuzione verificare le rispettive licenze.
|
||||||
48
modules/hdbscan/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
# HDBSCAN
|
||||||
|
|
||||||
|
Modulo futuro per completare la pipeline di clustering degli embedding
|
||||||
|
facciali.
|
||||||
|
|
||||||
|
## Pipeline prevista
|
||||||
|
|
||||||
|
```text
|
||||||
|
kNN graph
|
||||||
|
|
|
||||||
|
v
|
||||||
|
core distance
|
||||||
|
|
|
||||||
|
v
|
||||||
|
mutual reachability
|
||||||
|
|
|
||||||
|
v
|
||||||
|
minimum spanning tree
|
||||||
|
|
|
||||||
|
v
|
||||||
|
single linkage hierarchy
|
||||||
|
|
|
||||||
|
v
|
||||||
|
condensed tree
|
||||||
|
|
|
||||||
|
v
|
||||||
|
cluster stability
|
||||||
|
|
|
||||||
|
v
|
||||||
|
cluster assignment
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output previsto
|
||||||
|
|
||||||
|
Per ogni embedding:
|
||||||
|
|
||||||
|
```text
|
||||||
|
face_index
|
||||||
|
cluster_id
|
||||||
|
membership_strength
|
||||||
|
noise_flag
|
||||||
|
```
|
||||||
|
|
||||||
|
Il risultato del clustering resterà distinto dall'identità semantica
|
||||||
|
assegnata dall'utente.
|
||||||
|
|
||||||
|
Un cluster automatico non rappresenta necessariamente una persona
|
||||||
|
definitiva e più cluster potranno essere associati alla stessa persona.
|
||||||
43
modules/knn-opencl/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# kNN OpenCL
|
||||||
|
|
||||||
|
Modulo futuro per costruire il grafo dei vicini degli embedding SFace
|
||||||
|
tramite OpenCL.
|
||||||
|
|
||||||
|
## Input previsto
|
||||||
|
|
||||||
|
```text
|
||||||
|
embedding float32[N][128]
|
||||||
|
```
|
||||||
|
|
||||||
|
Gli embedding saranno già L2-normalizzati.
|
||||||
|
|
||||||
|
Di conseguenza:
|
||||||
|
|
||||||
|
```text
|
||||||
|
cosine_similarity(a,b) = dot(a,b)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output previsto
|
||||||
|
|
||||||
|
```text
|
||||||
|
indices[N][32]
|
||||||
|
scores[N][32]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Obiettivo iniziale
|
||||||
|
|
||||||
|
```text
|
||||||
|
embedding float32[N][128]
|
||||||
|
|
|
||||||
|
v
|
||||||
|
brute-force dot product
|
||||||
|
|
|
||||||
|
v
|
||||||
|
TOPK=32
|
||||||
|
|
|
||||||
|
+-- neighbor index
|
||||||
|
`-- similarity score
|
||||||
|
```
|
||||||
|
|
||||||
|
La prima implementazione privilegerà la correttezza e la validazione
|
||||||
|
CPU/GPU.
|
||||||
29
modules/knn-opencl/reference/VALIDATION.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Validazione kNN OpenCL Mali-G610
|
||||||
|
|
||||||
|
## Parametri
|
||||||
|
|
||||||
|
- Database: 350000 embedding
|
||||||
|
- Query: 256
|
||||||
|
- Dimensione: 128
|
||||||
|
- TOPK: 32
|
||||||
|
- Chunk: 50000
|
||||||
|
- GPU: Mali-G610 r0p0
|
||||||
|
|
||||||
|
## Correttezza
|
||||||
|
|
||||||
|
- Posizioni TOPK confrontate: 8192
|
||||||
|
- Posizioni identiche CPU/OpenCL: 8192
|
||||||
|
- Exact ratio: 100.000%
|
||||||
|
|
||||||
|
## Prestazioni
|
||||||
|
|
||||||
|
- CPU: 9663.226 ms
|
||||||
|
- OpenCL: 1373.797 ms
|
||||||
|
- Speedup: 7.034x
|
||||||
|
- Throughput: 65220680 vettori/s
|
||||||
|
|
||||||
|
## Variante float4 min-replace
|
||||||
|
|
||||||
|
- Kernel: 1244.576 ms
|
||||||
|
- Throughput: 71.992 M vettori/s
|
||||||
|
- Invalid indices: 0
|
||||||
968
modules/knn-opencl/reference/knn_benchmark.cpp
Normal file
|
|
@ -0,0 +1,968 @@
|
||||||
|
#include <CL/cl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
static constexpr uint32_t DIM = 128;
|
||||||
|
static constexpr uint32_t TOPK = 32;
|
||||||
|
static constexpr uint32_t QUERIES = 256;
|
||||||
|
|
||||||
|
// Numero massimo di vettori elaborati da un singolo kernel launch.
|
||||||
|
static constexpr size_t CHUNK_SIZE = 50000;
|
||||||
|
|
||||||
|
static void check_cl(cl_int err, const char *what) {
|
||||||
|
if (err != CL_SUCCESS) {
|
||||||
|
std::cerr << "OpenCL error " << err << " at " << what << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void normalize(float *v) {
|
||||||
|
double s = 0.0;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < DIM; ++i)
|
||||||
|
s += double(v[i]) * double(v[i]);
|
||||||
|
|
||||||
|
float inv = 1.0f / std::sqrt(float(s));
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < DIM; ++i)
|
||||||
|
v[i] *= inv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<float> make_embeddings(size_t n) {
|
||||||
|
std::vector<float> x(n * DIM);
|
||||||
|
|
||||||
|
std::mt19937 rng(123456789);
|
||||||
|
std::normal_distribution<float> d(0.0f, 1.0f);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < x.size(); ++i)
|
||||||
|
x[i] = d(rng);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
normalize(&x[i * DIM]);
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Neighbor {
|
||||||
|
float score;
|
||||||
|
uint32_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void cpu_knn_one(
|
||||||
|
const std::vector<float>& db,
|
||||||
|
const float *query,
|
||||||
|
size_t n,
|
||||||
|
Neighbor *out)
|
||||||
|
{
|
||||||
|
std::vector<Neighbor> all(n);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n; ++i) {
|
||||||
|
const float *v = &db[i * DIM];
|
||||||
|
|
||||||
|
float s = 0.0f;
|
||||||
|
|
||||||
|
for (uint32_t j = 0; j < DIM; ++j)
|
||||||
|
s += v[j] * query[j];
|
||||||
|
|
||||||
|
all[i] = {s, static_cast<uint32_t>(i)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::nth_element(
|
||||||
|
all.begin(),
|
||||||
|
all.begin() + TOPK,
|
||||||
|
all.end(),
|
||||||
|
[](const Neighbor& a, const Neighbor& b) {
|
||||||
|
return a.score > b.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
all.begin(),
|
||||||
|
all.begin() + TOPK,
|
||||||
|
[](const Neighbor& a, const Neighbor& b) {
|
||||||
|
return a.score > b.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (uint32_t k = 0; k < TOPK; ++k)
|
||||||
|
out[k] = all[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
static double benchmark_cpu(
|
||||||
|
const std::vector<float>& db,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::vector<Neighbor>& result,
|
||||||
|
size_t n)
|
||||||
|
{
|
||||||
|
result.resize(QUERIES * TOPK);
|
||||||
|
|
||||||
|
|
||||||
|
auto t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (uint32_t q = 0; q < QUERIES; ++q) {
|
||||||
|
cpu_knn_one(
|
||||||
|
db,
|
||||||
|
&queries[q * DIM],
|
||||||
|
n,
|
||||||
|
&result[q * TOPK]);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto t1 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
return std::chrono::duration<double, std::milli>(t1 - t0).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string cl_string(cl_device_id dev, cl_device_info info) {
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceInfo(dev, info, 0, nullptr, &size),
|
||||||
|
"clGetDeviceInfo");
|
||||||
|
|
||||||
|
std::string s(size, '\0');
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceInfo(dev, info, size, s.data(), nullptr),
|
||||||
|
"clGetDeviceInfo");
|
||||||
|
|
||||||
|
while (!s.empty() && s.back() == '\0')
|
||||||
|
s.pop_back();
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
static cl_device_id find_mali() {
|
||||||
|
cl_uint np = 0;
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetPlatformIDs(0, nullptr, &np),
|
||||||
|
"clGetPlatformIDs");
|
||||||
|
|
||||||
|
std::vector<cl_platform_id> platforms(np);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetPlatformIDs(np, platforms.data(), nullptr),
|
||||||
|
"clGetPlatformIDs");
|
||||||
|
|
||||||
|
for (auto p : platforms) {
|
||||||
|
cl_uint nd = 0;
|
||||||
|
|
||||||
|
if (clGetDeviceIDs(
|
||||||
|
p,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&nd) != CL_SUCCESS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<cl_device_id> devices(nd);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceIDs(
|
||||||
|
p,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
nd,
|
||||||
|
devices.data(),
|
||||||
|
nullptr),
|
||||||
|
"clGetDeviceIDs");
|
||||||
|
|
||||||
|
for (auto d : devices) {
|
||||||
|
std::string name = cl_string(d, CL_DEVICE_NAME);
|
||||||
|
std::string vendor = cl_string(d, CL_DEVICE_VENDOR);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "OpenCL GPU: "
|
||||||
|
<< vendor
|
||||||
|
<< " / "
|
||||||
|
<< name
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
if (name.find("Mali-G610") != std::string::npos)
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void merge_topk(
|
||||||
|
const std::vector<uint32_t>& chunk_indices,
|
||||||
|
const std::vector<float>& chunk_values,
|
||||||
|
size_t chunk_offset,
|
||||||
|
std::vector<Neighbor>& result)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Per ogni query abbiamo TOPK candidati dal chunk corrente.
|
||||||
|
*
|
||||||
|
* Il risultato globale viene mantenuto in:
|
||||||
|
*
|
||||||
|
* result[q * TOPK ... q * TOPK + TOPK)
|
||||||
|
*
|
||||||
|
* Uniamo quindi al massimo 2*TOPK elementi per query.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (uint32_t q = 0; q < QUERIES; ++q) {
|
||||||
|
|
||||||
|
std::vector<Neighbor> candidates;
|
||||||
|
candidates.reserve(TOPK * 2);
|
||||||
|
|
||||||
|
Neighbor *dst = &result[q * TOPK];
|
||||||
|
|
||||||
|
for (uint32_t k = 0; k < TOPK; ++k) {
|
||||||
|
candidates.push_back(dst[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t k = 0; k < TOPK; ++k) {
|
||||||
|
uint32_t local_index =
|
||||||
|
chunk_indices[q * TOPK + k];
|
||||||
|
|
||||||
|
float score =
|
||||||
|
chunk_values[q * TOPK + k];
|
||||||
|
|
||||||
|
if (local_index == 0xffffffffu)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
candidates.push_back({
|
||||||
|
score,
|
||||||
|
static_cast<uint32_t>(
|
||||||
|
chunk_offset + local_index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
candidates.begin(),
|
||||||
|
candidates.end(),
|
||||||
|
[](const Neighbor& a, const Neighbor& b) {
|
||||||
|
return a.score > b.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (uint32_t k = 0; k < TOPK; ++k)
|
||||||
|
dst[k] = candidates[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double benchmark_opencl(
|
||||||
|
const std::vector<float>& db,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::vector<Neighbor>& result,
|
||||||
|
size_t n)
|
||||||
|
{
|
||||||
|
cl_int err;
|
||||||
|
|
||||||
|
cl_device_id dev = find_mali();
|
||||||
|
|
||||||
|
if (!dev) {
|
||||||
|
std::cerr << "Mali-G610 not found\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_context ctx =
|
||||||
|
clCreateContext(
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&dev,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "clCreateContext");
|
||||||
|
|
||||||
|
cl_command_queue queue =
|
||||||
|
clCreateCommandQueue(ctx, dev, 0, &err);
|
||||||
|
|
||||||
|
check_cl(err, "clCreateCommandQueue");
|
||||||
|
|
||||||
|
const char *source = R"CLC(
|
||||||
|
|
||||||
|
#define DIM 128
|
||||||
|
#define TOPK 32
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Numero di work-item che collaborano per ogni query.
|
||||||
|
*/
|
||||||
|
#define LANES_PER_QUERY 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni work-group contiene 8 work-item.
|
||||||
|
*
|
||||||
|
* Ogni work-item calcola un TOPK locale.
|
||||||
|
* Poi il work-item 0 fonde gli 8 TOPK locali.
|
||||||
|
*/
|
||||||
|
|
||||||
|
__kernel void knn(
|
||||||
|
__global const float *db,
|
||||||
|
__global const float *queries,
|
||||||
|
__global uint *indices,
|
||||||
|
__global float *values,
|
||||||
|
uint n)
|
||||||
|
{
|
||||||
|
uint lane = get_local_id(0);
|
||||||
|
uint q = get_group_id(0);
|
||||||
|
|
||||||
|
if (q >= 256)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locale per ogni work-item.
|
||||||
|
*/
|
||||||
|
float best_score[TOPK];
|
||||||
|
uint best_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
best_score[k] = -3.402823466e+38f;
|
||||||
|
best_index[k] = 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
__global const float *query =
|
||||||
|
&queries[q * DIM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ogni lane analizza una parte del database.
|
||||||
|
*
|
||||||
|
* Esempio con 8 lane:
|
||||||
|
*
|
||||||
|
* lane 0 -> 0, 8, 16, 24, ...
|
||||||
|
* lane 1 -> 1, 9, 17, 25, ...
|
||||||
|
* ...
|
||||||
|
* lane 7 -> 7, 15, 23, 31, ...
|
||||||
|
*/
|
||||||
|
for (uint i = lane; i < n; i += LANES_PER_QUERY) {
|
||||||
|
|
||||||
|
__global const float *v =
|
||||||
|
&db[i * DIM];
|
||||||
|
|
||||||
|
float s = 0.0f;
|
||||||
|
|
||||||
|
for (uint j = 0; j < DIM; ++j)
|
||||||
|
s += v[j] * query[j];
|
||||||
|
|
||||||
|
if (s <= best_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > best_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int k = TOPK - 1; k > pos; --k) {
|
||||||
|
best_score[k] =
|
||||||
|
best_score[k - 1];
|
||||||
|
|
||||||
|
best_index[k] =
|
||||||
|
best_index[k - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
best_score[pos] = s;
|
||||||
|
best_index[pos] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOPK locali nella local memory.
|
||||||
|
*
|
||||||
|
* 8 lane × 32 elementi.
|
||||||
|
*/
|
||||||
|
__local float local_scores[LANES_PER_QUERY][TOPK];
|
||||||
|
__local uint local_indices[LANES_PER_QUERY][TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
local_scores[lane][k] = best_score[k];
|
||||||
|
local_indices[lane][k] = best_index[k];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tutti i lane devono aver scritto
|
||||||
|
* la local memory prima della riduzione.
|
||||||
|
*/
|
||||||
|
barrier(CLK_LOCAL_MEM_FENCE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solo lane 0 esegue il merge finale.
|
||||||
|
*/
|
||||||
|
if (lane == 0) {
|
||||||
|
|
||||||
|
float final_score[TOPK];
|
||||||
|
uint final_index[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
final_score[k] =
|
||||||
|
-3.402823466e+38f;
|
||||||
|
|
||||||
|
final_index[k] =
|
||||||
|
0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fonde gli 8 TOPK locali.
|
||||||
|
*
|
||||||
|
* Al massimo 8 × 32 = 256 candidati.
|
||||||
|
*/
|
||||||
|
for (uint l = 0;
|
||||||
|
l < LANES_PER_QUERY;
|
||||||
|
++l) {
|
||||||
|
|
||||||
|
for (uint k = 0;
|
||||||
|
k < TOPK;
|
||||||
|
++k) {
|
||||||
|
|
||||||
|
float s =
|
||||||
|
local_scores[l][k];
|
||||||
|
|
||||||
|
uint idx =
|
||||||
|
local_indices[l][k];
|
||||||
|
|
||||||
|
if (idx == 0xffffffff)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (s <= final_score[TOPK - 1])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int pos = TOPK - 1;
|
||||||
|
|
||||||
|
while (pos > 0 &&
|
||||||
|
s > final_score[pos - 1]) {
|
||||||
|
--pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int p = TOPK - 1;
|
||||||
|
p > pos;
|
||||||
|
--p) {
|
||||||
|
|
||||||
|
final_score[p] =
|
||||||
|
final_score[p - 1];
|
||||||
|
|
||||||
|
final_index[p] =
|
||||||
|
final_index[p - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
final_score[pos] = s;
|
||||||
|
final_index[pos] = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Risultato finale della query.
|
||||||
|
*/
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
indices[q * TOPK + k] =
|
||||||
|
final_index[k];
|
||||||
|
|
||||||
|
values[q * TOPK + k] =
|
||||||
|
final_score[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)CLC";
|
||||||
|
|
||||||
|
cl_program program =
|
||||||
|
clCreateProgramWithSource(
|
||||||
|
ctx,
|
||||||
|
1,
|
||||||
|
&source,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "clCreateProgramWithSource");
|
||||||
|
|
||||||
|
err = clBuildProgram(
|
||||||
|
program,
|
||||||
|
1,
|
||||||
|
&dev,
|
||||||
|
"-cl-fast-relaxed-math",
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (err != CL_SUCCESS) {
|
||||||
|
|
||||||
|
size_t log_size = 0;
|
||||||
|
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
dev,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&log_size);
|
||||||
|
|
||||||
|
std::string log(log_size, '\0');
|
||||||
|
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
dev,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
log_size,
|
||||||
|
log.data(),
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
std::cerr << log << "\n";
|
||||||
|
|
||||||
|
check_cl(err, "clBuildProgram");
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_kernel kernel =
|
||||||
|
clCreateKernel(program, "knn", &err);
|
||||||
|
|
||||||
|
check_cl(err, "clCreateKernel");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Buffer GPU riutilizzato per ogni chunk.
|
||||||
|
*/
|
||||||
|
const size_t chunk_capacity =
|
||||||
|
std::min(CHUNK_SIZE, n);
|
||||||
|
|
||||||
|
cl_mem db_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
ctx,
|
||||||
|
CL_MEM_READ_ONLY,
|
||||||
|
chunk_capacity * DIM * sizeof(float),
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "db buffer");
|
||||||
|
|
||||||
|
cl_mem query_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
ctx,
|
||||||
|
CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
|
||||||
|
queries.size() * sizeof(float),
|
||||||
|
const_cast<float*>(queries.data()),
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "query buffer");
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices(
|
||||||
|
QUERIES * TOPK);
|
||||||
|
|
||||||
|
std::vector<float> values(
|
||||||
|
QUERIES * TOPK);
|
||||||
|
|
||||||
|
cl_mem index_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
ctx,
|
||||||
|
CL_MEM_WRITE_ONLY,
|
||||||
|
indices.size() * sizeof(uint32_t),
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "index buffer");
|
||||||
|
|
||||||
|
cl_mem value_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
ctx,
|
||||||
|
CL_MEM_WRITE_ONLY,
|
||||||
|
values.size() * sizeof(float),
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check_cl(err, "value buffer");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
kernel,
|
||||||
|
0,
|
||||||
|
sizeof(db_buf),
|
||||||
|
&db_buf),
|
||||||
|
"arg0");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
kernel,
|
||||||
|
1,
|
||||||
|
sizeof(query_buf),
|
||||||
|
&query_buf),
|
||||||
|
"arg1");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
kernel,
|
||||||
|
2,
|
||||||
|
sizeof(index_buf),
|
||||||
|
&index_buf),
|
||||||
|
"arg2");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
kernel,
|
||||||
|
3,
|
||||||
|
sizeof(value_buf),
|
||||||
|
&value_buf),
|
||||||
|
"arg3");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Risultato globale.
|
||||||
|
*
|
||||||
|
* Inizializziamo gli score al minimo possibile,
|
||||||
|
* così il primo chunk può essere mergiato
|
||||||
|
* senza una gestione speciale.
|
||||||
|
*/
|
||||||
|
result.resize(QUERIES * TOPK);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < result.size(); ++i) {
|
||||||
|
result[i] = {
|
||||||
|
-3.402823466e+38f,
|
||||||
|
0xffffffffu
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_chunks =
|
||||||
|
(n + CHUNK_SIZE - 1) / CHUNK_SIZE;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "GPU CHUNK_SIZE : "
|
||||||
|
<< CHUNK_SIZE
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "GPU CHUNKS : "
|
||||||
|
<< num_chunks
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
size_t global = QUERIES * 8;
|
||||||
|
size_t local = 8;
|
||||||
|
|
||||||
|
double write_ms = 0.0;
|
||||||
|
double kernel_ms = 0.0;
|
||||||
|
double read_ms = 0.0;
|
||||||
|
|
||||||
|
|
||||||
|
auto t0 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (size_t chunk_offset = 0;
|
||||||
|
chunk_offset < n;
|
||||||
|
chunk_offset += CHUNK_SIZE) {
|
||||||
|
|
||||||
|
size_t chunk_n =
|
||||||
|
std::min(
|
||||||
|
CHUNK_SIZE,
|
||||||
|
n - chunk_offset);
|
||||||
|
|
||||||
|
const float *chunk_ptr =
|
||||||
|
&db[chunk_offset * DIM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copia del chunk nel buffer GPU.
|
||||||
|
*
|
||||||
|
* La coda è in-order, quindi il write,
|
||||||
|
* kernel e read vengono eseguiti nell'ordine
|
||||||
|
* corretto.
|
||||||
|
*/
|
||||||
|
auto tw0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueWriteBuffer(
|
||||||
|
queue,
|
||||||
|
db_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
chunk_n * DIM * sizeof(float),
|
||||||
|
chunk_ptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"write db chunk");
|
||||||
|
|
||||||
|
auto tw1 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
write_ms +=
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
tw1 - tw0).count();
|
||||||
|
|
||||||
|
uint32_t nn =
|
||||||
|
static_cast<uint32_t>(chunk_n);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
kernel,
|
||||||
|
4,
|
||||||
|
sizeof(nn),
|
||||||
|
&nn),
|
||||||
|
"arg4");
|
||||||
|
|
||||||
|
auto tk0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueNDRangeKernel(
|
||||||
|
queue,
|
||||||
|
kernel,
|
||||||
|
1,
|
||||||
|
nullptr,
|
||||||
|
&global,
|
||||||
|
&local,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"clEnqueueNDRangeKernel");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clFinish(queue),
|
||||||
|
"clFinish after kernel");
|
||||||
|
|
||||||
|
auto tk1 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
kernel_ms +=
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
tk1 - tk0).count();
|
||||||
|
|
||||||
|
auto tr0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
queue,
|
||||||
|
index_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
indices.size() * sizeof(uint32_t),
|
||||||
|
indices.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"read indices");
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
queue,
|
||||||
|
value_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
values.size() * sizeof(float),
|
||||||
|
values.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"read values");
|
||||||
|
|
||||||
|
auto tr1 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
read_ms +=
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
tr1 - tr0).count();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gli indici prodotti dal kernel sono locali
|
||||||
|
* al chunk. merge_topk() aggiunge chunk_offset
|
||||||
|
* per ottenere gli indici globali.
|
||||||
|
*/
|
||||||
|
merge_topk(
|
||||||
|
indices,
|
||||||
|
values,
|
||||||
|
chunk_offset,
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto t1 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
clReleaseMemObject(value_buf);
|
||||||
|
clReleaseMemObject(index_buf);
|
||||||
|
clReleaseMemObject(query_buf);
|
||||||
|
clReleaseMemObject(db_buf);
|
||||||
|
clReleaseKernel(kernel);
|
||||||
|
clReleaseProgram(program);
|
||||||
|
clReleaseCommandQueue(queue);
|
||||||
|
clReleaseContext(ctx);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "GPU WRITE TOTAL : "
|
||||||
|
<< write_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "GPU KERNEL TOTAL: "
|
||||||
|
<< kernel_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "GPU READ TOTAL : "
|
||||||
|
<< read_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
return std::chrono::duration<double, std::milli>(
|
||||||
|
t1 - t0).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
|
size_t n = 100000;
|
||||||
|
|
||||||
|
if (argc > 1)
|
||||||
|
n = std::strtoull(
|
||||||
|
argv[1],
|
||||||
|
nullptr,
|
||||||
|
10);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "=== BATCH KNN BENCHMARK ===\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "DATABASE : "
|
||||||
|
<< n
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "QUERIES : "
|
||||||
|
<< QUERIES
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "DIM : "
|
||||||
|
<< DIM
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "TOPK : "
|
||||||
|
<< TOPK
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "SEED : 123456789\n\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "Generating database...\n";
|
||||||
|
|
||||||
|
auto db =
|
||||||
|
make_embeddings(n);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "Generating queries...\n";
|
||||||
|
|
||||||
|
auto queries =
|
||||||
|
make_embeddings(QUERIES);
|
||||||
|
|
||||||
|
std::vector<Neighbor> cpu;
|
||||||
|
std::vector<Neighbor> gpu;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n=== CPU ===\n";
|
||||||
|
|
||||||
|
double cpu_ms =
|
||||||
|
benchmark_cpu(
|
||||||
|
db,
|
||||||
|
queries,
|
||||||
|
cpu,
|
||||||
|
n);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "time_ms = "
|
||||||
|
<< cpu_ms
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "q0_top1 = "
|
||||||
|
<< cpu[0].index
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n=== OPENCL MALI ===\n";
|
||||||
|
|
||||||
|
double gpu_ms =
|
||||||
|
benchmark_opencl(
|
||||||
|
db,
|
||||||
|
queries,
|
||||||
|
gpu,
|
||||||
|
n);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "time_ms = "
|
||||||
|
<< gpu_ms
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "q0_top1 = "
|
||||||
|
<< gpu[0].index
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
size_t total =
|
||||||
|
QUERIES * TOPK;
|
||||||
|
|
||||||
|
size_t same = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0;
|
||||||
|
i < total;
|
||||||
|
++i) {
|
||||||
|
|
||||||
|
if (cpu[i].index == gpu[i].index)
|
||||||
|
++same;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "exact_topk_positions = "
|
||||||
|
<< same
|
||||||
|
<< "/"
|
||||||
|
<< total
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "exact_ratio = "
|
||||||
|
<< (double(same) /
|
||||||
|
double(total)) * 100.0
|
||||||
|
<< "%\n";
|
||||||
|
|
||||||
|
for (size_t i = 0; i < total; ++i) {
|
||||||
|
if (cpu[i].index != gpu[i].index) {
|
||||||
|
size_t q = i / TOPK;
|
||||||
|
size_t k = i % TOPK;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "MISMATCH q=" << q
|
||||||
|
<< " k=" << k
|
||||||
|
<< " CPU index=" << cpu[i].index
|
||||||
|
<< " score=" << cpu[i].score
|
||||||
|
<< " GPU index=" << gpu[i].index
|
||||||
|
<< " score=" << gpu[i].score
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n=== SPEEDUP ===\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "CPU/OpenCL = "
|
||||||
|
<< cpu_ms / gpu_ms
|
||||||
|
<< "x\n";
|
||||||
|
|
||||||
|
double pairs =
|
||||||
|
double(n) *
|
||||||
|
double(QUERIES);
|
||||||
|
|
||||||
|
double billion =
|
||||||
|
pairs *
|
||||||
|
double(DIM) /
|
||||||
|
1e9;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n=== WORK ===\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "similarity_ops = "
|
||||||
|
<< billion
|
||||||
|
<< " billion dot-product elements\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n=== THROUGHPUT ===\n";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "database_vectors_per_second = "
|
||||||
|
<< (double(n) *
|
||||||
|
double(QUERIES))
|
||||||
|
/ (gpu_ms / 1000.0)
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,972 @@
|
||||||
|
#define CL_TARGET_OPENCL_VERSION 300
|
||||||
|
#include <CL/cl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
static constexpr int DIM = 128;
|
||||||
|
static constexpr int DIM4 = 32;
|
||||||
|
static constexpr int TOPK = 32;
|
||||||
|
static constexpr int QUERIES = 32;
|
||||||
|
static constexpr int CHUNK_SIZE = 5000;
|
||||||
|
static constexpr int LANES = 8;
|
||||||
|
static constexpr int TOTAL_DB = 12000;
|
||||||
|
|
||||||
|
static void check(cl_int err, const char* what) {
|
||||||
|
if (err != CL_SUCCESS) {
|
||||||
|
std::cerr << what << " failed: " << err << "\n";
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* kernel_source = R"CLC(
|
||||||
|
|
||||||
|
#define DIM4 32
|
||||||
|
#define TOPK 32
|
||||||
|
#define LANES 8
|
||||||
|
#define QUERIES 32
|
||||||
|
|
||||||
|
inline float dot_f4(
|
||||||
|
__global const float4* db,
|
||||||
|
__global const float4* queries,
|
||||||
|
uint q,
|
||||||
|
uint db_index)
|
||||||
|
{
|
||||||
|
uint db_base = db_index * DIM4;
|
||||||
|
uint q_base = q * DIM4;
|
||||||
|
|
||||||
|
float4 acc0 = (float4)(0.0f);
|
||||||
|
float4 acc1 = (float4)(0.0f);
|
||||||
|
float4 acc2 = (float4)(0.0f);
|
||||||
|
float4 acc3 = (float4)(0.0f);
|
||||||
|
|
||||||
|
for (uint d = 0; d < DIM4; d += 4) {
|
||||||
|
acc0 += db[db_base + d + 0] * queries[q_base + d + 0];
|
||||||
|
acc1 += db[db_base + d + 1] * queries[q_base + d + 1];
|
||||||
|
acc2 += db[db_base + d + 2] * queries[q_base + d + 2];
|
||||||
|
acc3 += db[db_base + d + 3] * queries[q_base + d + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 s = acc0 + acc1 + acc2 + acc3;
|
||||||
|
return s.x + s.y + s.z + s.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
__kernel void knn_minreplace(
|
||||||
|
__global const float4* db,
|
||||||
|
__global const float4* queries,
|
||||||
|
uint db_count,
|
||||||
|
__global float* local_scores_out,
|
||||||
|
__global uint* local_indices_out)
|
||||||
|
{
|
||||||
|
uint gid = get_global_id(0);
|
||||||
|
uint lane = gid % LANES;
|
||||||
|
uint q = gid / LANES;
|
||||||
|
|
||||||
|
if (q >= QUERIES)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float best[TOPK];
|
||||||
|
uint best_idx[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
best[k] = -INFINITY;
|
||||||
|
best_idx[k] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float min_score = -INFINITY;
|
||||||
|
uint min_pos = 0;
|
||||||
|
|
||||||
|
for (uint i = lane; i < db_count; i += LANES) {
|
||||||
|
|
||||||
|
float score = dot_f4(db, queries, q, i);
|
||||||
|
|
||||||
|
if (score > min_score) {
|
||||||
|
|
||||||
|
best[min_pos] = score;
|
||||||
|
best_idx[min_pos] = i;
|
||||||
|
|
||||||
|
min_score = best[0];
|
||||||
|
min_pos = 0;
|
||||||
|
|
||||||
|
for (uint k = 1; k < TOPK; ++k) {
|
||||||
|
if (best[k] < min_score) {
|
||||||
|
min_score = best[k];
|
||||||
|
min_pos = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint base = (q * LANES + lane) * TOPK;
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
local_scores_out[base + k] = best[k];
|
||||||
|
local_indices_out[base + k] = best_idx[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__kernel void merge_topk(
|
||||||
|
__global const float* local_scores,
|
||||||
|
__global const uint* local_indices,
|
||||||
|
__global float* final_scores,
|
||||||
|
__global uint* final_indices)
|
||||||
|
{
|
||||||
|
uint q = get_global_id(0);
|
||||||
|
|
||||||
|
if (q >= QUERIES)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float best[TOPK];
|
||||||
|
uint best_idx[TOPK];
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
best[k] = -INFINITY;
|
||||||
|
best_idx[k] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float min_score = -INFINITY;
|
||||||
|
uint min_pos = 0;
|
||||||
|
|
||||||
|
for (uint lane = 0; lane < LANES; ++lane) {
|
||||||
|
|
||||||
|
uint base = (q * LANES + lane) * TOPK;
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
|
||||||
|
float score = local_scores[base + k];
|
||||||
|
|
||||||
|
if (score > min_score) {
|
||||||
|
|
||||||
|
best[min_pos] = score;
|
||||||
|
best_idx[min_pos] = local_indices[base + k];
|
||||||
|
|
||||||
|
min_score = best[0];
|
||||||
|
min_pos = 0;
|
||||||
|
|
||||||
|
for (uint j = 1; j < TOPK; ++j) {
|
||||||
|
if (best[j] < min_score) {
|
||||||
|
min_score = best[j];
|
||||||
|
min_pos = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort final TOPK descending, to match the semantic output
|
||||||
|
// of the original benchmark.
|
||||||
|
for (uint i = 0; i < TOPK; ++i) {
|
||||||
|
uint max_pos = i;
|
||||||
|
|
||||||
|
for (uint j = i + 1; j < TOPK; ++j) {
|
||||||
|
if (best[j] > best[max_pos])
|
||||||
|
max_pos = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_pos != i) {
|
||||||
|
float ts = best[i];
|
||||||
|
best[i] = best[max_pos];
|
||||||
|
best[max_pos] = ts;
|
||||||
|
|
||||||
|
uint ti = best_idx[i];
|
||||||
|
best_idx[i] = best_idx[max_pos];
|
||||||
|
best_idx[max_pos] = ti;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint out_base = q * TOPK;
|
||||||
|
|
||||||
|
for (uint k = 0; k < TOPK; ++k) {
|
||||||
|
final_scores[out_base + k] = best[k];
|
||||||
|
final_indices[out_base + k] = best_idx[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)CLC";
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
cl_int err;
|
||||||
|
|
||||||
|
cl_uint num_platforms = 0;
|
||||||
|
check(
|
||||||
|
clGetPlatformIDs(0, nullptr, &num_platforms),
|
||||||
|
"clGetPlatformIDs");
|
||||||
|
|
||||||
|
std::vector<cl_platform_id> platforms(num_platforms);
|
||||||
|
|
||||||
|
check(
|
||||||
|
clGetPlatformIDs(
|
||||||
|
num_platforms,
|
||||||
|
platforms.data(),
|
||||||
|
nullptr),
|
||||||
|
"clGetPlatformIDs(list)");
|
||||||
|
|
||||||
|
cl_platform_id platform = platforms[0];
|
||||||
|
|
||||||
|
cl_uint num_devices = 0;
|
||||||
|
|
||||||
|
check(
|
||||||
|
clGetDeviceIDs(
|
||||||
|
platform,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&num_devices),
|
||||||
|
"clGetDeviceIDs(count)");
|
||||||
|
|
||||||
|
std::vector<cl_device_id> devices(num_devices);
|
||||||
|
|
||||||
|
check(
|
||||||
|
clGetDeviceIDs(
|
||||||
|
platform,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
num_devices,
|
||||||
|
devices.data(),
|
||||||
|
nullptr),
|
||||||
|
"clGetDeviceIDs(list)");
|
||||||
|
|
||||||
|
cl_device_id device = devices[0];
|
||||||
|
|
||||||
|
char device_name[256] = {};
|
||||||
|
|
||||||
|
clGetDeviceInfo(
|
||||||
|
device,
|
||||||
|
CL_DEVICE_NAME,
|
||||||
|
sizeof(device_name),
|
||||||
|
device_name,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
std::cout << "=== FULL KNN FLOAT4 + MIN-REPLACE ===\n";
|
||||||
|
std::cout << "Device : " << device_name << "\n";
|
||||||
|
std::cout << "DIM : " << DIM << "\n";
|
||||||
|
std::cout << "TOPK : " << TOPK << "\n";
|
||||||
|
std::cout << "QUERIES : " << QUERIES << "\n";
|
||||||
|
std::cout << "TOTAL_DB : " << TOTAL_DB << "\n";
|
||||||
|
std::cout << "CHUNK_SIZE : " << CHUNK_SIZE << "\n";
|
||||||
|
std::cout << "LANES : " << LANES << "\n\n";
|
||||||
|
|
||||||
|
cl_context context =
|
||||||
|
clCreateContext(
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&device,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateContext");
|
||||||
|
|
||||||
|
cl_command_queue queue =
|
||||||
|
clCreateCommandQueue(
|
||||||
|
context,
|
||||||
|
device,
|
||||||
|
CL_QUEUE_PROFILING_ENABLE,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateCommandQueue");
|
||||||
|
|
||||||
|
const size_t db_bytes =
|
||||||
|
static_cast<size_t>(CHUNK_SIZE) *
|
||||||
|
DIM4 *
|
||||||
|
4 *
|
||||||
|
sizeof(float);
|
||||||
|
|
||||||
|
const size_t query_bytes =
|
||||||
|
static_cast<size_t>(QUERIES) *
|
||||||
|
DIM4 *
|
||||||
|
4 *
|
||||||
|
sizeof(float);
|
||||||
|
|
||||||
|
const size_t lane_candidates =
|
||||||
|
static_cast<size_t>(QUERIES) *
|
||||||
|
LANES *
|
||||||
|
TOPK;
|
||||||
|
|
||||||
|
const size_t candidate_bytes =
|
||||||
|
lane_candidates * sizeof(float);
|
||||||
|
|
||||||
|
const size_t index_bytes =
|
||||||
|
lane_candidates * sizeof(cl_uint);
|
||||||
|
|
||||||
|
const size_t final_count =
|
||||||
|
static_cast<size_t>(QUERIES) * TOPK;
|
||||||
|
|
||||||
|
const size_t final_score_bytes =
|
||||||
|
final_count * sizeof(float);
|
||||||
|
|
||||||
|
const size_t final_index_bytes =
|
||||||
|
final_count * sizeof(cl_uint);
|
||||||
|
|
||||||
|
cl_mem db_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_ONLY,
|
||||||
|
db_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(db)");
|
||||||
|
|
||||||
|
cl_mem query_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_ONLY,
|
||||||
|
query_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(query)");
|
||||||
|
|
||||||
|
cl_mem local_scores_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_WRITE,
|
||||||
|
candidate_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(local_scores)");
|
||||||
|
|
||||||
|
cl_mem local_indices_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_WRITE,
|
||||||
|
index_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(local_indices)");
|
||||||
|
|
||||||
|
cl_mem final_scores_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_WRITE,
|
||||||
|
final_score_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(final_scores)");
|
||||||
|
|
||||||
|
cl_mem final_indices_buf =
|
||||||
|
clCreateBuffer(
|
||||||
|
context,
|
||||||
|
CL_MEM_READ_WRITE,
|
||||||
|
final_index_bytes,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateBuffer(final_indices)");
|
||||||
|
|
||||||
|
cl_program program =
|
||||||
|
clCreateProgramWithSource(
|
||||||
|
context,
|
||||||
|
1,
|
||||||
|
&kernel_source,
|
||||||
|
nullptr,
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateProgramWithSource");
|
||||||
|
|
||||||
|
err = clBuildProgram(
|
||||||
|
program,
|
||||||
|
1,
|
||||||
|
&device,
|
||||||
|
"-cl-fast-relaxed-math",
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
if (err != CL_SUCCESS) {
|
||||||
|
|
||||||
|
size_t log_size = 0;
|
||||||
|
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
device,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&log_size);
|
||||||
|
|
||||||
|
std::vector<char> log(log_size + 1);
|
||||||
|
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
device,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
log_size,
|
||||||
|
log.data(),
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
std::cerr << log.data() << "\n";
|
||||||
|
|
||||||
|
check(err, "clBuildProgram");
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_kernel knn =
|
||||||
|
clCreateKernel(
|
||||||
|
program,
|
||||||
|
"knn_minreplace",
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateKernel(knn_minreplace)");
|
||||||
|
|
||||||
|
cl_kernel merge =
|
||||||
|
clCreateKernel(
|
||||||
|
program,
|
||||||
|
"merge_topk",
|
||||||
|
&err);
|
||||||
|
|
||||||
|
check(err, "clCreateKernel(merge_topk)");
|
||||||
|
|
||||||
|
std::vector<float> db(
|
||||||
|
static_cast<size_t>(TOTAL_DB) * DIM);
|
||||||
|
|
||||||
|
std::vector<float> queries(
|
||||||
|
static_cast<size_t>(QUERIES) * DIM);
|
||||||
|
|
||||||
|
std::mt19937 rng_db(123456789);
|
||||||
|
std::mt19937 rng_q(987654321);
|
||||||
|
|
||||||
|
std::normal_distribution<float> normal(0.0f, 1.0f);
|
||||||
|
|
||||||
|
for (int i = 0; i < TOTAL_DB; ++i) {
|
||||||
|
|
||||||
|
float norm = 0.0f;
|
||||||
|
|
||||||
|
for (int d = 0; d < DIM; ++d) {
|
||||||
|
|
||||||
|
float x = normal(rng_db);
|
||||||
|
|
||||||
|
db[
|
||||||
|
static_cast<size_t>(i) * DIM + d
|
||||||
|
] = x;
|
||||||
|
|
||||||
|
norm += x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
norm = 1.0f / std::sqrt(norm);
|
||||||
|
|
||||||
|
for (int d = 0; d < DIM; ++d)
|
||||||
|
db[
|
||||||
|
static_cast<size_t>(i) * DIM + d
|
||||||
|
] *= norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int q = 0; q < QUERIES; ++q) {
|
||||||
|
|
||||||
|
float norm = 0.0f;
|
||||||
|
|
||||||
|
for (int d = 0; d < DIM; ++d) {
|
||||||
|
|
||||||
|
float x = normal(rng_q);
|
||||||
|
|
||||||
|
queries[
|
||||||
|
static_cast<size_t>(q) * DIM + d
|
||||||
|
] = x;
|
||||||
|
|
||||||
|
norm += x * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
norm = 1.0f / std::sqrt(norm);
|
||||||
|
|
||||||
|
for (int d = 0; d < DIM; ++d)
|
||||||
|
queries[
|
||||||
|
static_cast<size_t>(q) * DIM + d
|
||||||
|
] *= norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> final_scores(
|
||||||
|
final_count);
|
||||||
|
|
||||||
|
std::vector<cl_uint> final_indices(
|
||||||
|
final_count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TOP-K globale persistente fra tutti i chunk.
|
||||||
|
*
|
||||||
|
* final_scores/final_indices contengono il risultato
|
||||||
|
* del chunk corrente.
|
||||||
|
*
|
||||||
|
* global_scores/global_indices contengono invece il
|
||||||
|
* TOP-K complessivo elaborato fino a quel momento.
|
||||||
|
*/
|
||||||
|
std::vector<float> global_scores(
|
||||||
|
final_count,
|
||||||
|
-INFINITY);
|
||||||
|
|
||||||
|
std::vector<cl_uint> global_indices(
|
||||||
|
final_count,
|
||||||
|
0);
|
||||||
|
|
||||||
|
double total_knn_ms = 0.0;
|
||||||
|
double total_merge_ms = 0.0;
|
||||||
|
double total_global_merge_ms = 0.0;
|
||||||
|
|
||||||
|
const int chunks =
|
||||||
|
(TOTAL_DB + CHUNK_SIZE - 1) /
|
||||||
|
CHUNK_SIZE;
|
||||||
|
|
||||||
|
for (int chunk = 0; chunk < chunks; ++chunk) {
|
||||||
|
|
||||||
|
const int begin =
|
||||||
|
chunk * CHUNK_SIZE;
|
||||||
|
|
||||||
|
const int count =
|
||||||
|
std::min(
|
||||||
|
CHUNK_SIZE,
|
||||||
|
TOTAL_DB - begin);
|
||||||
|
|
||||||
|
const size_t db_upload_bytes =
|
||||||
|
static_cast<size_t>(count) *
|
||||||
|
DIM4 *
|
||||||
|
4 *
|
||||||
|
sizeof(float);
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueWriteBuffer(
|
||||||
|
queue,
|
||||||
|
db_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
db_upload_bytes,
|
||||||
|
db.data() +
|
||||||
|
static_cast<size_t>(begin) * DIM,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"write db");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueWriteBuffer(
|
||||||
|
queue,
|
||||||
|
query_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
query_bytes,
|
||||||
|
queries.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"write queries");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
knn,
|
||||||
|
0,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&db_buf),
|
||||||
|
"arg 0");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
knn,
|
||||||
|
1,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&query_buf),
|
||||||
|
"arg 1");
|
||||||
|
|
||||||
|
cl_uint db_count = count;
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
knn,
|
||||||
|
2,
|
||||||
|
sizeof(cl_uint),
|
||||||
|
&db_count),
|
||||||
|
"arg 2");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
knn,
|
||||||
|
3,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&local_scores_buf),
|
||||||
|
"arg 3");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
knn,
|
||||||
|
4,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&local_indices_buf),
|
||||||
|
"arg 4");
|
||||||
|
|
||||||
|
size_t global =
|
||||||
|
static_cast<size_t>(QUERIES) *
|
||||||
|
LANES;
|
||||||
|
|
||||||
|
size_t local = LANES;
|
||||||
|
|
||||||
|
auto t0 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueNDRangeKernel(
|
||||||
|
queue,
|
||||||
|
knn,
|
||||||
|
1,
|
||||||
|
nullptr,
|
||||||
|
&global,
|
||||||
|
&local,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"enqueue knn");
|
||||||
|
|
||||||
|
check(clFinish(queue), "finish knn");
|
||||||
|
|
||||||
|
auto t1 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
total_knn_ms +=
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
t1 - t0).count();
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
merge,
|
||||||
|
0,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&local_scores_buf),
|
||||||
|
"merge arg 0");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
merge,
|
||||||
|
1,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&local_indices_buf),
|
||||||
|
"merge arg 1");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
merge,
|
||||||
|
2,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&final_scores_buf),
|
||||||
|
"merge arg 2");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clSetKernelArg(
|
||||||
|
merge,
|
||||||
|
3,
|
||||||
|
sizeof(cl_mem),
|
||||||
|
&final_indices_buf),
|
||||||
|
"merge arg 3");
|
||||||
|
|
||||||
|
size_t merge_global = QUERIES;
|
||||||
|
|
||||||
|
auto tm0 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueNDRangeKernel(
|
||||||
|
queue,
|
||||||
|
merge,
|
||||||
|
1,
|
||||||
|
nullptr,
|
||||||
|
&merge_global,
|
||||||
|
nullptr,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"enqueue merge");
|
||||||
|
|
||||||
|
check(clFinish(queue), "finish merge");
|
||||||
|
|
||||||
|
auto tm1 =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
total_merge_ms +=
|
||||||
|
std::chrono::duration<double, std::milli>(
|
||||||
|
tm1 - tm0).count();
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
queue,
|
||||||
|
final_scores_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
final_score_bytes,
|
||||||
|
final_scores.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"read final scores");
|
||||||
|
|
||||||
|
check(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
queue,
|
||||||
|
final_indices_buf,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
final_index_bytes,
|
||||||
|
final_indices.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr),
|
||||||
|
"read final indices");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Merge CPU fra:
|
||||||
|
*
|
||||||
|
* TOP-K globale accumulato finora
|
||||||
|
* TOP-K prodotto dal chunk corrente
|
||||||
|
*
|
||||||
|
* Gli indici restituiti dalla GPU sono locali al
|
||||||
|
* chunk. Li convertiamo quindi in indici globali
|
||||||
|
* aggiungendo begin.
|
||||||
|
*/
|
||||||
|
auto global_merge_start =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (int q = 0; q < QUERIES; ++q) {
|
||||||
|
|
||||||
|
const size_t base =
|
||||||
|
static_cast<size_t>(q) * TOPK;
|
||||||
|
|
||||||
|
for (int k = 0; k < TOPK; ++k) {
|
||||||
|
|
||||||
|
const float candidate_score =
|
||||||
|
final_scores[base + k];
|
||||||
|
|
||||||
|
const cl_uint candidate_index =
|
||||||
|
static_cast<cl_uint>(begin) +
|
||||||
|
final_indices[base + k];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trova il risultato peggiore nel TOP-K
|
||||||
|
* globale corrente.
|
||||||
|
*/
|
||||||
|
size_t worst_pos = base;
|
||||||
|
float worst_score =
|
||||||
|
global_scores[base];
|
||||||
|
|
||||||
|
for (int j = 1; j < TOPK; ++j) {
|
||||||
|
|
||||||
|
const size_t pos =
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(j);
|
||||||
|
|
||||||
|
if (global_scores[pos] <
|
||||||
|
worst_score)
|
||||||
|
{
|
||||||
|
worst_score =
|
||||||
|
global_scores[pos];
|
||||||
|
|
||||||
|
worst_pos = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sostituzione del minimo.
|
||||||
|
*/
|
||||||
|
if (candidate_score > worst_score) {
|
||||||
|
|
||||||
|
global_scores[worst_pos] =
|
||||||
|
candidate_score;
|
||||||
|
|
||||||
|
global_indices[worst_pos] =
|
||||||
|
candidate_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto global_merge_end =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
total_global_merge_ms +=
|
||||||
|
std::chrono::duration<
|
||||||
|
double,
|
||||||
|
std::milli>(
|
||||||
|
global_merge_end -
|
||||||
|
global_merge_start).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ordina il TOP-K globale di ogni query in ordine
|
||||||
|
* decrescente di similarita.
|
||||||
|
*/
|
||||||
|
for (int q = 0; q < QUERIES; ++q) {
|
||||||
|
|
||||||
|
const size_t base =
|
||||||
|
static_cast<size_t>(q) * TOPK;
|
||||||
|
|
||||||
|
for (int i = 0; i < TOPK; ++i) {
|
||||||
|
|
||||||
|
int max_pos = i;
|
||||||
|
|
||||||
|
for (int j = i + 1;
|
||||||
|
j < TOPK;
|
||||||
|
++j)
|
||||||
|
{
|
||||||
|
if (global_scores[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(j)
|
||||||
|
] >
|
||||||
|
global_scores[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(max_pos)
|
||||||
|
])
|
||||||
|
{
|
||||||
|
max_pos = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_pos != i) {
|
||||||
|
|
||||||
|
std::swap(
|
||||||
|
global_scores[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(i)
|
||||||
|
],
|
||||||
|
global_scores[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(max_pos)
|
||||||
|
]);
|
||||||
|
|
||||||
|
std::swap(
|
||||||
|
global_indices[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(i)
|
||||||
|
],
|
||||||
|
global_indices[
|
||||||
|
base +
|
||||||
|
static_cast<size_t>(max_pos)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica intervallo indici globali.
|
||||||
|
*/
|
||||||
|
size_t invalid_indices = 0;
|
||||||
|
|
||||||
|
for (cl_uint index : global_indices) {
|
||||||
|
if (index >=
|
||||||
|
static_cast<cl_uint>(TOTAL_DB))
|
||||||
|
{
|
||||||
|
++invalid_indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double checksum_scores = 0.0;
|
||||||
|
double checksum_indices = 0.0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < final_count; ++i) {
|
||||||
|
checksum_scores += global_scores[i];
|
||||||
|
checksum_indices += global_indices[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
double vectors =
|
||||||
|
static_cast<double>(TOTAL_DB) *
|
||||||
|
QUERIES;
|
||||||
|
|
||||||
|
double vector_rate =
|
||||||
|
vectors /
|
||||||
|
total_knn_ms /
|
||||||
|
1000.0;
|
||||||
|
|
||||||
|
double gflops =
|
||||||
|
vector_rate *
|
||||||
|
DIM *
|
||||||
|
2.0 /
|
||||||
|
1000.0;
|
||||||
|
|
||||||
|
double db_traffic =
|
||||||
|
vector_rate *
|
||||||
|
DIM *
|
||||||
|
sizeof(float) /
|
||||||
|
1000.0;
|
||||||
|
|
||||||
|
std::cout << "\n=== RESULT ===\n";
|
||||||
|
|
||||||
|
std::cout << "KNN kernel time : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< total_knn_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout << "Merge time : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< total_merge_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout << "Global CPU merge: "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< total_global_merge_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout << "Total GPU time : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< total_knn_ms + total_merge_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout << "Search core time: "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< total_knn_ms +
|
||||||
|
total_merge_ms +
|
||||||
|
total_global_merge_ms
|
||||||
|
<< " ms\n";
|
||||||
|
|
||||||
|
std::cout << "Invalid indices : "
|
||||||
|
<< invalid_indices
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout << "Vector rate : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< vector_rate
|
||||||
|
<< " M vector/s\n";
|
||||||
|
|
||||||
|
std::cout << "Dot rate : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< gflops
|
||||||
|
<< " GFLOP/s\n";
|
||||||
|
|
||||||
|
std::cout << "DB traffic : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< db_traffic
|
||||||
|
<< " GB/s effective\n";
|
||||||
|
|
||||||
|
std::cout << "Score checksum : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< checksum_scores
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout << "Index checksum : "
|
||||||
|
<< std::fixed
|
||||||
|
<< std::setprecision(3)
|
||||||
|
<< checksum_indices
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
clReleaseKernel(knn);
|
||||||
|
clReleaseKernel(merge);
|
||||||
|
clReleaseProgram(program);
|
||||||
|
|
||||||
|
clReleaseMemObject(db_buf);
|
||||||
|
clReleaseMemObject(query_buf);
|
||||||
|
clReleaseMemObject(local_scores_buf);
|
||||||
|
clReleaseMemObject(local_indices_buf);
|
||||||
|
clReleaseMemObject(final_scores_buf);
|
||||||
|
clReleaseMemObject(final_indices_buf);
|
||||||
|
|
||||||
|
clReleaseCommandQueue(queue);
|
||||||
|
clReleaseContext(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
23
modules/knn-opencl/reference/validation-minreplace-350k.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
arm_release_ver: g24p0-00eac0, rk_so_ver: 10
|
||||||
|
=== FULL KNN FLOAT4 + MIN-REPLACE ===
|
||||||
|
Device : Mali-G610 r0p0
|
||||||
|
DIM : 128
|
||||||
|
TOPK : 32
|
||||||
|
QUERIES : 256
|
||||||
|
TOTAL_DB : 350000
|
||||||
|
CHUNK_SIZE : 50000
|
||||||
|
LANES : 8
|
||||||
|
|
||||||
|
|
||||||
|
=== RESULT ===
|
||||||
|
KNN kernel time : 1244.576 ms
|
||||||
|
Merge time : 8.022 ms
|
||||||
|
Global CPU merge: 14.039 ms
|
||||||
|
Total GPU time : 1252.599 ms
|
||||||
|
Search core time: 1266.638 ms
|
||||||
|
Invalid indices : 0
|
||||||
|
Vector rate : 71.992 M vector/s
|
||||||
|
Dot rate : 18.430 GFLOP/s
|
||||||
|
DB traffic : 36.860 GB/s effective
|
||||||
|
Score checksum : 2804.191
|
||||||
|
Index checksum : 1425305049.000
|
||||||
35
modules/knn-opencl/reference/validation-opencl-350k.txt
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
=== BATCH KNN BENCHMARK ===
|
||||||
|
DATABASE : 350000
|
||||||
|
QUERIES : 256
|
||||||
|
DIM : 128
|
||||||
|
TOPK : 32
|
||||||
|
SEED : 123456789
|
||||||
|
|
||||||
|
Generating database...
|
||||||
|
Generating queries...
|
||||||
|
|
||||||
|
=== CPU ===
|
||||||
|
time_ms = 9663.226
|
||||||
|
q0_top1 = 0
|
||||||
|
|
||||||
|
=== OPENCL MALI ===
|
||||||
|
arm_release_ver: g24p0-00eac0, rk_so_ver: 10
|
||||||
|
OpenCL GPU: ARM / Mali-G610 r0p0
|
||||||
|
GPU CHUNK_SIZE : 50000
|
||||||
|
GPU CHUNKS : 7
|
||||||
|
GPU WRITE TOTAL : 48.242 ms
|
||||||
|
GPU KERNEL TOTAL: 1318.590 ms
|
||||||
|
GPU READ TOTAL : 0.712 ms
|
||||||
|
time_ms = 1373.797
|
||||||
|
q0_top1 = 0
|
||||||
|
exact_topk_positions = 8192/8192
|
||||||
|
exact_ratio = 100.000%
|
||||||
|
|
||||||
|
=== SPEEDUP ===
|
||||||
|
CPU/OpenCL = 7.034x
|
||||||
|
|
||||||
|
=== WORK ===
|
||||||
|
similarity_ops = 11.469 billion dot-product elements
|
||||||
|
|
||||||
|
=== THROUGHPUT ===
|
||||||
|
database_vectors_per_second = 65220680.078
|
||||||
29
modules/mst/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Minimum Spanning Tree
|
||||||
|
|
||||||
|
Modulo futuro per costruire il minimum spanning tree del grafo di mutual
|
||||||
|
reachability.
|
||||||
|
|
||||||
|
## Input previsto
|
||||||
|
|
||||||
|
Un grafo pesato:
|
||||||
|
|
||||||
|
```text
|
||||||
|
source
|
||||||
|
destination
|
||||||
|
mutual_reachability_weight
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output previsto
|
||||||
|
|
||||||
|
Per un dataset con `N` vertici connessi, l'MST conterrà:
|
||||||
|
|
||||||
|
```text
|
||||||
|
N - 1 archi
|
||||||
|
```
|
||||||
|
|
||||||
|
La prima implementazione privilegerà:
|
||||||
|
|
||||||
|
- correttezza;
|
||||||
|
- determinismo;
|
||||||
|
- confronto con una versione CPU di riferimento;
|
||||||
|
- verificabilità dei risultati.
|
||||||
37
modules/mutual-reachability/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Mutual Reachability
|
||||||
|
|
||||||
|
Modulo futuro per convertire il grafo kNN in un grafo pesato utilizzabile
|
||||||
|
da HDBSCAN.
|
||||||
|
|
||||||
|
## Core distance
|
||||||
|
|
||||||
|
Con `min_samples=32`, la core distance di un punto corrisponde alla
|
||||||
|
distanza dal suo trentaduesimo vicino.
|
||||||
|
|
||||||
|
## Mutual reachability distance
|
||||||
|
|
||||||
|
```text
|
||||||
|
mrd(i,j) =
|
||||||
|
max(
|
||||||
|
core_distance(i),
|
||||||
|
core_distance(j),
|
||||||
|
distance(i,j)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input previsto
|
||||||
|
|
||||||
|
```text
|
||||||
|
indices[N][32]
|
||||||
|
scores[N][32]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output previsto
|
||||||
|
|
||||||
|
Un insieme di archi candidati:
|
||||||
|
|
||||||
|
```text
|
||||||
|
source
|
||||||
|
destination
|
||||||
|
mutual_reachability_weight
|
||||||
|
```
|
||||||
855
src/face_engine.cc
Normal file
|
|
@ -0,0 +1,855 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "stb_image.h"
|
||||||
|
|
||||||
|
#include "rknn_api.h"
|
||||||
|
#include "face_engine.h"
|
||||||
|
|
||||||
|
static constexpr int SCRFD_SIZE = 640;
|
||||||
|
static constexpr int SFACE_SIZE = 112;
|
||||||
|
static constexpr int EMBED_DIM = 128;
|
||||||
|
|
||||||
|
static uint16_t float_to_fp16(float value)
|
||||||
|
{
|
||||||
|
uint32_t bits;
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
|
||||||
|
uint32_t sign = (bits >> 31) & 1;
|
||||||
|
int exp = ((bits >> 23) & 0xff) - 127;
|
||||||
|
uint32_t mant = bits & 0x7fffff;
|
||||||
|
|
||||||
|
if (exp == 128) {
|
||||||
|
return (sign << 15) | 0x7c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exp > 15) {
|
||||||
|
return (sign << 15) | 0x7c00;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exp < -14) {
|
||||||
|
if (exp < -24)
|
||||||
|
return sign << 15;
|
||||||
|
|
||||||
|
mant |= 0x800000;
|
||||||
|
int shift = -exp - 14;
|
||||||
|
uint16_t m = mant >> (shift + 13);
|
||||||
|
|
||||||
|
return (sign << 15) | m;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t h_exp = (uint16_t)(exp + 15);
|
||||||
|
uint16_t h_mant = (uint16_t)(mant >> 13);
|
||||||
|
|
||||||
|
return (sign << 15) | (h_exp << 10) | h_mant;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float fp16_to_float(uint16_t h)
|
||||||
|
{
|
||||||
|
uint32_t sign = (h >> 15) & 1;
|
||||||
|
uint32_t exp = (h >> 10) & 0x1f;
|
||||||
|
uint32_t mant = h & 0x3ff;
|
||||||
|
|
||||||
|
uint32_t bits;
|
||||||
|
|
||||||
|
if (exp == 0) {
|
||||||
|
if (mant == 0) {
|
||||||
|
bits = sign << 31;
|
||||||
|
} else {
|
||||||
|
float v = mant / 1024.0f;
|
||||||
|
v = std::ldexp(v, -14);
|
||||||
|
return sign ? -v : v;
|
||||||
|
}
|
||||||
|
} else if (exp == 31) {
|
||||||
|
bits = (sign << 31) | 0x7f800000 | (mant << 13);
|
||||||
|
} else {
|
||||||
|
uint32_t fexp = exp - 15 + 127;
|
||||||
|
bits = (sign << 31) | (fexp << 23) | (mant << 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
float v;
|
||||||
|
std::memcpy(&v, &bits, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float iou(const Face& a, const Face& b)
|
||||||
|
{
|
||||||
|
float xx1 = std::max(a.x1, b.x1);
|
||||||
|
float yy1 = std::max(a.y1, b.y1);
|
||||||
|
float xx2 = std::min(a.x2, b.x2);
|
||||||
|
float yy2 = std::min(a.y2, b.y2);
|
||||||
|
|
||||||
|
float w = std::max(0.0f, xx2 - xx1);
|
||||||
|
float h = std::max(0.0f, yy2 - yy1);
|
||||||
|
float inter = w * h;
|
||||||
|
|
||||||
|
float area_a = std::max(0.0f, a.x2-a.x1) *
|
||||||
|
std::max(0.0f, a.y2-a.y1);
|
||||||
|
|
||||||
|
float area_b = std::max(0.0f, b.x2-b.x1) *
|
||||||
|
std::max(0.0f, b.y2-b.y1);
|
||||||
|
|
||||||
|
return inter / (area_a + area_b - inter + 1e-6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nms(std::vector<Face>& faces, float threshold)
|
||||||
|
{
|
||||||
|
std::sort(faces.begin(), faces.end(),
|
||||||
|
[](const Face& a, const Face& b) {
|
||||||
|
return a.score > b.score;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<Face> result;
|
||||||
|
|
||||||
|
for (const auto& f : faces) {
|
||||||
|
bool keep = true;
|
||||||
|
|
||||||
|
for (const auto& r : result) {
|
||||||
|
if (iou(f, r) > threshold) {
|
||||||
|
keep = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keep)
|
||||||
|
result.push_back(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
faces.swap(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Image {
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
std::vector<uint8_t> rgb;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool normalize_rotation(
|
||||||
|
int rotation_degrees,
|
||||||
|
int& normalized_rotation)
|
||||||
|
{
|
||||||
|
int value = rotation_degrees % 360;
|
||||||
|
|
||||||
|
if (value < 0)
|
||||||
|
value += 360;
|
||||||
|
|
||||||
|
if (
|
||||||
|
value != 0
|
||||||
|
&& value != 90
|
||||||
|
&& value != 180
|
||||||
|
&& value != 270
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized_rotation = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Image rotate_image(
|
||||||
|
const Image& source,
|
||||||
|
int rotation_degrees)
|
||||||
|
{
|
||||||
|
if (rotation_degrees == 0)
|
||||||
|
return source;
|
||||||
|
|
||||||
|
Image destination;
|
||||||
|
|
||||||
|
if (
|
||||||
|
rotation_degrees == 90
|
||||||
|
|| rotation_degrees == 270
|
||||||
|
) {
|
||||||
|
destination.w = source.h;
|
||||||
|
destination.h = source.w;
|
||||||
|
} else {
|
||||||
|
destination.w = source.w;
|
||||||
|
destination.h = source.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination.rgb.resize(
|
||||||
|
static_cast<std::size_t>(destination.w)
|
||||||
|
* static_cast<std::size_t>(destination.h)
|
||||||
|
* 3);
|
||||||
|
|
||||||
|
for (int source_y = 0; source_y < source.h; ++source_y) {
|
||||||
|
for (int source_x = 0; source_x < source.w; ++source_x) {
|
||||||
|
int destination_x = 0;
|
||||||
|
int destination_y = 0;
|
||||||
|
|
||||||
|
switch (rotation_degrees) {
|
||||||
|
case 90:
|
||||||
|
destination_x = source.h - 1 - source_y;
|
||||||
|
destination_y = source_x;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 180:
|
||||||
|
destination_x = source.w - 1 - source_x;
|
||||||
|
destination_y = source.h - 1 - source_y;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 270:
|
||||||
|
destination_x = source_y;
|
||||||
|
destination_y = source.w - 1 - source_x;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
destination_x = source_x;
|
||||||
|
destination_y = source_y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t source_offset =
|
||||||
|
(
|
||||||
|
static_cast<std::size_t>(source_y)
|
||||||
|
* static_cast<std::size_t>(source.w)
|
||||||
|
+ static_cast<std::size_t>(source_x)
|
||||||
|
) * 3;
|
||||||
|
|
||||||
|
const std::size_t destination_offset =
|
||||||
|
(
|
||||||
|
static_cast<std::size_t>(destination_y)
|
||||||
|
* static_cast<std::size_t>(destination.w)
|
||||||
|
+ static_cast<std::size_t>(destination_x)
|
||||||
|
) * 3;
|
||||||
|
|
||||||
|
destination.rgb[destination_offset + 0] =
|
||||||
|
source.rgb[source_offset + 0];
|
||||||
|
|
||||||
|
destination.rgb[destination_offset + 1] =
|
||||||
|
source.rgb[source_offset + 1];
|
||||||
|
|
||||||
|
destination.rgb[destination_offset + 2] =
|
||||||
|
source.rgb[source_offset + 2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool load_image(const std::string& path, Image& img)
|
||||||
|
{
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
unsigned char* p =
|
||||||
|
stbi_load(path.c_str(), &img.w, &img.h, &c, 3);
|
||||||
|
|
||||||
|
if (!p) {
|
||||||
|
std::cerr << "Errore caricamento: " << path << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.rgb.assign(p, p + img.w * img.h * 3);
|
||||||
|
stbi_image_free(p);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resize_bilinear_rgb(
|
||||||
|
const Image& src,
|
||||||
|
std::vector<uint8_t>& dst,
|
||||||
|
int dw,
|
||||||
|
int dh)
|
||||||
|
{
|
||||||
|
dst.resize(dw * dh * 3);
|
||||||
|
|
||||||
|
float sx = (float)src.w / dw;
|
||||||
|
float sy = (float)src.h / dh;
|
||||||
|
|
||||||
|
for (int y = 0; y < dh; ++y) {
|
||||||
|
float fy = (y + 0.5f) * sy - 0.5f;
|
||||||
|
int y0 = (int)std::floor(fy);
|
||||||
|
float wy = fy - y0;
|
||||||
|
|
||||||
|
if (y0 < 0) {
|
||||||
|
y0 = 0;
|
||||||
|
wy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int y1 = std::min(y0 + 1, src.h - 1);
|
||||||
|
|
||||||
|
for (int x = 0; x < dw; ++x) {
|
||||||
|
float fx = (x + 0.5f) * sx - 0.5f;
|
||||||
|
int x0 = (int)std::floor(fx);
|
||||||
|
float wx = fx - x0;
|
||||||
|
|
||||||
|
if (x0 < 0) {
|
||||||
|
x0 = 0;
|
||||||
|
wx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x1 = std::min(x0 + 1, src.w - 1);
|
||||||
|
|
||||||
|
for (int c = 0; c < 3; ++c) {
|
||||||
|
float p00 = src.rgb[(y0*src.w+x0)*3+c];
|
||||||
|
float p01 = src.rgb[(y0*src.w+x1)*3+c];
|
||||||
|
float p10 = src.rgb[(y1*src.w+x0)*3+c];
|
||||||
|
float p11 = src.rgb[(y1*src.w+x1)*3+c];
|
||||||
|
|
||||||
|
float v =
|
||||||
|
p00 * (1-wx) * (1-wy) +
|
||||||
|
p01 * wx * (1-wy) +
|
||||||
|
p10 * (1-wx) * wy +
|
||||||
|
p11 * wx * wy;
|
||||||
|
|
||||||
|
dst[(y*dw+x)*3+c] =
|
||||||
|
(uint8_t)std::clamp((int)std::round(v), 0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Scrfd {
|
||||||
|
public:
|
||||||
|
rknn_context ctx = 0;
|
||||||
|
|
||||||
|
bool init(const char* model)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(model, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
perror(model);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
size_t size = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(size);
|
||||||
|
fread(data.data(), 1, size, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
int ret = rknn_init(&ctx, data.data(), size, 0, nullptr);
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "rknn_init SCRFD failed: " << ret << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Face> detect(const Image& image)
|
||||||
|
{
|
||||||
|
float scale =
|
||||||
|
std::min(640.0f / image.w, 640.0f / image.h);
|
||||||
|
|
||||||
|
int rw = (int)std::round(image.w * scale);
|
||||||
|
int rh = (int)std::round(image.h * scale);
|
||||||
|
|
||||||
|
std::vector<uint8_t> resized;
|
||||||
|
resize_bilinear_rgb(image, resized, rw, rh);
|
||||||
|
|
||||||
|
std::vector<uint16_t> input(
|
||||||
|
SCRFD_SIZE * SCRFD_SIZE * 3);
|
||||||
|
|
||||||
|
for (int y = 0; y < rh; ++y) {
|
||||||
|
for (int x = 0; x < rw; ++x) {
|
||||||
|
int dst = (y * SCRFD_SIZE + x) * 3;
|
||||||
|
int src = (y * rw + x) * 3;
|
||||||
|
|
||||||
|
for (int c = 0; c < 3; ++c) {
|
||||||
|
float v =
|
||||||
|
((float)resized[src+c] - 127.5f) / 128.0f;
|
||||||
|
|
||||||
|
input[dst+c] = float_to_fp16(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rknn_input in{};
|
||||||
|
in.index = 0;
|
||||||
|
in.type = RKNN_TENSOR_FLOAT16;
|
||||||
|
in.fmt = RKNN_TENSOR_NHWC;
|
||||||
|
in.size = input.size() * sizeof(uint16_t);
|
||||||
|
in.buf = input.data();
|
||||||
|
in.pass_through = 1;
|
||||||
|
|
||||||
|
int ret = rknn_inputs_set(ctx, 1, &in);
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SCRFD inputs_set failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = rknn_run(ctx, nullptr);
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SCRFD run failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
rknn_output outputs[9]{};
|
||||||
|
|
||||||
|
for (int i = 0; i < 9; ++i) {
|
||||||
|
outputs[i].want_float = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = rknn_outputs_get(ctx, 9, outputs, nullptr);
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SCRFD outputs_get failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const int strides[3] = {8, 16, 32};
|
||||||
|
const int counts[3] = {12800, 3200, 800};
|
||||||
|
|
||||||
|
std::vector<Face> faces;
|
||||||
|
|
||||||
|
const float score_threshold = 0.50f;
|
||||||
|
|
||||||
|
for (int level = 0; level < 3; ++level) {
|
||||||
|
const int stride = strides[level];
|
||||||
|
const int count = counts[level];
|
||||||
|
|
||||||
|
const uint16_t* scores =
|
||||||
|
(const uint16_t*)outputs[level].buf;
|
||||||
|
|
||||||
|
const uint16_t* bbox =
|
||||||
|
(const uint16_t*)outputs[3 + level].buf;
|
||||||
|
|
||||||
|
const uint16_t* kps =
|
||||||
|
(const uint16_t*)outputs[6 + level].buf;
|
||||||
|
|
||||||
|
int feat_w = SCRFD_SIZE / stride;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
float score = fp16_to_float(scores[i]);
|
||||||
|
|
||||||
|
if (score < score_threshold)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int anchor = i % 2;
|
||||||
|
int p = i / 2;
|
||||||
|
|
||||||
|
int gx = p % feat_w;
|
||||||
|
int gy = p / feat_w;
|
||||||
|
|
||||||
|
float cx = gx * stride;
|
||||||
|
float cy = gy * stride;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* AnchorGenerator con due anchor:
|
||||||
|
* centers duplicati sullo stesso punto.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(void)anchor;
|
||||||
|
|
||||||
|
float l = fp16_to_float(bbox[i*4+0]) * stride;
|
||||||
|
float t = fp16_to_float(bbox[i*4+1]) * stride;
|
||||||
|
float r = fp16_to_float(bbox[i*4+2]) * stride;
|
||||||
|
float b = fp16_to_float(bbox[i*4+3]) * stride;
|
||||||
|
|
||||||
|
Face f;
|
||||||
|
f.score = score;
|
||||||
|
|
||||||
|
f.x1 = (cx - l) / scale;
|
||||||
|
f.y1 = (cy - t) / scale;
|
||||||
|
f.x2 = (cx + r) / scale;
|
||||||
|
f.y2 = (cy + b) / scale;
|
||||||
|
|
||||||
|
for (int j = 0; j < 5; ++j) {
|
||||||
|
float x =
|
||||||
|
cx + fp16_to_float(kps[i*10+j*2])
|
||||||
|
* stride;
|
||||||
|
|
||||||
|
float y =
|
||||||
|
cy + fp16_to_float(kps[i*10+j*2+1])
|
||||||
|
* stride;
|
||||||
|
|
||||||
|
f.kps[j].x = x / scale;
|
||||||
|
f.kps[j].y = y / scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
f.x1 = std::clamp(f.x1, 0.0f, (float)image.w);
|
||||||
|
f.y1 = std::clamp(f.y1, 0.0f, (float)image.h);
|
||||||
|
f.x2 = std::clamp(f.x2, 0.0f, (float)image.w);
|
||||||
|
f.y2 = std::clamp(f.y2, 0.0f, (float)image.h);
|
||||||
|
|
||||||
|
faces.push_back(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rknn_outputs_release(ctx, 9, outputs);
|
||||||
|
|
||||||
|
nms(faces, 0.45f);
|
||||||
|
|
||||||
|
return faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Scrfd()
|
||||||
|
{
|
||||||
|
if (ctx)
|
||||||
|
rknn_destroy(ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const Point sface_template[5] = {
|
||||||
|
{38.2946f, 51.6963f},
|
||||||
|
{73.5318f, 51.5014f},
|
||||||
|
{56.0252f, 71.7366f},
|
||||||
|
{41.5493f, 92.3655f},
|
||||||
|
{70.7299f, 92.2041f}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trova la trasformazione similarity:
|
||||||
|
*
|
||||||
|
* x' = a*x - b*y + tx
|
||||||
|
* y' = b*x + a*y + ty
|
||||||
|
*/
|
||||||
|
static void similarity_transform(
|
||||||
|
const Point src[5],
|
||||||
|
float& a,
|
||||||
|
float& b,
|
||||||
|
float& tx,
|
||||||
|
float& ty)
|
||||||
|
{
|
||||||
|
double sx = 0, sy = 0;
|
||||||
|
double dx = 0, dy = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
sx += src[i].x;
|
||||||
|
sy += src[i].y;
|
||||||
|
dx += sface_template[i].x;
|
||||||
|
dy += sface_template[i].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
sx /= 5;
|
||||||
|
sy /= 5;
|
||||||
|
dx /= 5;
|
||||||
|
dy /= 5;
|
||||||
|
|
||||||
|
double num_a = 0;
|
||||||
|
double num_b = 0;
|
||||||
|
double den = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
double x = src[i].x - sx;
|
||||||
|
double y = src[i].y - sy;
|
||||||
|
|
||||||
|
double X = sface_template[i].x - dx;
|
||||||
|
double Y = sface_template[i].y - dy;
|
||||||
|
|
||||||
|
num_a += x*X + y*Y;
|
||||||
|
num_b += x*Y - y*X;
|
||||||
|
den += x*x + y*y;
|
||||||
|
}
|
||||||
|
|
||||||
|
a = (float)(num_a / den);
|
||||||
|
b = (float)(num_b / den);
|
||||||
|
|
||||||
|
tx = (float)(dx - a*sx + b*sy);
|
||||||
|
ty = (float)(dy - b*sx - a*sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint16_t> align_face(
|
||||||
|
const Image& image,
|
||||||
|
const Face& face)
|
||||||
|
{
|
||||||
|
float a, b, tx, ty;
|
||||||
|
|
||||||
|
similarity_transform(face.kps, a, b, tx, ty);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inversa della similarity transform.
|
||||||
|
*
|
||||||
|
* src = A^-1 * (dst - t)
|
||||||
|
*/
|
||||||
|
float denom = a*a + b*b;
|
||||||
|
|
||||||
|
std::vector<uint16_t> output(
|
||||||
|
SFACE_SIZE * SFACE_SIZE * 3);
|
||||||
|
|
||||||
|
for (int y = 0; y < SFACE_SIZE; ++y) {
|
||||||
|
for (int x = 0; x < SFACE_SIZE; ++x) {
|
||||||
|
|
||||||
|
float X = x - tx;
|
||||||
|
float Y = y - ty;
|
||||||
|
|
||||||
|
float sx =
|
||||||
|
(a*X + b*Y) / denom;
|
||||||
|
|
||||||
|
float sy =
|
||||||
|
(-b*X + a*Y) / denom;
|
||||||
|
|
||||||
|
int x0 = (int)std::floor(sx);
|
||||||
|
int y0 = (int)std::floor(sy);
|
||||||
|
|
||||||
|
float wx = sx - x0;
|
||||||
|
float wy = sy - y0;
|
||||||
|
|
||||||
|
uint8_t rgb[3] = {0,0,0};
|
||||||
|
|
||||||
|
if (x0 >= 0 &&
|
||||||
|
y0 >= 0 &&
|
||||||
|
x0 + 1 < image.w &&
|
||||||
|
y0 + 1 < image.h) {
|
||||||
|
|
||||||
|
int x1 = x0 + 1;
|
||||||
|
int y1 = y0 + 1;
|
||||||
|
|
||||||
|
for (int c = 0; c < 3; ++c) {
|
||||||
|
float p00 =
|
||||||
|
image.rgb[(y0*image.w+x0)*3+c];
|
||||||
|
|
||||||
|
float p01 =
|
||||||
|
image.rgb[(y0*image.w+x1)*3+c];
|
||||||
|
|
||||||
|
float p10 =
|
||||||
|
image.rgb[(y1*image.w+x0)*3+c];
|
||||||
|
|
||||||
|
float p11 =
|
||||||
|
image.rgb[(y1*image.w+x1)*3+c];
|
||||||
|
|
||||||
|
float v =
|
||||||
|
p00*(1-wx)*(1-wy) +
|
||||||
|
p01*wx*(1-wy) +
|
||||||
|
p10*(1-wx)*wy +
|
||||||
|
p11*wx*wy;
|
||||||
|
|
||||||
|
rgb[c] =
|
||||||
|
(uint8_t)std::clamp(
|
||||||
|
(int)std::round(v), 0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int idx = (y*SFACE_SIZE+x)*3;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SFace OpenCV:
|
||||||
|
* blobFromImage(..., swapRB=true, scalefactor=1)
|
||||||
|
*
|
||||||
|
* Noi partiamo già da RGB, quindi non facciamo
|
||||||
|
* nessun ulteriore swap e nessuna normalizzazione.
|
||||||
|
*/
|
||||||
|
output[idx+0] = float_to_fp16(rgb[0]);
|
||||||
|
output[idx+1] = float_to_fp16(rgb[1]);
|
||||||
|
output[idx+2] = float_to_fp16(rgb[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SFace {
|
||||||
|
public:
|
||||||
|
rknn_context ctx = 0;
|
||||||
|
|
||||||
|
bool init(const char* model)
|
||||||
|
{
|
||||||
|
FILE* fp = fopen(model, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
perror(model);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
size_t size = ftell(fp);
|
||||||
|
fseek(fp, 0, SEEK_SET);
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(size);
|
||||||
|
fread(data.data(), 1, size, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
int ret = rknn_init(&ctx, data.data(), size, 0, nullptr);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "rknn_init SFace failed: "
|
||||||
|
<< ret << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> feature(
|
||||||
|
const std::vector<uint16_t>& input)
|
||||||
|
{
|
||||||
|
rknn_input in{};
|
||||||
|
|
||||||
|
in.index = 0;
|
||||||
|
in.type = RKNN_TENSOR_FLOAT16;
|
||||||
|
in.fmt = RKNN_TENSOR_NHWC;
|
||||||
|
in.size = input.size() * sizeof(uint16_t);
|
||||||
|
in.buf = (void*)input.data();
|
||||||
|
in.pass_through = 1;
|
||||||
|
|
||||||
|
int ret = rknn_inputs_set(ctx, 1, &in);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SFace inputs_set failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = rknn_run(ctx, nullptr);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SFace run failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
rknn_output output{};
|
||||||
|
|
||||||
|
output.want_float = 0;
|
||||||
|
|
||||||
|
ret = rknn_outputs_get(
|
||||||
|
ctx, 1, &output, nullptr);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
std::cerr << "SFace outputs_get failed\n";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint16_t* p =
|
||||||
|
(const uint16_t*)output.buf;
|
||||||
|
|
||||||
|
std::vector<float> emb(EMBED_DIM);
|
||||||
|
|
||||||
|
for (int i = 0; i < EMBED_DIM; ++i)
|
||||||
|
emb[i] = fp16_to_float(p[i]);
|
||||||
|
|
||||||
|
rknn_outputs_release(ctx, 1, &output);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* L2 normalization.
|
||||||
|
*/
|
||||||
|
double norm = 0;
|
||||||
|
|
||||||
|
for (float v : emb)
|
||||||
|
norm += (double)v * v;
|
||||||
|
|
||||||
|
norm = std::sqrt(norm);
|
||||||
|
|
||||||
|
if (norm > 0) {
|
||||||
|
for (float& v : emb)
|
||||||
|
v /= (float)norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
~SFace()
|
||||||
|
{
|
||||||
|
if (ctx)
|
||||||
|
rknn_destroy(ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<Face> process_image(
|
||||||
|
const Image& image,
|
||||||
|
Scrfd& detector,
|
||||||
|
SFace& recognizer)
|
||||||
|
{
|
||||||
|
auto faces = detector.detect(image);
|
||||||
|
|
||||||
|
for (auto& face : faces) {
|
||||||
|
auto aligned = align_face(image, face);
|
||||||
|
face.embedding =
|
||||||
|
recognizer.feature(aligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
return faces;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FaceEngine::Impl {
|
||||||
|
Scrfd detector;
|
||||||
|
SFace recognizer;
|
||||||
|
bool initialized = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
FaceEngine::FaceEngine()
|
||||||
|
: impl_(new Impl())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FaceEngine::~FaceEngine()
|
||||||
|
{
|
||||||
|
delete impl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FaceEngine::init(
|
||||||
|
const std::string& scrfd_model_path,
|
||||||
|
const std::string& sface_model_path)
|
||||||
|
{
|
||||||
|
if (impl_->initialized)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!impl_->detector.init(scrfd_model_path.c_str()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!impl_->recognizer.init(sface_model_path.c_str()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
impl_->initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FaceEngine::process_image(
|
||||||
|
const std::string& image_path,
|
||||||
|
std::vector<Face>& faces,
|
||||||
|
int& image_width,
|
||||||
|
int& image_height,
|
||||||
|
int rotation_degrees)
|
||||||
|
{
|
||||||
|
faces.clear();
|
||||||
|
image_width = 0;
|
||||||
|
image_height = 0;
|
||||||
|
|
||||||
|
if (!impl_->initialized) {
|
||||||
|
std::cerr << "FaceEngine non inizializzato\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int normalized_rotation = 0;
|
||||||
|
|
||||||
|
if (!normalize_rotation(
|
||||||
|
rotation_degrees,
|
||||||
|
normalized_rotation)) {
|
||||||
|
std::cerr
|
||||||
|
<< "Rotazione non valida: "
|
||||||
|
<< rotation_degrees
|
||||||
|
<< ". Valori ammessi: 0, 90, 180, 270\n";
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image source_image;
|
||||||
|
|
||||||
|
if (!load_image(image_path, source_image))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Image oriented_image = rotate_image(
|
||||||
|
source_image,
|
||||||
|
normalized_rotation);
|
||||||
|
|
||||||
|
image_width = oriented_image.w;
|
||||||
|
image_height = oriented_image.h;
|
||||||
|
|
||||||
|
faces = ::process_image(
|
||||||
|
oriented_image,
|
||||||
|
impl_->detector,
|
||||||
|
impl_->recognizer);
|
||||||
|
|
||||||
|
for (const Face& face : faces) {
|
||||||
|
if (face.embedding.size() != EMBED_DIM) {
|
||||||
|
std::cerr
|
||||||
|
<< "Embedding non valido per "
|
||||||
|
<< image_path
|
||||||
|
<< ": attesi "
|
||||||
|
<< EMBED_DIM
|
||||||
|
<< " valori, trovati "
|
||||||
|
<< face.embedding.size()
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
faces.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
781
src/face_knn.cc
Normal file
|
|
@ -0,0 +1,781 @@
|
||||||
|
#include "knn_opencl.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
|
class ScopedStdoutToStderr {
|
||||||
|
public:
|
||||||
|
ScopedStdoutToStderr()
|
||||||
|
{
|
||||||
|
std::cout.flush();
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
saved_stdout_ =
|
||||||
|
::dup(STDOUT_FILENO);
|
||||||
|
|
||||||
|
if (saved_stdout_ < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Impossibile salvare stdout"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
::dup2(
|
||||||
|
STDERR_FILENO,
|
||||||
|
STDOUT_FILENO
|
||||||
|
) < 0
|
||||||
|
) {
|
||||||
|
::close(saved_stdout_);
|
||||||
|
saved_stdout_ = -1;
|
||||||
|
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Impossibile deviare stdout"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedStdoutToStderr()
|
||||||
|
{
|
||||||
|
if (saved_stdout_ < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout.flush();
|
||||||
|
std::fflush(stdout);
|
||||||
|
|
||||||
|
::dup2(
|
||||||
|
saved_stdout_,
|
||||||
|
STDOUT_FILENO
|
||||||
|
);
|
||||||
|
|
||||||
|
::close(saved_stdout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedStdoutToStderr(
|
||||||
|
const ScopedStdoutToStderr&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
ScopedStdoutToStderr& operator=(
|
||||||
|
const ScopedStdoutToStderr&
|
||||||
|
) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int saved_stdout_ = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Arguments {
|
||||||
|
std::string queries_path;
|
||||||
|
|
||||||
|
std::string database_path;
|
||||||
|
|
||||||
|
std::string self_map_path;
|
||||||
|
|
||||||
|
std::string backend = "cpu";
|
||||||
|
|
||||||
|
std::size_t query_count = 0;
|
||||||
|
|
||||||
|
std::size_t database_count = 0;
|
||||||
|
|
||||||
|
std::uint32_t dimension = 128;
|
||||||
|
|
||||||
|
std::uint32_t top_k = 1;
|
||||||
|
|
||||||
|
float threshold = 0.50f;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void print_usage(
|
||||||
|
const char* program)
|
||||||
|
{
|
||||||
|
std::cerr
|
||||||
|
<< "Uso:\n"
|
||||||
|
<< " "
|
||||||
|
<< program
|
||||||
|
<< " --queries FILE"
|
||||||
|
<< " --database FILE"
|
||||||
|
<< " --query-count N"
|
||||||
|
<< " --database-count N"
|
||||||
|
<< " --dimension N"
|
||||||
|
<< " --top-k N"
|
||||||
|
<< " --backend cpu"
|
||||||
|
<< " [--threshold SCORE]"
|
||||||
|
<< " [--self-map FILE]"
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string require_value(
|
||||||
|
int argc,
|
||||||
|
char** argv,
|
||||||
|
int& index)
|
||||||
|
{
|
||||||
|
if (index + 1 >= argc) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string(
|
||||||
|
"Valore mancante per "
|
||||||
|
)
|
||||||
|
+ argv[index]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
|
||||||
|
return argv[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::size_t parse_size(
|
||||||
|
const std::string& text,
|
||||||
|
const char* name)
|
||||||
|
{
|
||||||
|
std::size_t consumed = 0;
|
||||||
|
|
||||||
|
const auto value =
|
||||||
|
std::stoull(
|
||||||
|
text,
|
||||||
|
&consumed,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
consumed != text.size()
|
||||||
|
|| value == 0
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string(name)
|
||||||
|
+ " non valido: "
|
||||||
|
+ text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<std::size_t>(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::uint32_t parse_u32(
|
||||||
|
const std::string& text,
|
||||||
|
const char* name)
|
||||||
|
{
|
||||||
|
const std::size_t value =
|
||||||
|
parse_size(
|
||||||
|
text,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
value
|
||||||
|
> std::numeric_limits<
|
||||||
|
std::uint32_t
|
||||||
|
>::max()
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string(name)
|
||||||
|
+ " supera uint32"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<std::uint32_t>(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float parse_float(
|
||||||
|
const std::string& text,
|
||||||
|
const char* name)
|
||||||
|
{
|
||||||
|
std::size_t consumed = 0;
|
||||||
|
|
||||||
|
const float value =
|
||||||
|
std::stof(
|
||||||
|
text,
|
||||||
|
&consumed
|
||||||
|
);
|
||||||
|
|
||||||
|
if (consumed != text.size()) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string(name)
|
||||||
|
+ " non valido: "
|
||||||
|
+ text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Arguments parse_arguments(
|
||||||
|
int argc,
|
||||||
|
char** argv)
|
||||||
|
{
|
||||||
|
Arguments arguments;
|
||||||
|
|
||||||
|
for (
|
||||||
|
int index = 1;
|
||||||
|
index < argc;
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
const std::string option =
|
||||||
|
argv[index];
|
||||||
|
|
||||||
|
if (
|
||||||
|
option == "--help"
|
||||||
|
|| option == "-h"
|
||||||
|
) {
|
||||||
|
print_usage(argv[0]);
|
||||||
|
std::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option == "--queries") {
|
||||||
|
arguments.queries_path =
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--database"
|
||||||
|
) {
|
||||||
|
arguments.database_path =
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--self-map"
|
||||||
|
) {
|
||||||
|
arguments.self_map_path =
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--backend"
|
||||||
|
) {
|
||||||
|
arguments.backend =
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--query-count"
|
||||||
|
) {
|
||||||
|
arguments.query_count =
|
||||||
|
parse_size(
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
),
|
||||||
|
"query-count"
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--database-count"
|
||||||
|
) {
|
||||||
|
arguments.database_count =
|
||||||
|
parse_size(
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
),
|
||||||
|
"database-count"
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--dimension"
|
||||||
|
) {
|
||||||
|
arguments.dimension =
|
||||||
|
parse_u32(
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
),
|
||||||
|
"dimension"
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--top-k"
|
||||||
|
) {
|
||||||
|
arguments.top_k =
|
||||||
|
parse_u32(
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
),
|
||||||
|
"top-k"
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
option == "--threshold"
|
||||||
|
) {
|
||||||
|
arguments.threshold =
|
||||||
|
parse_float(
|
||||||
|
require_value(
|
||||||
|
argc,
|
||||||
|
argv,
|
||||||
|
index
|
||||||
|
),
|
||||||
|
"threshold"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Opzione sconosciuta: "
|
||||||
|
+ option
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
arguments.queries_path.empty()
|
||||||
|
|| arguments.database_path.empty()
|
||||||
|
|| arguments.query_count == 0
|
||||||
|
|| arguments.database_count == 0
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Argomenti obbligatori mancanti"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
arguments.backend != "cpu"
|
||||||
|
&& arguments.backend != "opencl"
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Backend non supportato: "
|
||||||
|
+ arguments.backend
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<float> read_float_file(
|
||||||
|
const std::string& path,
|
||||||
|
std::size_t expected_values)
|
||||||
|
{
|
||||||
|
std::ifstream input(
|
||||||
|
path,
|
||||||
|
std::ios::binary
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Impossibile aprire: "
|
||||||
|
+ path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
input.seekg(
|
||||||
|
0,
|
||||||
|
std::ios::end
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::streamoff byte_count =
|
||||||
|
input.tellg();
|
||||||
|
|
||||||
|
input.seekg(
|
||||||
|
0,
|
||||||
|
std::ios::beg
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto expected_bytes =
|
||||||
|
static_cast<std::streamoff>(
|
||||||
|
expected_values
|
||||||
|
* sizeof(float)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (byte_count != expected_bytes) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Dimensione file incoerente: "
|
||||||
|
+ path
|
||||||
|
+ ", byte="
|
||||||
|
+ std::to_string(byte_count)
|
||||||
|
+ ", attesi="
|
||||||
|
+ std::to_string(
|
||||||
|
expected_bytes
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<float> values(
|
||||||
|
expected_values
|
||||||
|
);
|
||||||
|
|
||||||
|
input.read(
|
||||||
|
reinterpret_cast<char*>(
|
||||||
|
values.data()
|
||||||
|
),
|
||||||
|
byte_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Lettura incompleta: "
|
||||||
|
+ path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::int64_t> read_self_map(
|
||||||
|
const std::string& path,
|
||||||
|
std::size_t query_count)
|
||||||
|
{
|
||||||
|
std::vector<std::int64_t> values(
|
||||||
|
query_count,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (path.empty()) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream input(path);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Impossibile aprire self-map: "
|
||||||
|
+ path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t index = 0;
|
||||||
|
std::int64_t value = -1;
|
||||||
|
|
||||||
|
while (input >> value) {
|
||||||
|
if (index >= query_count) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Self-map contiene "
|
||||||
|
"troppe righe"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
values[index] = value;
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index != query_count) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Self-map contiene "
|
||||||
|
+ std::to_string(index)
|
||||||
|
+ " righe, attese "
|
||||||
|
+ std::to_string(
|
||||||
|
query_count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void write_json(
|
||||||
|
const face_knn::SearchOutput& output,
|
||||||
|
const std::vector<std::int64_t>& self_map,
|
||||||
|
float threshold)
|
||||||
|
{
|
||||||
|
std::size_t recognized_count = 0;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< std::setprecision(9)
|
||||||
|
<< "{\n"
|
||||||
|
<< " \"backend\": \""
|
||||||
|
<< output.backend
|
||||||
|
<< "\",\n"
|
||||||
|
<< " \"device_name\": \""
|
||||||
|
<< output.device_name
|
||||||
|
<< "\",\n"
|
||||||
|
<< " \"query_count\": "
|
||||||
|
<< output.query_count
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"database_count\": "
|
||||||
|
<< output.database_count
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"dimension\": "
|
||||||
|
<< output.dimension
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"top_k\": "
|
||||||
|
<< output.top_k
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"threshold\": "
|
||||||
|
<< threshold
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"leave_one_out\": "
|
||||||
|
<< (
|
||||||
|
self_map.empty()
|
||||||
|
? "false"
|
||||||
|
: "true"
|
||||||
|
)
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"elapsed_ms\": "
|
||||||
|
<< output.elapsed_ms
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"results\": [\n";
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t query_index = 0;
|
||||||
|
query_index < output.query_count;
|
||||||
|
++query_index
|
||||||
|
) {
|
||||||
|
auto neighbors =
|
||||||
|
face_knn::query_neighbors(
|
||||||
|
output,
|
||||||
|
query_index
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::int64_t excluded =
|
||||||
|
self_map[query_index];
|
||||||
|
|
||||||
|
if (excluded >= 0) {
|
||||||
|
neighbors =
|
||||||
|
face_knn::exclude_neighbor(
|
||||||
|
neighbors,
|
||||||
|
static_cast<
|
||||||
|
std::uint32_t
|
||||||
|
>(excluded)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neighbors.empty()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Nessun vicino disponibile "
|
||||||
|
"dopo leave-one-out"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& best =
|
||||||
|
neighbors.front();
|
||||||
|
|
||||||
|
const bool recognized =
|
||||||
|
best.score >= threshold;
|
||||||
|
|
||||||
|
if (recognized) {
|
||||||
|
recognized_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< " {\n"
|
||||||
|
<< " \"query_index\": "
|
||||||
|
<< query_index
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"excluded_index\": ";
|
||||||
|
|
||||||
|
if (excluded < 0) {
|
||||||
|
std::cout << "null";
|
||||||
|
} else {
|
||||||
|
std::cout << excluded;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"recognized\": "
|
||||||
|
<< (
|
||||||
|
recognized
|
||||||
|
? "true"
|
||||||
|
: "false"
|
||||||
|
)
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"best_index\": "
|
||||||
|
<< best.index
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"best_score\": "
|
||||||
|
<< best.score
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"neighbors\": [\n";
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t position = 0;
|
||||||
|
position < neighbors.size();
|
||||||
|
++position
|
||||||
|
) {
|
||||||
|
const auto& neighbor =
|
||||||
|
neighbors[position];
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< " {"
|
||||||
|
<< "\"index\": "
|
||||||
|
<< neighbor.index
|
||||||
|
<< ", \"score\": "
|
||||||
|
<< neighbor.score
|
||||||
|
<< "}";
|
||||||
|
|
||||||
|
if (
|
||||||
|
position + 1
|
||||||
|
< neighbors.size()
|
||||||
|
) {
|
||||||
|
std::cout << ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< " ]\n"
|
||||||
|
<< " }";
|
||||||
|
|
||||||
|
if (
|
||||||
|
query_index + 1
|
||||||
|
< output.query_count
|
||||||
|
) {
|
||||||
|
std::cout << ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t below_count =
|
||||||
|
output.query_count
|
||||||
|
- recognized_count;
|
||||||
|
|
||||||
|
const double coverage =
|
||||||
|
output.query_count > 0
|
||||||
|
? (
|
||||||
|
static_cast<double>(
|
||||||
|
recognized_count
|
||||||
|
)
|
||||||
|
/ static_cast<double>(
|
||||||
|
output.query_count
|
||||||
|
)
|
||||||
|
* 100.0
|
||||||
|
)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< " ],\n"
|
||||||
|
<< " \"recognized_count\": "
|
||||||
|
<< recognized_count
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"below_threshold_count\": "
|
||||||
|
<< below_count
|
||||||
|
<< ",\n"
|
||||||
|
<< " \"coverage_percent\": "
|
||||||
|
<< coverage
|
||||||
|
<< "\n"
|
||||||
|
<< "}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
int main(
|
||||||
|
int argc,
|
||||||
|
char** argv)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const Arguments arguments =
|
||||||
|
parse_arguments(
|
||||||
|
argc,
|
||||||
|
argv
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t query_values =
|
||||||
|
arguments.query_count
|
||||||
|
* arguments.dimension;
|
||||||
|
|
||||||
|
const std::size_t database_values =
|
||||||
|
arguments.database_count
|
||||||
|
* arguments.dimension;
|
||||||
|
|
||||||
|
const auto queries =
|
||||||
|
read_float_file(
|
||||||
|
arguments.queries_path,
|
||||||
|
query_values
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto database =
|
||||||
|
read_float_file(
|
||||||
|
arguments.database_path,
|
||||||
|
database_values
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto self_map =
|
||||||
|
read_self_map(
|
||||||
|
arguments.self_map_path,
|
||||||
|
arguments.query_count
|
||||||
|
);
|
||||||
|
|
||||||
|
face_knn::SearchOptions options;
|
||||||
|
|
||||||
|
options.dimension =
|
||||||
|
arguments.dimension;
|
||||||
|
|
||||||
|
options.top_k =
|
||||||
|
arguments.top_k;
|
||||||
|
|
||||||
|
options.kernel_path =
|
||||||
|
"kernels/knn_float4_topk.cl";
|
||||||
|
|
||||||
|
face_knn::SearchOutput output;
|
||||||
|
|
||||||
|
if (
|
||||||
|
arguments.backend == "opencl"
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* Il runtime Mali scrive la propria versione
|
||||||
|
* direttamente su stdout. Durante OpenCL
|
||||||
|
* deviamo stdout su stderr, affinché l'output
|
||||||
|
* JSON resti puro.
|
||||||
|
*/
|
||||||
|
ScopedStdoutToStderr redirect;
|
||||||
|
|
||||||
|
output =
|
||||||
|
face_knn::search_opencl(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
arguments.database_count,
|
||||||
|
arguments.query_count,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
output =
|
||||||
|
face_knn::search_cpu(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
arguments.database_count,
|
||||||
|
arguments.query_count,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_json(
|
||||||
|
output,
|
||||||
|
self_map,
|
||||||
|
arguments.threshold
|
||||||
|
);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
std::cerr
|
||||||
|
<< "[face_knn] errore: "
|
||||||
|
<< error.what()
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
print_usage(argv[0]);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
144
src/face_recognition.cc
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
#include <cstddef>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "face_engine.h"
|
||||||
|
#include "face_config.h"
|
||||||
|
|
||||||
|
static constexpr float MATCH_THRESHOLD = 0.363f;
|
||||||
|
|
||||||
|
static float cosine(
|
||||||
|
const std::vector<float>& a,
|
||||||
|
const std::vector<float>& b)
|
||||||
|
{
|
||||||
|
if (a.size() != b.size())
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
float score = 0.0f;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < a.size(); ++i)
|
||||||
|
score += a[i] * b[i];
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_faces(
|
||||||
|
const std::string& image_path,
|
||||||
|
const std::vector<Face>& faces)
|
||||||
|
{
|
||||||
|
std::cout << "\n" << image_path << "\n";
|
||||||
|
std::cout << "Faces: " << faces.size() << "\n";
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < faces.size(); ++i) {
|
||||||
|
const Face& face = faces[i];
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\nFace " << i
|
||||||
|
<< " score=" << face.score
|
||||||
|
<< "\n bbox="
|
||||||
|
<< face.x1 << " "
|
||||||
|
<< face.y1 << " "
|
||||||
|
<< face.x2 << " "
|
||||||
|
<< face.y2
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
std::cout << " landmarks:\n";
|
||||||
|
|
||||||
|
for (int j = 0; j < 5; ++j) {
|
||||||
|
std::cout
|
||||||
|
<< " "
|
||||||
|
<< j
|
||||||
|
<< ": "
|
||||||
|
<< face.kps[j].x
|
||||||
|
<< " "
|
||||||
|
<< face.kps[j].y
|
||||||
|
<< "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc != 2 && argc != 3) {
|
||||||
|
std::cerr
|
||||||
|
<< "Uso:\n"
|
||||||
|
<< " " << argv[0]
|
||||||
|
<< " image.jpg\n"
|
||||||
|
<< " " << argv[0]
|
||||||
|
<< " reference.jpg query.jpg\n";
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string root =
|
||||||
|
FACE_CLUSTERING_ROOT;
|
||||||
|
|
||||||
|
const std::string scrfd_model =
|
||||||
|
root + "/models/SCRFD_500M_KPS_640.rknn";
|
||||||
|
|
||||||
|
const std::string sface_model =
|
||||||
|
root + "/models/face_recognition_sface_2021dec.rknn";
|
||||||
|
|
||||||
|
FaceEngine engine;
|
||||||
|
|
||||||
|
if (!engine.init(scrfd_model, sface_model))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
std::vector<Face> faces1;
|
||||||
|
int image1_width = 0;
|
||||||
|
int image1_height = 0;
|
||||||
|
|
||||||
|
if (!engine.process_image(
|
||||||
|
argv[1],
|
||||||
|
faces1,
|
||||||
|
image1_width,
|
||||||
|
image1_height)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_faces(argv[1], faces1);
|
||||||
|
|
||||||
|
if (argc == 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
std::vector<Face> faces2;
|
||||||
|
int image2_width = 0;
|
||||||
|
int image2_height = 0;
|
||||||
|
|
||||||
|
if (!engine.process_image(
|
||||||
|
argv[2],
|
||||||
|
faces2,
|
||||||
|
image2_width,
|
||||||
|
image2_height)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_faces(argv[2], faces2);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\n========================================\n"
|
||||||
|
<< "COSINE SIMILARITY\n"
|
||||||
|
<< "========================================\n";
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < faces1.size(); ++i) {
|
||||||
|
for (std::size_t j = 0; j < faces2.size(); ++j) {
|
||||||
|
const float similarity = cosine(
|
||||||
|
faces1[i].embedding,
|
||||||
|
faces2[j].embedding);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "ref[" << i
|
||||||
|
<< "] vs query[" << j
|
||||||
|
<< "] = "
|
||||||
|
<< similarity;
|
||||||
|
|
||||||
|
if (similarity >= MATCH_THRESHOLD)
|
||||||
|
std::cout << " MATCH";
|
||||||
|
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
368
src/face_scan.cc
Normal file
|
|
@ -0,0 +1,368 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "face_engine.h"
|
||||||
|
|
||||||
|
static constexpr std::size_t EMBEDDING_SIZE = 128;
|
||||||
|
|
||||||
|
static std::filesystem::path executable_path()
|
||||||
|
{
|
||||||
|
std::vector<char> buffer(4096);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const ssize_t length = readlink(
|
||||||
|
"/proc/self/exe",
|
||||||
|
buffer.data(),
|
||||||
|
buffer.size() - 1);
|
||||||
|
|
||||||
|
if (length < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Impossibile leggere /proc/self/exe");
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t path_length =
|
||||||
|
static_cast<std::size_t>(length);
|
||||||
|
|
||||||
|
if (path_length < buffer.size() - 1) {
|
||||||
|
buffer[path_length] = '\0';
|
||||||
|
|
||||||
|
return std::filesystem::path(
|
||||||
|
buffer.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.resize(buffer.size() * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::filesystem::path application_root()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Struttura prevista:
|
||||||
|
*
|
||||||
|
* root/
|
||||||
|
* bin/face_scan
|
||||||
|
* lib/librknnrt.so
|
||||||
|
* models/<model>.rknn
|
||||||
|
*
|
||||||
|
* executable_path():
|
||||||
|
* root/bin/face_scan
|
||||||
|
*
|
||||||
|
* parent_path():
|
||||||
|
* root/bin
|
||||||
|
*
|
||||||
|
* parent_path().parent_path():
|
||||||
|
* root
|
||||||
|
*/
|
||||||
|
return executable_path()
|
||||||
|
.parent_path()
|
||||||
|
.parent_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_json_string(const std::string& value)
|
||||||
|
{
|
||||||
|
std::cout << '"';
|
||||||
|
|
||||||
|
for (unsigned char ch : value) {
|
||||||
|
switch (ch) {
|
||||||
|
case '"':
|
||||||
|
std::cout << "\\\"";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
std::cout << "\\\\";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\b':
|
||||||
|
std::cout << "\\b";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\f':
|
||||||
|
std::cout << "\\f";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
std::cout << "\\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\r':
|
||||||
|
std::cout << "\\r";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
std::cout << "\\t";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (ch < 0x20) {
|
||||||
|
const char* hex = "0123456789abcdef";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\\u00"
|
||||||
|
<< hex[(ch >> 4) & 0x0f]
|
||||||
|
<< hex[ch & 0x0f];
|
||||||
|
} else {
|
||||||
|
std::cout << static_cast<char>(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool finite_face(const Face& face)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(face.score) ||
|
||||||
|
!std::isfinite(face.x1) ||
|
||||||
|
!std::isfinite(face.y1) ||
|
||||||
|
!std::isfinite(face.x2) ||
|
||||||
|
!std::isfinite(face.y2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Point& point : face.kps) {
|
||||||
|
if (!std::isfinite(point.x) ||
|
||||||
|
!std::isfinite(point.y)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (face.embedding.size() != EMBEDDING_SIZE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (float value : face.embedding) {
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_face_json(
|
||||||
|
const Face& face,
|
||||||
|
std::size_t face_index)
|
||||||
|
{
|
||||||
|
const float width = face.x2 - face.x1;
|
||||||
|
const float height = face.y2 - face.y1;
|
||||||
|
|
||||||
|
std::cout << "{";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"face_index\":"
|
||||||
|
<< face_index
|
||||||
|
<< ",";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"score\":"
|
||||||
|
<< face.score
|
||||||
|
<< ",";
|
||||||
|
|
||||||
|
std::cout << "\"bbox\":{";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"x\":"
|
||||||
|
<< face.x1
|
||||||
|
<< ",";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"y\":"
|
||||||
|
<< face.y1
|
||||||
|
<< ",";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"width\":"
|
||||||
|
<< width
|
||||||
|
<< ",";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "\"height\":"
|
||||||
|
<< height;
|
||||||
|
|
||||||
|
std::cout << "},";
|
||||||
|
|
||||||
|
std::cout << "\"landmarks\":[";
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
|
if (i != 0)
|
||||||
|
std::cout << ",";
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "["
|
||||||
|
<< face.kps[i].x
|
||||||
|
<< ","
|
||||||
|
<< face.kps[i].y
|
||||||
|
<< "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "],";
|
||||||
|
|
||||||
|
std::cout << "\"embedding\":[";
|
||||||
|
|
||||||
|
for (std::size_t i = 0;
|
||||||
|
i < face.embedding.size();
|
||||||
|
++i) {
|
||||||
|
if (i != 0)
|
||||||
|
std::cout << ",";
|
||||||
|
|
||||||
|
std::cout << face.embedding[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "]";
|
||||||
|
|
||||||
|
std::cout << "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int rotation_degrees = 0;
|
||||||
|
const char* image_path = nullptr;
|
||||||
|
|
||||||
|
if (argc == 2) {
|
||||||
|
image_path = argv[1];
|
||||||
|
} else if (
|
||||||
|
argc == 4
|
||||||
|
&& std::string(argv[1]) == "--rotation"
|
||||||
|
) {
|
||||||
|
char* parse_end = nullptr;
|
||||||
|
|
||||||
|
const long parsed_rotation = std::strtol(
|
||||||
|
argv[2],
|
||||||
|
&parse_end,
|
||||||
|
10);
|
||||||
|
|
||||||
|
if (
|
||||||
|
parse_end == argv[2]
|
||||||
|
|| *parse_end != '\0'
|
||||||
|
|| parsed_rotation < -360000
|
||||||
|
|| parsed_rotation > 360000
|
||||||
|
) {
|
||||||
|
std::cerr
|
||||||
|
<< "Rotazione non valida: "
|
||||||
|
<< argv[2]
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotation_degrees =
|
||||||
|
static_cast<int>(parsed_rotation);
|
||||||
|
|
||||||
|
image_path = argv[3];
|
||||||
|
} else {
|
||||||
|
std::cerr
|
||||||
|
<< "Uso:\n"
|
||||||
|
<< " " << argv[0]
|
||||||
|
<< " image.jpg\n"
|
||||||
|
<< " " << argv[0]
|
||||||
|
<< " --rotation 90 image.jpg\n";
|
||||||
|
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path root;
|
||||||
|
|
||||||
|
try {
|
||||||
|
root = application_root();
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
std::cerr
|
||||||
|
<< "Impossibile determinare la root applicativa: "
|
||||||
|
<< error.what()
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string scrfd_model =
|
||||||
|
(
|
||||||
|
root
|
||||||
|
/ "models"
|
||||||
|
/ "SCRFD_500M_KPS_640.rknn"
|
||||||
|
).string();
|
||||||
|
|
||||||
|
const std::string sface_model =
|
||||||
|
(
|
||||||
|
root
|
||||||
|
/ "models"
|
||||||
|
/ "face_recognition_sface_2021dec.rknn"
|
||||||
|
).string();
|
||||||
|
|
||||||
|
FaceEngine engine;
|
||||||
|
|
||||||
|
if (!engine.init(scrfd_model, sface_model)) {
|
||||||
|
std::cerr
|
||||||
|
<< "Impossibile inizializzare FaceEngine\n";
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Face> faces;
|
||||||
|
int image_width = 0;
|
||||||
|
int image_height = 0;
|
||||||
|
|
||||||
|
if (!engine.process_image(
|
||||||
|
image_path,
|
||||||
|
faces,
|
||||||
|
image_width,
|
||||||
|
image_height,
|
||||||
|
rotation_degrees)) {
|
||||||
|
std::cerr
|
||||||
|
<< "Scansione fallita: "
|
||||||
|
<< image_path
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < faces.size(); ++i) {
|
||||||
|
if (!finite_face(faces[i])) {
|
||||||
|
std::cerr
|
||||||
|
<< "Risultato non valido per face_index="
|
||||||
|
<< i
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::setprecision(9);
|
||||||
|
|
||||||
|
std::cout << "{";
|
||||||
|
|
||||||
|
std::cout << "\"image\":{";
|
||||||
|
|
||||||
|
std::cout << "\"path\":";
|
||||||
|
write_json_string(image_path);
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< ",\"width\":"
|
||||||
|
<< image_width
|
||||||
|
<< ",\"height\":"
|
||||||
|
<< image_height
|
||||||
|
<< ",\"rotation\":"
|
||||||
|
<< rotation_degrees;
|
||||||
|
|
||||||
|
std::cout << "},";
|
||||||
|
|
||||||
|
std::cout << "\"faces\":[";
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < faces.size(); ++i) {
|
||||||
|
if (i != 0)
|
||||||
|
std::cout << ",";
|
||||||
|
|
||||||
|
write_face_json(faces[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "]";
|
||||||
|
|
||||||
|
std::cout << "}\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
484
src/knn_opencl.cc
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
#include "knn_opencl.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace face_knn {
|
||||||
|
|
||||||
|
SearchOutput search_opencl_impl(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options);
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool better_neighbor(
|
||||||
|
const Neighbor& left,
|
||||||
|
const Neighbor& right)
|
||||||
|
{
|
||||||
|
if (left.score != right.score) {
|
||||||
|
return left.score > right.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.index < right.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void validate_finite_values(
|
||||||
|
const std::vector<float>& values,
|
||||||
|
const char* name)
|
||||||
|
{
|
||||||
|
for (
|
||||||
|
std::size_t index = 0;
|
||||||
|
index < values.size();
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
if (!std::isfinite(values[index])) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
std::string(name)
|
||||||
|
+ " contiene un valore non finito"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::uint32_t effective_top_k(
|
||||||
|
std::size_t database_count,
|
||||||
|
std::uint32_t requested_top_k)
|
||||||
|
{
|
||||||
|
return std::min(
|
||||||
|
requested_top_k,
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
database_count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
void validate_input(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options)
|
||||||
|
{
|
||||||
|
if (options.dimension == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"dimension deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.top_k == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"top_k deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.lanes_per_query == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"lanes_per_query deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.chunk_size == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"chunk_size deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_count == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"database_count deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query_count == 0) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"query_count deve essere maggiore di zero"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
database_count
|
||||||
|
> std::numeric_limits<
|
||||||
|
std::uint32_t
|
||||||
|
>::max()
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"database_count supera uint32"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t expected_database =
|
||||||
|
database_count
|
||||||
|
* static_cast<std::size_t>(
|
||||||
|
options.dimension
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t expected_queries =
|
||||||
|
query_count
|
||||||
|
* static_cast<std::size_t>(
|
||||||
|
options.dimension
|
||||||
|
);
|
||||||
|
|
||||||
|
if (database.size() != expected_database) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"dimensione database incoerente"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queries.size() != expected_queries) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"dimensione query incoerente"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_finite_values(
|
||||||
|
database,
|
||||||
|
"database"
|
||||||
|
);
|
||||||
|
|
||||||
|
validate_finite_values(
|
||||||
|
queries,
|
||||||
|
"queries"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SearchOutput search_cpu(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options)
|
||||||
|
{
|
||||||
|
validate_input(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
database_count,
|
||||||
|
query_count,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::uint32_t result_top_k =
|
||||||
|
effective_top_k(
|
||||||
|
database_count,
|
||||||
|
options.top_k
|
||||||
|
);
|
||||||
|
|
||||||
|
SearchOutput output;
|
||||||
|
|
||||||
|
output.query_count = query_count;
|
||||||
|
output.database_count = database_count;
|
||||||
|
output.dimension = options.dimension;
|
||||||
|
output.top_k = result_top_k;
|
||||||
|
output.backend = "cpu";
|
||||||
|
output.device_name = "CPU";
|
||||||
|
|
||||||
|
output.neighbors.resize(
|
||||||
|
query_count
|
||||||
|
* static_cast<std::size_t>(
|
||||||
|
result_top_k
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto started_at =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t query_index = 0;
|
||||||
|
query_index < query_count;
|
||||||
|
++query_index
|
||||||
|
) {
|
||||||
|
const float* query =
|
||||||
|
&queries[
|
||||||
|
query_index
|
||||||
|
* options.dimension
|
||||||
|
];
|
||||||
|
|
||||||
|
std::vector<Neighbor> candidates;
|
||||||
|
|
||||||
|
candidates.reserve(database_count);
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t database_index = 0;
|
||||||
|
database_index < database_count;
|
||||||
|
++database_index
|
||||||
|
) {
|
||||||
|
const float* database_vector =
|
||||||
|
&database[
|
||||||
|
database_index
|
||||||
|
* options.dimension
|
||||||
|
];
|
||||||
|
|
||||||
|
float score = 0.0f;
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::uint32_t component = 0;
|
||||||
|
component < options.dimension;
|
||||||
|
++component
|
||||||
|
) {
|
||||||
|
score +=
|
||||||
|
database_vector[component]
|
||||||
|
* query[component];
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.push_back({
|
||||||
|
score,
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
database_index
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
result_top_k
|
||||||
|
< candidates.size()
|
||||||
|
) {
|
||||||
|
std::nth_element(
|
||||||
|
candidates.begin(),
|
||||||
|
candidates.begin()
|
||||||
|
+ result_top_k,
|
||||||
|
candidates.end(),
|
||||||
|
better_neighbor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
candidates.begin(),
|
||||||
|
candidates.begin()
|
||||||
|
+ result_top_k,
|
||||||
|
better_neighbor
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t output_offset =
|
||||||
|
query_index
|
||||||
|
* result_top_k;
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::uint32_t position = 0;
|
||||||
|
position < result_top_k;
|
||||||
|
++position
|
||||||
|
) {
|
||||||
|
output.neighbors[
|
||||||
|
output_offset + position
|
||||||
|
] = candidates[position];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto completed_at =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
output.elapsed_ms =
|
||||||
|
std::chrono::duration<
|
||||||
|
double,
|
||||||
|
std::milli
|
||||||
|
>(
|
||||||
|
completed_at - started_at
|
||||||
|
).count();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SearchOutput search_opencl(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options)
|
||||||
|
{
|
||||||
|
return search_opencl_impl(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
database_count,
|
||||||
|
query_count,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Neighbor> query_neighbors(
|
||||||
|
const SearchOutput& output,
|
||||||
|
std::size_t query_index)
|
||||||
|
{
|
||||||
|
if (query_index >= output.query_count) {
|
||||||
|
throw std::out_of_range(
|
||||||
|
"query_index fuori intervallo"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t begin =
|
||||||
|
query_index
|
||||||
|
* output.top_k;
|
||||||
|
|
||||||
|
const std::size_t end =
|
||||||
|
begin
|
||||||
|
+ output.top_k;
|
||||||
|
|
||||||
|
if (end > output.neighbors.size()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"layout SearchOutput incoerente"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
output.neighbors.begin() + begin,
|
||||||
|
output.neighbors.begin() + end,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Neighbor> exclude_neighbor(
|
||||||
|
const std::vector<Neighbor>& neighbors,
|
||||||
|
std::uint32_t excluded_index)
|
||||||
|
{
|
||||||
|
std::vector<Neighbor> filtered;
|
||||||
|
|
||||||
|
filtered.reserve(neighbors.size());
|
||||||
|
|
||||||
|
for (const Neighbor& neighbor : neighbors) {
|
||||||
|
if (neighbor.index == invalid_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neighbor.index == excluded_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered.push_back(neighbor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ValidationResult compare_outputs(
|
||||||
|
const SearchOutput& expected,
|
||||||
|
const SearchOutput& actual,
|
||||||
|
float score_tolerance,
|
||||||
|
float recognition_threshold)
|
||||||
|
{
|
||||||
|
if (score_tolerance < 0.0f) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"score_tolerance non valida"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
expected.query_count
|
||||||
|
!= actual.query_count
|
||||||
|
|| expected.database_count
|
||||||
|
!= actual.database_count
|
||||||
|
|| expected.dimension
|
||||||
|
!= actual.dimension
|
||||||
|
|| expected.top_k
|
||||||
|
!= actual.top_k
|
||||||
|
|| expected.neighbors.size()
|
||||||
|
!= actual.neighbors.size()
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Output kNN non confrontabili"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValidationResult result;
|
||||||
|
|
||||||
|
result.positions =
|
||||||
|
expected.neighbors.size();
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t index = 0;
|
||||||
|
index < result.positions;
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
const Neighbor& expected_neighbor =
|
||||||
|
expected.neighbors[index];
|
||||||
|
|
||||||
|
const Neighbor& actual_neighbor =
|
||||||
|
actual.neighbors[index];
|
||||||
|
|
||||||
|
if (
|
||||||
|
expected_neighbor.index
|
||||||
|
== actual_neighbor.index
|
||||||
|
) {
|
||||||
|
result.exact_index_matches += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float error = std::fabs(
|
||||||
|
expected_neighbor.score
|
||||||
|
- actual_neighbor.score
|
||||||
|
);
|
||||||
|
|
||||||
|
result.maximum_score_error =
|
||||||
|
std::max(
|
||||||
|
result.maximum_score_error,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t query_index = 0;
|
||||||
|
query_index < expected.query_count;
|
||||||
|
++query_index
|
||||||
|
) {
|
||||||
|
const auto expected_neighbors =
|
||||||
|
query_neighbors(
|
||||||
|
expected,
|
||||||
|
query_index
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto actual_neighbors =
|
||||||
|
query_neighbors(
|
||||||
|
actual,
|
||||||
|
query_index
|
||||||
|
);
|
||||||
|
|
||||||
|
const bool expected_recognized =
|
||||||
|
!expected_neighbors.empty()
|
||||||
|
&& expected_neighbors.front().score
|
||||||
|
>= recognition_threshold;
|
||||||
|
|
||||||
|
const bool actual_recognized =
|
||||||
|
!actual_neighbors.empty()
|
||||||
|
&& actual_neighbors.front().score
|
||||||
|
>= recognition_threshold;
|
||||||
|
|
||||||
|
if (
|
||||||
|
expected_recognized
|
||||||
|
== actual_recognized
|
||||||
|
) {
|
||||||
|
result.decision_matches += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.exact_indices =
|
||||||
|
result.exact_index_matches
|
||||||
|
== result.positions;
|
||||||
|
|
||||||
|
result.decisions_equal =
|
||||||
|
result.decision_matches
|
||||||
|
== expected.query_count;
|
||||||
|
|
||||||
|
if (
|
||||||
|
result.maximum_score_error
|
||||||
|
> score_tolerance
|
||||||
|
) {
|
||||||
|
result.decisions_equal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace face_knn
|
||||||
944
src/knn_opencl_backend.cc
Normal file
|
|
@ -0,0 +1,944 @@
|
||||||
|
#define CL_TARGET_OPENCL_VERSION 120
|
||||||
|
|
||||||
|
#include <CL/cl.h>
|
||||||
|
|
||||||
|
#include "knn_opencl.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace face_knn {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void check_cl(
|
||||||
|
cl_int status,
|
||||||
|
const char* operation)
|
||||||
|
{
|
||||||
|
if (status != CL_SUCCESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string(operation)
|
||||||
|
+ ": OpenCL status "
|
||||||
|
+ std::to_string(status)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string device_string(
|
||||||
|
cl_device_id device,
|
||||||
|
cl_device_info property)
|
||||||
|
{
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceInfo(
|
||||||
|
device,
|
||||||
|
property,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&size
|
||||||
|
),
|
||||||
|
"clGetDeviceInfo size"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string value(
|
||||||
|
size,
|
||||||
|
'\0'
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceInfo(
|
||||||
|
device,
|
||||||
|
property,
|
||||||
|
size,
|
||||||
|
value.data(),
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clGetDeviceInfo value"
|
||||||
|
);
|
||||||
|
|
||||||
|
while (
|
||||||
|
!value.empty()
|
||||||
|
&& value.back() == '\0'
|
||||||
|
) {
|
||||||
|
value.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cl_device_id find_mali_device(
|
||||||
|
std::string& device_name)
|
||||||
|
{
|
||||||
|
cl_uint platform_count = 0;
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetPlatformIDs(
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&platform_count
|
||||||
|
),
|
||||||
|
"clGetPlatformIDs count"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (platform_count == 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Nessuna piattaforma OpenCL"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cl_platform_id> platforms(
|
||||||
|
platform_count
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetPlatformIDs(
|
||||||
|
platform_count,
|
||||||
|
platforms.data(),
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clGetPlatformIDs list"
|
||||||
|
);
|
||||||
|
|
||||||
|
cl_device_id fallback = nullptr;
|
||||||
|
std::string fallback_name;
|
||||||
|
|
||||||
|
for (
|
||||||
|
cl_platform_id platform
|
||||||
|
: platforms
|
||||||
|
) {
|
||||||
|
cl_uint device_count = 0;
|
||||||
|
|
||||||
|
const cl_int status =
|
||||||
|
clGetDeviceIDs(
|
||||||
|
platform,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
status != CL_SUCCESS
|
||||||
|
|| device_count == 0
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cl_device_id> devices(
|
||||||
|
device_count
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clGetDeviceIDs(
|
||||||
|
platform,
|
||||||
|
CL_DEVICE_TYPE_GPU,
|
||||||
|
device_count,
|
||||||
|
devices.data(),
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clGetDeviceIDs list"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (
|
||||||
|
cl_device_id device
|
||||||
|
: devices
|
||||||
|
) {
|
||||||
|
const std::string name =
|
||||||
|
device_string(
|
||||||
|
device,
|
||||||
|
CL_DEVICE_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::string vendor =
|
||||||
|
device_string(
|
||||||
|
device,
|
||||||
|
CL_DEVICE_VENDOR
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::string full_name =
|
||||||
|
vendor + " / " + name;
|
||||||
|
|
||||||
|
if (fallback == nullptr) {
|
||||||
|
fallback = device;
|
||||||
|
fallback_name = full_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
name.find("Mali-G610")
|
||||||
|
!= std::string::npos
|
||||||
|
) {
|
||||||
|
device_name = full_name;
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fallback != nullptr) {
|
||||||
|
device_name = fallback_name;
|
||||||
|
|
||||||
|
std::cerr
|
||||||
|
<< "[face_knn] Mali-G610 non trovata; "
|
||||||
|
<< "uso GPU OpenCL: "
|
||||||
|
<< fallback_name
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Nessuna GPU OpenCL disponibile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string read_text_file(
|
||||||
|
const std::string& path)
|
||||||
|
{
|
||||||
|
std::ifstream input(path);
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Kernel OpenCL non leggibile: "
|
||||||
|
+ path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string content{
|
||||||
|
std::istreambuf_iterator<char>(
|
||||||
|
input
|
||||||
|
),
|
||||||
|
std::istreambuf_iterator<char>()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (content.empty()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Kernel OpenCL vuoto: "
|
||||||
|
+ path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool better_neighbor(
|
||||||
|
const Neighbor& left,
|
||||||
|
const Neighbor& right)
|
||||||
|
{
|
||||||
|
if (left.score != right.score) {
|
||||||
|
return left.score > right.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
return left.index < right.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Neighbor invalid_neighbor()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
-std::numeric_limits<float>::infinity(),
|
||||||
|
invalid_index,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void merge_top_k(
|
||||||
|
const std::vector<std::uint32_t>& chunk_indices,
|
||||||
|
const std::vector<float>& chunk_scores,
|
||||||
|
std::size_t chunk_offset,
|
||||||
|
std::size_t query_count,
|
||||||
|
std::uint32_t top_k,
|
||||||
|
std::vector<Neighbor>& global_result)
|
||||||
|
{
|
||||||
|
for (
|
||||||
|
std::size_t query_index = 0;
|
||||||
|
query_index < query_count;
|
||||||
|
++query_index
|
||||||
|
) {
|
||||||
|
const std::size_t result_offset =
|
||||||
|
query_index
|
||||||
|
* top_k;
|
||||||
|
|
||||||
|
std::vector<Neighbor> candidates;
|
||||||
|
|
||||||
|
candidates.reserve(
|
||||||
|
static_cast<std::size_t>(
|
||||||
|
top_k
|
||||||
|
) * 2
|
||||||
|
);
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::uint32_t position = 0;
|
||||||
|
position < top_k;
|
||||||
|
++position
|
||||||
|
) {
|
||||||
|
const Neighbor& current =
|
||||||
|
global_result[
|
||||||
|
result_offset
|
||||||
|
+ position
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
current.index
|
||||||
|
!= invalid_index
|
||||||
|
) {
|
||||||
|
candidates.push_back(
|
||||||
|
current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::uint32_t position = 0;
|
||||||
|
position < top_k;
|
||||||
|
++position
|
||||||
|
) {
|
||||||
|
const std::size_t chunk_position =
|
||||||
|
result_offset
|
||||||
|
+ position;
|
||||||
|
|
||||||
|
const std::uint32_t local_index =
|
||||||
|
chunk_indices[
|
||||||
|
chunk_position
|
||||||
|
];
|
||||||
|
|
||||||
|
if (
|
||||||
|
local_index
|
||||||
|
== invalid_index
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.push_back({
|
||||||
|
chunk_scores[
|
||||||
|
chunk_position
|
||||||
|
],
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
chunk_offset
|
||||||
|
+ local_index
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(
|
||||||
|
candidates.begin(),
|
||||||
|
candidates.end(),
|
||||||
|
better_neighbor
|
||||||
|
);
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::uint32_t position = 0;
|
||||||
|
position < top_k;
|
||||||
|
++position
|
||||||
|
) {
|
||||||
|
Neighbor value =
|
||||||
|
invalid_neighbor();
|
||||||
|
|
||||||
|
if (
|
||||||
|
position
|
||||||
|
< candidates.size()
|
||||||
|
) {
|
||||||
|
value =
|
||||||
|
candidates[position];
|
||||||
|
}
|
||||||
|
|
||||||
|
global_result[
|
||||||
|
result_offset
|
||||||
|
+ position
|
||||||
|
] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct OpenCLResources {
|
||||||
|
cl_context context = nullptr;
|
||||||
|
|
||||||
|
cl_command_queue queue = nullptr;
|
||||||
|
|
||||||
|
cl_program program = nullptr;
|
||||||
|
|
||||||
|
cl_kernel kernel = nullptr;
|
||||||
|
|
||||||
|
cl_mem database_buffer = nullptr;
|
||||||
|
|
||||||
|
cl_mem query_buffer = nullptr;
|
||||||
|
|
||||||
|
cl_mem index_buffer = nullptr;
|
||||||
|
|
||||||
|
cl_mem score_buffer = nullptr;
|
||||||
|
|
||||||
|
~OpenCLResources()
|
||||||
|
{
|
||||||
|
if (score_buffer != nullptr) {
|
||||||
|
clReleaseMemObject(
|
||||||
|
score_buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index_buffer != nullptr) {
|
||||||
|
clReleaseMemObject(
|
||||||
|
index_buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query_buffer != nullptr) {
|
||||||
|
clReleaseMemObject(
|
||||||
|
query_buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (database_buffer != nullptr) {
|
||||||
|
clReleaseMemObject(
|
||||||
|
database_buffer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kernel != nullptr) {
|
||||||
|
clReleaseKernel(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (program != nullptr) {
|
||||||
|
clReleaseProgram(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue != nullptr) {
|
||||||
|
clReleaseCommandQueue(queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context != nullptr) {
|
||||||
|
clReleaseContext(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::string build_log(
|
||||||
|
cl_program program,
|
||||||
|
cl_device_id device)
|
||||||
|
{
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
device,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
&size
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string value(
|
||||||
|
size,
|
||||||
|
'\0'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (size > 0) {
|
||||||
|
clGetProgramBuildInfo(
|
||||||
|
program,
|
||||||
|
device,
|
||||||
|
CL_PROGRAM_BUILD_LOG,
|
||||||
|
size,
|
||||||
|
value.data(),
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (
|
||||||
|
!value.empty()
|
||||||
|
&& value.back() == '\0'
|
||||||
|
) {
|
||||||
|
value.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
SearchOutput search_opencl_impl(
|
||||||
|
const std::vector<float>& database,
|
||||||
|
const std::vector<float>& queries,
|
||||||
|
std::size_t database_count,
|
||||||
|
std::size_t query_count,
|
||||||
|
const SearchOptions& options)
|
||||||
|
{
|
||||||
|
validate_input(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
database_count,
|
||||||
|
query_count,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
query_count
|
||||||
|
> std::numeric_limits<
|
||||||
|
std::uint32_t
|
||||||
|
>::max()
|
||||||
|
) {
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"query_count supera uint32"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint32_t top_k =
|
||||||
|
std::min(
|
||||||
|
options.top_k,
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
database_count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::string kernel_path =
|
||||||
|
options.kernel_path.empty()
|
||||||
|
? "kernels/knn_float4_topk.cl"
|
||||||
|
: options.kernel_path;
|
||||||
|
|
||||||
|
const std::string kernel_source =
|
||||||
|
read_text_file(
|
||||||
|
kernel_path
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string device_name;
|
||||||
|
|
||||||
|
const cl_device_id device =
|
||||||
|
find_mali_device(
|
||||||
|
device_name
|
||||||
|
);
|
||||||
|
|
||||||
|
std::cerr
|
||||||
|
<< "[face_knn] OpenCL GPU: "
|
||||||
|
<< device_name
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
OpenCLResources resources;
|
||||||
|
|
||||||
|
cl_int status = CL_SUCCESS;
|
||||||
|
|
||||||
|
resources.context =
|
||||||
|
clCreateContext(
|
||||||
|
nullptr,
|
||||||
|
1,
|
||||||
|
&device,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateContext"
|
||||||
|
);
|
||||||
|
|
||||||
|
resources.queue =
|
||||||
|
clCreateCommandQueue(
|
||||||
|
resources.context,
|
||||||
|
device,
|
||||||
|
0,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateCommandQueue"
|
||||||
|
);
|
||||||
|
|
||||||
|
const char* source_pointer =
|
||||||
|
kernel_source.c_str();
|
||||||
|
|
||||||
|
const size_t source_size =
|
||||||
|
kernel_source.size();
|
||||||
|
|
||||||
|
resources.program =
|
||||||
|
clCreateProgramWithSource(
|
||||||
|
resources.context,
|
||||||
|
1,
|
||||||
|
&source_pointer,
|
||||||
|
&source_size,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateProgramWithSource"
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::string build_options =
|
||||||
|
"-cl-fast-relaxed-math"
|
||||||
|
" -D DIM="
|
||||||
|
+ std::to_string(
|
||||||
|
options.dimension
|
||||||
|
)
|
||||||
|
+ " -D TOPK="
|
||||||
|
+ std::to_string(top_k)
|
||||||
|
+ " -D QUERY_COUNT="
|
||||||
|
+ std::to_string(query_count)
|
||||||
|
+ " -D LANES_PER_QUERY="
|
||||||
|
+ std::to_string(
|
||||||
|
options.lanes_per_query
|
||||||
|
);
|
||||||
|
|
||||||
|
status = clBuildProgram(
|
||||||
|
resources.program,
|
||||||
|
1,
|
||||||
|
&device,
|
||||||
|
build_options.c_str(),
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status != CL_SUCCESS) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"clBuildProgram fallito: "
|
||||||
|
+ build_log(
|
||||||
|
resources.program,
|
||||||
|
device
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resources.kernel =
|
||||||
|
clCreateKernel(
|
||||||
|
resources.program,
|
||||||
|
"knn",
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateKernel"
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t chunk_capacity =
|
||||||
|
std::min(
|
||||||
|
options.chunk_size,
|
||||||
|
database_count
|
||||||
|
);
|
||||||
|
|
||||||
|
resources.database_buffer =
|
||||||
|
clCreateBuffer(
|
||||||
|
resources.context,
|
||||||
|
CL_MEM_READ_ONLY,
|
||||||
|
chunk_capacity
|
||||||
|
* options.dimension
|
||||||
|
* sizeof(float),
|
||||||
|
nullptr,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateBuffer database"
|
||||||
|
);
|
||||||
|
|
||||||
|
resources.query_buffer =
|
||||||
|
clCreateBuffer(
|
||||||
|
resources.context,
|
||||||
|
CL_MEM_READ_ONLY
|
||||||
|
| CL_MEM_COPY_HOST_PTR,
|
||||||
|
queries.size()
|
||||||
|
* sizeof(float),
|
||||||
|
const_cast<float*>(
|
||||||
|
queries.data()
|
||||||
|
),
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateBuffer queries"
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t result_count =
|
||||||
|
query_count
|
||||||
|
* static_cast<std::size_t>(
|
||||||
|
top_k
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<std::uint32_t> chunk_indices(
|
||||||
|
result_count,
|
||||||
|
invalid_index
|
||||||
|
);
|
||||||
|
|
||||||
|
std::vector<float> chunk_scores(
|
||||||
|
result_count,
|
||||||
|
-std::numeric_limits<float>::infinity()
|
||||||
|
);
|
||||||
|
|
||||||
|
resources.index_buffer =
|
||||||
|
clCreateBuffer(
|
||||||
|
resources.context,
|
||||||
|
CL_MEM_WRITE_ONLY,
|
||||||
|
result_count
|
||||||
|
* sizeof(std::uint32_t),
|
||||||
|
nullptr,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateBuffer indices"
|
||||||
|
);
|
||||||
|
|
||||||
|
resources.score_buffer =
|
||||||
|
clCreateBuffer(
|
||||||
|
resources.context,
|
||||||
|
CL_MEM_WRITE_ONLY,
|
||||||
|
result_count
|
||||||
|
* sizeof(float),
|
||||||
|
nullptr,
|
||||||
|
&status
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
status,
|
||||||
|
"clCreateBuffer scores"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
resources.kernel,
|
||||||
|
0,
|
||||||
|
sizeof(
|
||||||
|
resources.database_buffer
|
||||||
|
),
|
||||||
|
&resources.database_buffer
|
||||||
|
),
|
||||||
|
"clSetKernelArg database"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
resources.kernel,
|
||||||
|
1,
|
||||||
|
sizeof(
|
||||||
|
resources.query_buffer
|
||||||
|
),
|
||||||
|
&resources.query_buffer
|
||||||
|
),
|
||||||
|
"clSetKernelArg queries"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
resources.kernel,
|
||||||
|
2,
|
||||||
|
sizeof(
|
||||||
|
resources.index_buffer
|
||||||
|
),
|
||||||
|
&resources.index_buffer
|
||||||
|
),
|
||||||
|
"clSetKernelArg indices"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
resources.kernel,
|
||||||
|
3,
|
||||||
|
sizeof(
|
||||||
|
resources.score_buffer
|
||||||
|
),
|
||||||
|
&resources.score_buffer
|
||||||
|
),
|
||||||
|
"clSetKernelArg scores"
|
||||||
|
);
|
||||||
|
|
||||||
|
SearchOutput output;
|
||||||
|
|
||||||
|
output.query_count =
|
||||||
|
query_count;
|
||||||
|
|
||||||
|
output.database_count =
|
||||||
|
database_count;
|
||||||
|
|
||||||
|
output.dimension =
|
||||||
|
options.dimension;
|
||||||
|
|
||||||
|
output.top_k =
|
||||||
|
top_k;
|
||||||
|
|
||||||
|
output.backend =
|
||||||
|
"opencl";
|
||||||
|
|
||||||
|
output.device_name =
|
||||||
|
device_name;
|
||||||
|
|
||||||
|
output.neighbors.assign(
|
||||||
|
result_count,
|
||||||
|
invalid_neighbor()
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::size_t global_size =
|
||||||
|
query_count
|
||||||
|
* options.lanes_per_query;
|
||||||
|
|
||||||
|
const std::size_t local_size =
|
||||||
|
options.lanes_per_query;
|
||||||
|
|
||||||
|
const std::size_t chunk_count =
|
||||||
|
(
|
||||||
|
database_count
|
||||||
|
+ options.chunk_size
|
||||||
|
- 1
|
||||||
|
)
|
||||||
|
/ options.chunk_size;
|
||||||
|
|
||||||
|
std::cerr
|
||||||
|
<< "[face_knn] kernel_path="
|
||||||
|
<< kernel_path
|
||||||
|
<< "\n"
|
||||||
|
<< "[face_knn] build_options="
|
||||||
|
<< build_options
|
||||||
|
<< "\n"
|
||||||
|
<< "[face_knn] chunk_size="
|
||||||
|
<< chunk_capacity
|
||||||
|
<< "\n"
|
||||||
|
<< "[face_knn] chunks="
|
||||||
|
<< chunk_count
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
const auto started_at =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (
|
||||||
|
std::size_t chunk_offset = 0;
|
||||||
|
chunk_offset < database_count;
|
||||||
|
chunk_offset += options.chunk_size
|
||||||
|
) {
|
||||||
|
const std::size_t chunk_size =
|
||||||
|
std::min(
|
||||||
|
options.chunk_size,
|
||||||
|
database_count
|
||||||
|
- chunk_offset
|
||||||
|
);
|
||||||
|
|
||||||
|
const float* chunk_pointer =
|
||||||
|
&database[
|
||||||
|
chunk_offset
|
||||||
|
* options.dimension
|
||||||
|
];
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueWriteBuffer(
|
||||||
|
resources.queue,
|
||||||
|
resources.database_buffer,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
chunk_size
|
||||||
|
* options.dimension
|
||||||
|
* sizeof(float),
|
||||||
|
chunk_pointer,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clEnqueueWriteBuffer database"
|
||||||
|
);
|
||||||
|
|
||||||
|
const std::uint32_t kernel_count =
|
||||||
|
static_cast<std::uint32_t>(
|
||||||
|
chunk_size
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clSetKernelArg(
|
||||||
|
resources.kernel,
|
||||||
|
4,
|
||||||
|
sizeof(kernel_count),
|
||||||
|
&kernel_count
|
||||||
|
),
|
||||||
|
"clSetKernelArg database count"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueNDRangeKernel(
|
||||||
|
resources.queue,
|
||||||
|
resources.kernel,
|
||||||
|
1,
|
||||||
|
nullptr,
|
||||||
|
&global_size,
|
||||||
|
&local_size,
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clEnqueueNDRangeKernel"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clFinish(
|
||||||
|
resources.queue
|
||||||
|
),
|
||||||
|
"clFinish"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
resources.queue,
|
||||||
|
resources.index_buffer,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
chunk_indices.size()
|
||||||
|
* sizeof(std::uint32_t),
|
||||||
|
chunk_indices.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clEnqueueReadBuffer indices"
|
||||||
|
);
|
||||||
|
|
||||||
|
check_cl(
|
||||||
|
clEnqueueReadBuffer(
|
||||||
|
resources.queue,
|
||||||
|
resources.score_buffer,
|
||||||
|
CL_TRUE,
|
||||||
|
0,
|
||||||
|
chunk_scores.size()
|
||||||
|
* sizeof(float),
|
||||||
|
chunk_scores.data(),
|
||||||
|
0,
|
||||||
|
nullptr,
|
||||||
|
nullptr
|
||||||
|
),
|
||||||
|
"clEnqueueReadBuffer scores"
|
||||||
|
);
|
||||||
|
|
||||||
|
merge_top_k(
|
||||||
|
chunk_indices,
|
||||||
|
chunk_scores,
|
||||||
|
chunk_offset,
|
||||||
|
query_count,
|
||||||
|
top_k,
|
||||||
|
output.neighbors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto completed_at =
|
||||||
|
std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
output.elapsed_ms =
|
||||||
|
std::chrono::duration<
|
||||||
|
double,
|
||||||
|
std::milli
|
||||||
|
>(
|
||||||
|
completed_at - started_at
|
||||||
|
).count();
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace face_knn
|
||||||
24
test/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Immagini di test
|
||||||
|
|
||||||
|
Inserire in questa directory immagini locali per verificare:
|
||||||
|
|
||||||
|
- rilevamento dei volti;
|
||||||
|
- bounding box;
|
||||||
|
- landmark;
|
||||||
|
- generazione degli embedding;
|
||||||
|
- cosine similarity;
|
||||||
|
- output JSON.
|
||||||
|
|
||||||
|
Esempio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_recognition test/reference.jpg test/query.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Oppure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/face_scan test/image.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
Le immagini sono escluse dal repository tramite `.gitignore`.
|
||||||
107
test/knn/test_knn_cpu.cc
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "knn_opencl.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
const std::vector<float> database = {
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
0.70710678f, 0.70710678f,
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<float> queries = {
|
||||||
|
1.0f, 0.0f,
|
||||||
|
0.0f, 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
face_knn::SearchOptions options;
|
||||||
|
|
||||||
|
options.dimension = 2;
|
||||||
|
options.top_k = 2;
|
||||||
|
|
||||||
|
const auto output =
|
||||||
|
face_knn::search_cpu(
|
||||||
|
database,
|
||||||
|
queries,
|
||||||
|
3,
|
||||||
|
2,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto first =
|
||||||
|
face_knn::query_neighbors(
|
||||||
|
output,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto second =
|
||||||
|
face_knn::query_neighbors(
|
||||||
|
output,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
first.size() != 2
|
||||||
|
|| second.size() != 2
|
||||||
|
) {
|
||||||
|
std::cerr
|
||||||
|
<< "Numero vicini errato\n";
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
first[0].index != 0
|
||||||
|
|| second[0].index != 1
|
||||||
|
) {
|
||||||
|
std::cerr
|
||||||
|
<< "Top-1 errato\n";
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
std::fabs(first[0].score - 1.0f)
|
||||||
|
> 0.000001f
|
||||||
|
|| std::fabs(second[0].score - 1.0f)
|
||||||
|
> 0.000001f
|
||||||
|
) {
|
||||||
|
std::cerr
|
||||||
|
<< "Score top-1 errato\n";
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto filtered =
|
||||||
|
face_knn::exclude_neighbor(
|
||||||
|
first,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
filtered.empty()
|
||||||
|
|| filtered[0].index != 2
|
||||||
|
) {
|
||||||
|
std::cerr
|
||||||
|
<< "Leave-one-out errato\n";
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "OK: backend CPU dinamico\n"
|
||||||
|
<< "query_count="
|
||||||
|
<< output.query_count
|
||||||
|
<< "\n"
|
||||||
|
<< "database_count="
|
||||||
|
<< output.database_count
|
||||||
|
<< "\n"
|
||||||
|
<< "top_k="
|
||||||
|
<< output.top_k
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
test/test.jpg
Normal file
|
After Width: | Height: | Size: 2 MiB |
BIN
test/test2.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
test/test3.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
test/test3f.jpg
Normal file
|
After Width: | Height: | Size: 401 KiB |
BIN
test/test4.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
test/test5.jpg
Normal file
|
After Width: | Height: | Size: 230 KiB |
BIN
test/test6.jpg
Normal file
|
After Width: | Height: | Size: 3 MiB |