SWDEV-497841 - Add virtual memory heap
Add initial implementation of virtual memory heap with dynamic virtual memory mapping support for memory pools. DEBUG_HIP_MEM_POOL_VMHEAP controls the new method. Change-Id: I8dc5be2e0f34ab472f1800f43bb6243639a5e500
This commit is contained in:
@@ -315,7 +315,7 @@ bool Device::existsActiveStreamForDevice() {
|
||||
|
||||
// ================================================================================================
|
||||
Device::~Device() {
|
||||
if (default_mem_pool_ != nullptr) {
|
||||
if ((IS_LINUX || !DEBUG_HIP_MEM_POOL_VMHEAP) && (default_mem_pool_ != nullptr)) {
|
||||
default_mem_pool_->release();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2022-2023 Advanced Micro Devices, Inc.
|
||||
/* Copyright (c) 2022-2025 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -90,16 +90,19 @@ bool Heap::RemoveMemory(amd::Memory* memory, MemoryTimestamp* ts) {
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
Heap::SortedMap::iterator Heap::EraseAllocaton(Heap::SortedMap::iterator& it) {
|
||||
Heap::SortedMap::iterator Heap::EraseAllocation(Heap::SortedMap::iterator& it) {
|
||||
auto memory = it->first.second;
|
||||
const device::Memory* dev_mem = memory->getDeviceMemory(*device_->devices()[0]);
|
||||
void* dev_mem_vaddr = reinterpret_cast<void*>(dev_mem->virtualAddress());
|
||||
total_size_ -= it->first.first;
|
||||
if (dev_mem_vaddr == nullptr) {
|
||||
dev_mem_vaddr = memory->getSvmPtr();
|
||||
}
|
||||
|
||||
if (dev_mem_vaddr != nullptr) {
|
||||
amd::SvmBuffer::free(memory->getContext(), dev_mem_vaddr);
|
||||
if (use_vm_heap_) {
|
||||
vm_heap_.Free(memory);
|
||||
} else {
|
||||
amd::SvmBuffer::free(memory->getContext(), memory->getSvmPtr());
|
||||
amd::SvmBuffer::free(memory->getContext(), dev_mem_vaddr);
|
||||
}
|
||||
// Clear HIP event
|
||||
it->second.SetEvent(nullptr);
|
||||
@@ -119,11 +122,15 @@ bool Heap::ReleaseAllMemory(size_t min_bytes_to_hold, bool safe_release) {
|
||||
it->second.Wait();
|
||||
}
|
||||
if (it->second.IsSafeRelease()) {
|
||||
it = EraseAllocaton(it);
|
||||
it = EraseAllocation(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
// Handle managed pool with trim
|
||||
if (vm_heap_.FreeMappedSize() > min_bytes_to_hold) {
|
||||
vm_heap_.TrimPhysMemory(min_bytes_to_hold);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,11 +138,12 @@ bool Heap::ReleaseAllMemory(size_t min_bytes_to_hold, bool safe_release) {
|
||||
bool Heap::ReleaseAllMemory() {
|
||||
for (auto it = allocations_.begin(); it != allocations_.end();) {
|
||||
// Make sure the heap holds the minimum number of bytes
|
||||
if (total_size_ <= release_threshold_) {
|
||||
// @note: Managed memory controls the threshold on its own
|
||||
if (!use_vm_heap_ && (total_size_ <= release_threshold_)) {
|
||||
return true;
|
||||
}
|
||||
if (it->second.IsSafeRelease()) {
|
||||
it = EraseAllocaton(it);
|
||||
it = EraseAllocation(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
@@ -187,13 +195,17 @@ void* MemoryPool::AllocateMemory(size_t size, Stream* stream, void* dptr) {
|
||||
}
|
||||
cl_svm_mem_flags flags = (state_.interprocess_) ? ROCCLR_MEM_INTERPROCESS : 0;
|
||||
flags |= (state_.phys_mem_) ? ROCCLR_MEM_PHYMEM : 0;
|
||||
dev_ptr = amd::SvmBuffer::malloc(*context, flags, size, dev_info.memBaseAddrAlign_, nullptr);
|
||||
if (state_.use_vm_heap_) {
|
||||
dev_ptr = Alloc(size);
|
||||
} else {
|
||||
dev_ptr = amd::SvmBuffer::malloc(*context, flags, size, dev_info.memBaseAddrAlign_, nullptr);
|
||||
}
|
||||
if (dev_ptr == nullptr) {
|
||||
size_t free = 0, total =0;
|
||||
hipError_t err = hipMemGetInfo(&free, &total);
|
||||
if (err == hipSuccess) {
|
||||
LogPrintfError("Allocation failed : Device memory : required :%zu | free :%zu | total :%zu",
|
||||
size, free, total);
|
||||
LogPrintfError("Allocation failed : Device memory : required :\
|
||||
%zu | free :%zu | total :%zu", size, free, total);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -236,7 +248,7 @@ bool MemoryPool::FreeMemory(amd::Memory* memory, Stream* stream, Event* event) {
|
||||
{
|
||||
amd::ScopedLock lock(lock_pool_ops_);
|
||||
|
||||
if (memory->getUserData().phys_mem_obj != nullptr) {
|
||||
if (!state_.use_vm_heap_ && memory->getUserData().phys_mem_obj != nullptr) {
|
||||
memory = memory->getUserData().phys_mem_obj;
|
||||
}
|
||||
|
||||
@@ -408,8 +420,9 @@ hipError_t MemoryPool::GetAttribute(hipMemPoolAttr attr, void* value) {
|
||||
*reinterpret_cast<uint64_t*>(value) = free_heap_.GetReleaseThreshold();
|
||||
break;
|
||||
case hipMemPoolAttrReservedMemCurrent:
|
||||
// All allocate memory by the pool in OS
|
||||
*reinterpret_cast<uint64_t*>(value) = busy_heap_.GetTotalSize() + free_heap_.GetTotalSize();
|
||||
// All allocated memory by the pool in OS
|
||||
*reinterpret_cast<uint64_t*>(value) = (state_.use_vm_heap_) ? MappedSize() :
|
||||
(busy_heap_.GetTotalSize() + free_heap_.GetTotalSize());
|
||||
break;
|
||||
case hipMemPoolAttrReservedMemHigh:
|
||||
// High watermark of all allocated memory in OS, since the last reset
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright (c) 2022 Advanced Micro Devices, Inc.
|
||||
/* Copyright (c) 2022-2025 Advanced Micro Devices, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <hip/hip_runtime.h>
|
||||
#include "hip_event.hpp"
|
||||
#include "hip_internal.hpp"
|
||||
#include "platform/vmheap.hpp"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -101,8 +102,12 @@ class Heap : public amd::EmbeddedObject {
|
||||
public:
|
||||
typedef std::map<std::pair<size_t, amd::Memory*>, MemoryTimestamp> SortedMap;
|
||||
|
||||
Heap(hip::Device* device):
|
||||
total_size_(0), max_total_size_(0), release_threshold_(0), device_(device) {}
|
||||
Heap(hip::Device* device, amd::VmHeap& vm_heap)
|
||||
: total_size_(0)
|
||||
, max_total_size_(0)
|
||||
, release_threshold_(0)
|
||||
, device_(device)
|
||||
, vm_heap_(vm_heap) {}
|
||||
~Heap() {}
|
||||
|
||||
/// Adds allocation into the heap on a specific stream
|
||||
@@ -134,7 +139,10 @@ public:
|
||||
bool IsEmpty() const { return (allocations_.size() == 0) ? true : false; }
|
||||
|
||||
/// Set the memory release threshold
|
||||
void SetReleaseThreshold(uint64_t value) { release_threshold_ = value; }
|
||||
void SetReleaseThreshold(uint64_t value) {
|
||||
release_threshold_ = value;
|
||||
vm_heap_.SetUnmapThreshold(value);
|
||||
}
|
||||
|
||||
/// Set the memory release threshold
|
||||
uint64_t GetReleaseThreshold() const { return release_threshold_; }
|
||||
@@ -149,7 +157,7 @@ public:
|
||||
void SetMaxTotalSize(uint64_t value) { max_total_size_ = value; }
|
||||
|
||||
/// Erases single allocation form the heap's map
|
||||
SortedMap::iterator EraseAllocaton(SortedMap::iterator& it);
|
||||
SortedMap::iterator EraseAllocation(SortedMap::iterator& it);
|
||||
|
||||
/// Add a safe stream for quick looks-ups in all allocations
|
||||
void AddSafeStream(Stream* event_stream, Stream* wait_stream) {
|
||||
@@ -162,6 +170,13 @@ public:
|
||||
bool IsActiveMemory(amd::Memory* memory) const {
|
||||
return (allocations_.find({memory->getSize(), memory}) != allocations_.end());
|
||||
}
|
||||
|
||||
/// Enabled VM heap for memory, instead of direct allocations
|
||||
void EnableVmHeap() { use_vm_heap_ = true; }
|
||||
|
||||
/// Returns true if heap uses virtual memory
|
||||
bool UseVmHeap() const { return use_vm_heap_; }
|
||||
|
||||
const auto& Allocations() { return allocations_; }
|
||||
|
||||
private:
|
||||
@@ -174,14 +189,16 @@ private:
|
||||
uint64_t max_total_size_; //!< Maximum heap allocation size
|
||||
uint64_t release_threshold_; //!< Threshold size in bytes for memory release from heap, default 0
|
||||
|
||||
hip::Device* device_; //!< Hip device the allocations will reside
|
||||
hip::Device* device_; //!< Hip device the allocations will reside
|
||||
amd::VmHeap& vm_heap_; //!< Managed heap for memory allocaitons
|
||||
bool use_vm_heap_ = false; //!< Use virtual heap or direct allocations
|
||||
};
|
||||
|
||||
/// Allocates memory in the pool on the specified stream and places the allocation into busy_heap_
|
||||
/// @note: the logic also will look in free_heap for possible reuse.
|
||||
/// hipMemPoolReuseAllowOpportunistic option will validate if HIP event,
|
||||
/// associated with memory is done, then reuse can be performed.
|
||||
class MemoryPool : public amd::ReferenceCountedObject {
|
||||
class MemoryPool : public amd::ReferenceCountedObject, amd::VmHeap {
|
||||
public:
|
||||
struct SharedAccess {
|
||||
int device_id_; //!< Device ID for access with a specified shared resource
|
||||
@@ -197,9 +214,10 @@ class MemoryPool : public amd::ReferenceCountedObject {
|
||||
};
|
||||
|
||||
MemoryPool(hip::Device* device, const hipMemPoolProps* props = nullptr, bool phys_mem = false)
|
||||
: busy_heap_(device),
|
||||
free_heap_(device),
|
||||
lock_pool_ops_(true), /* Pool operations */
|
||||
: VmHeap(device->asContext()->devices()[0], *device->NullStream()),
|
||||
busy_heap_(device, *this),
|
||||
free_heap_(device, *this),
|
||||
lock_pool_ops_(true),
|
||||
device_(device),
|
||||
shared_(nullptr),
|
||||
max_total_size_(0) {
|
||||
@@ -220,9 +238,15 @@ class MemoryPool : public amd::ReferenceCountedObject {
|
||||
.reserved = {}};
|
||||
}
|
||||
state_.interprocess_ = properties_.handleTypes != hipMemHandleTypeNone;
|
||||
// Check if VM heap can be enabled
|
||||
if (DEBUG_HIP_MEM_POOL_VMHEAP && !state_.phys_mem_ && !state_.interprocess_) {
|
||||
state_.use_vm_heap_ = true;
|
||||
busy_heap_.EnableVmHeap();
|
||||
free_heap_.EnableVmHeap();
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~MemoryPool() {
|
||||
virtual ~MemoryPool() override {
|
||||
if (!busy_heap_.IsEmpty()) {
|
||||
LogError("Shouldn't destroy pool with busy allocations!");
|
||||
}
|
||||
@@ -320,6 +344,7 @@ class MemoryPool : public amd::ReferenceCountedObject {
|
||||
uint32_t interprocess_ : 1; //!< Memory pool can be used in interprocess communications
|
||||
uint32_t graph_in_use_ : 1; //!< Memory pool was used in a graph execution
|
||||
uint32_t phys_mem_ : 1; //!< Mempool is used for graphs and will have physical allocations
|
||||
uint32_t use_vm_heap_ : 1; //!< Use VM heap or direct allocations
|
||||
};
|
||||
uint32_t value_;
|
||||
} state_;
|
||||
@@ -327,7 +352,8 @@ class MemoryPool : public amd::ReferenceCountedObject {
|
||||
hipMemPoolProps properties_; //!< Properties of the memory pool
|
||||
amd::Monitor lock_pool_ops_; //!< Access to the pool must be lock protected
|
||||
std::map<hip::Device*, hipMemAccessFlags> access_map_; //!< Map of access to the pool from devices
|
||||
hip::Device* device_; //!< Hip device the heap will reside
|
||||
|
||||
hip::Device* device_; //!< Hip device the heap will reside
|
||||
SharedMemPool* shared_; //!< Pointer to shared memory for IPC
|
||||
uint64_t max_total_size_; //!< Max of total reserved memory in the pool since last reset
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user