Repository navigation
Expand file tree
/
Copy pathbatch_inference.cpp
More file actions
204 lines (187 loc) · 8.83 KB
/
Copy pathbatch_inference.cpp
File metadata and controls
204 lines (187 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include "vrt/batch_inference.h"
#include <algorithm>
#include <stdexcept>
#include "vrt/cuda_check.h"
#include "vrt/nvtx.h"
namespace vrt {
BatchInference::BatchInference(const std::vector<std::string>& engine_paths, cudaStream_t stream,
float conf, float iou, int max_det, int sets)
: stream_(stream), conf_(conf), iou_(iou), max_det_(max_det) {
if (engine_paths.empty()) throw std::invalid_argument("no engines");
if (sets < 1) throw std::invalid_argument("need at least one result set");
for (const auto& path : engine_paths) {
auto ctx = std::make_unique<TrtEngine>(TrtModel::load(path));
const TensorInfo& in = ctx->input();
const TensorInfo& out = ctx->output();
if (in.dims.nbDims != 4 || in.dims.d[1] != 3 || out.dims.nbDims != 3)
throw std::runtime_error(path + ": expected input [B, 3, H, W] and output [B, anchors, C]");
if (out.dims.d[0] != in.dims.d[0]) throw std::runtime_error(path + ": batch mismatch");
engines_.push_back({static_cast<int>(in.dims.d[0]), std::move(ctx)});
}
std::sort(engines_.begin(), engines_.end(),
[](const Engine& a, const Engine& b) { return a.batch < b.batch; });
max_batch_ = engines_.back().batch;
// All engines share one input and one output buffer sized for the largest batch; a batch-k
// engine uses the first k slots. That only works if every engine has the same per-image
// shapes and types, so check.
const TensorInfo& big_in = engines_.back().ctx->input();
const TensorInfo& big_out = engines_.back().ctx->output();
for (const Engine& e : engines_) {
const TensorInfo& in = e.ctx->input();
const TensorInfo& out = e.ctx->output();
if (in.dtype != big_in.dtype ||
in.bytes / static_cast<std::size_t>(e.batch) !=
big_in.bytes / static_cast<std::size_t>(max_batch_) ||
out.dtype != nvinfer1::DataType::kFLOAT ||
out.count / static_cast<std::size_t>(e.batch) !=
big_out.count / static_cast<std::size_t>(max_batch_))
throw std::runtime_error("engines differ in per-image shape or type");
}
if (big_in.dtype == nvinfer1::DataType::kFLOAT) {
format_ = TensorFormat::kFloat32;
} else if (big_in.dtype != nvinfer1::DataType::kHALF) {
throw std::runtime_error("input must be float16 or float32");
}
in_slot_bytes_ = big_in.bytes / static_cast<std::size_t>(max_batch_);
num_anchors_ = static_cast<int>(big_out.dims.d[1]);
num_classes_ = static_cast<int>(big_out.dims.d[2]) - 5;
VRT_CUDA_CHECK(cudaMalloc(&d_in_, big_in.bytes));
VRT_CUDA_CHECK(cudaMalloc(reinterpret_cast<void**>(&d_out_), big_out.bytes));
for (Engine& e : engines_) {
e.ctx->bind(e.ctx->input().name, d_in_);
e.ctx->bind(e.ctx->output().name, d_out_);
e.ctx->capture_graph(stream_);
}
post_ = std::make_unique<GpuBatchPostprocess>(max_batch_, num_anchors_, num_classes_, max_det_);
result_bytes_ = post_->result_bytes();
sets_.resize(static_cast<std::size_t>(sets));
for (ResultSet& s : sets_) {
VRT_CUDA_CHECK(cudaHostAlloc(&s.host, result_bytes_ * static_cast<std::size_t>(max_batch_),
cudaHostAllocDefault));
for (auto& e : s.ev) VRT_CUDA_CHECK(cudaEventCreate(&e));
}
}
BatchInference::~BatchInference() {
cudaStreamSynchronize(stream_);
for (auto& [k, g] : post_graphs_) cudaGraphExecDestroy(g);
for (ResultSet& s : sets_) {
for (auto& e : s.ev) cudaEventDestroy(e);
cudaFreeHost(s.host);
}
cudaFree(d_out_);
cudaFree(d_in_);
}
const BatchInference::Engine& BatchInference::pick(int k) const {
for (const Engine& e : engines_)
if (e.batch >= k) return e;
throw std::invalid_argument("batch larger than the largest engine");
}
// Batched NMS over the first k slots, as one graph. Captured on first use for each k;
// recaptured for all k if the letterbox scale changes (different frame size).
cudaGraphExec_t BatchInference::post_graph(int k, float scale) {
if (scale != graph_scale_) {
VRT_CUDA_CHECK(cudaStreamSynchronize(stream_));
for (auto& [n, g] : post_graphs_) VRT_CUDA_CHECK(cudaGraphExecDestroy(g));
post_graphs_.clear();
graph_scale_ = scale;
}
auto it = post_graphs_.find(k);
if (it != post_graphs_.end()) return it->second;
VRT_CUDA_CHECK(cudaStreamBeginCapture(stream_, cudaStreamCaptureModeThreadLocal));
post_->run(d_out_, k, scale, conf_, iou_, stream_);
cudaGraph_t graph = nullptr;
VRT_CUDA_CHECK(cudaStreamEndCapture(stream_, &graph));
cudaGraphExec_t exec = nullptr;
const cudaError_t err = cudaGraphInstantiate(&exec, graph, 0);
cudaGraphDestroy(graph);
VRT_CUDA_CHECK(err);
post_graphs_[k] = exec;
return exec;
}
void BatchInference::prepare(int frame_w, int frame_h) {
const TensorInfo& in = engines_.back().ctx->input();
letterbox_ = std::make_unique<GpuLetterbox>(frame_w, frame_h, static_cast<int>(in.dims.d[3]),
static_cast<int>(in.dims.d[2]));
for (int k = 1; k <= max_batch_; ++k) post_graph(k, letterbox_->scale());
VRT_CUDA_CHECK(cudaStreamSynchronize(stream_));
}
int BatchInference::submit(const std::vector<BatchItem>& items) {
NvtxRange range("batch submit");
const int k = static_cast<int>(items.size());
if (k < 1) throw std::invalid_argument("empty batch");
const int ticket = next_set_;
ResultSet& set = sets_[static_cast<std::size_t>(ticket)];
if (set.pending) throw std::logic_error("submit: every result set is still in flight");
const Engine& e = pick(k);
const GpuFrame& first = *items[0].frame;
if (!letterbox_ || letterbox_->src_w() != first.width || letterbox_->src_h() != first.height) {
VRT_CUDA_CHECK(cudaStreamSynchronize(stream_)); // queued batches still use the old tables
letterbox_ = std::make_unique<GpuLetterbox>(first.width, first.height,
static_cast<int>(e.ctx->input().dims.d[3]),
static_cast<int>(e.ctx->input().dims.d[2]));
}
for (const BatchItem& it : items)
if (it.frame->width != first.width || it.frame->height != first.height)
throw std::invalid_argument("all frames in a batch must have the same size");
// Capture (if this k is new) before queuing anything of this batch on the stream.
const cudaGraphExec_t post = post_graph(k, letterbox_->scale());
VRT_CUDA_CHECK(cudaEventRecord(set.ev[0], stream_));
for (int i = 0; i < k; ++i) {
const BatchItem& it = items[static_cast<std::size_t>(i)];
const GpuFrame& f = *it.frame;
// The frame's surface copy was queued on its camera's decode stream.
VRT_CUDA_CHECK(cudaStreamWaitEvent(stream_, it.ready, 0));
Nv12View v;
v.luma = f.luma;
v.chroma = f.chroma;
v.luma_pitch = f.luma_pitch;
v.chroma_pitch = f.chroma_pitch;
v.width = f.width;
v.height = f.height;
letterbox_->run(v, yuv_coeffs(f.matrix == YuvMatrix::kBt709, f.full_range),
static_cast<char*>(d_in_) + static_cast<std::size_t>(i) * in_slot_bytes_,
format_, stream_);
}
VRT_CUDA_CHECK(cudaEventRecord(set.ev[1], stream_));
e.ctx->enqueue(stream_);
VRT_CUDA_CHECK(cudaEventRecord(set.ev[2], stream_));
VRT_CUDA_CHECK(cudaGraphLaunch(post, stream_));
VRT_CUDA_CHECK(cudaMemcpyAsync(set.host, post_->output(),
result_bytes_ * static_cast<std::size_t>(k),
cudaMemcpyDeviceToHost, stream_));
VRT_CUDA_CHECK(cudaEventRecord(set.ev[3], stream_));
set.batch = k;
set.engine = e.batch;
set.pending = true;
next_set_ = (next_set_ + 1) % static_cast<int>(sets_.size());
return ticket;
}
std::vector<Detections> BatchInference::wait(int ticket, BatchTimes* times) {
NvtxRange range("batch wait");
ResultSet& set = sets_.at(static_cast<std::size_t>(ticket));
if (!set.pending) throw std::logic_error("wait: nothing in flight for this ticket");
VRT_CUDA_CHECK(cudaEventSynchronize(set.ev[3]));
set.pending = false;
const int k = set.batch;
std::vector<Detections> out(static_cast<std::size_t>(k));
for (int i = 0; i < k; ++i) {
const auto* res = reinterpret_cast<const GpuDetections*>(
static_cast<const char*>(set.host) + static_cast<std::size_t>(i) * result_bytes_);
Detections& d = out[static_cast<std::size_t>(i)];
d.resize(static_cast<std::size_t>(res->count));
for (int j = 0; j < res->count; ++j) {
const float* r = res->dets + 6 * j;
d[static_cast<std::size_t>(j)] = {r[0], r[1], r[2], r[3], r[4], static_cast<int>(r[5])};
}
}
if (times) {
times->batch = k;
times->engine = set.engine;
VRT_CUDA_CHECK(cudaEventElapsedTime(×->preprocess_ms, set.ev[0], set.ev[1]));
VRT_CUDA_CHECK(cudaEventElapsedTime(×->infer_ms, set.ev[1], set.ev[2]));
VRT_CUDA_CHECK(cudaEventElapsedTime(×->postprocess_ms, set.ev[2], set.ev[3]));
VRT_CUDA_CHECK(cudaEventElapsedTime(×->total_ms, set.ev[0], set.ev[3]));
}
return out;
}
} // namespace vrt