-
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 4 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,19 @@ | ||
| 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}) | ||
| endif() | ||
|
|
||
| list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake/modules") | ||
|
|
||
| include(CTest) | ||
|
|
||
| find_package(Kokkos REQUIRED) | ||
|
|
||
| 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,144 @@ | ||
| #include <cstddef> | ||
| #include <Kokkos_Core.hpp> | ||
| #include <sstream> | ||
| #include <fstream> | ||
|
|
||
| #ifdef _WIN32 | ||
| #include <windows.h> | ||
| #else | ||
| #include <sys/sysinfo.h> | ||
| #endif | ||
|
|
||
| namespace Kokkos { | ||
| namespace Experimental { | ||
|
|
||
| // On some systems, overcommit is disabled, and the kernel will not allow memory | ||
| // allocation beyond the commit limit. This means that allocations that only touch | ||
| // a small amount of memory will be accounted for at their full allocation size. | ||
| bool is_overcommit_limit_set() { | ||
| std::ifstream overcommit_file("/proc/sys/vm/overcommit_memory"); | ||
| int overcommit_value = 0; | ||
|
|
||
| if (overcommit_file.is_open()) { | ||
| overcommit_file >> overcommit_value; | ||
| overcommit_file.close(); | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } else { | ||
| return false; | ||
| } | ||
| return (overcommit_value == 2); | ||
| } | ||
|
|
||
| size_t get_committed_as() { | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| std::ifstream meminfo("/proc/meminfo"); | ||
| size_t committed_as = 0; | ||
| char line[256]; | ||
|
|
||
| if (meminfo.is_open()) { | ||
| while (meminfo.getline(line, 256)) { | ||
| if (strncmp(line, "Committed_AS:", 13) == 0) { | ||
| std::istringstream iss(line); | ||
| iss.ignore(256, ':'); | ||
| iss >> committed_as; | ||
| committed_as = (iss.fail()) ? 0 : committed_as * 1024; | ||
| meminfo.close(); | ||
| break; | ||
| } | ||
| } | ||
| meminfo.close(); | ||
| } | ||
| return committed_as; | ||
| } | ||
|
|
||
| size_t get_commitLimit() { | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| std::ifstream meminfo("/proc/meminfo"); | ||
| size_t commitLimit = 0; | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| char line[256]; | ||
|
|
||
| if (meminfo.is_open()) { | ||
| while (meminfo.getline(line, 256)) { | ||
| if (strncmp(line, "CommitLimit:", 12) == 0) { | ||
| std::istringstream iss(line); | ||
| iss.ignore(256, ':'); | ||
| iss >> commitLimit; | ||
| commitLimit = (iss.fail()) ? 0 : commitLimit * 1024; | ||
| meminfo.close(); | ||
| break; | ||
| } | ||
| } | ||
| meminfo.close(); | ||
| } | ||
| return commitLimit; | ||
| } | ||
|
|
||
| template <typename Space = Kokkos::DefaultExecutionSpace::memory_space> | ||
| 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_limit = is_overcommit_limit_set(); | ||
| struct sysinfo info; | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (overcommit_limit) { | ||
| *total = get_commitLimit(); | ||
| *free = *total - get_committed_as(); | ||
| return; | ||
| } | ||
| if (sysinfo(&info) == 0) { | ||
| *free = info.freeram * info.mem_unit; | ||
| *total = info.totalram * info.mem_unit; | ||
| return; | ||
| } | ||
|
||
| #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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
|
|
||
| include(FetchContent) | ||
|
|
||
| FetchContent_Declare( | ||
| googletest | ||
| URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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,82 @@ | ||
| #include <Kokkos_Core.hpp> | ||
| #include <cexa_MemInfo.hpp> | ||
| #include <gtest/gtest.h> | ||
| #include <cstddef> | ||
AdRi1t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| template <typename MemorySpace = void> | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| void testMemInfo() { | ||
| size_t step1_total = 0ul; | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| size_t step2_total = 0ul; | ||
| size_t step2_free = 0ul; | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| size_t step1_free = 0ul; | ||
|
|
||
| if constexpr (std::is_same<MemorySpace, void>::value) { | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Kokkos::Experimental::MemGetInfo(&step1_free, &step1_total); | ||
| } else { | ||
| Kokkos::Experimental::MemGetInfo<MemorySpace>(&step1_free, &step1_total); | ||
| } | ||
| volatile double k = 0.0; | ||
| { | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Allocate 128 MiB of memory | ||
| Kokkos::View<double**, MemorySpace> data("data test", 128, 1024*1024); | ||
| if constexpr (std::is_same<MemorySpace, void>::value) { | ||
AdRi1t marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Kokkos::Experimental::MemGetInfo(&step2_free, &step2_total); | ||
| } else { | ||
| Kokkos::Experimental::MemGetInfo<MemorySpace>(&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. |
||
| TEST(MemInfo, HostSpace) { | ||
| Kokkos::initialize(); | ||
| testMemInfo<Kokkos::HostSpace>(); | ||
| Kokkos::finalize(); | ||
| } | ||
| TEST(MemInfo, HostSpaceUninitialized) { | ||
| size_t free = 0; | ||
| size_t total = 0; | ||
| Kokkos::Experimental::MemGetInfo<Kokkos::HostSpace>(&free, &total); | ||
| EXPECT_GT(free, 0); | ||
| EXPECT_GT(total, 0); | ||
| } | ||
|
|
||
| TEST(MemInfo, DefaultSpace) { | ||
| Kokkos::initialize(); | ||
| testMemInfo<>(); | ||
| Kokkos::finalize(); | ||
| } | ||
| TEST(MemInfo, DefaultSpaceUninitialized) { | ||
| size_t free = 0; | ||
| size_t total = 0; | ||
| Kokkos::Experimental::MemGetInfo(&free, &total); | ||
| EXPECT_GT(free, 0); | ||
| EXPECT_GT(total, 0); | ||
| } | ||
|
|
||
| #if defined(KOKKOS_ENABLE_CUDA) | ||
| TEST(MemInfo, CudaSpace) { | ||
| Kokkos::initialize(); | ||
| testMemInfo<Kokkos::CudaSpace>(); | ||
| testMemInfo<Kokkos::SharedSpace>(); | ||
| Kokkos::finalize(); | ||
| } | ||
| #endif | ||
|
|
||
| #if defined(KOKKOS_ENABLE_HIP) | ||
| TEST(MemInfo, HIPSpace) { | ||
| Kokkos::initialize(); | ||
| testMemInfo<Kokkos::HIPSpace>(); | ||
| testMemInfo<Kokkos::SharedSpace>(); | ||
| Kokkos::finalize(); | ||
| } | ||
| #endif | ||
|
|
||
AdRi1t marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| int main(int argc, char *argv[]) { | ||
| ::testing::InitGoogleTest(&argc, argv); | ||
| int result = RUN_ALL_TESTS(); | ||
| return result; | ||
science-enthusiast marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.