diff --git a/ggml/src/ggml-hrx/CMakeLists.txt b/ggml/src/ggml-hrx/CMakeLists.txt index 5ad6e6545b82..d54e6d1f9b4b 100644 --- a/ggml/src/ggml-hrx/CMakeLists.txt +++ b/ggml/src/ggml-hrx/CMakeLists.txt @@ -13,6 +13,13 @@ if(HRX_SOURCE_DIR) set(GGML_HRX_BUILD_DIR "${GGML_HRX_PREFIX}/src/ggml-hrx-deps-build") set(GGML_HRX_LIB "${GGML_HRX_BUILD_DIR}/libhrx/src/libhrx/${CMAKE_SHARED_LIBRARY_PREFIX}hrx${CMAKE_SHARED_LIBRARY_SUFFIX}") set(GGML_LOOMC_LIB "${GGML_HRX_BUILD_DIR}/loom/binding/c/${CMAKE_SHARED_LIBRARY_PREFIX}loomc${CMAKE_SHARED_LIBRARY_SUFFIX}") + if(WIN32) + # On Windows, linking against a DLL requires its companion import + # library (.lib), which CMake exposes as IMPORTED_IMPLIB. Both files + # are produced side-by-side with the DLL by the nested build. + set(GGML_HRX_LIB_IMPLIB "${GGML_HRX_BUILD_DIR}/libhrx/src/libhrx/${CMAKE_IMPORT_LIBRARY_PREFIX}hrx${CMAKE_IMPORT_LIBRARY_SUFFIX}") + set(GGML_LOOMC_LIB_IMPLIB "${GGML_HRX_BUILD_DIR}/loom/binding/c/${CMAKE_IMPORT_LIBRARY_PREFIX}loomc${CMAKE_IMPORT_LIBRARY_SUFFIX}") + endif() set(GGML_HRX_LOOM_LINK "${GGML_HRX_BUILD_DIR}/loom/src/loom/tools/loom-link/loom-link${CMAKE_EXECUTABLE_SUFFIX}") set(GGML_HRX_LOOM_FORMAT "${GGML_HRX_BUILD_DIR}/loom/src/loom/tools/loom-format/loom-format${CMAKE_EXECUTABLE_SUFFIX}") set(GGML_HRX_IREE_BENCHMARK_LOOM "${GGML_HRX_BUILD_DIR}/loom/src/loom/tools/iree-benchmark-loom/iree-benchmark-loom${CMAKE_EXECUTABLE_SUFFIX}") @@ -34,6 +41,9 @@ if(HRX_SOURCE_DIR) if(IREE_ROCM_PATH) list(APPEND GGML_HRX_CMAKE_ARGS -DIREE_ROCM_PATH=${IREE_ROCM_PATH}) endif() + if(IREE_ROCM_DEPENDENCY_MODE) + list(APPEND GGML_HRX_CMAKE_ARGS -DIREE_ROCM_DEPENDENCY_MODE=${IREE_ROCM_DEPENDENCY_MODE}) + endif() if(FETCHCONTENT_BASE_DIR) list(APPEND GGML_HRX_CMAKE_ARGS -DFETCHCONTENT_BASE_DIR=${FETCHCONTENT_BASE_DIR}) endif() @@ -41,11 +51,12 @@ if(HRX_SOURCE_DIR) ExternalProject_Add(ggml-hrx-deps SOURCE_DIR "${HRX_SOURCE_DIR}" PREFIX "${GGML_HRX_PREFIX}" + BINARY_DIR "${GGML_HRX_BUILD_DIR}" CMAKE_ARGS ${GGML_HRX_CMAKE_ARGS} BUILD_ALWAYS TRUE BUILD_COMMAND ${CMAKE_COMMAND} --build . --target hrx loomc_shared loom_tools_loom-link_loom-link loom_tools_loom-format_loom-format loom_tools_iree-benchmark-loom_iree-benchmark-loom --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND "" - BUILD_BYPRODUCTS "${GGML_HRX_LIB}" "${GGML_LOOMC_LIB}" "${GGML_HRX_LOOM_LINK}" "${GGML_HRX_LOOM_FORMAT}" "${GGML_HRX_IREE_BENCHMARK_LOOM}" + BUILD_BYPRODUCTS "${GGML_HRX_LIB}" "${GGML_LOOMC_LIB}" "${GGML_HRX_LIB_IMPLIB}" "${GGML_LOOMC_LIB_IMPLIB}" "${GGML_HRX_LOOM_LINK}" "${GGML_HRX_LOOM_FORMAT}" "${GGML_HRX_IREE_BENCHMARK_LOOM}" UPDATE_COMMAND "" ) @@ -53,6 +64,9 @@ if(HRX_SOURCE_DIR) set_target_properties(hrx::hrx PROPERTIES IMPORTED_LOCATION "${GGML_HRX_LIB}" INTERFACE_INCLUDE_DIRECTORIES "${HRX_SOURCE_DIR}/libhrx/include") + if(WIN32) + set_target_properties(hrx::hrx PROPERTIES IMPORTED_IMPLIB "${GGML_HRX_LIB_IMPLIB}") + endif() add_dependencies(hrx::hrx ggml-hrx-deps) add_library(loomc::loomc SHARED IMPORTED GLOBAL) @@ -60,7 +74,22 @@ if(HRX_SOURCE_DIR) IMPORTED_LOCATION "${GGML_LOOMC_LIB}" INTERFACE_INCLUDE_DIRECTORIES "${HRX_SOURCE_DIR}/loom/binding/c/include" INTERFACE_COMPILE_DEFINITIONS LOOMC_USING_SHARED_LIBRARY) + if(WIN32) + set_target_properties(loomc::loomc PROPERTIES IMPORTED_IMPLIB "${GGML_LOOMC_LIB_IMPLIB}") + endif() add_dependencies(loomc::loomc ggml-hrx-deps) + + # hrx::hrx/loomc::loomc are IMPORTED targets pointing at the nested + # ExternalProject build, so `install(TARGETS ...)` can't be used for them + # (CMake doesn't support installing IMPORTED targets). Install the runtime + # libraries directly so `cmake --install` produces a working tree; on + # Linux/macOS this mirrors GGML_HRX_BUNDLE_RUNTIME_LIBS's minimum + # requirement but doesn't depend on that Linux-only opt-in feature. + if(WIN32) + install(FILES "${GGML_HRX_LIB}" "${GGML_LOOMC_LIB}" DESTINATION "${CMAKE_INSTALL_BINDIR}") + else() + install(FILES "${GGML_HRX_LIB}" "${GGML_LOOMC_LIB}" DESTINATION "${CMAKE_INSTALL_LIBDIR}") + endif() else() find_package(hrx CONFIG REQUIRED) find_package(loomc CONFIG REQUIRED) @@ -267,6 +296,29 @@ ggml_add_backend_library(ggml-hrx target_link_libraries(ggml-hrx PRIVATE ggml-hrx-kernel-corpus hrx::hrx loomc::loomc) target_include_directories(ggml-hrx PRIVATE . "${CMAKE_CURRENT_BINARY_DIR}" ../../../vendor) target_compile_definitions(ggml-hrx PRIVATE GGML_USE_HRX) +if(WIN32) + # Unlike ELF/Mach-O shared libraries, Windows DLLs export no symbols by + # default. ggml-hrx's internal C++ API (Graph, DispatchScheduler, + # build_command_program(), etc.) is consumed directly -- not just through + # the standard GGML_BACKEND_API registration entrypoint -- by the + # ggml-hrx-compile-kernel/ggml-hrx-analyze-graph tools and the HRX test + # executables. Auto-export every symbol so linking against ggml-hrx.dll + # on Windows behaves like linking against libggml-hrx.so elsewhere. + set_target_properties(ggml-hrx PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + + # Windows has no RPATH equivalent: hrx.dll/loomc.dll must sit next to + # whichever .exe/.dll loads them. Every ggml-hrx binary (the backend + # itself, the CLI tools, and the tests) shares one output directory, so a + # single copy alongside ggml-hrx's own output makes them all runnable + # without requiring `cmake --install` first. + add_custom_command(TARGET ggml-hrx POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" + "$" + "$" + COMMENT "Copying hrx/loomc runtime DLLs next to ggml-hrx" + VERBATIM) +endif() if (GGML_HRX_BUNDLE_RUNTIME_LIBS) include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/BundleRuntime.cmake") diff --git a/ggml/src/ggml-hrx/backend-context.h b/ggml/src/ggml-hrx/backend-context.h index 0d5472f01cd8..8f680bc6f12e 100644 --- a/ggml/src/ggml-hrx/backend-context.h +++ b/ggml/src/ggml-hrx/backend-context.h @@ -66,10 +66,12 @@ struct ggml_backend_hrx_context { std::string name; }; +// Process-lifetime registry. Device and stream handles are not released here: +// this object is destroyed from a DLL static destructor, which on process exit +// runs after driver threads are already gone. Call hrx_gpu_shutdown() from +// live application code if the process will continue without HRX. struct ggml_backend_hrx_reg_context { bool initialized = false; std::vector> device_contexts; std::vector devices; - - ~ggml_backend_hrx_reg_context(); }; diff --git a/ggml/src/ggml-hrx/ggml-hrx.cpp b/ggml/src/ggml-hrx/ggml-hrx.cpp index 1c924a103d33..0e593a955ac5 100644 --- a/ggml/src/ggml-hrx/ggml-hrx.cpp +++ b/ggml/src/ggml-hrx/ggml-hrx.cpp @@ -57,27 +57,6 @@ static bool hrx_check(hrx_status_t status, const char * expression, const char * #define HRX_CHECK(expression) hrx_check((expression), #expression, __FILE__, __LINE__) -} // namespace - -ggml_backend_hrx_reg_context::~ggml_backend_hrx_reg_context() { - for (auto & context : device_contexts) { - if (context->buffer_stream != nullptr) { - hrx_stream_release(context->buffer_stream); - } - if (context->device != nullptr) { - hrx_device_release(context->device); - } - } - if (initialized) { - hrx_status_t status = hrx_gpu_shutdown(); - if (!hrx_status_is_ok(status)) { - hrx_status_ignore(status); - } - } -} - -namespace { - static std::optional device_string_property(hrx_device_t device, hrx_device_property_t property, const char * property_name) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b53be5e8a127..d259fc2e40d6 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -341,20 +341,24 @@ if (TARGET ggml-hrx) add_executable(test-hrx-buffer test-hrx-buffer.cpp) target_link_libraries(test-hrx-buffer PRIVATE ggml-hrx ggml hrx::hrx) target_include_directories(test-hrx-buffer PRIVATE ../ggml/include ../ggml/src ../ggml/src/ggml-hrx) + target_compile_features(test-hrx-buffer PRIVATE cxx_std_17) add_test(NAME test-hrx-buffer COMMAND test-hrx-buffer) add_executable(hrx-backend-test hrx-backend-test.cpp) - target_link_libraries(hrx-backend-test PRIVATE ggml-hrx ggml hrx::hrx loomc::loomc) + target_link_libraries(hrx-backend-test PRIVATE ggml-hrx ggml-hrx-kernel-corpus ggml hrx::hrx loomc::loomc) target_include_directories(hrx-backend-test PRIVATE ../ggml/include ../ggml/src ../ggml/src/ggml-hrx) + target_compile_features(hrx-backend-test PRIVATE cxx_std_17) add_test(NAME hrx-backend-test COMMAND hrx-backend-test) add_executable(test-hrx-loom-jit test-hrx-loom-jit.cpp) target_link_libraries(test-hrx-loom-jit PRIVATE ggml-hrx ggml-hrx-kernel-corpus hrx::hrx loomc::loomc) target_include_directories(test-hrx-loom-jit PRIVATE ../ggml/include ../ggml/src ../ggml/src/ggml-hrx) + target_compile_features(test-hrx-loom-jit PRIVATE cxx_std_17) add_test(NAME test-hrx-loom-jit COMMAND test-hrx-loom-jit) add_executable(test-hrx-ops test-hrx-ops.cpp) - target_link_libraries(test-hrx-ops PRIVATE ggml-hrx ggml hrx::hrx loomc::loomc) + target_link_libraries(test-hrx-ops PRIVATE ggml-hrx ggml-hrx-kernel-corpus ggml hrx::hrx loomc::loomc) target_include_directories(test-hrx-ops PRIVATE ../ggml/include ../ggml/src ../ggml/src/ggml-hrx) + target_compile_features(test-hrx-ops PRIVATE cxx_std_17) add_test(NAME test-hrx-ops COMMAND test-hrx-ops) endif() diff --git a/tests/hrx-backend-test.cpp b/tests/hrx-backend-test.cpp index b12879ef8f3c..1683aed2e2f5 100644 --- a/tests/hrx-backend-test.cpp +++ b/tests/hrx-backend-test.cpp @@ -37,6 +37,23 @@ #include #include +#ifdef _WIN32 +// MSVC's CRT doesn't provide the POSIX setenv()/unsetenv() functions used +// throughout this file. Shim them on top of _putenv_s() with matching +// semantics (setenv() with overwrite=0 only sets when unset; both return 0 +// on success). +static int setenv(const char * name, const char * value, int overwrite) { + if (!overwrite && std::getenv(name) != nullptr) { + return 0; + } + return _putenv_s(name, value) == 0 ? 0 : -1; +} + +static int unsetenv(const char * name) { + return _putenv_s(name, "") == 0 ? 0 : -1; +} +#endif // _WIN32 + #define REQUIRE(condition) \ do { \ if (!(condition)) { \ diff --git a/tests/test-hrx-ops.cpp b/tests/test-hrx-ops.cpp index 064361042068..cdd861be40ee 100644 --- a/tests/test-hrx-ops.cpp +++ b/tests/test-hrx-ops.cpp @@ -18,6 +18,23 @@ #include #include +#ifdef _WIN32 +// MSVC's CRT doesn't provide the POSIX setenv()/unsetenv() functions used +// throughout this file. Shim them on top of _putenv_s() with matching +// semantics (setenv() with overwrite=0 only sets when unset; both return 0 +// on success). +static int setenv(const char * name, const char * value, int overwrite) { + if (!overwrite && std::getenv(name) != nullptr) { + return 0; + } + return _putenv_s(name, value) == 0 ? 0 : -1; +} + +static int unsetenv(const char * name) { + return _putenv_s(name, "") == 0 ? 0 : -1; +} +#endif // _WIN32 + #define REQUIRE(condition) \ do { \ if (!(condition)) { \