-
Notifications
You must be signed in to change notification settings - Fork 0
Add runtime memory monitoring functions #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
31296c8
8fed5f7
6a8577a
443dbcc
5ce64a9
7678738
45f0022
ad51978
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # set CMAKE_BUILD_TYPE if not defined | ||
| if(NOT CMAKE_BUILD_TYPE) | ||
| set(default_build_type "RelWithDebInfo") | ||
| message(STATUS "Setting build type to '${default_build_type}' as none was specified.") | ||
| set( | ||
| CMAKE_BUILD_TYPE | ||
| "${default_build_type}" | ||
| CACHE | ||
| STRING | ||
| "Choose the type of build, options are: Debug, Release, RelWithDebInfo and MinSizeRel." | ||
| FORCE | ||
| ) | ||
| endif() | ||
|
|
||
| # find Kokkos as an already existing target | ||
| if(TARGET Kokkos::kokkos) | ||
| return() | ||
| endif() | ||
|
|
||
| # find Kokkos as installed | ||
| find_package(Kokkos CONFIG) | ||
| if(Kokkos_FOUND) | ||
| message(STATUS "Kokkos provided as installed: ${Kokkos_DIR} (version \"${Kokkos_VERSION}\")") | ||
|
|
||
| return() | ||
| endif() | ||
|
|
||
| # find Kokkos as an existing source directory | ||
| set( | ||
| CexaExperimental_KOKKOS_SOURCE_DIR | ||
| "${CMAKE_CURRENT_SOURCE_DIR}/../../../vendor/kokkos" | ||
| CACHE | ||
| PATH | ||
| "Path to the local source directory of Kokkos" | ||
| ) | ||
| if(EXISTS "${CexaExperimental_KOKKOS_SOURCE_DIR}/CMakeLists.txt") | ||
| message(STATUS "Kokkos provided as a source directory: ${CexaExperimental_KOKKOS_SOURCE_DIR}") | ||
|
|
||
| add_subdirectory("${CexaExperimental_KOKKOS_SOURCE_DIR}" kokkos) | ||
| set(Kokkos_FOUND True) | ||
|
|
||
| return() | ||
| endif() | ||
|
|
||
| # download Kokkos from release and find it | ||
| message(STATUS "Kokkos downloaded: ${CexaExperimental_KOKKOS_SOURCE_DIR}") | ||
|
|
||
| include(FetchContent) | ||
|
|
||
| FetchContent_Declare( | ||
| kokkos | ||
| DOWNLOAD_EXTRACT_TIMESTAMP ON | ||
| URL https://github.com/kokkos/kokkos/releases/download/4.5.01/kokkos-4.5.01.zip | ||
| SOURCE_DIR ${CexaExperimental_KOKKOS_SOURCE_DIR} | ||
| ) | ||
| FetchContent_MakeAvailable(kokkos) | ||
| set(Kokkos_FOUND True) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| cmake_minimum_required(VERSION 3.16) | ||
|
|
||
| project(cexa-experimental-meminfo LANGUAGES CXX) | ||
| set(CMAKE_BUILD_TYPE "RelWithDebInfo") | ||
|
|
||
| if (NOT ${Kokkos_DIR} STREQUAL "") | ||
| add_subdirectory(${Kokkos_DIR}) | ||
| include_directories(${Kokkos_INCLUDE_DIRS_RET}) | ||
| else() | ||
| list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake/modules") | ||
| find_package(Kokkos REQUIRED) | ||
| endif() | ||
|
|
||
| include(CTest) | ||
|
|
||
| add_subdirectory(src) | ||
| add_subdirectory(unit_test) | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| ### memInfo | ||
| This is a wrapper for the device _memGetInfo_ function, but it's also available for unified memory space and host space. | ||
|
|
||
| ### Usage | ||
| ``` | ||
| Kokkos::Experimental::memInfo(&free, &total); | ||
| ``` | ||
| - `total`: Amount of RAM on the system (HBM/DRAM) | ||
| - `free`: Available memory |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| add_library(memInfo INTERFACE) | ||
| target_include_directories(memInfo INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) | ||
| target_link_libraries(memInfo INTERFACE Kokkos::kokkos) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| #ifndef KOKKOS_MEMINFO_HPP | ||
| #define KOKKOS_MEMINFO_HPP | ||
|
|
||
| #include <cstddef> | ||
| #include <sstream> | ||
| #include <fstream> | ||
|
|
||
| #ifdef _WIN32 | ||
| #include <windows.h> | ||
| #endif | ||
|
|
||
| #include <Kokkos_Core.hpp> | ||
|
|
||
| namespace Kokkos { | ||
| namespace Experimental { | ||
|
|
||
| namespace { | ||
| constexpr int OVERCOMMIT_DISABLED = 2; | ||
| constexpr char MEM_FREE_KEY[] = "MemFree:"; | ||
| constexpr char MEM_TOTAL_KEY[] = "MemTotal:"; | ||
| constexpr char COMMITTED_AS_KEY[] = "Committed_AS:"; | ||
| constexpr char COMMIT_LIMIT_KEY[] = "CommitLimit:"; | ||
| constexpr char MEMINFO_PATH[] = "/proc/meminfo"; | ||
| constexpr char OVERCOMMIT_PATH[] = "/proc/sys/vm/overcommit_memory"; | ||
| } | ||
|
|
||
| // On some systems, overcommit is disabled, and the kernel does not allow | ||
| // memory allocation beyond the commit limit. This means that allocations | ||
| // that touch only a small amount of memory are still counted at their full size. | ||
| // man proc_sys_vm | ||
| bool is_overcommit_disabled() { | ||
| std::ifstream overcommit_file(OVERCOMMIT_PATH); | ||
| int overcommit_value = 0; | ||
|
|
||
| if (overcommit_file.is_open()) { | ||
| overcommit_file >> overcommit_value; | ||
| } else { | ||
| return false; | ||
| } | ||
| return (overcommit_value == OVERCOMMIT_DISABLED); | ||
| } | ||
|
|
||
| size_t get_meminfo_value(const char* key) { | ||
| std::ifstream meminfo(MEMINFO_PATH); | ||
| size_t value = 0; | ||
| std::string line; | ||
|
|
||
| if (meminfo.is_open()) { | ||
| while (std::getline(meminfo, line)) { | ||
| if (line.find(key) != std::string::npos) { | ||
| std::istringstream iss(line); | ||
| iss.ignore(256, ':'); | ||
| iss >> value; | ||
| value = (iss.fail()) ? 0 : value * 1024; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| template <typename Space = Kokkos::DefaultExecutionSpace> | ||
| void MemGetInfo(size_t* free, size_t* total) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not fond of this API. Can we return the value instead of playing with addresses (Is it host or device pointer ? etc. ) We could also a simple
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A boolean flag could also be either passed as an argument or returned in order to be sure whether the total and free memory available were obtained successfully. Right now there are unsuccessful code paths and there is no way to know that. cudaMemGetInfo returns a cudaError_t.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A more modern C++ will be to use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that However, I am feeling that it is better to go with the interface of |
||
| using MemorySpace = typename Space::memory_space; | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| MemGetInfo<MemorySpace>(free, total); | ||
| } | ||
|
|
||
| // Single node memory info | ||
| template <> | ||
| void MemGetInfo<Kokkos::HostSpace>(size_t* free, size_t* total) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would you happen to know if it works from inside a cpuset? Very often, in hpc, scheduler like slurm will put your process in a cpuset with limited resources.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Slurm and other schedulers use cgroups to limit jobs, I've set up a way to retrieve these values for cgroups v1. |
||
| #ifdef _WIN32 | ||
| MEMORYSTATUSEX statex; | ||
| statex.dwLength = sizeof(statex); | ||
| if (GlobalMemoryStatusEx(&statex) != 0) { | ||
| *free = statex.ullAvailPhys; | ||
| *total = statex.ullTotalPhys; | ||
| } | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #else | ||
| static bool overcommit_disabled = is_overcommit_disabled(); | ||
| if (overcommit_disabled) { | ||
| *total = get_meminfo_value(COMMIT_LIMIT_KEY); | ||
| *free = *total - get_meminfo_value(COMMITTED_AS_KEY); | ||
| } else { | ||
| *total = get_meminfo_value(MEM_TOTAL_KEY); | ||
| *free = get_meminfo_value(MEM_FREE_KEY); | ||
| } | ||
|
||
| #endif | ||
| } | ||
|
|
||
| #if defined(KOKKOS_ENABLE_CUDA) | ||
| template <> | ||
| void MemGetInfo<Kokkos::CudaSpace>(size_t* free, size_t* total) { | ||
| KOKKOS_IMPL_CUDA_SAFE_CALL(cudaMemGetInfo(free, total)); | ||
| } | ||
| template <> | ||
| void MemGetInfo<Kokkos::CudaUVMSpace>(size_t* free, size_t* total) { | ||
| MemGetInfo<Kokkos::HostSpace>(free, total); | ||
| } | ||
| #endif | ||
|
|
||
| #if defined(KOKKOS_ENABLE_HIP) | ||
| template <> | ||
| void MemGetInfo<Kokkos::HIPSpace>(size_t* free, size_t* total) { | ||
| KOKKOS_IMPL_HIP_SAFE_CALL(hipMemGetInfo(free, total)); | ||
| } | ||
| template <> | ||
| void MemGetInfo<Kokkos::HIPManagedSpace>(size_t* free, size_t* total) { | ||
| MemGetInfo<Kokkos::HostSpace>(free, total); | ||
| } | ||
| #endif | ||
|
|
||
| #if defined(KOKKOS_ENABLE_SYCL) | ||
| template <> | ||
| void MemGetInfo<Kokkos::SYCLDeviceUSMSpace>(size_t* free, size_t* total) { | ||
| std::vector<sycl::device> devices = Kokkos::Impl::get_sycl_devices(); | ||
| for (auto& dev : devices) { | ||
| if (dev.is_gpu()) { | ||
| *total += dev.get_info<sycl::info::device::global_mem_size>(); | ||
| // https://github.com/triSYCL/sycl/blob/sycl/unified/master/sycl/doc/extensions/supported/sycl_ext_intel_device_info.md#free-global-memory | ||
| if (dev.has(sycl::aspect::ext_intel_free_memory)) { | ||
| *free += dev.get_info<sycl::ext::intel::info::device::free_memory>(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
| } // namespace Experimental | ||
| } // namespace Kokkos | ||
|
|
||
| #endif // KOKKOS_MEMINFO_HPP | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
|
|
||
| include(FetchContent) | ||
|
|
||
| FetchContent_Declare( | ||
| googletest | ||
| GIT_REPOSITORY https://github.com/google/googletest.git | ||
| GIT_TAG 6910c9d9165801d8827d628cb72eb7ea9dd538c5 | ||
| DOWNLOAD_EXTRACT_TIMESTAMP true) | ||
|
|
||
| FetchContent_MakeAvailable(googletest) | ||
|
|
||
| enable_testing() | ||
| add_executable(MemInfoTest TestMemInfo.cpp) | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| target_link_libraries(MemInfoTest GTest::gtest_main Kokkos::kokkos memInfo) | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| include(GoogleTest) | ||
| gtest_discover_tests(MemInfoTest) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| #include <cexa_MemInfo.hpp> | ||
|
|
||
| #include <cstddef> | ||
AdRi1t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| #include <type_traits> | ||
|
|
||
| #include <Kokkos_Core.hpp> | ||
| #include <gtest/gtest.h> | ||
|
|
||
| template <typename Space = Kokkos::DefaultExecutionSpace> | ||
| void testMemInfo() { | ||
| std::size_t step1_total = 0ul; | ||
| std::size_t step2_total = 0ul; | ||
| std::size_t step1_free = 0ul; | ||
| std::size_t step2_free = 0ul; | ||
|
|
||
| Kokkos::Experimental::MemGetInfo<Space>(&step1_free, &step1_total); | ||
| // Allocate 64 MiB of memory | ||
| Kokkos::View<double**, typename Space::memory_space> data("data test", 1024, 8192); | ||
| Kokkos::fence(); | ||
| Kokkos::Experimental::MemGetInfo<Space>(&step2_free, &step2_total); | ||
|
||
| // Same total memory before and after aloccation | ||
| EXPECT_EQ(step1_total, step2_total); | ||
| // Check that free memory is less after allocation | ||
| EXPECT_LT(step2_free, step1_free); | ||
| } | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is repeating logic in the tests. The tests are one of the two types for different execution spaces: Kokkos::initialize(); size_t free = 0; But it is ok if you don't refactor. |
||
| #define TEST_SPACE(Space) \ | ||
| TEST(MemInfo, Space) { \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are calling the function |
||
| testMemInfo<Kokkos::Space>(); \ | ||
| } | ||
|
|
||
| TEST(MemInfo, DefaultSpace) { | ||
| testMemInfo<>(); | ||
| } | ||
|
|
||
| TEST_SPACE(HostSpace) | ||
|
|
||
| #if defined(KOKKOS_ENABLE_CUDA) | ||
| TEST_SPACE(CudaSpace) | ||
| TEST_SPACE(SharedSpace) | ||
| #endif | ||
|
|
||
| #if defined(KOKKOS_ENABLE_HIP) | ||
| TEST_SPACE(HIPSpace) | ||
| TEST_SPACE(SharedSpace) | ||
| #endif | ||
|
|
||
| #if defined(KOKKOS_ENABLE_SYCL) | ||
| TEST_SPACE(SYCLDeviceUSMSpace) | ||
| TEST_SPACE(SharedSpace) | ||
| #endif | ||
|
|
||
AdRi1t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| int main(int argc, char *argv[]) { | ||
| Kokkos::initialize(argc, argv); | ||
| ::testing::InitGoogleTest(&argc, argv); | ||
| int result = RUN_ALL_TESTS(); | ||
| Kokkos::finalize(); | ||
| return result; | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| #undef TEST_SPACE | ||
Uh oh!
There was an error while loading. Please reload this page.