Skip to content

Commit 8f6c79d

Browse files
PABannierclaude
andcommitted
fix: MSVC compatibility for Windows CI
- Define _USE_MATH_DEFINES for M_PI, add _popen/_pclose/_mkdir shims - Port benchmark.cpp directory listing to FindFirstFile on Windows - Run benchmarks in-process on Windows (no fork/pipe crash isolation) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dcc7004 commit 8f6c79d

2 files changed

Lines changed: 101 additions & 27 deletions

File tree

examples/benchmark.cpp

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
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]
@@ -32,10 +33,15 @@
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 {
130136
static 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

275318
static 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, "\nStarting %zu benchmark runs (in-process)...\n\n", runs.size());
543+
#else
482544
fprintf(stderr, "\nStarting %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();

sam3.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#define _USE_MATH_DEFINES
2+
13
#include "sam3.h"
24

35
/* ggml */
@@ -14,7 +16,16 @@
1416
#define STB_IMAGE_IMPLEMENTATION
1517
#include "stb_image.h"
1618
#define STB_IMAGE_WRITE_IMPLEMENTATION
19+
20+
#ifdef _WIN32
21+
#include <direct.h>
22+
#include <io.h>
23+
#define popen _popen
24+
#define pclose _pclose
25+
#define mkdir(path, mode) _mkdir(path)
26+
#else
1727
#include <sys/stat.h>
28+
#endif
1829

1930
/* C++ standard library */
2031
#include <algorithm>

0 commit comments

Comments
 (0)