Add AMD support via ort alternative to trt
This commit is contained in:
+17
-25
@@ -1,9 +1,8 @@
|
||||
#pragma once
|
||||
#include "arcface_embedder.hpp"
|
||||
#include "config.hpp"
|
||||
#include "ort_provider.hpp"
|
||||
#include "trt_arcface_embedder.hpp"
|
||||
#include "inference/face_embedder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -12,31 +11,25 @@
|
||||
// KPN node: runs ArcFace on every 112×112 crop in an AlignedSceneFrame,
|
||||
// producing one L2-normalised 512-dim embedding per face.
|
||||
//
|
||||
// Backend selection:
|
||||
// --arcface-engine <path> → TrtArcFaceEmbedder (raw TensorRT, no ORT)
|
||||
// otherwise → ArcFaceEmbedder (ONNX Runtime, picks best EP)
|
||||
// The inference backend (ONNX Runtime or raw TensorRT) is selected at compile
|
||||
// time; this node talks only to IFaceEmbedder via make_face_embedder(cfg).
|
||||
//
|
||||
// All crops in one frame are batched into a single forward pass (capped at
|
||||
// embed_batch_size). The backends serialise themselves; we only call them
|
||||
// from the single embedder thread.
|
||||
// embed_batch_size). The backend serialises itself; we only call it from the
|
||||
// single embedder thread.
|
||||
|
||||
struct EmbedderFunc {
|
||||
static constexpr std::string_view label() { return "embedder"; }
|
||||
|
||||
explicit EmbedderFunc(const Config& cfg, OrtProvider provider)
|
||||
: batch_size_(std::max(1, cfg.embed_batch_size))
|
||||
explicit EmbedderFunc(const Config& cfg)
|
||||
: embedder_(make_face_embedder(cfg))
|
||||
, batch_size_(std::max(1, cfg.embed_batch_size))
|
||||
{
|
||||
if (!cfg.arcface_engine.empty()) {
|
||||
trt_ = std::make_unique<TrtArcFaceEmbedder>(cfg.arcface_engine);
|
||||
if (trt_->max_batch() < static_cast<int>(batch_size_))
|
||||
throw std::runtime_error(
|
||||
"embed_batch_size " + std::to_string(batch_size_) +
|
||||
" exceeds engine max_batch " + std::to_string(trt_->max_batch()) +
|
||||
" — rebuild engine with EMBED_BATCH=" + std::to_string(batch_size_));
|
||||
} else {
|
||||
ort_ = std::make_unique<ArcFaceEmbedder>(
|
||||
cfg.arcface_model, provider, cfg.trt, cfg.embed_batch_size);
|
||||
}
|
||||
if (embedder_->max_batch() < static_cast<int>(batch_size_))
|
||||
throw std::runtime_error(
|
||||
"embed_batch_size " + std::to_string(batch_size_) +
|
||||
" exceeds backend max_batch " + std::to_string(embedder_->max_batch()) +
|
||||
" — rebuild the engine with EMBED_BATCH=" + std::to_string(batch_size_));
|
||||
}
|
||||
|
||||
EmbeddedSceneFrame operator()(AlignedSceneFrame af) {
|
||||
@@ -49,7 +42,7 @@ struct EmbedderFunc {
|
||||
for (size_t i = 0; i < crops.size(); i += batch_size_) {
|
||||
const size_t end = std::min(i + batch_size_, crops.size());
|
||||
std::vector<cv::Mat> chunk_crops(crops.begin() + i, crops.begin() + end);
|
||||
auto chunk = trt_ ? trt_->embed(chunk_crops) : ort_->embed(chunk_crops);
|
||||
auto chunk = embedder_->embed(chunk_crops);
|
||||
embeddings.insert(embeddings.end(), chunk.begin(), chunk.end());
|
||||
}
|
||||
|
||||
@@ -60,7 +53,6 @@ struct EmbedderFunc {
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ArcFaceEmbedder> ort_;
|
||||
std::unique_ptr<TrtArcFaceEmbedder> trt_;
|
||||
size_t batch_size_;
|
||||
std::unique_ptr<IFaceEmbedder> embedder_;
|
||||
size_t batch_size_;
|
||||
};
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
#pragma once
|
||||
#include "scrfd_decoder.hpp"
|
||||
#include "trt_scrfd_decoder.hpp"
|
||||
#include "config.hpp"
|
||||
#include "ort_provider.hpp"
|
||||
#include "inference/face_detector.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// ── FaceDetectorFunc ──────────────────────────────────────────────────────────
|
||||
// KPN node: runs SCRFD-500MF to detect ALL faces in a frame.
|
||||
//
|
||||
// Backend selection:
|
||||
// --detector-engine <path> → TrtScrfdDecoder (raw TensorRT, no ORT)
|
||||
// otherwise → SCRFDDecoder (ONNX Runtime)
|
||||
// The inference backend (ONNX Runtime or raw TensorRT) is selected at compile
|
||||
// time; this node talks only to IFaceDetector via make_face_detector(cfg).
|
||||
|
||||
struct FaceDetectorFunc {
|
||||
static constexpr std::string_view label() { return "face_detector"; }
|
||||
|
||||
explicit FaceDetectorFunc(const Config& cfg, OrtProvider provider)
|
||||
: max_faces_(cfg.max_faces)
|
||||
explicit FaceDetectorFunc(const Config& cfg)
|
||||
: detector_(make_face_detector(cfg))
|
||||
, max_faces_(cfg.max_faces)
|
||||
, min_face_px_(cfg.min_face_px)
|
||||
{
|
||||
if (!cfg.detector_engine.empty()) {
|
||||
trt_ = std::make_unique<TrtScrfdDecoder>(
|
||||
cfg.detector_engine, cfg.detector_conf, cfg.detector_nms);
|
||||
} else {
|
||||
ort_ = std::make_unique<SCRFDDecoder>(
|
||||
cfg.detector_model, cfg.detector_conf, cfg.detector_nms, provider, cfg.trt);
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
||||
SceneFrame operator()(Frame f) {
|
||||
if (f.eof) return {std::move(f), {}};
|
||||
|
||||
auto faces = trt_ ? trt_->detect(f.image) : ort_->detect(f.image);
|
||||
auto faces = detector_->detect(f.image);
|
||||
|
||||
// Drop faces below minimum pixel size (too small for reliable ArcFace alignment)
|
||||
faces.erase(
|
||||
@@ -54,8 +45,7 @@ struct FaceDetectorFunc {
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SCRFDDecoder> ort_;
|
||||
std::unique_ptr<TrtScrfdDecoder> trt_;
|
||||
int max_faces_{10};
|
||||
float min_face_px_{40.f};
|
||||
std::unique_ptr<IFaceDetector> detector_;
|
||||
int max_faces_{10};
|
||||
float min_face_px_{40.f};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
// ── FrameSourceFunc ───────────────────────────────────────────────────────────
|
||||
// KPN source node: reads a movie file and emits one Frame per sample interval.
|
||||
//
|
||||
// Decode backend: FFmpeg with NVDEC (_cuvid) when available, CPU otherwise.
|
||||
// Decode backend: FFmpeg hwaccel (CUDA/VAAPI, runtime-detected) when
|
||||
// available, CPU otherwise.
|
||||
//
|
||||
// Sampling strategy: seek to the next target timestamp rather than decoding
|
||||
// every frame, which is fast even for 1-FPS sampling of a 2-hour film.
|
||||
@@ -39,7 +40,7 @@ struct FrameSourceFunc {
|
||||
- cfg.start_sec;
|
||||
int n_frames = static_cast<int>(span_s * cfg.sample_fps);
|
||||
std::cerr << "[frame_source] decoder=" << decoder_->codec_name()
|
||||
<< " (" << (decoder_->hw_active() ? "NVDEC" : "CPU") << ")"
|
||||
<< " (" << decoder_->hw_backend() << ")"
|
||||
<< " video_fps=" << decoder_->fps()
|
||||
<< " start=" << cfg.start_sec << "s"
|
||||
<< (end_sec_ > 0 ? " end=" + std::to_string(end_sec_) + "s" : "")
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#pragma once
|
||||
#include "types.hpp"
|
||||
#include "config.hpp"
|
||||
#include "inference/similarity.hpp"
|
||||
#include "gallery/gallery_store.hpp"
|
||||
#include "gallery/gallery_calibration.hpp"
|
||||
|
||||
#include <cublas_v2.h>
|
||||
#include <cuda_runtime_api.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
@@ -35,25 +34,9 @@
|
||||
// Gallery scan: the full reference set (tens of thousands of 512-dim
|
||||
// embeddings) is uploaded to the GPU once at construction time and stays
|
||||
// resident there. Per frame, only the small query matrix (n_faces x 512) is
|
||||
// uploaded and a single cublasSgemm computes the full similarity matrix
|
||||
// (n_faces x N_gallery) in well under a millisecond — far faster than any
|
||||
// CPU GEMM or scalar scan, making gallery-side pruning unnecessary.
|
||||
|
||||
namespace identity_matcher_detail {
|
||||
struct CudaError : std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
inline void check_cuda(cudaError_t e, const char* what) {
|
||||
if (e != cudaSuccess)
|
||||
throw CudaError(std::string(what) + ": " + cudaGetErrorString(e));
|
||||
}
|
||||
|
||||
inline void check_cublas(cublasStatus_t s, const char* what) {
|
||||
if (s != CUBLAS_STATUS_SUCCESS)
|
||||
throw CudaError(std::string(what) + ": cublas error " + std::to_string(s));
|
||||
}
|
||||
} // namespace identity_matcher_detail
|
||||
// uploaded and a single SGEMM computes the full similarity matrix in well under
|
||||
// a millisecond. The GPU math backend (cuBLAS or rocBLAS) lives behind
|
||||
// ISimilarityEngine (backends/gemm_backend.cpp) and is selected at compile time.
|
||||
|
||||
struct IdentityMatcherFunc {
|
||||
static constexpr std::string_view label() { return "identity_matcher"; }
|
||||
@@ -69,8 +52,6 @@ struct IdentityMatcherFunc {
|
||||
, ratio_(cfg.match_ratio)
|
||||
, ratio_ceil_(cfg.match_ratio_ceil)
|
||||
{
|
||||
using namespace identity_matcher_detail;
|
||||
|
||||
std::cerr << "[identity_matcher] flattening gallery embeddings...\n";
|
||||
for (int ai = 0; ai < static_cast<int>(gallery_.actors.size()); ++ai) {
|
||||
for (const auto& emb : gallery_.actors[ai].embeddings) {
|
||||
@@ -100,47 +81,15 @@ struct IdentityMatcherFunc {
|
||||
<< gallery_.actors.size() << " actors, "
|
||||
<< flat_emb_.size() << " reference embeddings\n";
|
||||
|
||||
// Flatten gallery into a contiguous (N x 512) row-major host buffer,
|
||||
// then upload once. Row-major NxD == column-major DxN, which is the
|
||||
// layout cublasSgemm wants for the transposed operand below.
|
||||
std::vector<float> host_gallery(static_cast<size_t>(n_gallery_) * 512);
|
||||
for (int i = 0; i < n_gallery_; ++i)
|
||||
std::memcpy(host_gallery.data() + static_cast<size_t>(i) * 512,
|
||||
flat_emb_[i].data(), 512 * sizeof(float));
|
||||
|
||||
check_cuda(cudaMalloc(reinterpret_cast<void**>(&d_gallery_), host_gallery.size() * sizeof(float)),
|
||||
"cudaMalloc gallery");
|
||||
check_cuda(cudaMemcpy(d_gallery_, host_gallery.data(),
|
||||
host_gallery.size() * sizeof(float),
|
||||
cudaMemcpyHostToDevice),
|
||||
"cudaMemcpy gallery H2D");
|
||||
|
||||
check_cuda(cudaMalloc(reinterpret_cast<void**>(&d_query_), static_cast<size_t>(kMaxFaces) * 512 * sizeof(float)),
|
||||
"cudaMalloc query");
|
||||
check_cuda(cudaMalloc(reinterpret_cast<void**>(&d_sims_), static_cast<size_t>(kMaxFaces) * n_gallery_ * sizeof(float)),
|
||||
"cudaMalloc sims");
|
||||
|
||||
check_cuda(cudaStreamCreate(&stream_), "cudaStreamCreate");
|
||||
check_cublas(cublasCreate(&handle_), "cublasCreate");
|
||||
check_cublas(cublasSetStream(handle_, stream_), "cublasSetStream");
|
||||
|
||||
host_sims_.resize(static_cast<size_t>(kMaxFaces) * n_gallery_);
|
||||
|
||||
std::cerr << "[identity_matcher] gallery resident on GPU ("
|
||||
<< (host_gallery.size() * sizeof(float)) / (1024 * 1024) << " MiB)\n";
|
||||
}
|
||||
|
||||
~IdentityMatcherFunc() {
|
||||
if (d_gallery_) cudaFree(d_gallery_);
|
||||
if (d_query_) cudaFree(d_query_);
|
||||
if (d_sims_) cudaFree(d_sims_);
|
||||
if (handle_) cublasDestroy(handle_);
|
||||
if (stream_) cudaStreamDestroy(stream_);
|
||||
sim_engine_ = make_similarity_engine(host_gallery.data(), n_gallery_, kMaxFaces);
|
||||
}
|
||||
|
||||
MatchedSceneFrame operator()(TrackedSceneFrame tf) {
|
||||
using namespace identity_matcher_detail;
|
||||
|
||||
if (tf.source.eof) return {std::move(tf.source), {}};
|
||||
|
||||
const int n_faces = static_cast<int>(tf.embeddings.size());
|
||||
@@ -151,37 +100,18 @@ struct IdentityMatcherFunc {
|
||||
if (n_faces > kMaxFaces)
|
||||
throw std::runtime_error("identity_matcher: n_faces exceeds kMaxFaces");
|
||||
|
||||
// Build the (n_faces x 512) query matrix (row-major == col-major 512 x n_faces).
|
||||
std::vector<float> host_query(static_cast<size_t>(n_faces) * 512);
|
||||
for (int fi = 0; fi < n_faces; ++fi) {
|
||||
std::memcpy(host_query.data() + static_cast<size_t>(fi) * 512,
|
||||
tf.embeddings[fi].data(), 512 * sizeof(float));
|
||||
}
|
||||
|
||||
check_cuda(cudaMemcpyAsync(d_query_, host_query.data(),
|
||||
host_query.size() * sizeof(float),
|
||||
cudaMemcpyHostToDevice, stream_),
|
||||
"cudaMemcpy query H2D");
|
||||
|
||||
// S (N_gallery x n_faces) col-major = G(512 x N_gallery)^T * Q(512 x n_faces)
|
||||
// i.e. S[g + f*N_gallery] = cosine_similarity(gallery[g], query[f]).
|
||||
const float alpha = 1.f, beta = 0.f;
|
||||
check_cublas(cublasSgemm(handle_, CUBLAS_OP_T, CUBLAS_OP_N,
|
||||
n_gallery_, n_faces, 512,
|
||||
&alpha, d_gallery_, 512, d_query_, 512,
|
||||
&beta, d_sims_, n_gallery_),
|
||||
"cublasSgemm");
|
||||
|
||||
check_cuda(cudaMemcpyAsync(host_sims_.data(), d_sims_,
|
||||
static_cast<size_t>(n_gallery_) * n_faces * sizeof(float),
|
||||
cudaMemcpyDeviceToHost, stream_),
|
||||
"cudaMemcpy sims D2H");
|
||||
check_cuda(cudaStreamSynchronize(stream_), "cudaStreamSynchronize");
|
||||
// S (N_gallery × n_faces) col-major: face fi's gallery sims at sims + fi*n_gallery.
|
||||
const float* host_sims = sim_engine_->compute(host_query.data(), n_faces);
|
||||
|
||||
for (int fi = 0; fi < n_faces; ++fi) {
|
||||
const float* sims = host_sims_.data() + static_cast<size_t>(fi) * n_gallery_;
|
||||
const float* sims = host_sims + static_cast<size_t>(fi) * n_gallery_;
|
||||
|
||||
// Per-actor best cosine similarity (max over that actor's reference embeddings)
|
||||
std::vector<float> best_sim(gallery_.actors.size(),
|
||||
-std::numeric_limits<float>::max());
|
||||
for (int ei = 0; ei < n_gallery_; ++ei) {
|
||||
@@ -190,7 +120,6 @@ struct IdentityMatcherFunc {
|
||||
if (sim > best_sim[ai]) best_sim[ai] = sim;
|
||||
}
|
||||
|
||||
// Find best and second-best actor by similarity
|
||||
int best_actor = -1;
|
||||
int second_actor = -1;
|
||||
float best_s = -std::numeric_limits<float>::max();
|
||||
@@ -240,7 +169,6 @@ struct IdentityMatcherFunc {
|
||||
? cal_.probability(best_s, log_prior_odds_)
|
||||
: best_s;
|
||||
}
|
||||
// actor_idx == -1, name == "" → unknown face
|
||||
|
||||
actors.push_back(std::move(ia));
|
||||
}
|
||||
@@ -260,11 +188,5 @@ private:
|
||||
std::vector<int> flat_actor_;
|
||||
int n_gallery_{0};
|
||||
|
||||
float* d_gallery_{nullptr}; // (n_gallery_ x 512), resident for the lifetime of this node
|
||||
float* d_query_{nullptr}; // (kMaxFaces x 512)
|
||||
float* d_sims_{nullptr}; // (n_gallery_ x kMaxFaces), column-major
|
||||
std::vector<float> host_sims_;
|
||||
|
||||
cudaStream_t stream_{nullptr};
|
||||
cublasHandle_t handle_{nullptr};
|
||||
std::unique_ptr<ISimilarityEngine> sim_engine_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user