diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ffbc25e..709bc30f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,15 @@ include_directories(${gflags_SOURCE_DIR}/include) # glog set(WITH_GFLAGS OFF CACHE BOOL "Disable glog finding system gflags" FORCE) set(WITH_GTEST OFF CACHE BOOL "Disable glog finding system gtest" FORCE) -add_subdirectory(third_party/glog) +get_property(_infinitrain_build_testing_was_cached CACHE BUILD_TESTING PROPERTY TYPE SET) +block() + set(BUILD_TESTING OFF) + add_subdirectory(third_party/glog) +endblock() +if(NOT _infinitrain_build_testing_was_cached) + unset(BUILD_TESTING CACHE) +endif() +unset(_infinitrain_build_testing_was_cached) include_directories(${glog_SOURCE_DIR}/src) # eigen @@ -131,19 +139,27 @@ endif() # ------------------------------------------------------------------------------ add_library(infini_train STATIC ${SRC}) +add_library(InfiniTrain::infini_train ALIAS infini_train) +add_library(InfiniTrain::cpu_kernels ALIAS infini_train_cpu_kernels) +target_compile_features(infini_train PUBLIC cxx_std_20) +target_include_directories(infini_train PUBLIC + "$" +) target_link_libraries(infini_train PUBLIC glog gflags - infini_train_cpu_kernels + Eigen3::Eigen ) +if(USE_OMP) + target_link_libraries(infini_train PUBLIC OpenMP::OpenMP_CXX) +endif() if(USE_CUDA) # infini_train contains cuda runtime wrappers (*.cc) like cuda_blas_handle.cc/cuda_guard.cc # Those may need CUDA runtime/driver/cublas symbols at final link, so attach them here too. target_link_libraries(infini_train PUBLIC - infini_train_cuda_kernels CUDA::cudart CUDA::cublas CUDA::cuda_driver @@ -157,37 +173,66 @@ if(USE_CUDA) endif() # ------------------------------------------------------------------------------ -# Helper: link libraries in a group to fix static lib one-pass resolution -# (THIS is what fixes "undefined reference" from cuda_kernels -> core symbols) +# Final executable link interface # ------------------------------------------------------------------------------ + +set(_infini_train_executable_archives + infini_train + infini_train_cpu_kernels +) +if(USE_CUDA) + list(APPEND _infini_train_executable_archives infini_train_cuda_kernels) +endif() +set_property(GLOBAL PROPERTY INFINI_TRAIN_EXECUTABLE_ARCHIVES + ${_infini_train_executable_archives}) + function(link_infini_train_exe target_name) - if(USE_CUDA) - target_link_libraries(${target_name} PRIVATE - "-Wl,--start-group" - "-Wl,--whole-archive" - infini_train - infini_train_cpu_kernels - infini_train_cuda_kernels - "-Wl,--no-whole-archive" - "-Wl,--end-group" - ) + cmake_parse_arguments(LINK "" "" "EXTRA_ARCHIVES" ${ARGN}) + if(LINK_KEYWORDS_MISSING_VALUES) + message(FATAL_ERROR + "link_infini_train_exe: missing values for ${LINK_KEYWORDS_MISSING_VALUES}") + endif() + if(LINK_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "link_infini_train_exe: unexpected arguments: ${LINK_UNPARSED_ARGUMENTS}") + endif() + + get_target_property(_target_type ${target_name} TYPE) + if(_target_type STREQUAL "INTERFACE_LIBRARY") + set(_link_scope INTERFACE) + elseif(_target_type STREQUAL "EXECUTABLE") + set(_link_scope PRIVATE) else() - target_link_libraries(${target_name} PRIVATE - "-Wl,--start-group" - "-Wl,--whole-archive" - infini_train - infini_train_cpu_kernels - "-Wl,--no-whole-archive" - "-Wl,--end-group" - ) + message(FATAL_ERROR + "link_infini_train_exe requires an executable or interface library target") endif() + + get_property(_framework_archives GLOBAL PROPERTY INFINI_TRAIN_EXECUTABLE_ARCHIVES) + target_link_libraries(${target_name} ${_link_scope} + "-Wl,--start-group" + "-Wl,--whole-archive" + ${_framework_archives} + ${LINK_EXTRA_ARCHIVES} + "-Wl,--no-whole-archive" + "-Wl,--end-group" + ) endfunction() +# Libraries and providers link InfiniTrain::infini_train for the core API. Final +# executables must link InfiniTrain::infini_train_executable or a provider-specific +# executable interface; linking only InfiniTrain::infini_train may discard static +# registration objects. +# EXTRA_ARCHIVES is reserved for archives that still rely on static initialization. +add_library(infini_train_executable INTERFACE) +link_infini_train_exe(infini_train_executable) +add_library(InfiniTrain::infini_train_executable ALIAS infini_train_executable) +unset(_infini_train_executable_archives) # ------------------------------------------------------------------------------ # Examples # ------------------------------------------------------------------------------ +if(PROJECT_IS_TOP_LEVEL) add_executable(mnist example/mnist/main.cc example/mnist/dataset.cc @@ -220,10 +265,13 @@ add_executable(llama3 example/llama3/checkpoint_loader.cc ) link_infini_train_exe(llama3) +endif() # Tools +if(PROJECT_IS_TOP_LEVEL) add_subdirectory(tools/infini_run) set_target_properties(infini_run PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) +endif() # Tests if(BUILD_TEST) diff --git a/cmake/test_macros.cmake b/cmake/test_macros.cmake index 4b96db17..1d358ae6 100644 --- a/cmake/test_macros.cmake +++ b/cmake/test_macros.cmake @@ -5,7 +5,7 @@ # # Usage: # 1. Include this file in tests/CMakeLists.txt -# 2. Use infini_train_add_test macro to register tests +# 2. Use infini_train_add_test to register tests # # Examples: # infini_train_add_test( @@ -23,20 +23,28 @@ include_guard(GLOBAL) include(GoogleTest) # ----------------------------------------------------------------------------- -# infini_train_add_test - Test registration macro +# infini_train_add_test - Test registration function # ----------------------------------------------------------------------------- # Features: # 1. Create executable target # 2. Configure compile options, link libraries, and include paths -# 3. Use gtest_discover_tests to auto-discover CPU test cases -# 4. Register CUDA tests at binary granularity with CTest GPU resources -# 5. Set test labels +# 3. Use gtest_discover_tests to auto-discover host test cases +# 4. Register accelerator tests at binary granularity +# 5. Set test labels and execution properties # # Arguments: # SOURCES: Source file list (required) # LABELS: Test labels, e.g. "cpu" "cuda" "distributed" (optional, default "cpu") -# TEST_FILTER: gtest test filter pattern (optional) -# TEST_TIMEOUT: ctest timeout in seconds (optional, default 10) +# TEST_FILTER: gtest test filter pattern (optional) +# TEST_TIMEOUT: ctest timeout in seconds (optional, default 10) +# TEST_MAIN_SOURCE: per-target main source (optional) +# TEST_MAIN_TARGET: object-library main target (optional, defaults to test_main) +# RUNTIME_OUTPUT_DIRECTORY: executable output directory (optional) +# LINK_LIBRARIES: additional link libraries (optional) +# COMPILE_DEFINITIONS: target compile definitions (optional) +# BINARY_REGISTRATION: register the binary as one CTest test (optional) +# SKIP_DEFAULT_FRAMEWORK_LINK: LINK_LIBRARIES supplies the full link closure +# RUN_SERIAL: do not run this CTest target concurrently (optional) # # Examples: # # Single-label test (one liner) @@ -46,19 +54,55 @@ include(GoogleTest) # infini_train_add_test(test_example SOURCES test_example.cc LABELS cpu TEST_FILTER "-*CUDA*") # infini_train_add_test(test_example_cuda SOURCES test_example.cc LABELS cuda TEST_FILTER "*CUDA*") # ----------------------------------------------------------------------------- -macro(infini_train_add_test) - cmake_parse_arguments(ARG "" "TEST_NAME;TEST_FILTER;TEST_TIMEOUT" "SOURCES;LABELS" ${ARGN}) +function(infini_train_add_test) + cmake_parse_arguments(ARG + "BINARY_REGISTRATION;RUN_SERIAL;SKIP_DEFAULT_FRAMEWORK_LINK" + "TEST_NAME;TEST_FILTER;TEST_TIMEOUT;TEST_MAIN_SOURCE;TEST_MAIN_TARGET;RUNTIME_OUTPUT_DIRECTORY" + "SOURCES;LABELS;LINK_LIBRARIES;COMPILE_DEFINITIONS" + ${ARGN} + ) + + if(ARG_KEYWORDS_MISSING_VALUES) + message(FATAL_ERROR + "infini_train_add_test: missing values for ${ARG_KEYWORDS_MISSING_VALUES}") + endif() - if(NOT ARG_TEST_NAME) + if(ARG_TEST_NAME AND ARG_UNPARSED_ARGUMENTS) + message(FATAL_ERROR + "infini_train_add_test: unexpected arguments: ${ARG_UNPARSED_ARGUMENTS}") + elseif(NOT ARG_TEST_NAME) set(ARG_TEST_NAME ${ARG_UNPARSED_ARGUMENTS}) endif() - if(NOT ARG_SOURCES) + list(LENGTH ARG_TEST_NAME _test_name_count) + if(NOT _test_name_count EQUAL 1 OR NOT ARG_SOURCES) message(FATAL_ERROR "infini_train_add_test: TEST_NAME and SOURCES are required") endif() + if(ARG_TEST_MAIN_SOURCE AND ARG_TEST_MAIN_TARGET) + message(FATAL_ERROR + "infini_train_add_test: TEST_MAIN_SOURCE and TEST_MAIN_TARGET are mutually exclusive") + endif() - # 1. Create executable target - add_executable(${ARG_TEST_NAME} ${ARG_SOURCES} $) + # 1. Create executable target. + if(ARG_TEST_MAIN_SOURCE) + add_executable(${ARG_TEST_NAME} ${ARG_SOURCES} ${ARG_TEST_MAIN_SOURCE}) + elseif(ARG_TEST_MAIN_TARGET) + if(NOT TARGET ${ARG_TEST_MAIN_TARGET}) + message(FATAL_ERROR + "infini_train_add_test: TEST_MAIN_TARGET '${ARG_TEST_MAIN_TARGET}' does not exist") + endif() + add_executable(${ARG_TEST_NAME} ${ARG_SOURCES} $) + else() + if(NOT TARGET test_main) + message(FATAL_ERROR + "infini_train_add_test: default test_main target does not exist") + endif() + add_executable(${ARG_TEST_NAME} ${ARG_SOURCES} $) + endif() + if(ARG_RUNTIME_OUTPUT_DIRECTORY) + set_target_properties(${ARG_TEST_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${ARG_RUNTIME_OUTPUT_DIRECTORY}") + endif() # 2. Disable -Werror so tests can run under relaxed warning levels target_compile_options(${ARG_TEST_NAME} PRIVATE -Wno-error) @@ -66,91 +110,371 @@ macro(infini_train_add_test) # 3. Link Google Test (uses custom main from test_main that initializes GlobalEnv) target_link_libraries(${ARG_TEST_NAME} PRIVATE GTest::gtest) - # 4. Add include paths - target_include_directories(${ARG_TEST_NAME} PRIVATE - ${glog_SOURCE_DIR}/src - ) - - # 5. Link project library (reuses framework linking strategy) - link_infini_train_exe(${ARG_TEST_NAME}) + # 4. Link the framework. External backends may supply a complete executable + # link target that owns static archive retention and link ordering. + if(NOT ARG_SKIP_DEFAULT_FRAMEWORK_LINK) + link_infini_train_exe(${ARG_TEST_NAME}) + endif() + if(ARG_LINK_LIBRARIES) + target_link_libraries(${ARG_TEST_NAME} PRIVATE ${ARG_LINK_LIBRARIES}) + endif() + if(ARG_COMPILE_DEFINITIONS) + target_compile_definitions(${ARG_TEST_NAME} PRIVATE ${ARG_COMPILE_DEFINITIONS}) + endif() - # 6. Register tests + # 5. Register tests set(labels "cpu") if(ARG_LABELS) set(labels "${ARG_LABELS}") endif() set(test_timeout 10) - if(ARG_TEST_TIMEOUT) + if(DEFINED ARG_TEST_TIMEOUT AND NOT ARG_TEST_TIMEOUT STREQUAL "") set(test_timeout ${ARG_TEST_TIMEOUT}) endif() - list(FIND labels cuda _has_cuda_label) - if(NOT _has_cuda_label EQUAL -1) - set(_cuda_test_args) + # Hardware discovery would execute accelerator binaries during the build. + # Register them as one CTest entry and defer execution to ctest. + if(ARG_BINARY_REGISTRATION) + set(_binary_test_args) if(ARG_TEST_FILTER) - list(APPEND _cuda_test_args --gtest_filter=${ARG_TEST_FILTER}) + list(APPEND _binary_test_args --gtest_filter=${ARG_TEST_FILTER}) endif() add_test( NAME ${ARG_TEST_NAME} - COMMAND $ ${_cuda_test_args} + COMMAND $ ${_binary_test_args} ) set_tests_properties(${ARG_TEST_NAME} PROPERTIES LABELS "${labels}" TIMEOUT ${test_timeout} ) + if(ARG_RUN_SERIAL) + set_tests_properties(${ARG_TEST_NAME} PROPERTIES RUN_SERIAL TRUE) + endif() elseif(ARG_TEST_FILTER) + set(_discovered_test_properties LABELS "${labels}" TIMEOUT ${test_timeout}) + if(ARG_RUN_SERIAL) + list(APPEND _discovered_test_properties RUN_SERIAL TRUE) + endif() gtest_discover_tests(${ARG_TEST_NAME} TEST_FILTER "${ARG_TEST_FILTER}" DISCOVERY_TIMEOUT 10 - PROPERTIES LABELS "${labels}" TIMEOUT ${test_timeout} + PROPERTIES ${_discovered_test_properties} ) else() + set(_discovered_test_properties LABELS "${labels}" TIMEOUT ${test_timeout}) + if(ARG_RUN_SERIAL) + list(APPEND _discovered_test_properties RUN_SERIAL TRUE) + endif() gtest_discover_tests(${ARG_TEST_NAME} - PROPERTIES LABELS "${labels}" TIMEOUT ${test_timeout} + PROPERTIES ${_discovered_test_properties} ) endif() -endmacro() +endfunction() # ----------------------------------------------------------------------------- -# infini_train_add_test_suite - Register cpu/cuda/distributed targets in one call +# infini_train_add_test_suite - Declare a shared device-parameterized suite # ----------------------------------------------------------------------------- -# Calls infini_train_add_test three times (or fewer) with the correct -# TEST_FILTER and LABELS derived from the label list. +# Declaration is intentionally separate from device instantiation. This lets a +# top-level InfiniTrain build create CPU/CUDA targets while an embedding +# provider creates only its own PrivateUse1 targets from the same source list. # # Arguments: -# Base name; each target is named _