ReUseX  0.0.5
3D Point Cloud Processing for Building Reuse
Loading...
Searching...
No Matches
Sam3p1.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 Povl Filip Sonne-Frederiksen
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#pragma once
6#include "reusex/vision/IData.hpp"
7#include "reusex/vision/IVideoModel.hpp"
8#include "reusex/vision/common/object.hpp"
9#include "reusex/vision/tensor_rt/Data.hpp"
10#include "reusex/vision/tensor_rt/Sam3Type.hpp"
11#include "reusex/vision/tensor_rt/common/memory.hpp"
12#include "reusex/vision/tensor_rt/common/norm.hpp"
13#include "reusex/vision/tensor_rt/common/tensorrt.hpp"
14
15#include <tokenizers_cpp.h>
16
17#include <deque>
18#include <filesystem>
19#include <unordered_map>
20#include <vector>
21
23
24/* TensorRTSam3p1 implements the SAM 3.1 stateful video-tracker path on top of
25 * TensorRT. It COEXISTS with TensorRTSam3 (the stateless SAM 3 detector) and is
26 * selected when the model directory additionally contains the tracker engines
27 * (tracker-memory-encoder.engine + tracker-memory-attention.engine).
28 *
29 * The per-frame detector path (vision-encoder + text-encoder + optional
30 * geometry-encoder + decoder) is modelled CLOSELY on Sam3.cpp, but fixed to a
31 * batch size of 1 and adapted to consume a memory-conditioned image embedding.
32 * TensorRTSam3's members are private, so rather than sub-classing we replicate
33 * the minimal batch=1 detector path here (see comments citing Sam3.cpp).
34 *
35 * On top of the detector, TensorRTSam3p1 maintains a fixed-size memory bank:
36 * every frame's decoder output is fed through tracker-memory-encoder to produce
37 * a maskmem token, which is appended to a ring of pre-allocated slots (oldest
38 * evicted). Before running the detector on a new frame, the valid memory slots
39 * are packed contiguously and fed — together with the current frame features —
40 * through tracker-memory-attention to produce a memory-conditioned embedding
41 * that stabilises the detector features across time.
42 *
43 * NOTE: this class implements IVideoModel (stateful) NOT IModel (stateless). It
44 * MUST be driven by an ordered single-threaded loop (see IVideoModel contract),
45 * never from the shuffled Dataloader. */
47 private:
49
50 public:
51 /* Constructor. Mirrors TensorRTSam3's constructor (Sam3.cpp) but additionally
52 * takes the two tracker engine paths. Engine paths may be empty to indicate
53 * an absent (optional) engine, matching the Sam3.cpp convention for geometry.
54 */
55 TensorRTSam3p1(const std::string &vision_encoder_path,
56 const std::string &text_encoder_path,
57 const std::string &geometry_encoder_path,
58 const std::string &decoder_path,
59 const std::string &memory_encoder_path,
60 const std::string &memory_attention_path,
61 const std::string &tokenizer_path,
62 const std::filesystem::path &meta_path, int gpu_id);
63
64 /* Factory. Discovers the 4 detector engines + tokenizer.json + the 2 tracker
65 * engines (memory-encoder, memory-attention) + optional tracker-meta.json in
66 * model_path. Returns nullptr if any REQUIRED engine is missing (vision,
67 * text, decoder, memory-encoder, memory-attention are required; geometry
68 * optional).
69 * @param model_path: Directory containing the SAM 3.1 engine set.
70 * @return A unique pointer to a TensorRTSam3p1, or nullptr on failure.
71 */
72 static std::unique_ptr<TensorRTSam3p1>
73 create(const std::filesystem::path &model_path);
74
75 /* IVideoModel::reset — clears the memory bank and zeroes the frame counter.
76 * Call at every sequence boundary. */
77 void reset() override;
78
79 /* IVideoModel::step — process a single frame in temporal order. See the
80 * IVideoModel contract for the input/output semantics. */
81 IDataset::Pair step(const IDataset::Pair &in) override;
82
83 protected:
84 /* Loads all TensorRT engines and probes their static shapes (following the
85 * Sam3.cpp load_engines() pattern), then sizes GPU memory. Returns true on
86 * success. */
88
89 static std::string load_bytes_from_file(const std::string &file_path);
90
91 private:
92 // --- Detector stages (batch=1), modelled on Sam3.cpp -----------------------
93
94 // Preprocess the single input frame into preprocessed_images_ (Sam3.cpp
95 // preprocess()). ibatch is always 0 here.
96 void preprocess(const TensorRTData &input, void *stream);
97
98 // Run vision-encoder → fpn_feat_0/1/2 + fpn_pos_2 (Sam3.cpp encode_image()).
99 bool encode_image(void *stream);
100
101 // Run text-encoder for a single concept prompt (Sam3.cpp encode_text(),
102 // simplified to batch=1). Uses the tokenizer cache text_input_map_.
103 bool encode_text(const Sam3PromptUnit *prompt, void *stream);
104
105 // Run decoder on the (memory-conditioned) fpn features (Sam3.cpp decode(),
106 // batch=1, geometry omitted for the video path — text prompts only).
107 bool decode(void *stream);
108
109 // Postprocess decoder outputs into DetectionBoxArray (Sam3.cpp
110 // postprocess()).
111 void postprocess(InferResult &image_result, const std::string &label,
112 int label_id, float confidence_threshold, void *stream);
113
114 // --- Memory bank -----------------------------------------------------------
115
116 // Run tracker-memory-encoder(vision_feat, pred_mask, object_score_logits) →
117 // maskmem_features + maskmem_pos_enc, then append into the ring, evicting the
118 // oldest slot. object_score_logits is derived from the fused detection
119 // scores. The encoder outputs are spatial [C,H,W]; before storing them in a
120 // ring slot they are rearranged to seq-major [H*W,C] so that pack_memory() is
121 // a plain contiguous copy (see rearrange_chw_to_hwc()).
122 void append_memory(float object_score_logits, void *stream);
123
124 // Pack the currently-valid memory slots contiguously into mem_feat_concat_ /
125 // mem_pos_concat_ and fill memory_mask_ (true for valid rows). Slots are
126 // stored already in seq-major [H*W,C], so packing M slots yields the
127 // [M*H*W, 1, C] `memory` tensor via straight D2D copies. Returns the number
128 // of valid memory tokens packed (M * mem_tokens_per_frame_; 0 if bank is
129 // empty).
130 int pack_memory(void *stream);
131
132 // Run tracker-memory-attention(current_feat, current_pos, memory, memory_pos,
133 // memory_mask) → pix_feat_with_mem, overwriting fpn_feat_2_ in-place with the
134 // memory-conditioned embedding (conditioning happens at the 72x72 level =
135 // fpn_feat_2, NOT fpn_feat_0). No-op when the bank is empty.
136 bool apply_memory_attention(void *stream);
137
138 // Build the aggregate foreground mask [1,1,input_h,input_w] fed to the memory
139 // encoder from this frame's per-object detection masks: paste each object's
140 // (already confidence-thresholded) binary mask into the 1008x1008 tracker
141 // input frame (union), then convert to logits. Uploads into mem_pred_mask_.
142 void build_aggregate_mask(const InferResult &results, void *stream);
143
144 // Rearrange a device buffer from spatial [C,H,W] (row-major, the
145 // vision-encoder / memory-encoder layout) to seq-major [H*W,C] (the
146 // memory-attention token layout). Device-side: enqueues the tiled
147 // shared-memory transpose kernel (kernels/transpose.cuh) on `stream` and
148 // returns immediately — no host staging, no synchronisation (#254).
149 void rearrange_chw_to_hwc(float *d_src, float *d_dst, int c, int h, int w,
150 void *stream);
151
152 // Allocate all fixed GPU/CPU buffers once, sized from probed engine shapes
153 // (Sam3.cpp allocate_memory_once()).
154 void allocate_memory_once();
155
156 // set_run_dims wrapper for dynamic engines (Sam3.cpp set_binding_dim()).
157 void set_binding_dim(std::shared_ptr<TensorRT::Engine> &engine,
158 int binding_index, const std::vector<int> &dims);
159
160 private:
161 // Configuration
162 bool isdynamic_model_ = true;
163 int input_image_width_ = 1008;
164 int input_image_height_ = 1008;
165 int gpu_id_ = 0;
166
167 // Detector state (batch fixed to 1)
168 std::pair<int, int> original_image_size_ = {0, 0};
169 int num_queries_ = 200;
170 int mask_height_ = 288;
171 int mask_width_ = 288;
172
173 // Memory-bank sizing. Probed from the tracker engines where possible; the
174 // tracker-meta.json values are used only as fallback / cross-check.
175 // A memory token is SPATIAL: each stored frame is a [C=256,H=72,W=72] feature
176 // grid, i.e. mem_tokens_per_frame_ = feat_h_*feat_w_ = 5184 tokens of dim
177 // mem_dim_ = feat_c_ = 256. Conditioning is at the fpn_feat_2 (72x72) level.
178 int mem_bank_max_ = 7; // number of frames retained in the ring
179 int mem_tokens_per_frame_ = 0; // H*W spatial tokens per stored memory frame
180 int mem_dim_ = 0; // channel dim of each memory token (== feat_c_)
181 int feat_c_ = 0; // current-feat channel dim (fpn_feat_2 == 256)
182 int feat_h_ = 0; // current-feat height (72)
183 int feat_w_ = 0; // current-feat width (72)
184 int multiplex_count_ = 1; // reserved (SAM 3.1 multiplex heads)
185 int frame_counter_ = 0;
186
187 // EXPERIMENTAL. When true, the memory-attention output conditions the
188 // detector's fpn_feat_2 before decoding. Off by default: SAM 3.1's tracker
189 // memory-attention is built to propagate the SAM2-style mask tracker, not to
190 // condition the open-vocab detector's FPN features, and enabling it collapses
191 // detections (validated: coverage 31% -> 0.1%). The memory bank is still
192 // maintained per frame so a future detect-then-associate design can use it.
193 bool use_memory_conditioning_ = false;
194
195 // Model paths
196 std::string vision_encoder_path_;
197 std::string text_encoder_path_;
198 std::string geometry_encoder_path_;
199 std::string decoder_path_;
200 std::string memory_encoder_path_;
201 std::string memory_attention_path_;
202
203 // TRT engines
204 std::shared_ptr<TensorRT::Engine> vision_encoder_trt_;
205 std::shared_ptr<TensorRT::Engine> text_encoder_trt_;
206 std::shared_ptr<TensorRT::Engine> decoder_trt_;
207 std::shared_ptr<TensorRT::Engine> geometry_encoder_trt_;
208 std::shared_ptr<TensorRT::Engine> memory_encoder_trt_;
209 std::shared_ptr<TensorRT::Engine> memory_attention_trt_;
210
211 // Tokenizer cache: text -> (input_ids, attention_mask, class id). Same layout
212 // as Sam3.cpp text_input_map_.
213 std::unordered_map<std::string, std::tuple<std::array<int64_t, 32>,
214 std::array<int64_t, 32>, int>>
215 text_input_map_;
216
217 // Preprocess normalisation (identical to Sam3.cpp).
219 1.0f / 127.5f, -1.0f, norm_image::ChannelType::SwapRB);
220
221 // Probed shapes
222 std::vector<int> vision_input_shape_;
223 std::vector<int> fpn_feat_0_shape_;
224 std::vector<int> text_ids_shape_;
225
226 // Image buffers (batch=1)
227 tensor::Memory<float> preprocessed_images_;
228 std::shared_ptr<tensor::Memory<uint8_t>> original_image_buf_;
229 tensor::Memory<float> affine_matrix_;
230 tensor::Memory<float> mask_affine_matrix_;
231
232 // Vision encoder outputs. fpn_feat_2_ (the 72x72 top level) is overwritten in
233 // place by apply_memory_attention() to carry the memory-conditioned
234 // embedding; fpn_feat_0_/fpn_feat_1_/fpn_pos_2_ are passed to the decoder
235 // unchanged.
236 tensor::Memory<float> fpn_feat_0_;
237 tensor::Memory<float> fpn_feat_1_;
238 tensor::Memory<float> fpn_feat_2_;
239 tensor::Memory<float> fpn_pos_2_;
240
241 // Text prompt inputs / features (batch=1)
242 tensor::Memory<int64_t> text_input_ids_;
243 tensor::Memory<int64_t> text_attention_mask_;
244 tensor::Memory<float> text_features_;
245 tensor::Memory<bool> text_mask_;
246
247 // Decoder prompt inputs (text-only for the video path)
248 tensor::Memory<float> prompt_features_;
249 tensor::Memory<bool> prompt_mask_;
250
251 // Decoder outputs (batch=1)
252 tensor::Memory<float> pred_masks_;
253 tensor::Memory<float> pred_boxes_;
254 tensor::Memory<float> pred_logits_;
255 tensor::Memory<float> presence_logits_;
256
257 // Postprocess buffers (batch=1)
258 tensor::Memory<float> filter_boxes_;
259 tensor::Memory<float> filter_scores_;
260 tensor::Memory<int> filter_indices_;
261 tensor::Memory<int> box_count_;
262 tensor::Memory<uint8_t> mask_buffer_;
263 tensor::Memory<float> box_affine_matrices_;
264
265 // --- Memory-bank buffers ---------------------------------------------------
266
267 // Aggregate foreground mask fed to the memory encoder, at the FULL tracker
268 // input resolution [K=1,1,1008,1008] (the contract's pred_mask). Built each
269 // frame from the detector's per-object masks (thresholded union, upsampled).
270 tensor::Memory<float> mem_pred_mask_;
271 // Host staging for the aggregate mask before the H2D upload.
272 tensor::Memory<float> mem_pred_mask_host_;
273 // object_score_logits scalar input to the memory encoder [K=1,1].
274 tensor::Memory<float> mem_obj_score_;
275
276 // Per-frame memory-encoder outputs (one frame), SPATIAL [C=256,H=72,W=72],
277 // before being rearranged to seq-major [H*W,C] and copied into a ring slot.
278 tensor::Memory<float> maskmem_features_;
279 tensor::Memory<float> maskmem_pos_enc_;
280
281 // Ring of pre-allocated slots. Each slot holds one frame's maskmem feature
282 // and pos buffers, both stored in SEQ-MAJOR [H*W, C] =
283 // [mem_tokens_per_frame_, mem_dim_] layout so pack_memory() is a plain
284 // contiguous D2D copy into the [M,1,C] memory tensor.
285 struct MemorySlot {
286 tensor::Memory<float> feature;
288 };
289 std::vector<MemorySlot> mem_slots_;
290 // Indices of valid slots in chronological order (front = oldest, back =
291 // newest). append_memory() rotates the newest slot in and evicts the front.
292 std::deque<int> mem_valid_;
293 // Next slot index to (re)use as the ring rotates.
294 int mem_next_slot_ = 0;
295
296 // current_feat / current_pos for memory-attention, seq-major [H*W,1,C] =
297 // [5184,1,256], derived by rearranging fpn_feat_2_ / fpn_pos_2_ from [C,H,W].
298 tensor::Memory<float> current_feat_;
299 tensor::Memory<float> current_pos_;
300
301 // Contiguous packing of the valid slots consumed by memory-attention, plus
302 // the bool key-padding mask. `memory`/`memory_pos` are seq-major [M,1,C] with
303 // M = mem_bank_max_ * mem_tokens_per_frame_ rows max; memory_mask_ is [1,M].
304 // NOTE: the RoPE encoder ignores memory_mask (no key-padding path), so we
305 // pack exactly the valid slots contiguously and pass an all-true mask.
306 tensor::Memory<float> mem_feat_concat_;
307 tensor::Memory<float> mem_pos_concat_;
308 tensor::Memory<bool> memory_mask_;
309
310 // Memory-attention output (memory-conditioned current-frame embedding),
311 // spatial [C,H,W] = [256,72,72]. Copied back into fpn_feat_2_.
312 tensor::Memory<float> pix_feat_with_mem_;
313
314 // Tokenizer
315 std::unique_ptr<tokenizers::Tokenizer> tokenizer_;
316};
317
318} // namespace reusex::vision::tensor_rt
std::pair< std::unique_ptr< IData >, size_t > Pair
Definition IDataset.hpp:44
static std::string load_bytes_from_file(const std::string &file_path)
TensorRTSam3p1(const std::string &vision_encoder_path, const std::string &text_encoder_path, const std::string &geometry_encoder_path, const std::string &decoder_path, const std::string &memory_encoder_path, const std::string &memory_attention_path, const std::string &tokenizer_path, const std::filesystem::path &meta_path, int gpu_id)
static std::unique_ptr< TensorRTSam3p1 > create(const std::filesystem::path &model_path)
IDataset::Pair step(const IDataset::Pair &in) override
std::vector< DetectionBox > DetectionBoxArray
Convenience alias for a collection of DetectionBox results.
Definition object.hpp:216
static Norm alpha_beta(float alpha, float beta=0, ChannelType channel_type=ChannelType::None)