44 * Tracks an object (point prompt) across N video frames on CPU and Metal,
55 * then prints a formatted comparison table.
66 *
7- * Each model × backend run is isolated in a forked subprocess so that a
8- * crash (e.g. unsupported Metal op) does not kill the entire benchmark.
7+ * On POSIX systems each model × backend run is isolated in a forked subprocess
8+ * so that a crash (e.g. unsupported Metal op) does not kill the entire benchmark.
9+ * On Windows, benchmarks run in-process (no crash isolation).
910 *
1011 * Usage:
1112 * sam3_benchmark [options]
3233#include < string>
3334#include < vector>
3435
36+ #ifdef _WIN32
37+ #include < windows.h>
38+ #include < sys/stat.h>
39+ #else
3540#include < dirent.h>
3641#include < sys/stat.h>
3742#include < sys/wait.h>
3843#include < unistd.h>
44+ #endif
3945
4046// ── Wire format for child→parent result ─────────────────────────────────────
4147
@@ -130,6 +136,33 @@ struct ModelEntry {
130136static std::vector<ModelEntry> discover_models (const std::string & dir,
131137 const std::string & filter) {
132138 std::vector<ModelEntry> entries;
139+
140+ #ifdef _WIN32
141+ std::string pattern = dir + " \\ *.ggml" ;
142+ WIN32_FIND_DATAA fd;
143+ HANDLE hFind = FindFirstFileA (pattern.c_str (), &fd);
144+ if (hFind == INVALID_HANDLE_VALUE) {
145+ fprintf (stderr, " ERROR: cannot open models directory '%s'\n " , dir.c_str ());
146+ return entries;
147+ }
148+ do {
149+ if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue ;
150+ std::string fname = fd.cFileName ;
151+ if (!ends_with (fname, " .ggml" )) continue ;
152+ if (!filter.empty () && fname.find (filter) == std::string::npos) continue ;
153+
154+ std::string full_path = dir + " \\ " + fname;
155+ struct _stat st;
156+ if (_stat (full_path.c_str (), &st) != 0 ) continue ;
157+
158+ ModelEntry e;
159+ e.path = full_path;
160+ e.name = strip_extension (fname);
161+ e.file_size = st.st_size ;
162+ entries.push_back (e);
163+ } while (FindNextFileA (hFind, &fd));
164+ FindClose (hFind);
165+ #else
133166 DIR * d = opendir (dir.c_str ());
134167 if (!d) {
135168 fprintf (stderr, " ERROR: cannot open models directory '%s'\n " , dir.c_str ());
@@ -152,42 +185,35 @@ static std::vector<ModelEntry> discover_models(const std::string & dir,
152185 entries.push_back (e);
153186 }
154187 closedir (d);
188+ #endif
155189
156190 std::sort (entries.begin (), entries.end (), [](const ModelEntry & a, const ModelEntry & b) {
157191 return model_sort_key (a.name ) < model_sort_key (b.name );
158192 });
159193 return entries;
160194}
161195
162- // ── Child process: run a single benchmark ─ ──────────────────────────────────
196+ // ── Run a single benchmark (shared logic) ──────────────────────────────────
163197
164- static void child_benchmark (const std::string & model_path,
165- bool use_gpu,
166- const std::string & video_path,
167- int n_frames,
168- float px, float py,
169- int n_threads,
170- int encode_img_size,
171- int write_fd) {
198+ static BenchWire run_single_benchmark (const std::string & model_path,
199+ bool use_gpu,
200+ const std::string & video_path,
201+ int n_frames,
202+ float px, float py,
203+ int n_threads,
204+ int encode_img_size) {
172205 BenchWire wire = {};
173206
174- auto write_result = [&]() {
175- (void )write (write_fd, &wire, sizeof (wire));
176- close (write_fd);
177- };
178-
179207 auto fail = [&](const char * msg) {
180208 wire.ok = 0 ;
181209 snprintf (wire.error , sizeof (wire.error ), " %s" , msg);
182- write_result ();
183- _exit (1 );
184210 };
185211
186212 // Decode frames
187213 std::vector<sam3_image> frames (n_frames);
188214 for (int f = 0 ; f < n_frames; f++) {
189215 frames[f] = sam3_decode_video_frame (video_path, f);
190- if (frames[f].data .empty ()) { fail (" decode frame failed" ); return ; }
216+ if (frames[f].data .empty ()) { fail (" decode frame failed" ); return wire ; }
191217 }
192218
193219 // Load model
@@ -200,10 +226,10 @@ static void child_benchmark(const std::string & model_path,
200226 params.encode_img_size = encode_img_size;
201227
202228 auto model = sam3_load_model (params);
203- if (!model) { fail (" load failed" ); return ; }
229+ if (!model) { fail (" load failed" ); return wire ; }
204230
205231 auto state = sam3_create_state (*model, params);
206- if (!state) { fail (" state failed" ); return ; }
232+ if (!state) { fail (" state failed" ); return wire ; }
207233
208234 bool visual_only = sam3_is_visual_only (*model);
209235 sam3_tracker_ptr tracker;
@@ -219,23 +245,23 @@ static void child_benchmark(const std::string & model_path,
219245 vp.max_keep_alive = 100 ;
220246 tracker = sam3_create_tracker (*model, vp);
221247 }
222- if (!tracker) { fail (" tracker failed" ); return ; }
248+ if (!tracker) { fail (" tracker failed" ); return wire ; }
223249
224250 wire.t_load_ms = (ggml_time_us () - t0) / 1000.0 ;
225251
226252 // Frame 0: encode + add instance
227253 t0 = ggml_time_us ();
228254
229255 if (!sam3_encode_image (*state, *model, frames[0 ])) {
230- fail (" encode f0 failed" ); return ;
256+ fail (" encode f0 failed" ); return wire ;
231257 }
232258
233259 sam3_pvs_params pvs;
234260 pvs.pos_points .push_back ({px, py});
235261 pvs.multimask = false ;
236262
237263 int inst_id = sam3_tracker_add_instance (*tracker, *state, *model, pvs);
238- if (inst_id < 0 ) { fail (" add_instance failed" ); return ; }
264+ if (inst_id < 0 ) { fail (" add_instance failed" ); return wire ; }
239265
240266 wire.t_frame0_ms = (ggml_time_us () - t0) / 1000.0 ;
241267
@@ -266,11 +292,28 @@ static void child_benchmark(const std::string & model_path,
266292 wire.n_detections = (int )last_result.detections .size ();
267293 wire.ok = 1 ;
268294
269- write_result ();
270- _exit (0 );
295+ return wire;
271296}
272297
273- // ── Parent: launch child and collect result ─────────────────────────────────
298+ // ── Isolated benchmark execution ───────────────────────────────────────────
299+
300+ #ifndef _WIN32
301+ // POSIX: fork a subprocess for crash isolation
302+ static void child_benchmark (const std::string & model_path,
303+ bool use_gpu,
304+ const std::string & video_path,
305+ int n_frames,
306+ float px, float py,
307+ int n_threads,
308+ int encode_img_size,
309+ int write_fd) {
310+ BenchWire wire = run_single_benchmark (model_path, use_gpu, video_path,
311+ n_frames, px, py, n_threads, encode_img_size);
312+ (void )write (write_fd, &wire, sizeof (wire));
313+ close (write_fd);
314+ _exit (wire.ok ? 0 : 1 );
315+ }
316+ #endif
274317
275318static BenchResult run_benchmark_isolated (const ModelEntry & entry,
276319 bool use_gpu,
@@ -284,6 +327,21 @@ static BenchResult run_benchmark_isolated(const ModelEntry & entry,
284327 res.backend = use_gpu ? " Metal" : " CPU" ;
285328 res.file_size = entry.file_size ;
286329
330+ #ifdef _WIN32
331+ // Windows: run in-process (no crash isolation)
332+ BenchWire wire = run_single_benchmark (entry.path , use_gpu, video_path,
333+ n_frames, px, py, n_threads, encode_img_size);
334+ if (wire.ok ) {
335+ res.t_load_ms = wire.t_load_ms ;
336+ res.t_frame0_ms = wire.t_frame0_ms ;
337+ res.t_track_avg_ms = wire.t_track_avg_ms ;
338+ res.t_total_ms = wire.t_total_ms ;
339+ res.n_detections = wire.n_detections ;
340+ res.success = true ;
341+ } else {
342+ res.error = wire.error ;
343+ }
344+ #else
287345 int pipefd[2 ];
288346 if (pipe (pipefd) != 0 ) {
289347 res.error = " pipe() failed" ;
@@ -332,6 +390,7 @@ static BenchResult run_benchmark_isolated(const ModelEntry & entry,
332390 } else {
333391 res.error = " child failed (no result)" ;
334392 }
393+ #endif
335394
336395 return res;
337396}
@@ -479,7 +538,11 @@ int main(int argc, char ** argv) {
479538 return a.use_gpu > b.use_gpu ; // Metal first
480539 });
481540
541+ #ifdef _WIN32
542+ fprintf (stderr, " \n Starting %zu benchmark runs (in-process)...\n\n " , runs.size ());
543+ #else
482544 fprintf (stderr, " \n Starting %zu benchmark runs (each in a subprocess)...\n\n " , runs.size ());
545+ #endif
483546
484547 // Run benchmarks
485548 int64_t t_wall_start = ggml_time_us ();
0 commit comments