CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
336 lines
11 KiB
C++
336 lines
11 KiB
C++
// Copyright (C) 2021 - 2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
#ifndef ROCFFT_GPUBUF_H
|
|
#define ROCFFT_GPUBUF_H
|
|
|
|
#include "device_properties.h"
|
|
#include "rocfft_hip.h"
|
|
#include "sys_mem.h"
|
|
#include <cstdlib>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
struct DEVICEBUF_MEM_USAGE : public std::runtime_error
|
|
{
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
struct device_memory_accountant
|
|
{
|
|
public:
|
|
// acquire a reference to a singleton of this struct
|
|
static device_memory_accountant& singleton()
|
|
{
|
|
static device_memory_accountant instance;
|
|
return instance;
|
|
}
|
|
|
|
size_t num_devices() const
|
|
{
|
|
return mem_account_on_device.size();
|
|
}
|
|
|
|
void record_used_bytes(size_t allocation_size, int dev_id)
|
|
{
|
|
if(dev_id < 0 || static_cast<size_t>(dev_id) >= num_devices())
|
|
throw std::invalid_argument(
|
|
"Invalid device ID given to device memory accountant (recording).");
|
|
std::unique_lock lock(dev_mem_account_mutex);
|
|
auto& dev_account = mem_account_on_device[dev_id];
|
|
dev_account.used_bytes += allocation_size;
|
|
if(dev_account.is_integrated_device)
|
|
system_memory::singleton().record_used_bytes(allocation_size);
|
|
}
|
|
|
|
void release_used_bytes(size_t allocation_size, int dev_id)
|
|
{
|
|
if(dev_id < 0 || static_cast<size_t>(dev_id) >= num_devices())
|
|
throw std::invalid_argument(
|
|
"Invalid device ID given to device memory accountant (releasing).");
|
|
std::unique_lock lock(dev_mem_account_mutex);
|
|
auto& dev_account = mem_account_on_device[dev_id];
|
|
// prevent underflows
|
|
dev_account.used_bytes -= std::min(dev_account.used_bytes, allocation_size);
|
|
if(dev_account.is_integrated_device)
|
|
system_memory::singleton().release_used_bytes(allocation_size);
|
|
}
|
|
|
|
std::vector<size_t> get_usable_bytes_all_devices()
|
|
{
|
|
auto ndevices = num_devices();
|
|
std::vector<size_t> ret(ndevices);
|
|
for(size_t dev_id = 0; dev_id < ndevices; ++dev_id)
|
|
{
|
|
ret[dev_id] = get_usable_bytes(dev_id);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
size_t get_usable_bytes(int dev_id)
|
|
{
|
|
if(dev_id < 0 || static_cast<size_t>(dev_id) >= num_devices())
|
|
throw std::invalid_argument(
|
|
"Invalid device ID given to device memory accountant (observation).");
|
|
std::shared_lock lock(dev_mem_account_mutex);
|
|
const auto& dev_account = mem_account_on_device[dev_id];
|
|
|
|
if(dev_account.limit_bytes <= dev_account.used_bytes)
|
|
return 0;
|
|
|
|
auto usable_bytes = dev_account.limit_bytes - dev_account.used_bytes;
|
|
if(dev_account.is_integrated_device)
|
|
usable_bytes = std::min(usable_bytes, system_memory::singleton().get_usable_bytes());
|
|
return usable_bytes;
|
|
}
|
|
|
|
void set_limit_bytes_for_all_devices(size_t limit_bytes)
|
|
{
|
|
std::unique_lock lock(dev_mem_account_mutex);
|
|
for(auto& account : mem_account_on_device)
|
|
account.limit_bytes = std::min(account.total_bytes, limit_bytes);
|
|
}
|
|
|
|
std::string get_details(int dev_id)
|
|
{
|
|
if(dev_id < 0 || static_cast<size_t>(dev_id) >= num_devices())
|
|
throw std::invalid_argument(
|
|
"Invalid device ID given to device memory accountant (detail query).");
|
|
|
|
const auto usable_bytes = get_usable_bytes(dev_id);
|
|
const auto& dev_account = mem_account_on_device[dev_id];
|
|
std::shared_lock lock(dev_mem_account_mutex);
|
|
std::stringstream ss;
|
|
ss << "\tUsable device memory: " << byte_size_to_str(usable_bytes) << "\n"
|
|
<< "\tUsed device memory: " << byte_size_to_str(dev_account.used_bytes) << "\n"
|
|
<< "\tLimit on device memory usage: " << byte_size_to_str(dev_account.limit_bytes);
|
|
if(dev_account.is_integrated_device)
|
|
{
|
|
constexpr bool use_double_tabs = true;
|
|
ss << "\n"
|
|
<< "\tNOTE: integrated device, system memory details below:\n"
|
|
<< system_memory::singleton().get_details(use_double_tabs);
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
size_t get_max_total_mem_on_devices() const
|
|
{
|
|
size_t ret = 0;
|
|
for(const auto& account : mem_account_on_device)
|
|
ret = std::max(ret, account.total_bytes);
|
|
return ret;
|
|
}
|
|
|
|
size_t get_limit_bytes_on_device(int dev_id) const
|
|
{
|
|
if(dev_id < 0 || static_cast<size_t>(dev_id) >= num_devices())
|
|
throw std::invalid_argument("Invalid device ID given to device memory accountant "
|
|
"(querying limit bytes on device).");
|
|
return mem_account_on_device[dev_id].limit_bytes;
|
|
}
|
|
|
|
private:
|
|
struct mem_account_t
|
|
{
|
|
mem_account_t(size_t total_dev_mem, bool integrated_device)
|
|
: total_bytes(total_dev_mem)
|
|
, limit_bytes(total_dev_mem)
|
|
, used_bytes(0)
|
|
, is_integrated_device(integrated_device)
|
|
{
|
|
}
|
|
const size_t total_bytes;
|
|
size_t limit_bytes;
|
|
size_t used_bytes;
|
|
const bool is_integrated_device;
|
|
};
|
|
|
|
std::vector<mem_account_t> mem_account_on_device;
|
|
|
|
mutable std::shared_mutex dev_mem_account_mutex;
|
|
|
|
device_memory_accountant()
|
|
{
|
|
int dev_count = 0;
|
|
auto hip_status = hipGetDeviceCount(&dev_count);
|
|
if(hip_status != hipSuccess || dev_count <= 0)
|
|
{
|
|
throw std::runtime_error("Device count failed with code " + std::to_string(hip_status)
|
|
+ " in initialization of device memory account (dev_count = "
|
|
+ std::to_string(dev_count) + ").");
|
|
}
|
|
mem_account_on_device.reserve(dev_count);
|
|
for(int dev_id = 0; dev_id < dev_count; dev_id++)
|
|
{
|
|
rocfft_scoped_device dev(dev_id);
|
|
const auto dev_prop = get_curr_device_prop();
|
|
mem_account_on_device.emplace_back(dev_prop.totalGlobalMem, dev_prop.integrated);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Simple RAII class for GPU buffers. T is the type of pointer that
|
|
// data() returns
|
|
template <class T = void>
|
|
class gpubuf_t
|
|
{
|
|
public:
|
|
gpubuf_t() {}
|
|
// buffers are movable but not copyable
|
|
gpubuf_t(gpubuf_t&& other)
|
|
{
|
|
std::swap(buf, other.buf);
|
|
std::swap(owned, other.owned);
|
|
std::swap(bsize, other.bsize);
|
|
std::swap(device, other.device);
|
|
std::swap(is_managed_memory, other.is_managed_memory);
|
|
}
|
|
gpubuf_t& operator=(gpubuf_t&& other)
|
|
{
|
|
std::swap(buf, other.buf);
|
|
std::swap(owned, other.owned);
|
|
std::swap(bsize, other.bsize);
|
|
std::swap(device, other.device);
|
|
std::swap(is_managed_memory, other.is_managed_memory);
|
|
return *this;
|
|
}
|
|
gpubuf_t(const gpubuf_t&) = delete;
|
|
gpubuf_t& operator=(const gpubuf_t&) = delete;
|
|
|
|
static gpubuf_t make_nonowned(T* p, size_t size_bytes = 0)
|
|
{
|
|
gpubuf_t ret;
|
|
ret.owned = false;
|
|
ret.buf = p;
|
|
ret.bsize = size_bytes;
|
|
ret.is_managed_memory = false; // irrelevant if not owned
|
|
return ret;
|
|
}
|
|
|
|
~gpubuf_t()
|
|
{
|
|
free();
|
|
}
|
|
|
|
static bool use_alloc_managed()
|
|
{
|
|
return std::getenv("ROCFFT_MALLOC_MANAGED");
|
|
}
|
|
|
|
hipError_t alloc(const size_t size, bool make_it_shared = false)
|
|
{
|
|
free();
|
|
// remember the device that was current as of alloc, so we can
|
|
// free on the correct device
|
|
auto ret = hipGetDevice(&device);
|
|
if(ret != hipSuccess)
|
|
return ret;
|
|
|
|
if(size > device_memory_accountant::singleton().get_usable_bytes(device))
|
|
{
|
|
std::stringstream msg;
|
|
msg << "Unauthorized device allocation (device ID: " << device << ").\n"
|
|
<< "\tRequested size is " << byte_size_to_str(size) << "\n"
|
|
<< device_memory_accountant::singleton().get_details(device);
|
|
throw DEVICEBUF_MEM_USAGE{msg.str()};
|
|
}
|
|
|
|
bsize = size;
|
|
is_managed_memory = use_alloc_managed() || make_it_shared;
|
|
ret = is_managed_memory ? hipMallocManaged(&buf, bsize) : hipMalloc(&buf, bsize);
|
|
if(ret != hipSuccess)
|
|
{
|
|
buf = nullptr;
|
|
bsize = 0;
|
|
}
|
|
|
|
device_memory_accountant::singleton().record_used_bytes(bsize, device);
|
|
|
|
return ret;
|
|
}
|
|
|
|
size_t size() const
|
|
{
|
|
return bsize;
|
|
}
|
|
|
|
void free()
|
|
{
|
|
if(buf != nullptr)
|
|
{
|
|
if(owned)
|
|
{
|
|
// free on the device we allocated on
|
|
rocfft_scoped_device dev(device);
|
|
(void)hipFree(buf);
|
|
device_memory_accountant::singleton().release_used_bytes(bsize, device);
|
|
}
|
|
buf = nullptr;
|
|
bsize = 0;
|
|
}
|
|
owned = true;
|
|
}
|
|
|
|
// return a pointer to the allocated memory, offset by the
|
|
// specified number of bytes
|
|
T* data_offset(size_t offset_bytes = 0) const
|
|
{
|
|
void* ptr = static_cast<char*>(buf) + offset_bytes;
|
|
return static_cast<T*>(ptr);
|
|
}
|
|
|
|
T* data() const
|
|
{
|
|
return static_cast<T*>(buf);
|
|
}
|
|
|
|
// equality/bool tests
|
|
bool operator==(std::nullptr_t n) const
|
|
{
|
|
return buf == n;
|
|
}
|
|
bool operator!=(std::nullptr_t n) const
|
|
{
|
|
return buf != n;
|
|
}
|
|
operator bool() const
|
|
{
|
|
return buf;
|
|
}
|
|
|
|
private:
|
|
// The GPU buffer
|
|
void* buf = nullptr;
|
|
// whether this object owns the 'buf' pointer (and hence needs to
|
|
// free it)
|
|
bool owned = true;
|
|
bool is_managed_memory = false;
|
|
size_t bsize = 0;
|
|
int device = 0;
|
|
};
|
|
|
|
// default gpubuf that gives out void* pointers
|
|
typedef gpubuf_t<> gpubuf;
|
|
#endif
|