Skip to content

Commit 5e17f3b

Browse files
committed
cli: add configurable model install path and search models in common locations
1 parent 74e4319 commit 5e17f3b

File tree

4 files changed

+99
-43
lines changed

4 files changed

+99
-43
lines changed

CMakeLists.txt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
cmake_minimum_required(VERSION 3.28)
22

3-
project(vision.cpp VERSION 0.3.0 LANGUAGES CXX)
3+
project(vision.cpp VERSION 0.3.1 LANGUAGES CXX)
44

55
option(BUILD_SHARED_LIBS "Build shared libraries instead of static libraries" ON)
66
option(VISP_VULKAN "Enable Vulkan support" OFF)
77
option(VISP_DEV "Enable development mode" OFF)
88
option(VISP_CI "Enable for continuous integration environment" OFF)
99
option(VISP_TESTS "Build tests" ${PROJECT_IS_TOP_LEVEL})
10+
option(VISP_INSTALL_MODELS "Download and install default models" OFF)
1011
option(VISP_FMT_LIB "Use external fmt library instead of standard C++ <format>" OFF)
1112
option(VISP_ASAN "Enable AddressSanitizer" OFF)
1213

14+
include(GNUInstallDirs)
15+
set(VISP_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Location of header files")
16+
set(VISP_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Location of library files")
17+
set(VISP_BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Location of binary files")
18+
if(VISP_INSTALL_MODELS)
19+
set(VISP_MODEL_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/visioncpp" CACHE PATH "Directory to install default models to")
20+
endif()
21+
1322
if(PROJECT_IS_TOP_LEVEL)
1423
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1524
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
@@ -121,15 +130,13 @@ add_subdirectory(src/cli)
121130
if(VISP_TESTS)
122131
enable_testing()
123132
add_subdirectory(tests)
133+
endif()
134+
if(VISP_TESTS OR VISP_INSTALL_MODELS)
124135
add_subdirectory(models)
125136
endif()
126137

127138
# Installation
128139

129-
set(VISP_INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "Location of header files")
130-
set(VISP_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Location of library files")
131-
set(VISP_BIN_INSTALL_DIR ${CMAKE_INSTALL_BINDIR} CACHE PATH "Location of binary files")
132-
133140
install(TARGETS visioncpp
134141
RUNTIME DESTINATION ${VISP_BIN_INSTALL_DIR}
135142
LIBRARY DESTINATION ${VISP_LIB_INSTALL_DIR}
@@ -138,6 +145,9 @@ install(DIRECTORY include/ DESTINATION ${VISP_INCLUDE_INSTALL_DIR})
138145
if(PROJECT_IS_TOP_LEVEL)
139146
install(FILES README.md LICENSE DESTINATION .)
140147
endif()
148+
if(VISP_INSTALL_MODELS)
149+
install(FILES ${VISP_DEFAULT_MODELS} DESTINATION ${VISP_MODEL_INSTALL_DIR})
150+
endif()
141151

142152
install(TARGETS vision-cli RUNTIME DESTINATION ${VISP_BIN_INSTALL_DIR})
143153

models/CMakeLists.txt

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
1-
# Download models used in tests (happens only if VISP_TESTS is enabled)
1+
# Download models used in tests or for installation
22

3-
message(STATUS "Checking for models/MobileSAM-F16.gguf")
4-
file(DOWNLOAD
3+
function(download_model url filename hash)
4+
message(STATUS "Checking for ${filename}")
5+
file(DOWNLOAD
6+
${url}
7+
${CMAKE_CURRENT_LIST_DIR}/${filename}
8+
EXPECTED_HASH ${hash}
9+
SHOW_PROGRESS
10+
)
11+
set(DEFAULT_MODELS "${DEFAULT_MODELS};${CMAKE_CURRENT_LIST_DIR}/${filename}" PARENT_SCOPE)
12+
endfunction()
13+
14+
15+
download_model(
516
"https://huggingface.co/Acly/MobileSAM-GGUF/resolve/main/MobileSAM-F16.gguf"
6-
${CMAKE_CURRENT_LIST_DIR}/MobileSAM-F16.gguf
7-
EXPECTED_HASH "SHA256=b546366475e3ad744bb2eaf7634df88e9aaf25f6622797d2de300f5a530831f7"
8-
SHOW_PROGRESS
17+
"MobileSAM-F16.gguf"
18+
"SHA256=b546366475e3ad744bb2eaf7634df88e9aaf25f6622797d2de300f5a530831f7"
919
)
10-
message(STATUS "Checking for models/BiRefNet-lite-F16.gguf")
11-
file(DOWNLOAD
20+
download_model(
1221
"https://huggingface.co/Acly/BiRefNet-GGUF/resolve/main/BiRefNet-lite-F16.gguf"
13-
${CMAKE_CURRENT_LIST_DIR}/BiRefNet-lite-F16.gguf
14-
EXPECTED_HASH "SHA256=7b5397a2c98d66677f8f74317774bbeac49dbb321b8a3dc744af913db71d4fa5"
15-
SHOW_PROGRESS
22+
"BiRefNet-lite-F16.gguf"
23+
"SHA256=7b5397a2c98d66677f8f74317774bbeac49dbb321b8a3dc744af913db71d4fa5"
1624
)
17-
message(STATUS "Checking for models/Depth-Anything-V2-Small-F16.gguf")
18-
file(DOWNLOAD
25+
download_model(
1926
"https://huggingface.co/Acly/Depth-Anything-V2-GGUF/resolve/main/Depth-Anything-V2-Small-F16.gguf"
20-
${CMAKE_CURRENT_LIST_DIR}/Depth-Anything-V2-Small-F16.gguf
21-
EXPECTED_HASH "SHA256=0f83332d6a8b4375cd7fdcc168f3e3636f474f8e84b0959e903f513aace782f5"
22-
SHOW_PROGRESS
27+
"Depth-Anything-V2-Small-F16.gguf"
28+
"SHA256=0f83332d6a8b4375cd7fdcc168f3e3636f474f8e84b0959e903f513aace782f5"
2329
)
24-
message(STATUS "Checking for models/MIGAN-512-places2-F16.gguf")
25-
file(DOWNLOAD
30+
download_model(
2631
"https://huggingface.co/Acly/MIGAN-GGUF/resolve/main/MIGAN-512-places2-F16.gguf"
27-
${CMAKE_CURRENT_LIST_DIR}/MIGAN-512-places2-F16.gguf
28-
EXPECTED_HASH "SHA256=3e47592bf716d0dc306f8dc02d4476cfcdaf2c055fa3c3c8e0ced4db775eb64b"
29-
SHOW_PROGRESS
32+
"MIGAN-512-places2-F16.gguf"
33+
"SHA256=3e47592bf716d0dc306f8dc02d4476cfcdaf2c055fa3c3c8e0ced4db775eb64b"
3034
)
31-
message(STATUS "Checking for models/RealESRGAN-x4plus_anime-6B-F16.gguf")
32-
file(DOWNLOAD
33-
"https://huggingface.co/Acly/Real-ESRGAN-GGUF/resolve/main/RealESRGAN-x4plus_anime-6B-F16.gguf"
34-
${CMAKE_CURRENT_LIST_DIR}/RealESRGAN-x4plus_anime-6B-F16.gguf
35-
EXPECTED_HASH "SHA256=730469c5a2269cdef96d0d58aacf87bcf25d7a0d92256685808e6cdce0675c09"
36-
SHOW_PROGRESS
37-
)
35+
if(VISP_TEST)
36+
download_model(
37+
"https://huggingface.co/Acly/Real-ESRGAN-GGUF/resolve/main/RealESRGAN-x4plus_anime-6B-F16.gguf"
38+
"RealESRGAN-x4plus_anime-6B-F16.gguf"
39+
"SHA256=730469c5a2269cdef96d0d58aacf87bcf25d7a0d92256685808e6cdce0675c09"
40+
)
41+
endif()
42+
43+
set(VISP_DEFAULT_MODELS "${DEFAULT_MODELS}" PARENT_SCOPE)

src/cli/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
add_executable(vision-cli)
22
target_sources(vision-cli PRIVATE cli.cpp)
33
target_include_directories(vision-cli PRIVATE ..)
4-
target_compile_definitions(vision-cli PRIVATE ${VISP_ASSERT} ${VISP_DEFINITIONS})
4+
target_compile_definitions(vision-cli PRIVATE
5+
${VISP_ASSERT}
6+
${VISP_DEFINITIONS}
7+
VISP_MODEL_INSTALL_DIR="${VISP_MODEL_INSTALL_DIR}")
58
target_compile_options(vision-cli PRIVATE ${VISP_WARNINGS} ${VISP_COMP_OPTIONS})
69
target_link_options(vision-cli PRIVATE ${VISP_LINK_OPTIONS})
710
target_link_libraries(vision-cli PRIVATE ggml visioncpp ${VISP_FMT_LINK})

src/cli/cli.cpp

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ cli_args cli_parse(int argc, char** argv) {
140140
} else if (arg == "-o" || arg == "--output") {
141141
r.output = next_arg(argc, argv, i);
142142
} else if (arg == "-m" || arg == "--model") {
143-
r.model = validate_path(next_arg(argc, argv, i));
143+
r.model = next_arg(argc, argv, i);
144144
} else if (arg == "-p" || arg == "--prompt") {
145145
r.prompt = collect_args(argc, argv, i, '-');
146146
} else if (arg == "-b" || arg == "--backend") {
@@ -245,6 +245,42 @@ char const* to_string(tensor_data_layout l) {
245245
}
246246
}
247247

248+
path find_model(char const* model_name_or_path) {
249+
path p = path(model_name_or_path);
250+
if (exists(p) || p.is_absolute()) {
251+
return p;
252+
}
253+
path search_paths[5];
254+
search_paths[0] = path("models");
255+
if (char const* vision_model_dir = getenv("VISION_MODEL_DIR")) {
256+
search_paths[1] = path(vision_model_dir);
257+
}
258+
if (char const* xdg_data_home = getenv("XDG_DATA_HOME")) {
259+
search_paths[2] = path(xdg_data_home) / "visioncpp";
260+
}
261+
if (char const* home = getenv("HOME")) {
262+
search_paths[3] = path(home) / ".local/share/visioncpp";
263+
}
264+
if constexpr (VISP_MODEL_INSTALL_DIR[0] != '\0') {
265+
search_paths[4] = path(VISP_MODEL_INSTALL_DIR);
266+
}
267+
for (auto& sp : search_paths) {
268+
if (!sp.empty()) {
269+
path candidate = sp / p;
270+
if (exists(candidate)) {
271+
return candidate;
272+
}
273+
}
274+
}
275+
printf("Looking for %s\n", p.generic_string().c_str());
276+
for (auto& sp : search_paths) {
277+
if (!sp.empty()) {
278+
printf("Looking for %s\n", (sp / p).generic_string().c_str());
279+
}
280+
}
281+
throw except("Model file not found: {}", model_name_or_path);
282+
}
283+
248284
std::tuple<model_file, model_weights> load_model_weights(
249285
cli_args const& args,
250286
backend_device const& dev,
@@ -253,10 +289,11 @@ std::tuple<model_file, model_weights> load_model_weights(
253289
tensor_data_layout preferred_layout = tensor_data_layout::unknown) {
254290

255291
timer t;
256-
char const* model_path = args.model ? args.model : default_model;
257-
printf("Loading model weights from '%s'... ", model_path);
292+
path model_path = find_model(args.model ? args.model : default_model);
293+
auto model_path_str = model_path.generic_string();
294+
printf("Loading model weights from '%s'... ", model_path_str.c_str());
258295

259-
model_file file = model_load(model_path);
296+
model_file file = model_load(model_path_str.c_str());
260297
model_weights weights = model_init(file.n_tensors() + n_tensors);
261298
if (preferred_layout == tensor_data_layout::unknown) {
262299
preferred_layout = file.tensor_layout();
@@ -355,7 +392,7 @@ sam_prompt sam_parse_prompt(std::span<char const* const> args, i32x2 extent) {
355392
void run_sam(cli_args const& args) {
356393
backend_device backend = backend_init(args);
357394
auto [file, weights] = load_model_weights(
358-
args, backend, "models/MobileSAM-F16.gguf", 0, backend.preferred_layout());
395+
args, backend, "MobileSAM-F16.gguf", 0, backend.preferred_layout());
359396
sam_params params{};
360397

361398
require_inputs(args.inputs, 1, "<image>");
@@ -409,7 +446,7 @@ void run_sam(cli_args const& args) {
409446
void run_birefnet(cli_args const& args) {
410447
backend_device backend = backend_init(args);
411448
auto [file, weights] = load_model_weights(
412-
args, backend, "models/BiRefNet-F16.gguf", 0, backend.preferred_layout());
449+
args, backend, "BiRefNet-lite-F16.gguf", 0, backend.preferred_layout());
413450

414451
require_inputs(args.inputs, 1, "<image>");
415452
image_data image = image_load(args.inputs[0]);
@@ -453,7 +490,7 @@ void run_birefnet(cli_args const& args) {
453490
void run_depth_anything(cli_args const& args) {
454491
backend_device backend = backend_init(args);
455492
auto [file, weights] = load_model_weights(
456-
args, backend, "models/DepthAnythingV2-Small-F32.gguf", 0, backend.preferred_layout());
493+
args, backend, "DepthAnythingV2-Small-F32.gguf", 0, backend.preferred_layout());
457494

458495
require_inputs(args.inputs, 1, "<image>");
459496
image_data image = image_load(args.inputs[0]);
@@ -489,7 +526,7 @@ void run_depth_anything(cli_args const& args) {
489526
void run_migan(cli_args const& args) {
490527
backend_device backend = backend_init(args);
491528
auto [file, weights] = load_model_weights(
492-
args, backend, "models/MIGAN-512-places2-F16.gguf", backend.preferred_layout());
529+
args, backend, "MIGAN-512-places2-F16.gguf", backend.preferred_layout());
493530
migan_params params = migan_detect_params(file);
494531
params.invert_mask = true; // -> inpaint opaque areas
495532

@@ -527,7 +564,7 @@ void run_migan(cli_args const& args) {
527564
void run_esrgan(cli_args const& args) {
528565
backend_device backend = backend_init(args);
529566
auto [file, weights] = load_model_weights(
530-
args, backend, "models/RealESRGAN-x4.gguf", 0, backend.preferred_layout());
567+
args, backend, "RealESRGAN-x4.gguf", 0, backend.preferred_layout());
531568
esrgan_params params = esrgan_detect_params(file);
532569
printf("- scale: %dx\n", params.scale);
533570
printf("- block count: %d\n", params.n_blocks);

0 commit comments

Comments
 (0)