chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
@@ -0,0 +1,58 @@
set(ALLOCATOR_SRCS
allocator.cc
cpu_allocator.cc
aligned_allocator.cc
buffered_allocator.cc
best_fit_allocator.cc
naive_best_fit_allocator.cc
allocator_strategy.cc
allocator_facade.cc
auto_growth_best_fit_allocator.cc
auto_growth_best_fit_allocator_v2.cc
virtual_memory_auto_growth_best_fit_allocator.cc
retry_allocator.cc
memory_block.cc
memory_block_desc.cc
meta_cache.cc
buddy_allocator.cc
system_allocator.cc)
if(WITH_GPU OR WITH_ROCM)
list(
APPEND
ALLOCATOR_SRCS
cuda_allocator.cc
cuda_managed_allocator.cc
cuda_malloc_async_allocator.cc
pinned_allocator.cc
stream_safe_cuda_allocator.cc
thread_local_allocator.cc)
endif()
if(CUDA_VERSION VERSION_GREATER_EQUAL 10.2)
list(APPEND ALLOCATOR_SRCS cuda_virtual_mem_allocator.cc
cuda_virtual_mem_allocator_v2.cc vmm_backing_map.cc
vmm_auto_growth_best_fit_allocator_v2.cc)
endif()
if(NOT WIN32)
list(APPEND ALLOCATOR_SRCS mmap_allocator.cc)
if(WITH_GPU)
list(APPEND ALLOCATOR_SRCS cuda_ipc_allocator.cc)
endif()
if(WITH_XPU)
list(APPEND ALLOCATOR_SRCS xpu_ipc_allocator.cc)
endif()
endif()
if(WITH_CUSTOM_DEVICE)
list(APPEND ALLOCATOR_SRCS custom_allocator.cc
stream_safe_custom_device_allocator.cc)
endif()
if(WITH_XPU)
list(APPEND ALLOCATOR_SRCS xpu_allocator.cc xpu_pinned_allocator.cc
stream_safe_xpu_allocator.cc)
endif()
collect_srcs(core_srcs SRCS ${ALLOCATOR_SRCS})
@@ -0,0 +1,71 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/aligned_allocator.h"
#include <utility>
#include "paddle/common/macros.h"
#include "paddle/phi/core/enforce.h"
REGISTER_FILE_SYMBOLS(aligned_allocator);
namespace paddle::memory::allocation {
// For memory address alignment
class AlignedAllocation : public Allocation {
public:
AlignedAllocation(DecoratedAllocationPtr underlying_allocation, size_t offset)
: Allocation(
reinterpret_cast<uint8_t*>(underlying_allocation->ptr()) + offset,
underlying_allocation->base_ptr(),
underlying_allocation->size() - offset,
underlying_allocation->place()),
underlying_allocation_(std::move(underlying_allocation)) {}
private:
DecoratedAllocationPtr underlying_allocation_;
};
AlignedAllocator::AlignedAllocator(
std::shared_ptr<Allocator> underlying_allocator, size_t alignment)
: underlying_allocator_(std::move(underlying_allocator)),
alignment_(alignment) {
PADDLE_ENFORCE_GT(
alignment_,
0,
common::errors::InvalidArgument(
"Alignment should be larger than 0, but got %d", alignment_));
if (alignment_ & (alignment_ - 1)) {
PADDLE_THROW(common::errors::InvalidArgument(
"Alignment should be power of 2 (2^N), but got %d", alignment_));
}
}
bool AlignedAllocator::IsAllocThreadSafe() const {
return underlying_allocator_->IsAllocThreadSafe();
}
phi::Allocation* AlignedAllocator::AllocateImpl(size_t size) {
auto raw_allocation = underlying_allocator_->Allocate(size + alignment_);
size_t offset = AlignedPtrOffset(raw_allocation->ptr(), alignment_);
auto* p = new AlignedAllocation(
static_unique_ptr_cast<Allocation>(std::move(raw_allocation)), offset);
return p;
}
void AlignedAllocator::FreeImpl(phi::Allocation* allocation) {
delete allocation;
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,44 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <memory>
#include <utility>
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class PADDLE_API AlignedAllocator : public Allocator {
public:
AlignedAllocator(std::shared_ptr<Allocator> underlying_allocator,
size_t alignment);
bool IsAllocThreadSafe() const override;
protected:
phi::Allocation* AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation* allocation) override;
private:
std::shared_ptr<Allocator> underlying_allocator_;
size_t alignment_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,57 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/backends/gpu/gpu_info.h"
#include "paddle/phi/core/memory/stats.h"
namespace paddle::memory::allocation {
void Allocator::FreeImpl(phi::Allocation* allocation) {
static_cast<Allocation*>(allocation)
->TopDecoratedAllocator()
->Free(allocation);
}
void MultiScalePoolAllocator::RecordAlloc(uintptr_t allocator,
uint64_t id,
size_t size) {
#if defined(PADDLE_WITH_CUDA)
std::lock_guard<SpinLock> lock(spinlock_);
const auto current_device_id = phi::backends::gpu::GetCurrentDeviceId();
const auto max_reserved =
paddle::memory::DeviceMemoryStatPeakValue("Reserved", current_device_id);
const auto cur_allocated = paddle::memory::DeviceMemoryStatCurrentValue(
"Allocated", current_device_id);
allocation_records_.emplace_back(
allocator, true, id, size, cur_allocated, max_reserved);
#endif
}
void MultiScalePoolAllocator::RecordFree(uintptr_t allocator,
uint64_t id,
size_t size) {
#if defined(PADDLE_WITH_CUDA)
std::lock_guard<SpinLock> lock(spinlock_);
const auto current_device_id = phi::backends::gpu::GetCurrentDeviceId();
const auto max_reserved =
paddle::memory::DeviceMemoryStatPeakValue("Reserved", current_device_id);
const auto cur_allocated = paddle::memory::DeviceMemoryStatCurrentValue(
"Allocated", current_device_id);
allocation_records_.emplace_back(
allocator, false, id, size, cur_allocated, max_reserved);
#endif
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,338 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "paddle/common/enforce.h"
#include "paddle/common/flags.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/allocator.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/inlined_vector.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/platform/device/gpu/gpu_types.h"
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/core/cuda_stream.h"
#endif
#ifdef PADDLE_WITH_NCCL
#include <nccl.h>
#include "paddle/phi/backends/dynload/nccl.h"
#endif
COMMON_DECLARE_string(allocator_strategy);
COMMON_DECLARE_bool(sync_after_alloc);
COMMON_DECLARE_int64(alloc_fill_value);
COMMON_DECLARE_bool(record_alloc_event);
namespace paddle {
namespace memory {
class AllocatorVisitor;
namespace allocation {
// Exception when `Alloc`/`AllocShared` failed
struct BadAlloc : public std::exception {
inline explicit BadAlloc(std::string err_msg, const char* file, int line)
: err_str_(phi::enforce::GetCompleteTraceBackString(
std::move(err_msg), file, line)) {}
const char* what() const noexcept override { return err_str_.c_str(); }
std::string err_str_;
};
class Allocator;
// Allocation is the object holding the actually pointer. Use
// `Allocation::ptr()` will returns the pointer that allocated.
//
// NOTE: this is the base class of Allocation. Each allocator can use its own
// allocation object.
// NOTE: the `Allocation::ptr()` could be nullptr, if the allocation size is 0
/**
* Allocation is returned by Allocator::Allocate() method.
*
* An allocator may be decorated by another allocator. For example, we can
* decorate a RetryAllocator to any allocator to perform allocation retry when
* first allocation request fails.
*
* Explanations of Allocator design are as follows:
*
* Suppose we have an allocator which is decorated by several allocators:
*
* A(1) <- A(2) <- A(3) <- ... <- A(n)
*
* , and the public allocator is A(1).
*
* The allocation process would be:
*
* A(n).Allocate() -> ... -> A(2).Allocate() -> A(1).Allocate()
*
* , and the free process would be:
*
* A(1).Free() -> A(2).Free() -> ... -> A(n).Free()
*
* Therefore, we should record the allocator chain when allocating, so
* that we can free the allocation in the reverse order of allocator chain.
* The field `decorated_allocators_` is used to record this chain.
*
* Another example is that we want to add additional fields in Allocation,
* e.g., something what is done in AlignedAllocator, etc.
* In this case, we should declare a derived class of Allocation, which
* contains an underlying Allocation allocated by the underlying allocator.
* Therefore, `decorated_allocators_` of the new Allocation object
* would
* be a new chain, differing from the underlying Allocation object.
*/
class Allocation : public phi::Allocation {
public:
Allocation(void* ptr, size_t size, Place place)
: phi::Allocation(ptr, size, place), base_ptr_(ptr) {}
Allocation(void* ptr, void* base_ptr, size_t size, const Place& place)
: phi::Allocation(ptr, size, place), base_ptr_(base_ptr) {}
void* base_ptr() const { return base_ptr_; }
private:
inline void RegisterDecoratedAllocator(Allocator* allocator) {
decorated_allocators_.emplace_back(allocator);
}
inline void PopDecoratedAllocator() { decorated_allocators_.pop_back(); }
inline Allocator* TopDecoratedAllocator() {
return decorated_allocators_.back();
}
private:
void* base_ptr_; // the point that directly requested from system
/**
* NOTE(zjl): Since decorated_allocators_ is usually a small vector.
* We reserve a small buffer to it to prevent frequent heap allocation
*
* Instead, we can use a std::vector<Allocator *> here, and reserve
* kReserveAllocatorNum in constructor of Allocation.
* But using std::vector<Allocator *> would make ocr recognition model
* fail in CE. The train duration is 8% slower than KPI.
*/
static constexpr size_t kReserveAllocatorNum = 8;
using DecoratedAllocatorStack =
InlinedVector<Allocator*, kReserveAllocatorNum>;
DecoratedAllocatorStack decorated_allocators_;
friend class Allocator;
friend class MultiScalePoolAllocator;
};
using AllocationPtr = phi::Allocator::AllocationPtr;
using DecoratedAllocationPtr =
std::unique_ptr<Allocation, phi::Allocator::DeleterType>;
template <typename T>
static T&& FillValue(T&& allocation) {
#if defined(PADDLE_WITH_CUDA)
if (allocation != nullptr) {
if (FLAGS_sync_after_alloc || FLAGS_alloc_fill_value >= 0) {
bool need_sync = !phi::is_cpu_place(allocation->place());
if (need_sync) {
PADDLE_ENFORCE_GPU_SUCCESS(cudaDeviceSynchronize());
}
if (FLAGS_alloc_fill_value >= 0) {
PADDLE_ENFORCE_LE(FLAGS_alloc_fill_value,
255,
common::errors::InvalidArgument(
"The value of FLAGS_alloc_fill_value must be in "
"range [0, 255]. Expected 0 <= "
"FLAGS_alloc_fill_value <= 255, but received "
"FLAGS_alloc_fill_value = %ld.",
FLAGS_alloc_fill_value));
const int fill_value = static_cast<int>(FLAGS_alloc_fill_value);
if (phi::is_gpu_place(allocation->place())) {
PADDLE_ENFORCE_GPU_SUCCESS(
cudaMemset(allocation->ptr(), fill_value, allocation->size()));
} else {
std::memset(allocation->ptr(), fill_value, allocation->size());
}
if (need_sync) {
PADDLE_ENFORCE_GPU_SUCCESS(cudaDeviceSynchronize());
}
}
}
}
#endif
return std::forward<T>(allocation);
}
// Base interface class of memory Allocator.
class PADDLE_API Allocator : public phi::Allocator {
public:
static void AllocationDeleter(phi::Allocation* allocation) {
Allocator* allocator =
static_cast<Allocation*>(allocation)->TopDecoratedAllocator();
allocator->Free(allocation);
}
// Allocate an allocation.
// size may be 0, but it would be too complex if we handle size == 0
// in each Allocator. So we handle size == 0 inside AllocatorFacade
// in our design.
AllocationPtr Allocate(size_t size) override {
auto* ptr = AllocateImpl(size);
static_cast<Allocation*>(ptr)->RegisterDecoratedAllocator(this);
return FillValue(AllocationPtr(ptr, AllocationDeleter));
}
void Free(phi::Allocation* allocation) override {
static_cast<Allocation*>(allocation)->PopDecoratedAllocator();
FreeImpl(allocation);
}
uint64_t Release(const Place& place) { return ReleaseImpl(place); }
size_t Compact(const Place& place) { return CompactImpl(place); }
virtual void Accept(AllocatorVisitor* visitor);
protected:
virtual phi::Allocation* AllocateImpl(size_t size) = 0;
virtual void FreeImpl(phi::Allocation* allocation);
virtual uint64_t ReleaseImpl(const Place& place UNUSED) { return 0; }
virtual size_t CompactImpl(const Place& place UNUSED) {
PADDLE_THROW(phi::errors::Unimplemented("Compact is not supported"));
return 0;
}
};
inline size_t AlignedSize(size_t size, size_t alignment) {
auto remaining = size % alignment; // NOLINT
return remaining == 0 ? size : size + alignment - remaining;
}
inline size_t AlignedPtrOffset(const void* ptr, size_t alignment) {
auto ptr_addr = reinterpret_cast<uintptr_t>(ptr);
auto diff = ptr_addr % alignment;
return diff == 0 ? 0 : alignment - diff;
}
template <typename Derived, typename Base, typename BaseDel>
decltype(auto) static_unique_ptr_cast(std::unique_ptr<Base, BaseDel>&& p) {
static_assert(std::is_base_of<Base, Derived>::value,
"Derived type must derive from Base.");
auto d = static_cast<Derived*>(p.release());
return std::unique_ptr<Derived, BaseDel>(d, p.get_deleter());
}
/**
* \brief MultiScalePoolAllocator is a decorator of Allocator.
* It allocates small request from small_allocator and large request from
* large_allocator.
*/
class PADDLE_API MultiScalePoolAllocator : public Allocator {
public:
MultiScalePoolAllocator(const std::shared_ptr<Allocator>& small_allocator,
const std::shared_ptr<Allocator>& large_allocator,
size_t alignment,
const GPUPlace& place)
: small_allocator_(small_allocator),
large_allocator_(large_allocator),
alignment_(alignment),
place_(place) {}
// Allocate an allocation from small_allocator or large_allocator according to
// size.
AllocationPtr Allocate(size_t size) override {
auto allocation = IsSmallRequest(size) ? small_allocator_->Allocate(size)
: large_allocator_->Allocate(size);
static_cast<Allocation*>(allocation.get())
->RegisterDecoratedAllocator(this);
if (FLAGS_record_alloc_event) {
uint64_t id = global_seq_counter_.fetch_add(1, std::memory_order_relaxed);
uintptr_t allocator_instance = reinterpret_cast<uintptr_t>(this);
RecordAlloc(allocator_instance, id, size);
allocation->set_id(id);
}
return allocation;
};
// Free an allocation from small_allocator or large_allocator.
void Free(phi::Allocation* allocation) override {
if (FLAGS_record_alloc_event) {
uint64_t id = allocation->id();
uintptr_t allocator_instance = reinterpret_cast<uintptr_t>(this);
RecordFree(allocator_instance, id, allocation->size());
}
auto* decorated_allocation = static_cast<Allocation*>(allocation);
decorated_allocation->PopDecoratedAllocator();
Allocator* underlying_allocator =
decorated_allocation->TopDecoratedAllocator();
PADDLE_ENFORCE_EQ(
underlying_allocator == small_allocator_.get() ||
underlying_allocator == large_allocator_.get(),
true,
common::errors::InvalidArgument(
"MultiScalePoolAllocator found an unexpected underlying "
"allocator when freeing allocation %p.",
allocation->ptr()));
underlying_allocator->Free(allocation);
};
// Get allocate event when start FLAGS_record_alloc_event.
std::vector<std::tuple<uintptr_t, bool, uint64_t, size_t, int64_t, int64_t>>
GetEvents() {
std::lock_guard<SpinLock> lock(spinlock_);
return allocation_records_;
}
// Get small_allocator_ and large_allocator_.
std::shared_ptr<Allocator>& GetSmallAllocator() { return small_allocator_; }
std::shared_ptr<Allocator>& GetLargeAllocator() { return large_allocator_; }
virtual bool IsSmallRequest(size_t size) = 0;
private:
phi::Allocation* AllocateImpl(size_t UNUSED) override { return nullptr; }
std::shared_ptr<Allocator> small_allocator_;
std::shared_ptr<Allocator> large_allocator_;
size_t alignment_;
Place place_;
// Record allocate event into `allocation_records_` when
// `FLAGS_record_alloc_event` is True.
void RecordAlloc(uintptr_t allocator, uint64_t id, size_t size);
// Record free event into `allocation_records_` when
// `FLAGS_record_alloc_event` is True.
void RecordFree(uintptr_t allocator, uint64_t id, size_t size);
// Return tuple is <allocator_instance, is_allocate, id, allocate_size,
// cur_allocated, max_reserved>, if more fields are added later, consider
// using a struct to combine them.
std::vector<std::tuple<uintptr_t, bool, uint64_t, size_t, int64_t, int64_t>>
allocation_records_;
SpinLock spinlock_;
static inline std::atomic<uint64_t> global_seq_counter_{0};
};
} // namespace allocation
} // namespace memory
} // namespace paddle
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,159 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <memory>
#include "paddle/phi/core/memory/allocation/allocator.h"
#ifdef PADDLE_WITH_CUDA
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
#endif
#ifdef PADDLE_WITH_XPU
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
#endif
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/stream.h"
#ifdef PADDLE_WITH_CUSTOM_DEVICE
#include "paddle/phi/backends/device_manager.h"
#include "paddle/phi/core/memory/allocation/custom_allocator.h"
#endif
namespace paddle {
namespace memory {
class AllocatorVisitor;
namespace allocation {
// Allocator Facade is the interface exposed to other modules.
// All the configuration or dirty code under development should
// be hidden behind this facade.
//
// NOTE(yy): This class is a singleton class.
// NOTE(yy): To create a stable ABI and make compilation faster. Here we use
// a Pimpl trick;
class AllocatorFacadePrivate;
class AllocatorFacade {
public:
using Allocation = phi::Allocation;
AllocatorFacade(const AllocatorFacade& o) = delete;
const AllocatorFacade& operator=(const AllocatorFacade& o) = delete;
~AllocatorFacade();
PADDLE_API static AllocatorFacade& Instance();
AllocatorFacadePrivate* GetPrivate() const;
PADDLE_API const std::shared_ptr<Allocator>& GetAllocator(const Place& place);
PADDLE_API const std::shared_ptr<Allocator>& GetAutoGrowthAllocator(
const Place& place);
void* GetBasePtr(const std::shared_ptr<Allocation>& allocation);
PADDLE_API const std::shared_ptr<Allocator>& GetZeroAllocator(
const Place& place);
// Allocate a shared allocation.
std::shared_ptr<Allocation> AllocShared(const Place& place, size_t size);
// Allocate a unique allocation.
PADDLE_API AllocationPtr Alloc(const Place& place, size_t size);
// Release unused memory pool.
uint64_t Release(const Place& place);
// Compact memory of free blocks held by the VmmAllocator.
size_t Compact(const Place& place);
/**
* @brief Accepts an AllocatorVisitor and iterates over all nested Allocator
* instances associated with a specific memory location (Place), executing the
* visitor's corresponding Visit method for each one.
*
* This method facilitates the traversal of the Allocator hierarchy for the
* given memory Place, allowing the visitor to collect statistics or perform
* operations on all constituent allocators.
*
* @param place The memory location
* @param visitor A pointer to the AllocatorVisitor whose Visit methods will
* be executed against the nested allocators found at the specified Place.
*/
void Accept(const Place& place, AllocatorVisitor* visitor);
std::shared_ptr<Allocation> AllocShared(const Place& place,
size_t size,
const phi::Stream& stream);
AllocationPtr Alloc(const Place& place,
size_t size,
const phi::Stream& stream);
bool InSameStream(const std::shared_ptr<Allocation>& allocation,
const phi::Stream& stream);
PADDLE_API bool IsStreamSafeCUDAAllocatorUsed();
PADDLE_API bool IsCUDAMallocAsyncAllocatorUsed();
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
// TODO(zhiqiu): change gpuStream_t to phi::Stream if needed.
uint64_t Release(const GPUPlace& place, gpuStream_t stream);
bool RecordStream(std::shared_ptr<Allocation> allocation, gpuStream_t stream);
void EraseStream(std::shared_ptr<Allocation> allocation, gpuStream_t stream);
PADDLE_API const std::shared_ptr<Allocator>& GetAllocator(const Place& place,
gpuStream_t stream);
gpuStream_t GetStream(const std::shared_ptr<Allocation>& allocation) const;
void SetDefaultStream(const GPUPlace& place, gpuStream_t stream);
#elif defined(PADDLE_WITH_XPU)
PADDLE_API const std::shared_ptr<Allocator>& GetAllocator(const Place& place,
XPUStream stream);
bool RecordStream(std::shared_ptr<Allocation> allocation, XPUStream stream);
void SetDefaultStream(const XPUPlace& place, XPUStream stream);
void EraseStream(std::shared_ptr<Allocation> allocation, XPUStream stream);
#endif
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) || \
defined(PADDLE_WITH_CUSTOM_DEVICE)
void PrepareMemoryPoolForCUDAGraph(int64_t id);
void RemoveMemoryPoolOfCUDAGraph(int64_t id);
#endif
#if defined(PADDLE_WITH_XPU)
void RemoveMemoryPoolOfXPUGraph(int64_t id);
void PrepareMemoryPoolForXPUGraph(int64_t id);
#endif
#ifdef PADDLE_WITH_CUSTOM_DEVICE
uint64_t Release(const CustomPlace& place, phi::stream::stream_t stream);
bool RecordStream(std::shared_ptr<Allocation> allocation,
phi::stream::stream_t stream);
void EraseStream(std::shared_ptr<Allocation> allocation,
phi::stream::stream_t stream);
PADDLE_API const std::shared_ptr<Allocator>& GetAllocator(
const Place& place, phi::stream::stream_t stream);
phi::stream::stream_t GetStream(
const std::shared_ptr<Allocation>& allocation) const;
void SetDefaultStream(const CustomPlace& place, phi::stream::stream_t stream);
#endif
// TODO(yy): Allocate a Copy-On-Write allocation?
private:
AllocatorFacade();
AllocatorFacadePrivate* m_;
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP) || \
defined(PADDLE_WITH_CUSTOM_DEVICE) || defined(PADDLE_WITH_XPU)
std::unordered_map<int64_t, std::unique_ptr<AllocatorFacadePrivate>>
cuda_graph_map_;
std::unordered_map<int64_t, int64_t> cuda_graph_ref_cnt_;
#endif
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,53 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/allocator_strategy.h"
#include "paddle/common/flags.h"
#include "paddle/phi/core/enforce.h"
COMMON_DECLARE_string(allocator_strategy);
namespace paddle {
namespace memory {
namespace allocation {
static AllocatorStrategy GetStrategyFromFlag() {
if (FLAGS_allocator_strategy == "naive_best_fit") {
return AllocatorStrategy::kNaiveBestFit;
}
if (FLAGS_allocator_strategy == "auto_growth") {
return AllocatorStrategy::kAutoGrowth;
}
if (FLAGS_allocator_strategy == "thread_local") {
return AllocatorStrategy::kThreadLocal;
}
PADDLE_THROW(common::errors::InvalidArgument(
"Unsupported allocator strategy: %s, candidates are naive_best_fit, "
"auto_growth or thread_local.",
FLAGS_allocator_strategy));
}
AllocatorStrategy GetAllocatorStrategy() {
static AllocatorStrategy strategy = GetStrategyFromFlag();
return strategy;
}
void UseAllocatorStrategyGFlag() {}
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,32 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/common/macros.h"
#include "paddle/utils/test_macros.h"
namespace paddle {
namespace memory {
namespace allocation {
enum class AllocatorStrategy { kNaiveBestFit, kAutoGrowth, kThreadLocal };
extern AllocatorStrategy GetAllocatorStrategy();
// Do nothing, just make sure linker do not prune this file.
PADDLE_API void UseAllocatorStrategyGFlag();
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,390 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/auto_growth_best_fit_allocator.h"
#include <algorithm>
#include <mutex> // NOLINT
#include <utility>
#include "glog/logging.h"
#include "paddle/common/flags.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/backends/device_manager.h"
#include "paddle/phi/core/memory/allocation/aligned_allocator.h"
PHI_DEFINE_EXPORTED_READONLY_bool(
free_idle_chunk,
false,
"Whether to free idle chunk when each allocation is freed. "
"If false, all freed allocation would be cached to speed up next "
"allocation request. If true, no allocation would be cached. This "
"flag only works when FLAGS_allocator_strategy=auto_growth.");
PHI_DEFINE_EXPORTED_READONLY_bool(
free_when_no_cache_hit,
false,
"Whether to free idle chunks when no cache hit. If true, idle "
"chunk would be freed when no cache hit; if false, idle "
"chunk would be freed when out of memory occurs. This flag "
"only works when FLAGS_allocator_strategy=auto_growth.");
PHI_DEFINE_EXPORTED_READONLY_bool(print_allocator_trace_info,
false,
"print trace memory info");
PHI_DEFINE_EXPORTED_READONLY_bool(dump_chunk_info, false, "dump chunk info");
PHI_DEFINE_EXPORTED_uint64(
alignment_size,
256,
"All sizes are rounded up to a multiple of this value. Default: 256.");
PHI_DEFINE_EXPORTED_uint64(
small_pool_size_in_mb,
0,
"Threshold (MiB) separating the small and large pools. "
"0 disables the small pool and enables single-pool mode "
"(all requests go to the large pool). When > 0, requests "
"<= threshold use the small pool; larger requests use the "
"large pool. Default: 0.");
PHI_DEFINE_EXPORTED_uint64(small_pool_auto_growth_chunk_size_in_mb,
0,
"The minimal chunk size for the small pool in MiB. "
"If small_pool_size_in_mb > 0, this overrides "
"the constructor-provided global growth size "
"(FLAGS_auto_growth_chunk_size_in_mb).");
PHI_DEFINE_EXPORTED_uint64(large_pool_auto_growth_chunk_size_in_mb,
0,
"The minimal chunk size for the large pool in MiB. "
"If small_pool_size_in_mb > 0, this overrides "
"the constructor-provided global growth size "
"(FLAGS_auto_growth_chunk_size_in_mb).");
PHI_DEFINE_EXPORTED_uint64(
large_pool_pre_alloc_in_mb,
0,
"Pre-reserve this many MiB in the large pool. 0 disables pre-allocation.");
PHI_DEFINE_EXPORTED_uint64(
small_pool_pre_alloc_in_mb,
0,
"Pre-reserve this many MiB in the small pool. 0 disables pre-allocation.");
namespace paddle::memory::allocation {
AutoGrowthBestFitAllocator::AutoGrowthBestFitAllocator(
std::shared_ptr<Allocator> underlying_allocator,
size_t alignment,
size_t chunk_size,
bool allow_free_idle_chunk,
int extra_padding_size)
: underlying_allocator_(std::move(underlying_allocator)),
alignment_(alignment),
chunk_size_(std::max(AlignedSize(chunk_size, alignment), alignment)),
allow_free_idle_chunk_(allow_free_idle_chunk),
extra_padding_size_(extra_padding_size) {
total_alloc_times_ = 0;
total_alloc_size_ = 0;
total_free_times_ = 0;
total_free_size_ = 0;
VLOG(7) << "chunk_size_:" << chunk_size_;
}
void AutoGrowthBestFitAllocator::DumpInfo() const {
for (auto chunk_it = chunks_.begin(); chunk_it != chunks_.end(); ++chunk_it) {
std::cout << "Chunk\t";
std::ostringstream oss_used;
std::ostringstream oss_free;
size_t total = 0, free = 0, used = 0;
for (auto &b : chunk_it->blocks_) {
total += b.size_;
if (b.is_free_) {
free += b.size_;
oss_free << "(" << b.size_ << "," << b.ptr_ << ")";
} else {
used += b.size_;
oss_used << "(" << b.size_ << "," << b.ptr_ << ")";
}
}
std::cout << total << "\t" << used << "\t" << free << "\t";
std::cout << "[" << oss_used.str() << "]\t[" << oss_free.str() << "]"
<< std::endl;
}
}
bool AutoGrowthBestFitAllocator::is_small_free_block(size_t size) {
auto small_pool_size = FLAGS_small_pool_size_in_mb << 20;
if (size <= small_pool_size) {
return true;
} else {
return false;
}
}
size_t AutoGrowthBestFitAllocator::auto_growth_size(bool is_small,
size_t chunk_size) {
// fallback to single pool and use constructor-provided chunk_size.
if (FLAGS_small_pool_size_in_mb == 0) {
return chunk_size;
}
const uint64_t pool_auto_growth_chunk_size_mb =
is_small ? FLAGS_small_pool_auto_growth_chunk_size_in_mb
: FLAGS_large_pool_auto_growth_chunk_size_in_mb;
const size_t auto_growth_size =
pool_auto_growth_chunk_size_mb
? (static_cast<size_t>(pool_auto_growth_chunk_size_mb) << 20)
: 0;
return AlignedSize(auto_growth_size, alignment_);
}
void AutoGrowthBestFitAllocator::PreAlloc() {
auto small_pool_pre_alloc = FLAGS_small_pool_pre_alloc_in_mb << 20;
auto large_pool_pre_alloc = FLAGS_large_pool_pre_alloc_in_mb << 20;
if (small_pool_pre_alloc > 0) {
VLOG(10) << "PreAlloc small_pool_pre_alloc_in_mb = "
<< FLAGS_small_pool_pre_alloc_in_mb;
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(small_pool_pre_alloc)));
auto *chunk = &(*chunks_.rbegin());
uint8_t *p = reinterpret_cast<uint8_t *>(chunk->allocation_->ptr());
auto &blocks = chunk->blocks_;
blocks.emplace_back(
p, small_pool_pre_alloc, /*is_free=*/true, /*is_small=*/true, chunk);
small_free_blocks_.emplace(std::make_pair(small_pool_pre_alloc, p),
--(blocks.end()));
}
if (large_pool_pre_alloc > 0) {
VLOG(10) << "PreAlloc large_pool_pre_alloc_in_mb = "
<< FLAGS_large_pool_pre_alloc_in_mb;
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(large_pool_pre_alloc)));
auto *chunk = &(*chunks_.rbegin());
uint8_t *p = reinterpret_cast<uint8_t *>(chunk->allocation_->ptr());
auto &blocks = chunk->blocks_;
blocks.emplace_back(
p, large_pool_pre_alloc, /*is_free=*/true, /*is_small=*/false, chunk);
large_free_blocks_.emplace(std::make_pair(large_pool_pre_alloc, p),
--(blocks.end()));
}
}
phi::Allocation *AutoGrowthBestFitAllocator::AllocateImpl(
size_t unaligned_size) {
phi::RecordEvent record("AutoGrowthBestFitAllocator::Allocate",
phi::TracerEventType::UserDefined,
9 /*level*/);
size_t size = AlignedSize(unaligned_size + extra_padding_size_, alignment_);
VLOG(10) << "Allocate " << unaligned_size << " bytes, aligned to " << size
<< ", extra size " << extra_padding_size_;
std::lock_guard<SpinLock> guard(spinlock_);
bool is_small = is_small_free_block(size);
auto &free_blocks = is_small ? small_free_blocks_ : large_free_blocks_;
auto iter = free_blocks.lower_bound(std::make_pair(size, nullptr));
BlockIt block_it;
if (iter != free_blocks.end()) {
block_it = iter->second;
free_blocks.erase(iter);
auto *chunk = block_it->chunk_;
size_t remaining_size = block_it->size_ - size;
VLOG(10) << "Allocate " << size << " bytes from chunk size "
<< block_it->size_ << ", remaining " << remaining_size;
if (remaining_size == 0) {
block_it->is_free_ = false;
block_it->is_small_ = is_small;
} else {
auto remaining_free_block = chunk->blocks_.insert(
block_it,
Block(block_it->ptr_, remaining_size, true, is_small, chunk));
free_blocks.emplace(std::make_pair(remaining_size, block_it->ptr_),
remaining_free_block);
block_it->ptr_ =
reinterpret_cast<uint8_t *>(block_it->ptr_) + remaining_size;
block_it->size_ = size;
block_it->is_free_ = false;
block_it->is_small_ = is_small;
}
} else {
if (FLAGS_dump_chunk_info) {
std::cout << "MemDbg memory not enough growth chunk, need size = " << size
<< std::endl;
DumpInfo();
}
if (FLAGS_free_when_no_cache_hit) {
FreeIdleChunks();
}
size_t realloc_size =
std::max(size, auto_growth_size(is_small, chunk_size_));
try {
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(realloc_size)));
} catch (BadAlloc &ex) {
if (FLAGS_dump_chunk_info) {
std::cout << "MemDbg OOM" << std::endl;
DumpInfo();
}
if (FLAGS_free_when_no_cache_hit) throw ex;
FreeIdleChunks();
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(realloc_size)));
}
auto *chunk = &(*chunks_.rbegin());
realloc_size = chunk->allocation_->size();
uint8_t *p = reinterpret_cast<uint8_t *>(chunk->allocation_->ptr());
auto &blocks = chunk->blocks_;
size_t remaining_size = realloc_size - size;
if (remaining_size > 0) {
blocks.emplace_back(p, remaining_size, true, is_small, chunk);
free_blocks.emplace(std::make_pair(remaining_size, p), --(blocks.end()));
}
blocks.emplace_back(p + remaining_size, size, false, is_small, chunk);
block_it = --(blocks.end());
VLOG(5) << "Not found and reallocate " << realloc_size << "("
<< static_cast<void *>(p) << "), and remaining " << remaining_size;
if (FLAGS_dump_chunk_info) {
std::cout << "MemDbg memory after growth chunk, realloc_size = "
<< realloc_size << std::endl;
DumpInfo();
}
}
++total_alloc_times_;
total_alloc_size_ += size;
VLOG(10) << "Alloc " << block_it->size_ << " bytes, ptr = " << block_it->ptr_;
auto block_t = new BlockAllocation(block_it);
return block_t;
}
void AutoGrowthBestFitAllocator::FreeImpl(phi::Allocation *allocation) {
phi::RecordEvent record("AutoGrowthBestFitAllocator::Free",
phi::TracerEventType::UserDefined,
9 /*level*/);
VLOG(10) << "Free " << allocation->size()
<< " bytes, ptr = " << allocation->ptr();
std::lock_guard<SpinLock> guard(spinlock_);
auto block_it = static_cast<BlockAllocation *>(allocation)->block_it_;
auto &blocks = block_it->chunk_->blocks_;
bool is_small = block_it->is_small_;
auto &free_blocks = is_small ? small_free_blocks_ : large_free_blocks_;
total_free_times_ += 1;
total_free_size_ += block_it->size_;
block_it->is_free_ = true;
if (block_it != blocks.begin()) {
auto prev_it = block_it;
--prev_it;
if (prev_it->is_free_) {
free_blocks.erase(std::make_pair(prev_it->size_, prev_it->ptr_));
prev_it->size_ += block_it->size_;
blocks.erase(block_it);
block_it = prev_it;
}
}
auto next_it = block_it;
++next_it;
// It's weird that using `next_it == blocks.end()` will cause a judgment fail.
if (block_it != (--blocks.end()) && next_it->is_free_) {
free_blocks.erase(std::make_pair(next_it->size_, next_it->ptr_));
block_it->size_ += next_it->size_;
blocks.erase(next_it);
}
free_blocks.emplace(std::make_pair(block_it->size_, block_it->ptr_),
block_it);
delete allocation;
if (FLAGS_free_idle_chunk) {
FreeIdleChunks();
}
if (FLAGS_dump_chunk_info) {
DumpInfo();
}
}
uint64_t AutoGrowthBestFitAllocator::FreeIdleChunks() {
if (FLAGS_dump_chunk_info) {
std::cout << "FreeIdleChunks called" << std::endl;
}
if (!allow_free_idle_chunk_) {
return 0;
}
uint64_t bytes = 0;
for (auto chunk_it = chunks_.begin(); chunk_it != chunks_.end();) {
auto &blocks = chunk_it->blocks_;
if (blocks.size() == 1 && blocks.begin()->is_free_) {
auto &block = *blocks.begin();
bool is_small = block.is_small_;
auto &free_blocks = is_small ? small_free_blocks_ : large_free_blocks_;
VLOG(2) << "Free chunk with size " << block.size_;
if (FLAGS_dump_chunk_info) {
std::cout << "FreeIdleChunks chunk is " << block.size_ << ", "
<< block.ptr_ << std::endl;
}
bytes += block.size_;
free_blocks.erase(std::make_pair(block.size_, block.ptr_));
chunk_it = chunks_.erase(chunk_it);
} else {
++chunk_it;
}
}
if (FLAGS_print_allocator_trace_info) {
Trace();
}
return bytes;
}
void AutoGrowthBestFitAllocator::Trace() const {
size_t small_cur_idle_bytes = 0;
auto small_it = small_free_blocks_.begin();
for (; small_it != small_free_blocks_.end(); ++small_it) {
small_cur_idle_bytes += small_it->second->size_;
}
size_t large_cur_idle_bytes = 0;
auto large_it = large_free_blocks_.begin();
for (; large_it != large_free_blocks_.end(); ++large_it) {
large_cur_idle_bytes += large_it->second->size_;
}
VLOG(1) << "alloc:"
<< total_alloc_size_ / static_cast<double>(1024 * 1024) // NOLINT
<< "m free:"
<< total_free_size_ / static_cast<double>(1024 * 1024) // NOLINT
<< "m busy:"
<< (total_alloc_size_ - total_free_size_) / // NOLINT
static_cast<double>(1024 * 1024)
<< "m small idle:"
<< small_cur_idle_bytes / static_cast<double>(1024 * 1024) // NOLINT
<< "m large idle:"
<< large_cur_idle_bytes / static_cast<double>(1024 * 1024) // NOLINT
<< "m alloc_times:" << total_alloc_times_
<< " free_times:" << total_free_times_
<< " small free_blocks_num:" << small_free_blocks_.size()
<< " large free_blocks_num:" << large_free_blocks_.size()
<< " curr_chunks_num:" << chunks_.size();
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,135 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <list>
#include <map>
#include <memory>
#include <mutex> // NOLINT
#include <utility>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/mem_visitor.h"
COMMON_DECLARE_bool(enable_auto_growth_allocator_add_lock);
namespace paddle {
namespace memory {
class AllBlocksInfoVisitor;
namespace allocation {
class PADDLE_API AutoGrowthBestFitAllocator : public Allocator {
public:
AutoGrowthBestFitAllocator(std::shared_ptr<Allocator> underlying_allocator,
size_t alignment,
size_t chunk_size = 0,
bool allow_free_idle_chunk = true,
int extra_padding_size = 0);
bool IsAllocThreadSafe() const override { return true; }
void DumpInfo() const;
void PreAlloc() override;
void Accept(AllocatorVisitor *visitor) override { visitor->Visit(this); }
protected:
phi::Allocation *AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation *allocation) override;
bool is_small_free_block(size_t size);
size_t auto_growth_size(bool is_small, size_t chunk_size);
// Release the memory block which is not used in pool.
uint64_t ReleaseImpl(const Place &place) override {
// TODO(vivienfanghuagood): the next line may cause the process to deadlock.
if (FLAGS_enable_auto_growth_allocator_add_lock) {
std::lock_guard<SpinLock> guard(spinlock_);
return FreeIdleChunks();
}
return FreeIdleChunks();
}
protected:
uint64_t FreeIdleChunks();
void Trace() const;
template <typename T>
using List = std::list<T>;
struct Chunk;
struct Block {
Block(void *ptr, size_t size, bool is_free, bool is_small, Chunk *chunk)
: ptr_(ptr),
size_(size),
is_free_(is_free),
is_small_(is_small),
chunk_(chunk) {}
void *ptr_;
size_t size_;
bool is_free_;
bool is_small_;
Chunk *chunk_; // which chunk it is from
};
struct Chunk {
explicit Chunk(DecoratedAllocationPtr allocation)
: allocation_(std::move(allocation)) {}
DecoratedAllocationPtr allocation_;
List<Block> blocks_;
};
struct BlockAllocation : public Allocation {
explicit BlockAllocation(const List<Block>::iterator &it)
: Allocation(it->ptr_,
it->chunk_->allocation_->base_ptr(),
it->size_,
it->chunk_->allocation_->place()),
block_it_(it) {}
List<Block>::iterator block_it_;
};
using BlockIt = List<Block>::iterator;
std::shared_ptr<Allocator> underlying_allocator_;
std::map<std::pair<size_t, void *>, BlockIt> small_free_blocks_;
std::map<std::pair<size_t, void *>, BlockIt> large_free_blocks_;
std::list<Chunk> chunks_;
size_t alignment_;
size_t chunk_size_;
bool allow_free_idle_chunk_;
int extra_padding_size_;
// stat info
size_t total_alloc_times_;
size_t total_alloc_size_;
size_t total_free_times_;
size_t total_free_size_;
SpinLock spinlock_;
friend class paddle::memory::AllBlocksInfoVisitor;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,165 @@
// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/phi/core/memory/allocation/auto_growth_best_fit_allocator_v2.h"
#include <algorithm>
#include <mutex> // NOLINT
#include "glog/logging.h"
#include "paddle/common/flags.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/backends/device_manager.h"
#include "paddle/phi/core/memory/allocation/aligned_allocator.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
PD_DECLARE_bool(free_idle_chunk);
PD_DECLARE_bool(free_when_no_cache_hit);
namespace paddle::memory::allocation {
AutoGrowthBestFitAllocatorV2::AutoGrowthBestFitAllocatorV2(
const std::shared_ptr<Allocator> &underlying_allocator,
size_t alignment,
GPUPlace place,
size_t chunk_size,
bool allow_free_idle_chunk,
int extra_padding_size)
: AutoGrowthBestFitAllocator(underlying_allocator,
alignment,
chunk_size,
true,
extra_padding_size),
place_(place) {}
phi::Allocation *AutoGrowthBestFitAllocatorV2::AllocateImpl(
size_t unaligned_size) {
phi::RecordEvent record("AutoGrowthBestFitAllocatorV2::Allocate",
phi::TracerEventType::UserDefined,
9 /*level*/);
size_t size = AlignedSize(unaligned_size + extra_padding_size_, alignment_);
VLOG(10) << "Allocate " << unaligned_size << " bytes, aligned to " << size
<< ", extra size " << extra_padding_size_;
std::lock_guard<SpinLock> guard(spinlock_);
BlockIt block_it;
if (AutoGrowthBestFitAllocatorV2State::GetInstance().IsWarmup()) {
auto iter = free_blocks_.lower_bound(std::make_pair(size, nullptr));
if (iter != free_blocks_.end() && iter->second->size_ >= unaligned_size &&
iter->second->size_ <= size) {
block_it = iter->second;
free_blocks_.erase(iter);
block_it->is_free_ = false;
VLOG(10) << "Allocate " << size << " bytes from chunk size "
<< block_it->size_ << " by strict_matching_state.";
} else {
size_t actual_avail, actual_total;
{
platform::CUDADeviceGuard guard(place_.device);
#ifdef PADDLE_WITH_HIP
auto result = hipMemGetInfo(&actual_avail, &actual_total);
#else
auto result = cudaMemGetInfo(&actual_avail, &actual_total);
#endif
if (result != gpuSuccess) {
actual_avail = 0;
}
}
if (actual_avail < size) {
FreeIdleChunks();
}
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(size)));
auto *chunk = &(*chunks_.rbegin());
size = chunk->allocation_->size();
uint8_t *p = reinterpret_cast<uint8_t *>(chunk->allocation_->ptr());
auto &blocks = chunk->blocks_;
blocks.emplace_back(p, size, false, true, chunk);
block_it = --(blocks.end());
VLOG(2) << "Not found and reallocate " << size << "("
<< static_cast<void *>(p) << ") by strict_matching_state.";
}
} else {
if (is_first_switch_to_regular_) {
FreeIdleChunks();
is_first_switch_to_regular_ = false;
}
auto iter = free_blocks_.lower_bound(std::make_pair(size, nullptr));
if (iter != free_blocks_.end()) {
block_it = iter->second;
free_blocks_.erase(iter);
auto *chunk = block_it->chunk_;
size_t remaining_size = block_it->size_ - size;
VLOG(10) << "Allocate " << size << " bytes from chunk size "
<< block_it->size_ << ", remaining " << remaining_size;
if (remaining_size == 0) {
block_it->is_free_ = false;
} else {
auto remaining_free_block = chunk->blocks_.insert(
block_it, Block(block_it->ptr_, remaining_size, true, true, chunk));
free_blocks_.emplace(std::make_pair(remaining_size, block_it->ptr_),
remaining_free_block);
block_it->ptr_ =
reinterpret_cast<uint8_t *>(block_it->ptr_) + remaining_size;
block_it->size_ = size;
block_it->is_free_ = false;
}
} else {
if (FLAGS_free_when_no_cache_hit) {
FreeIdleChunks();
}
size_t realloc_size = std::max(size, chunk_size_);
try {
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(realloc_size)));
} catch (BadAlloc &ex) {
if (FLAGS_free_when_no_cache_hit) throw ex;
FreeIdleChunks();
chunks_.emplace_back(static_unique_ptr_cast<Allocation>(
underlying_allocator_->Allocate(realloc_size)));
}
auto *chunk = &(*chunks_.rbegin());
realloc_size = chunk->allocation_->size();
uint8_t *p = reinterpret_cast<uint8_t *>(chunk->allocation_->ptr());
auto &blocks = chunk->blocks_;
size_t remaining_size = realloc_size - size;
if (remaining_size > 0) {
blocks.emplace_back(p, remaining_size, true, true, chunk);
free_blocks_.emplace(std::make_pair(remaining_size, p),
--(blocks.end()));
}
blocks.emplace_back(p + remaining_size, size, false, true, chunk);
block_it = --(blocks.end());
VLOG(2) << "Not found and reallocate " << realloc_size << "("
<< static_cast<void *>(p) << "), and remaining "
<< remaining_size;
}
}
++total_alloc_times_;
total_alloc_size_ += size;
VLOG(10) << "Alloc " << block_it->size_ << " bytes, ptr = " << block_it->ptr_;
return new BlockAllocation(block_it);
}
} // namespace paddle::memory::allocation
#endif
@@ -0,0 +1,72 @@
// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include <list>
#include <map>
#include <memory>
#include <mutex> // NOLINT
#include <utility>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/auto_growth_best_fit_allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
namespace paddle {
namespace memory {
namespace allocation {
class AutoGrowthBestFitAllocatorV2 : public AutoGrowthBestFitAllocator {
public:
AutoGrowthBestFitAllocatorV2(
const std::shared_ptr<Allocator> &underlying_allocator,
size_t alignment,
GPUPlace place,
size_t chunk_size = 0,
bool allow_free_idle_chunk = true,
int extra_padding_size = 0);
protected:
phi::Allocation *AllocateImpl(size_t size) override;
private:
GPUPlace place_;
bool is_first_switch_to_regular_{true};
std::map<std::pair<size_t, void *>, BlockIt> free_blocks_;
};
class AutoGrowthBestFitAllocatorV2State {
public:
AutoGrowthBestFitAllocatorV2State() = default;
~AutoGrowthBestFitAllocatorV2State() {}
void SetWarmup(bool warmup) { is_warmup_ = warmup; }
bool IsWarmup() { return is_warmup_; }
static AutoGrowthBestFitAllocatorV2State &GetInstance() {
static AutoGrowthBestFitAllocatorV2State instance;
return instance;
}
private:
bool is_warmup_{true};
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,188 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/best_fit_allocator.h"
#include <cmath>
#include <mutex>
#include "paddle/common/macros.h"
#include "paddle/phi/core/enforce.h"
REGISTER_FILE_SYMBOLS(best_fit_allocator);
namespace paddle::memory::allocation {
static int HighestBitPos(size_t N) {
if (UNLIKELY(N == 0)) {
return 0;
} else {
#ifdef __GNUCC__
return sizeof(unsigned int) * 8 - __builtin_clz(N);
#else
return static_cast<int>(std::log2(N) + 1);
#endif
}
}
BestFitAllocator::BestFitAllocator(phi::Allocation* allocation)
: allocation_(allocation) {
details::Chunk chunk;
chunk.size_ = allocation_->size();
chunk.offset_ = 0;
chunk.is_free = true;
chunks_.emplace_back(chunk);
free_chunks_[HighestBitPos(chunk.size_)].insert(
{chunk.size_, chunks_.begin()});
}
size_t BestFitAllocator::FreeSize() const {
size_t acc = 0;
for (auto& array_item : free_chunks_) {
for (auto& pair : array_item) {
acc += pair.second->size_;
}
}
return acc;
}
BestFitAllocator::ListIt BestFitAllocator::SplitChunk(size_t request_size,
size_t free_chunk_offset,
MapIt bin_iterator) {
auto to_split_it = bin_iterator->second;
free_chunks_[free_chunk_offset].erase(bin_iterator);
PADDLE_ENFORCE_EQ(to_split_it->is_free,
true,
common::errors::PreconditionNotMet(
"The memory chunk to split is not free"));
PADDLE_ENFORCE_GE(to_split_it->size_,
request_size,
common::errors::PreconditionNotMet(
"The size of memory chunk to split is "
"not larger than size of request memory"));
auto remaining_size = to_split_it->size_ - request_size;
details::Chunk to_use;
details::Chunk remaining;
to_use.size_ = request_size;
to_use.is_free = false;
remaining.size_ = remaining_size;
remaining.is_free = true;
// calc offsets
to_use.offset_ = to_split_it->offset_;
remaining.offset_ = to_use.offset_ + to_use.size_;
// insert to chunk list
auto to_use_it = chunks_.insert(to_split_it, to_use);
if (remaining.size_ != 0) {
auto bit_size = static_cast<size_t>(HighestBitPos(remaining.size_));
free_chunks_[bit_size].insert(
{remaining.size_, chunks_.insert(to_split_it, remaining)});
}
chunks_.erase(to_split_it);
return to_use_it;
}
void BestFitAllocator::InsertFreeNode(const ListIt& it) {
auto pos = static_cast<size_t>(HighestBitPos(it->size_));
auto& free_map = free_chunks_[pos];
free_map.insert({it->size_, it});
}
void BestFitAllocator::EraseFreeNode(const ListIt& it) {
size_t pos = static_cast<size_t>(HighestBitPos(it->size_));
auto& free_map = free_chunks_[pos];
auto map_it = free_map.find(it->size_);
while (map_it->second != it && map_it != free_map.end()) {
++map_it;
}
PADDLE_ENFORCE_NE(
map_it,
free_map.end(),
common::errors::NotFound("The node to erase is not found in map"));
free_map.erase(map_it);
}
size_t BestFitAllocator::NumFreeChunks() const {
size_t num = 0;
for (auto& array_item : free_chunks_) {
num += array_item.size();
}
return num;
}
void BestFitAllocator::FreeImpl(phi::Allocation* allocation) {
std::lock_guard<SpinLock> guard(spinlock_);
auto* bf_allocation = dynamic_cast<BestFitAllocation*>(allocation);
PADDLE_ENFORCE_NOT_NULL(
bf_allocation,
common::errors::InvalidArgument(
"The input allocation is not type of BestFitAllocation."));
auto chunk_it = bf_allocation->ChunkIterator();
PADDLE_ENFORCE_EQ(chunk_it->is_free,
false,
common::errors::PreconditionNotMet(
"The chunk of allocation to free is freed already"));
chunk_it->is_free = true;
if (chunk_it != chunks_.begin()) {
auto prev_it = chunk_it;
--prev_it;
if (prev_it->is_free) {
// Merge Left.
EraseFreeNode(prev_it);
prev_it->size_ += chunk_it->size_;
chunks_.erase(chunk_it);
chunk_it = prev_it;
}
}
auto next_it = chunk_it;
++next_it;
if (next_it != chunks_.end() && next_it->is_free) {
EraseFreeNode(next_it);
chunk_it->size_ += next_it->size_;
chunks_.erase(next_it);
}
InsertFreeNode(chunk_it);
delete allocation;
}
phi::Allocation* BestFitAllocator::AllocateImpl(size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
auto highest_set_bit = static_cast<size_t>(HighestBitPos(size));
MapIt map_it;
for (; highest_set_bit < free_chunks_.size(); ++highest_set_bit) {
map_it = free_chunks_[highest_set_bit].lower_bound(size);
if (map_it != free_chunks_[highest_set_bit].end()) {
break;
}
}
if (UNLIKELY(highest_set_bit == free_chunks_.size())) {
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"Cannot allocate %d, All fragments size is %d.", size, FreeSize()));
}
auto chunk_it = SplitChunk(size, highest_set_bit, map_it);
return new BestFitAllocation(this, chunk_it);
}
BestFitAllocation::BestFitAllocation(
paddle::memory::allocation::BestFitAllocator* allocator,
typename details::ChunkList::iterator chunk_it)
: Allocation(reinterpret_cast<void*>(
reinterpret_cast<uintptr_t>(allocator->BasePtr()) +
chunk_it->offset_), // NOLINT
chunk_it->size_,
allocator->GetPlace()),
chunk_it_(chunk_it) {}
} // namespace paddle::memory::allocation
@@ -0,0 +1,141 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <array>
#include <list>
#include <map>
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
namespace paddle {
namespace memory {
namespace allocation {
namespace details {
struct Chunk {
bool is_free{true};
// Offset to the base allocation.
uintptr_t offset_;
size_t size_;
};
// Here we use std::list to maintain chunk list.
// NOTE(yy): The traditional implementation of ChunkList is add `prev`/`next`
// pointers in `Chunk`, and split the allocation as `ChunkHeader` and
// `Payload`. Such as
// *-------*---------------*---------------*--------------*
// | Chunk | prev_ pointer | next_ pointer | payload .... |
// *-------*---------------*---------------*--------------*
// This implementation can just return a raw pointer, and we can get the list
// structure by the raw pointer. However, we cannot use the same code on GPU
// since CPU cannot access GPU memory directly.
//
// So we choose to use `std::list` and return an allocation instance, which
// contains the list node iterator, then we can unify CPU/GPU code.
//
// To return an allocation is not a bad idea, since Tensor/Vector should holds
// an allocation instead of raw pointer directly.
using ChunkList = std::list<Chunk>;
// Here we use a multi-level map of free chunks.
// the map is
// MSB offset --> size --> [ChunkList::iterator]
//
// The time complexities:
// find a free chunk:
// O(logN),
// where N is the number of free nodes with the same MSB offset.
// find the position of a chunk iterator:
// O(logN + K),
// where N is the number of free nodes with the same MSB offset.
// where K is the number of free nodes with the same size.
// insert a free chunk:
// O(logN),
// where N is the number of free nodes with the same MSB offset.
// erase a free chunk:
// O(1)
using FreeChunkBin =
std::array<std::multimap<size_t, ChunkList::iterator>, sizeof(size_t) * 8>;
} // namespace details
class BestFitAllocator;
// The BestFitAllocation maintain the List Node iterator.
class BestFitAllocation : public Allocation {
private:
using ListIt = typename details::ChunkList::iterator;
public:
BestFitAllocation(BestFitAllocator* allocator, ListIt chunk_it);
const ListIt& ChunkIterator() const { return chunk_it_; }
private:
typename details::ChunkList::iterator chunk_it_;
};
// TODO(yy): Current BestFitAllocator is not thread-safe. To make it thread
// safe, we must wrap a locked_allocator. However, we can implement a thread
// safe allocator by locking each bin and chunks list independently. It will
// make BestFitAllocator faster in multi-thread situation.
//
// This allocator implements a best-fit allocator with merging the free nodes.
//
// To allocate a buffer, it will find the best-fit chunk. If the best-fit chunk
// is larger than request size, the original block will be split into two
// chunks. The first block will be used and the second block will be put into
// free chunks.
//
// To free an allocation, it will set the chunk of allocation to free and merge
// the prev-chunk and the next-chunk when possible.
class PADDLE_API BestFitAllocator : public Allocator {
public:
explicit BestFitAllocator(phi::Allocation* allocation);
void* BasePtr() const { return allocation_->ptr(); }
const Place& GetPlace() const { return allocation_->place(); }
size_t NumFreeChunks() const;
bool IsAllocThreadSafe() const override { return true; }
private:
size_t FreeSize() const;
using MapIt = typename details::FreeChunkBin::value_type::iterator;
using ListIt = typename details::ChunkList::iterator;
ListIt SplitChunk(size_t request_size,
size_t free_chunk_offset,
MapIt bin_iterator);
void EraseFreeNode(const ListIt& it);
void InsertFreeNode(const ListIt& it);
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
phi::Allocation* allocation_; // not owned
details::ChunkList chunks_;
details::FreeChunkBin free_chunks_;
SpinLock spinlock_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,383 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include "paddle/phi/core/memory/allocation/buddy_allocator.h"
#include <algorithm>
#include "glog/logging.h"
#include "paddle/common/flags.h"
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#define USE_DEVICE
COMMON_DECLARE_uint64(reallocate_gpu_memory_in_mb);
#endif
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/platform/device/device_wrapper.h"
namespace paddle::memory::detail {
BuddyAllocator::BuddyAllocator(
std::unique_ptr<SystemAllocator> system_allocator,
size_t min_chunk_size,
size_t max_chunk_size,
size_t extra_padding_size,
const std::string dev_type)
: min_chunk_size_(min_chunk_size),
max_chunk_size_(max_chunk_size),
extra_padding_size_(extra_padding_size),
cache_(system_allocator->UseGpu()),
system_allocator_(std::move(system_allocator)) {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
if (!dev_type.empty()) {
init_allocate_size_func_ = [dev_type]() {
return phi::DeviceManager::GetInitAllocSize(
phi::PlaceHelper::CreatePlace(dev_type));
};
re_allocate_size_func_ = [dev_type]() {
return phi::DeviceManager::GetReallocSize(
phi::PlaceHelper::CreatePlace(dev_type));
};
use_custom_device_ = true;
} else {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
init_allocate_size_func_ = &platform::GpuInitAllocSize;
re_allocate_size_func_ = &platform::GpuReallocSize;
#endif
}
#endif
VLOG(1) << "min_chunk_size_: " << min_chunk_size_
<< ", max_chunk_size_:" << max_chunk_size_
<< ", extra_padding_size_: " << extra_padding_size_;
}
BuddyAllocator::~BuddyAllocator() {
VLOG(10) << "BuddyAllocator destructor makes sure that all of these "
"have actually been freed";
while (!pool_.empty()) {
auto block = static_cast<MemoryBlock*>(std::get<2>(*pool_.begin()));
auto desc = cache_.LoadDesc(block);
VLOG(10) << "Free from block (" << block << ", " << desc->get_total_size()
<< ")";
system_allocator_->Free(block, desc->get_total_size(), desc->get_index());
cache_.Invalidate(block);
pool_.erase(pool_.begin());
}
}
inline size_t align(size_t size, size_t alignment) {
size_t remaining = size % alignment;
return remaining == 0 ? size : size + (alignment - remaining);
}
void* BuddyAllocator::Alloc(size_t unaligned_size) {
// adjust allocation alignment
size_t size =
align(unaligned_size + sizeof(MemoryBlock::Desc) + extra_padding_size_,
min_chunk_size_);
VLOG(10) << "alloc: " << unaligned_size
<< ", padding for desc: " << sizeof(MemoryBlock::Desc)
<< ", extra padding: " << extra_padding_size_
<< ", alignment: " << min_chunk_size_;
// acquire the allocator lock
std::lock_guard<std::mutex> lock(mutex_);
VLOG(10) << "Allocate " << unaligned_size << " bytes from chunk size "
<< size;
// if the allocation is huge, send directly to the system allocator
if (size > max_chunk_size_) {
VLOG(10) << "Allocate from system allocator.";
return SystemAlloc(size);
}
// query and allocate from the existing chunk
auto it = FindExistChunk(size);
// refill the pool if failure
if (it == pool_.end()) {
it = RefillPool(size);
// if still failure, fail fatally
if (it == pool_.end()) {
return nullptr;
}
} else {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
if (use_custom_device_) {
VLOG(10) << "Allocation from existing memory block " << std::get<2>(*it)
<< " at address " << std::get<2>(*it);
} else {
VLOG(10) << "Allocation from existing memory block " << std::get<2>(*it)
<< " at address "
<< reinterpret_cast<MemoryBlock*>(std::get<2>(*it))->Data();
}
#else
VLOG(10) << "Allocation from existing memory block " << std::get<2>(*it)
<< " at address "
<< reinterpret_cast<MemoryBlock*>(std::get<2>(*it))->Data();
#endif
}
total_used_ += size;
total_free_ -= size;
#ifdef PADDLE_WITH_CUSTOM_DEVICE
if (use_custom_device_) {
return SplitToAlloc(it, size);
}
#endif
// split the allocation and return data for use
return reinterpret_cast<MemoryBlock*>(SplitToAlloc(it, size))->Data();
}
void BuddyAllocator::Free(void* p) {
// Point back to metadata
auto block = static_cast<MemoryBlock*>(p)->Metadata();
#ifdef PADDLE_WITH_CUSTOM_DEVICE
if (use_custom_device_) {
block = static_cast<MemoryBlock*>(p);
}
#endif
// Acquire the allocator lock
std::lock_guard<std::mutex> lock(mutex_);
VLOG(10) << "Free from address " << block;
auto* desc = cache_.LoadDesc(block);
if (desc->get_type() == MemoryBlock::HUGE_CHUNK) {
VLOG(10) << "Free directly from system allocator";
system_allocator_->Free(block, desc->get_total_size(), desc->get_index());
// Invalidate GPU allocation from cache
cache_.Invalidate(block);
return;
}
block->MarkAsFree(&cache_);
total_used_ -= desc->get_total_size();
total_free_ += desc->get_total_size();
// Trying to merge the right buddy
MemoryBlock* right_buddy = block->GetRightBuddy(&cache_);
if (right_buddy) {
VLOG(10) << "Merging this block " << block << " with its right buddy "
<< right_buddy;
auto rb_desc = cache_.LoadDesc(right_buddy);
if (rb_desc->get_type() == MemoryBlock::FREE_CHUNK) {
// Take away right buddy from pool
pool_.erase(IndexSizeAddress(
rb_desc->get_index(), rb_desc->get_total_size(), right_buddy));
// merge its right buddy to the block
block->Merge(&cache_, right_buddy);
}
}
// Trying to merge the left buddy
MemoryBlock* left_buddy = block->GetLeftBuddy(&cache_);
if (left_buddy) {
VLOG(10) << "Merging this block " << block << " with its left buddy "
<< left_buddy;
// auto left_buddy = block->left_buddy(cache_);
auto* lb_desc = cache_.LoadDesc(left_buddy);
if (lb_desc->get_type() == MemoryBlock::FREE_CHUNK) {
// Take away right buddy from pool
pool_.erase(IndexSizeAddress(
lb_desc->get_index(), lb_desc->get_total_size(), left_buddy));
// merge the block to its left buddy
left_buddy->Merge(&cache_, block);
block = left_buddy;
desc = lb_desc;
}
}
// Dumping this block into pool
VLOG(10) << "Inserting free block (" << block << ", "
<< desc->get_total_size() << ")";
pool_.insert(
IndexSizeAddress(desc->get_index(), desc->get_total_size(), block));
}
uint64_t BuddyAllocator::Release() {
std::lock_guard<std::mutex> lock(mutex_);
int num = 0;
uint64_t bytes = 0;
for (auto iter = pool_.begin(); iter != pool_.end();) {
auto remain_size = std::get<1>(*iter);
auto remain_ptr = std::get<2>(*iter);
auto found = chunks_.find({remain_size, remain_ptr});
if (found != chunks_.end()) {
size_t index = found->second;
++num;
bytes += remain_size;
total_free_ -= remain_size;
auto block = static_cast<MemoryBlock*>(remain_ptr);
system_allocator_->Free(remain_ptr, remain_size, index);
cache_.Invalidate(block);
iter = pool_.erase(iter);
} else {
iter++;
}
}
VLOG(10) << "Release " << num << " chunks, Free " << bytes << " bytes.";
return bytes;
}
size_t BuddyAllocator::Used() { return total_used_; }
size_t BuddyAllocator::GetMinChunkSize() { return min_chunk_size_; }
size_t BuddyAllocator::GetMaxChunkSize() { return max_chunk_size_; }
void* BuddyAllocator::SystemAlloc(size_t size) {
size_t index = 0;
void* p = system_allocator_->Alloc(&index, size);
VLOG(8) << "Allocated " << p << " size " << size << " from system allocator.";
if (p == nullptr) return nullptr;
static_cast<MemoryBlock*>(p)->Init(
&cache_, MemoryBlock::HUGE_CHUNK, index, size, nullptr, nullptr);
#ifdef PADDLE_WITH_CUSTOM_DEVICE
if (use_custom_device_) {
return p;
}
#endif
return static_cast<MemoryBlock*>(p)->Data();
}
BuddyAllocator::PoolSet::iterator BuddyAllocator::RefillPool(
size_t request_bytes) {
size_t allocate_bytes = max_chunk_size_; // NOLINT
size_t index = 0;
#ifdef PADDLE_WITH_CUSTOM_DEVICE
allocate_bytes = DeviceAllocateSize(
init_allocate_size_func_, re_allocate_size_func_, request_bytes);
#else
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
allocate_bytes = DeviceAllocateSize(
&platform::GpuInitAllocSize, &platform::GpuReallocSize, request_bytes);
#endif
#endif
// Allocate a new block
void* p = system_allocator_->Alloc(&index, allocate_bytes);
if (p == nullptr) return pool_.end();
VLOG(8) << "Creating and inserting new block " << p << " size "
<< allocate_bytes << " from system allocator";
static_cast<MemoryBlock*>(p)->Init(&cache_,
MemoryBlock::FREE_CHUNK,
index,
allocate_bytes,
nullptr,
nullptr);
total_free_ += allocate_bytes;
// record the chunk.
chunks_.insert({{allocate_bytes, p}, index});
// dump the block into pool
return pool_.insert(IndexSizeAddress(index, allocate_bytes, p)).first;
}
BuddyAllocator::PoolSet::iterator BuddyAllocator::FindExistChunk(size_t size) {
size_t index = 0;
while (true) {
auto it = pool_.lower_bound(IndexSizeAddress(index, size, nullptr));
// no match chunk memory
if (it == pool_.end()) return it;
if (std::get<0>(*it) > index) {
// find suitable one
if (std::get<1>(*it) >= size) {
return it;
}
// update and continue
index = std::get<0>(*it);
continue;
}
return it;
}
}
void* BuddyAllocator::SplitToAlloc(BuddyAllocator::PoolSet::iterator it,
size_t size) {
auto block = static_cast<MemoryBlock*>(std::get<2>(*it));
auto desc = cache_.LoadDesc(block);
pool_.erase(it);
VLOG(10) << "Split block (" << block << ", " << desc->get_total_size()
<< ") into";
block->Split(&cache_, size);
VLOG(10) << "Left block (" << block << ", " << desc->get_total_size() << ")";
desc->set_type(MemoryBlock::ARENA_CHUNK);
// the rest of memory if exist
MemoryBlock* right_buddy = block->GetRightBuddy(&cache_);
if (right_buddy) {
auto* rb_desc = cache_.LoadDesc(right_buddy);
if (rb_desc->get_type() == MemoryBlock::FREE_CHUNK) {
VLOG(10) << "Insert right block (" << right_buddy << ", "
<< rb_desc->get_total_size() << ")";
pool_.insert(IndexSizeAddress(
rb_desc->get_index(), rb_desc->get_total_size(), right_buddy));
}
}
return block;
}
size_t BuddyAllocator::DeviceAllocateSize(
std::function<size_t()> init_allocate_size_func,
std::function<size_t()> re_allocate_size_func,
size_t request_bytes) {
size_t allocate_bytes = max_chunk_size_;
#if defined(USE_DEVICE)
const bool use_gpu = system_allocator_->UseGpu();
VLOG(10) << "use_gpu " << use_gpu << ", total_used " << total_used_
<< ", total_free " << total_free_;
if (use_gpu) {
if (total_used_ == 0 && total_free_ == 0) {
// Compute the allocation size for gpu for the first allocation.
allocate_bytes = std::max(init_allocate_size_func(), request_bytes);
} else {
// Compute the re-allocation size, we store the re-allocation size when
// user set FLAGS_reallocate_gpu_memory_in_mb to fix value.
if (realloc_size_ == 0 || FLAGS_reallocate_gpu_memory_in_mb == 0ul) {
realloc_size_ = re_allocate_size_func();
}
allocate_bytes = std::max(realloc_size_, request_bytes);
}
}
#endif
return allocate_bytes;
}
} // namespace paddle::memory::detail
@@ -0,0 +1,135 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#pragma once
#include <stdint.h>
#include <functional>
#include <map>
#include <memory>
#include <mutex> // NOLINT
#include <set>
#include <tuple>
#include <utility>
#include <vector>
#include "paddle/phi/backends/cpu/cpu_info.h"
#include "paddle/phi/core/memory/allocation/memory_block.h"
#include "paddle/phi/core/memory/allocation/system_allocator.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle {
namespace memory {
namespace detail {
class BuddyAllocator {
public:
BuddyAllocator(std::unique_ptr<SystemAllocator> system_allocator,
size_t min_chunk_size,
size_t max_chunk_size,
size_t extra_padding_size = 0,
const std::string dev_type = "");
~BuddyAllocator();
public:
void* Alloc(size_t unaligned_size);
void Free(void* ptr);
// Release the unused memory pool, a real free operation for the OS.
uint64_t Release();
size_t Used();
size_t GetMinChunkSize();
size_t GetMaxChunkSize();
public:
// Disable copy and assignment
BuddyAllocator(const BuddyAllocator&) = delete;
BuddyAllocator& operator=(const BuddyAllocator&) = delete;
private:
// Tuple (allocator index, memory size, memory address)
using IndexSizeAddress = std::tuple<size_t, size_t, void*>;
// Each element in PoolSet is a free allocation
using PoolSet = std::set<IndexSizeAddress>;
// Each element in PoolMap is an allocation record
// key: <size, ptr>, value: index
using PoolMap = std::map<std::pair<size_t, void*>, size_t>;
/*! \brief Allocate fixed-size memory from system */
void* SystemAlloc(size_t size);
/*! \brief If existing chunks are not suitable, refill pool */
PoolSet::iterator RefillPool(size_t request_bytes);
/**
* \brief Find the suitable chunk from existing pool and split
* it to left and right buddies
*
* \param it the iterator of pool list
* \param size the size of allocation
*
* \return the left buddy address
*/
void* SplitToAlloc(PoolSet::iterator it, size_t size);
/*! \brief Find the existing chunk which used to allocation */
PoolSet::iterator FindExistChunk(size_t size);
/*! \brief Allocate bytes from the device */
size_t DeviceAllocateSize(std::function<size_t()> init_allocate_size_func,
std::function<size_t()> re_allocate_size_func,
size_t request_bytes);
private:
size_t total_used_ = 0; // the total size of used memory
size_t total_free_ = 0; // the total size of free memory
size_t min_chunk_size_; // the minimum size of each chunk
size_t max_chunk_size_; // the maximum size of each chunk
size_t realloc_size_ = 0; // the size of re-allocated chunk
size_t extra_padding_size_ = 0; // the size of padding to the size of memory
// to alloc, especially used in NPU
private:
/**
* \brief A list of free allocation
*
* \note Only store free chunk memory in pool
*/
PoolSet pool_;
/**
* \brief Record the allocated chunks when Refill pool.
*/
PoolMap chunks_;
private:
/*! Unify the metadata format between GPU and CPU allocations */
MetadataCache cache_;
private:
/*! Allocate CPU/GPU memory from system */
std::unique_ptr<SystemAllocator> system_allocator_;
std::mutex mutex_;
#ifdef PADDLE_WITH_CUSTOM_DEVICE
std::function<size_t()> init_allocate_size_func_, re_allocate_size_func_;
bool use_custom_device_ = false;
#endif
};
} // namespace detail
} // namespace memory
} // namespace paddle
@@ -0,0 +1,76 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/buffered_allocator.h"
#include "paddle/common/macros.h"
REGISTER_FILE_SYMBOLS(buffered_allocator);
namespace paddle::memory::allocation {
BufferedAllocator::BufferedAllocator(std::shared_ptr<Allocator> allocator)
: underlying_allocator_(std::move(allocator)) {
PADDLE_ENFORCE_NOT_NULL(
underlying_allocator_,
common::errors::InvalidArgument(
"Underlying allocator of BufferedAllocator is NULL"));
if (underlying_allocator_->IsAllocThreadSafe()) {
mtx_ = std::make_unique<std::mutex>();
}
}
BufferedAllocator::~BufferedAllocator() { FreeCache(-1UL); }
void BufferedAllocator::FreeCache(size_t size) {
platform::LockGuardPtr<std::mutex> guard(mtx_);
if (UNLIKELY(size == 0)) return;
size_t cur = 0;
while (!allocations_.empty()) { // free the largest
auto it = --allocations_.end();
cur += it->second->size();
underlying_allocator_->Free(it->second.release());
allocations_.erase(it);
if (cur >= size) return;
}
}
bool BufferedAllocator::IsAllocThreadSafe() const { return mtx_ != nullptr; }
void BufferedAllocator::FreeImpl(phi::Allocation *allocation) {
platform::LockGuardPtr<std::mutex> guard(mtx_);
allocations_.emplace(allocation->size(),
AllocationPtr(allocation, Allocator::AllocationDeleter));
}
phi::Allocation *BufferedAllocator::AllocateImpl(size_t size) {
{
platform::LockGuardPtr<std::mutex> guard(mtx_);
auto it = allocations_.lower_bound(size);
if (it != allocations_.end() && it->first < size * 2) {
AllocationPtr result(std::move(it->second));
allocations_.erase(it);
return result.release();
}
}
try {
return underlying_allocator_->Allocate(size).release();
} catch (BadAlloc &) {
FreeCache(size);
return underlying_allocator_->Allocate(size).release();
}
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,59 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstdint>
#include <map>
#include <memory>
#include <vector>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/platform/lock_guard_ptr.h"
namespace paddle {
namespace memory {
namespace allocation {
// NOTE(zjl): BufferedAllocator maintains a memory pool to accelerate
// memory allocation and reuse memory.
// BufferedAllocator provides the same thread-safety level as
// underlying_allocator_
class PADDLE_API BufferedAllocator : public Allocator {
public:
explicit BufferedAllocator(std::shared_ptr<Allocator> allocator);
~BufferedAllocator();
bool IsAllocThreadSafe() const override;
// only used in unittest
inline void ClearCache() { FreeCache(-1UL); }
private:
void FreeCache(size_t size);
protected:
void FreeImpl(phi::Allocation *allocation) override;
phi::Allocation *AllocateImpl(size_t size) override;
private:
std::shared_ptr<Allocator> underlying_allocator_;
std::multimap<size_t, AllocationPtr> allocations_;
std::unique_ptr<std::mutex> mtx_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,53 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/cpu_allocator.h"
#include <cstdlib>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/stats.h"
namespace paddle::memory::allocation {
bool CPUAllocator::IsAllocThreadSafe() const { return true; }
void CPUAllocator::FreeImpl(phi::Allocation *allocation) {
auto size = allocation->size();
void *p = allocation->ptr();
#ifdef _WIN32
_aligned_free(p);
#else
free(p); // NOLINT
#endif
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -size);
delete allocation;
}
phi::Allocation *CPUAllocator::AllocateImpl(size_t size) {
void *p = nullptr;
#ifdef _WIN32
p = _aligned_malloc(size, kAlignment);
#else
int error = posix_memalign(&p, kAlignment, size);
PADDLE_ENFORCE_EQ(
error,
0,
common::errors::ResourceExhausted(
"Fail to alloc memory of %ld size, error code is %d.", size, error));
#endif
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
return new Allocation(p, size, CPUPlace());
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,45 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/core/memory/allocation/allocator.h"
#ifdef _WIN32
#define posix_memalign_free _aligned_free
#define posix_memalign(p, a, s) \
(((*(p)) = _aligned_malloc((s), (a))), *(p) ? 0 : errno)
#endif
namespace paddle {
namespace memory {
namespace allocation {
// CPU system allocator and allocation.
//
// NOTE(yy): Should we just use `malloc` here since there is an
// aligned_allocator.
//
// NOTE(yy): It is no need to use `BestFitAllocator` in CPU. We can import
// an open-sourced allocator into Paddle.
class PADDLE_API CPUAllocator : public Allocator {
public:
constexpr static size_t kAlignment = 4096UL;
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,94 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/cuda_allocator.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/memory/stats.h"
#ifdef PADDLE_WITH_CUDA
#include <cuda.h>
#include <cuda_runtime.h>
#endif
#ifdef PADDLE_WITH_HIP
#include <hip/hip_runtime.h>
#endif
#include <string>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle::memory::allocation {
bool CUDAAllocator::IsAllocThreadSafe() const { return true; }
void CUDAAllocator::FreeImpl(phi::Allocation* allocation) {
PADDLE_ENFORCE_EQ(
allocation->place(),
place_,
common::errors::PermissionDenied(
"GPU memory is freed in incorrect device. This may be a bug"));
platform::RecordedGpuFree(
allocation->ptr(), allocation->size(), place_.device);
delete allocation;
}
phi::Allocation* CUDAAllocator::AllocateImpl(size_t size) {
std::call_once(once_flag_, [this] { platform::SetDeviceId(place_.device); });
void* ptr;
auto result = platform::RecordedGpuMalloc(&ptr, size, place_.device);
if (LIKELY(result == gpuSuccess)) {
return new Allocation(ptr, size, Place(place_));
}
size_t avail, total, actual_avail, actual_total;
bool is_limited = platform::RecordedGpuMemGetInfo(
&avail, &total, &actual_avail, &actual_total, place_.device);
size_t allocated = total - avail;
std::string err_msg;
if (is_limited) {
auto limit_size = (total >> 20);
err_msg = string::Sprintf(
"Or set environment variable `FLAGS_gpu_memory_limit_mb` to a larger "
"value. Currently `FLAGS_gpu_memory_limit_mb` is %d, so the maximum "
"GPU memory usage is limited to %d MB.\n"
" The command is `export FLAGS_gpu_memory_limit_mb=xxx`.",
limit_size,
limit_size);
}
size_t actual_allocated_memory =
paddle::memory::DeviceMemoryStatCurrentValue("Allocated", place_.device);
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, %s memory has been "
"allocated(actual using allocated memory %s) and "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please decrease the batch size of your model. %s\n",
place_.device,
string::HumanReadableSize(size),
place_.device,
string::HumanReadableSize(allocated),
string::HumanReadableSize(actual_allocated_memory),
string::HumanReadableSize(avail),
place_.device,
err_msg));
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,42 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mutex> // NOLINT
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class PADDLE_API CUDAAllocator : public Allocator {
public:
explicit CUDAAllocator(const GPUPlace& place) : place_(place) {}
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
GPUPlace place_;
std::once_flag once_flag_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,175 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "glog/logging.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device_context.h"
namespace paddle {
namespace memory {
namespace allocation {
/**
* GPUContextAllocation is a wrapper of the underbeneath allocation.
* GPUContextAllocation adds a CUDA stream callback for the underbeneath
* allocation so that GPUContextAllocation can be used in a CUDA stream
* which deletes allocation in the callback.
*/
class GPUContextAllocation : public Allocation {
public:
explicit GPUContextAllocation(DecoratedAllocationPtr allocation)
: Allocation(allocation->ptr(),
allocation->base_ptr(),
allocation->size(),
allocation->place()),
underlying_allocation_(std::move(allocation)) {}
~GPUContextAllocation() {
PADDLE_WARN_NOT_NULL(
dev_ctx_,
common::errors::PreconditionNotMet(
"Device context is not set for GPUContextAllocation"));
auto *p_allocation = underlying_allocation_.release();
VLOG(4) << "Adding callback to delete GPUContextAllocation at "
<< p_allocation;
dev_ctx_->AddStreamCallback([p_allocation] {
VLOG(4) << "Delete GPUContextAllocation at " << p_allocation;
Allocator::AllocationDeleter(p_allocation);
});
}
void SetGPUContext(const phi::GPUContext *dev_ctx) { dev_ctx_ = dev_ctx; }
private:
DecoratedAllocationPtr underlying_allocation_;
const phi::GPUContext *dev_ctx_{nullptr};
};
/**
* GPUContextAllocator will allocate a GPUContextAllocation
* after waiting for a self-created event on the default stream. It does so to
* let the non-default stream be able to allocate GPU memory which will be
* released by stream callback
*/
class GPUContextAllocator : public Allocator {
public:
explicit GPUContextAllocator(GPUPlace place, gpuStream_t default_stream)
: place_(place), default_stream_(default_stream) {
platform::CUDADeviceGuard guard(place_.device);
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(
hipEventCreateWithFlags(&event_, hipEventDisableTiming));
#else
PADDLE_ENFORCE_GPU_SUCCESS(
cudaEventCreate(&event_, cudaEventDisableTiming));
#endif
}
~GPUContextAllocator() {
if (event_) {
platform::CUDADeviceGuard guard(place_.device);
#ifdef PADDLE_WITH_HIP
PADDLE_WARN_GPU_SUCCESS(hipEventDestroy(event_));
#else
PADDLE_WARN_GPU_SUCCESS(cudaEventDestroy(event_));
#endif
}
}
protected:
phi::Allocation *AllocateImpl(size_t size) override {
PADDLE_ENFORCE_NOT_NULL(
default_stream_,
common::errors::PreconditionNotMet(
"Default stream is not set for GPUContextAllocator"));
platform::CUDADeviceGuard guard(place_.device);
auto allocation = new GPUContextAllocation(
static_unique_ptr_cast<Allocation>(memory::Alloc(place_, size)));
// Wait for the event on stream
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(hipEventRecord(event_, default_stream_));
PADDLE_ENFORCE_GPU_SUCCESS(hipStreamWaitEvent(default_stream_, event_, 0));
#else
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventRecord(event_, default_stream_));
PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamWaitEvent(default_stream_, event_, 0));
#endif
return allocation;
}
void FreeImpl(phi::Allocation *allocation) override { delete allocation; }
private:
GPUPlace place_;
gpuEvent_t event_{nullptr};
gpuStream_t default_stream_{nullptr};
};
/**
* GPUContextAllocatorPool is a singleton stores mapping from
* CUDAPlace(s) to std::shared_ptr<GPUContextAllocator>. When a
* phi::GPUContext's compute stream isn't default stream, it can call this
* class to allocate GPU memory which will be released by a callback after
* stream execution.
*/
class GPUContextAllocatorPool {
public:
static GPUContextAllocatorPool &Instance() {
static GPUContextAllocatorPool pool;
return pool;
}
AllocationPtr Alloc(const phi::GPUContext &dev_ctx, size_t size) {
auto iter = allocators_.find(GPUPlace(dev_ctx.GetPlace().GetDeviceId()));
PADDLE_ENFORCE_NE(
iter,
allocators_.end(),
common::errors::NotFound("No allocator found for CUDAPlace."));
auto &allocator = iter->second;
AllocationPtr allocation = allocator->Allocate(size);
static_cast<GPUContextAllocation *>(allocation.get())
->SetGPUContext(&dev_ctx);
return allocation;
}
private:
GPUContextAllocatorPool() {
std::vector<int> devices = platform::GetSelectedDevices();
for (int i : devices) {
auto place = GPUPlace(i);
auto compute_stream =
phi::DeviceContextPool::Instance().GetByPlace(place)->stream();
auto allocator = std::shared_ptr<GPUContextAllocator>(
new GPUContextAllocator(place, compute_stream));
allocators_.insert(make_pair(place, allocator));
}
}
std::map<GPUPlace, std::shared_ptr<GPUContextAllocator>> allocators_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,97 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
#include "paddle/phi/core/memory/allocation/cuda_ipc_allocator.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <cstdlib>
#include <random>
#include <string>
#include "glog/logging.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
namespace paddle::memory::allocation {
namespace {
std::mutex ipc_mutex_;
std::unordered_map<std::string, std::weak_ptr<void>> ipc_handle_to_baseptr_;
} // namespace
std::shared_ptr<void> GetIpcBasePtr(std::string handle) {
std::lock_guard<std::mutex> lock(ipc_mutex_);
auto iter = ipc_handle_to_baseptr_.find(handle);
if (iter != ipc_handle_to_baseptr_.end()) {
auto baseptr = iter->second.lock();
if (baseptr) return baseptr;
}
// The IpcMemHandle can only open once for the same handle,
// so here we cache it here.
void *baseptr = nullptr;
auto ipc_handle = reinterpret_cast<const gpuIpcMemHandle_t *>(handle.c_str());
PADDLE_ENFORCE_GPU_SUCCESS(gpuIpcOpenMemHandle(
&baseptr, *ipc_handle, gpuIpcMemLazyEnablePeerAccess));
// Close ipc handle on the same device.
int device_id = platform::GetCurrentDeviceId();
// Add deleter to close ipc handle.
auto sp = std::shared_ptr<void>(baseptr, [handle, device_id](void *ptr) {
platform::CUDADeviceGuard guard(device_id);
std::lock_guard<std::mutex> lock(ipc_mutex_);
PADDLE_ENFORCE_GPU_SUCCESS(gpuIpcCloseMemHandle(ptr));
ipc_handle_to_baseptr_.erase(handle);
VLOG(6) << "cudaIpcCloseMemHandle for ptr:"
<< "\t" << ptr;
});
std::weak_ptr<void> wp = sp;
ipc_handle_to_baseptr_.insert(iter, {handle, wp});
return sp;
}
void IpcCollect() {
std::lock_guard<std::mutex> lock(ipc_mutex_);
size_t before = ipc_handle_to_baseptr_.size();
VLOG(6) << "The number of IPC handles before collection:" << before;
for (auto it = ipc_handle_to_baseptr_.begin();
it != ipc_handle_to_baseptr_.end();) {
if (it->second.expired()) {
it = ipc_handle_to_baseptr_.erase(it);
} else {
VLOG(6) << " Valid ipc handle is not expired";
++it;
}
}
size_t after = ipc_handle_to_baseptr_.size();
size_t collected = before - after;
VLOG(1) << "IpcCollect: collected " << collected << " expired IPC handles"
<< "out of " << before << " total handles";
}
CudaIpcAllocation::~CudaIpcAllocation() {
shared_ptr_.reset();
VLOG(6) << "tensor deleted cudaIpcCloseMemHandle for ptr:"
<< "\t" << this->ptr();
}
} // namespace paddle::memory::allocation
#endif
@@ -0,0 +1,60 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
#pragma once
#include <memory>
#include <mutex> // NOLINT
#include <string>
#include <unordered_set>
#include <utility>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
std::shared_ptr<void> GetIpcBasePtr(std::string handle);
void IpcCollect();
class CudaIpcAllocation : public Allocation {
public:
explicit CudaIpcAllocation(void *ptr,
size_t size,
int device_id,
std::shared_ptr<void> shared_ptr)
: Allocation(ptr, size, GPUPlace(device_id)),
device_id_(std::move(device_id)),
shared_ptr_(std::move(shared_ptr)) {}
inline const int &device_id() const { return device_id_; }
~CudaIpcAllocation() override;
private:
int device_id_;
std::shared_ptr<void> shared_ptr_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,399 @@
// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/cuda_malloc_async_allocator.h"
#include <cstddef>
#include <cstdint>
#include <mutex>
#include "paddle/common/flags.h"
#include "paddle/common/macros.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/stream_safe_cuda_allocator.h"
#include "glog/logging.h"
#ifdef PADDLE_WITH_CUDA
#include <cuda.h>
#include <cuda_runtime.h>
#include "paddle/phi/backends/gpu/cuda/cuda_graph.h"
#endif
#include <string>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
#include "paddle/utils/optional.h"
#ifdef PADDLE_WITH_CUDA
/*
* Note: [cuda_malloc_async_pool_memory_throttle_ratio]
* The primary purpose of the memory_throttle_ratio is to provide a
* threshold that determines when to initiate synchronization operations to
* deallocate memory. This mechanism helps in ensuring that the system does
* not exceed its memory capacity while also attempting to minimize performance
* degradation caused by frequent memory synchronization.
*
* ```
* utilization = (allocated_size + pending_release_size) / total_memory_size
* if(utilization > memory_throttle_ratio)
* sync(free_stream, malloc_stream)
* ```
*
* When the utilization exceeds the memory_throttle_ratio, we
* initiate a stream synchronization operation before malloc.
*
* During synchronization, all memory deallocation requests in the free queue
* are processed, effectively lowering the memory utilization before
* any new memory allocation operations are going to proceed.
*
* [Impact on Performance and Memory Usage]
*
* - Lower memory_throttle_ratio Values
* the synchronization operation will be triggered more frequently.
* This can lead to better memory utilization but might result in decreased
* performance due to the increased number of synchronization operations.
*
* - Higher memory_throttle_ratio Values
* Conversely, setting a higher value allows for more memory to be allocated
* before triggering synchronization, which can enhance performance by reducing
* the number of sync operations. However, this increases the risk of reaching
* an OOM condition since more memory can be allocated without
* immediate deallocation.
*/
COMMON_DECLARE_double(cuda_malloc_async_pool_memory_throttle_ratio);
namespace paddle::memory::allocation {
thread_local std::once_flag CUDAMallocAsyncAllocation::once_flag_;
inline void sync_streams(gpuStream_t to_record, gpuStream_t to_wait) {
cudaEvent_t event = nullptr;
PADDLE_ENFORCE_GPU_SUCCESS(
cudaEventCreateWithFlags(&event, cudaEventDisableTiming));
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventRecord(event, to_record));
PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamWaitEvent(to_wait, event));
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventDestroy(event));
}
// CUDAMallocAsyncAllocation
bool CUDAMallocAsyncAllocation::RecordStream(gpuStream_t stream) {
std::call_once(once_flag_,
[this] { phi::backends::gpu::SetDeviceId(place_.device); });
std::lock_guard<SpinLock> lock_guard(recorded_streams_lock_);
if (malloc_stream_ == stream) {
// Called record_stream on tensor whose original malloc_stream matches the
// recorded stream. This should have no effect.
return false;
}
recorded_streams_.insert(stream);
return true;
}
void CUDAMallocAsyncAllocation::EraseStream(gpuStream_t stream) {
std::lock_guard<SpinLock> lock_guard(recorded_streams_lock_);
recorded_streams_.erase(stream);
}
size_t CUDAMallocAsyncAllocation::Free() {
if (recorded_streams_.empty()) {
platform::RecordedGpuFreeAsync(
ptr(), size(), place_.device, malloc_stream_);
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
phi::backends::gpu::CUDAGraph::AddJoiningStreamDuringCapturing(
malloc_stream_);
}
return size();
} else {
sync_streams(malloc_stream_, free_stream_);
for (const auto& recorded_stream : recorded_streams_) {
sync_streams(recorded_stream, free_stream_);
}
platform::RecordedGpuFreeAsync(ptr(), size(), place_.device, free_stream_);
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
phi::backends::gpu::CUDAGraph::AddJoiningStreamDuringCapturing(
free_stream_);
}
return 0;
}
}
// CUDAMallocAsyncAllocator
CUDAMallocAsyncAllocator::CUDAMallocAsyncAllocator(
std::shared_ptr<Allocator> underlying_allocator,
const GPUPlace& place,
gpuStream_t default_stream)
: underlying_allocator_(std::move(underlying_allocator)),
place_(place),
default_stream_(default_stream),
current_allocated_size_(0),
pending_release_size_(0),
memory_throttle_ratio_(
FLAGS_cuda_malloc_async_pool_memory_throttle_ratio) {
// CUDA operations are not allowed here. The cuInit function must be called
// after a new fork, and since this constructor is typically initialized
// before cuInit, we should avoid calling any CUDA API here.
phi::backends::gpu::CUDAGraph::AddPreCaptureCallback([&]() {
VLOG(0) << "[Before capture callback] " << (this) << " "
<< std::this_thread::get_id();
this->ClearFreeStream(true);
});
}
uint64_t CUDAMallocAsyncAllocator::ReleaseImpl(const Place& place) {
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
VLOG(7) << "Memory release forbidden in CUDA Graph Captruing";
return 0;
}
uint64_t released_size = 0;
// we synchronize the event so all the block could be release.
if (underlying_allocator_)
released_size += underlying_allocator_->Release(place_);
VLOG(8) << "Release " << released_size << " bytes memory from all streams";
return released_size;
}
void CUDAMallocAsyncAllocator::ClearFreeStream(bool sync) {
LazyInitializeCudaFreeStream();
if (sync) {
VLOG(0) << "[CUDAMallocAsyncAllocator] " << (this)
<< " synchronize the free stream to ensure all unrelesed blocks "
<< "are freed";
PADDLE_ENFORCE_GPU_SUCCESS(cudaStreamSynchronize(free_stream_));
} else {
sync_streams(free_stream_, default_stream_);
}
current_allocated_size_ -= pending_release_size_;
pending_release_size_ = 0;
}
void CUDAMallocAsyncAllocator::MallocThrottling() {
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
// we disable MallocThrottling when capturing
return;
}
double allocated =
static_cast<double>(current_allocated_size_ + pending_release_size_);
double utilization = allocated / static_cast<double>(max_size_);
if (utilization > memory_throttle_ratio_) {
VLOG(10) << "utilization_ratio " << utilization
<< " current_allocated_size "
<< string::HumanReadableSize(current_allocated_size_)
<< " pending_release_size "
<< string::HumanReadableSize(pending_release_size_);
CUDAMallocAsyncAllocator::ClearFreeStream();
}
}
void CUDAMallocAsyncAllocator::FreeAllocation(
CUDAMallocAsyncAllocation* allocation) {
auto current_released_size = allocation->Free();
current_allocated_size_ -= current_released_size;
// The amount of pending release size (the space that has been queued to
// free_stream, that are going to be freed in the future)
pending_release_size_ += (allocation->size() - current_released_size);
}
/*
* There are four distinct scenarios involving `cudaMalloc`, `cudaFree`, and
* `cudaGraph`:
*
* 1. When both `cudaMalloc` and `cudaFree` occur within a graph.
* 2. When `cudaMalloc` happens within a graph, but `cudaFree` occurs outside
* the graph.
* 3. When `cudaMalloc` takes place outside a graph, but `cudaFree` happens
* within a graph.
* 4. When both `cudaMalloc` and `cudaFree` are executed outside any graph.
*
* For cases (1.) and (4.), the usage aligns with the typical pattern of
* `cudaMalloc`/`cudaFree`.
*
* In case (1.), `FreeImpl` removes the allocation from
* `graph_owned_allocations_`, followed by `FreeAllocation`. In case (2.), the
* callback within `AllocateImpl` would free the allocation after the graph is
* destroyed. In case (3.), `FreeImpl` releases the allocation after the CUDA
* graph has completed its capture. Finally, in case (4.), `FreeImpl` would call
* `FreeAllocation`, and the allocation would be freed.
*/
void CUDAMallocAsyncAllocator::FreeImpl(phi::Allocation* phi_allocation) {
auto* allocation = dynamic_cast<CUDAMallocAsyncAllocation*>(phi_allocation);
std::lock_guard<SpinLock> lock_guard(graph_owned_allocations_lock_);
// During graph capturing, only free the memory blocks owned by the graph;
// others are cached.
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
// Handles scenario (3.)
if (graph_owned_allocations_.find(allocation) ==
graph_owned_allocations_.end()) {
// If the block is not owned by the graph, cache it for release after
// capturing.
phi::backends::gpu::CUDAGraph::AddPostCaptureCallbackDuringCapturing(
[=]() {
// Release this block after capturing
VLOG(0) << "[PostCaptureCallback] Releasing ptr = "
<< allocation->ptr() << " size = "
<< string::HumanReadableSize(allocation->size());
FreeAllocation(allocation);
});
return;
}
// Handles scenario (1.)
graph_owned_allocations_.erase(allocation);
} else {
// Handles scenario (2.)
if (graph_owned_allocations_.find(allocation) !=
graph_owned_allocations_.end()) {
auto graph = graph_owned_allocations_[allocation];
VLOG(0) << "[Rescheduled cudaFreeAsync] Allocation ptr = "
<< allocation->ptr()
<< " is allocated in a graph but freed outside the graph."
<< " The allocation is rescheduled to be freed after the "
<< "destruction of graph " << graph;
graph_owned_allocations_.erase(allocation);
// No need to free the allocation
return;
}
}
// Handles scenario (1.) and (4.)
FreeAllocation(allocation);
}
void CUDAMallocAsyncAllocator::LazyInitializeCudaFreeStream() {
std::call_once(once_flag_, [this] {
size_t avail, total, actual_avail, actual_total;
platform::RecordedGpuMemGetInfo(
&avail, &total, &actual_avail, &actual_total, place_.device);
max_size_ = total;
VLOG(0) << "[CUDAMallocAsyncAllocator] " << (this) << " place " << place_
<< " max_size " << string::HumanReadableSize(max_size_)
<< " memory_throttle_ratio " << memory_throttle_ratio_
<< " tid = " << std::this_thread::get_id();
PADDLE_ENFORCE_GPU_SUCCESS(
cudaStreamCreateWithPriority(&free_stream_, cudaStreamNonBlocking, 0));
cudaDeviceGetDefaultMemPool(&mempool_, place_.device);
platform::SetDeviceId(place_.device);
});
}
phi::Allocation* CUDAMallocAsyncAllocator::AllocateImpl(size_t size) {
LazyInitializeCudaFreeStream();
MallocThrottling();
void* ptr;
auto result = platform::RecordedGpuMallocAsync(
&ptr, size, place_.device, default_stream_);
if (LIKELY(result == gpuSuccess)) {
auto* allocation = new CUDAMallocAsyncAllocation(
ptr, size, Place(place_), default_stream_, free_stream_);
VLOG(10) << "Allocate " << allocation->ptr() << " with allocator "
<< (this);
// If capturing, associate allocation with the current graph.
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
std::lock_guard<SpinLock> lock_guard(graph_owned_allocations_lock_);
auto capturing_graph = phi::backends::gpu::CUDAGraph::CapturingID();
graph_owned_allocations_[allocation] = capturing_graph;
// Handles scenario (2.)
phi::backends::gpu::CUDAGraph::AddPostResetCallbackDuringCapturing(
[=](paddle::optional<const phi::backends::gpu::CUDAGraph&> graph) {
std::lock_guard<SpinLock> lock_guard_free(
graph_owned_allocations_lock_);
// Returns if the allocation is freed during capture.
if (graph_owned_allocations_.find(allocation) ==
graph_owned_allocations_.end())
return;
bool replayed = graph.get().IsReplayed();
if (replayed) {
VLOG(0) << "[Rescheduled cudaFreeAsync] Graph " << capturing_graph
<< " is destructed. Allocation = " << allocation->ptr()
<< " is freed.";
FreeAllocation(allocation);
} else {
VLOG(0) << "[Rescheduled cudaFreeAsync] Graph " << capturing_graph
<< " is destructed without any replay. Allocation = "
<< allocation->ptr()
<< " is not initialized and would not be freed.";
}
});
}
current_allocated_size_ += size;
return allocation;
}
size_t avail, total, actual_avail, actual_total;
bool is_limited = platform::RecordedGpuMemGetInfo(
&avail, &total, &actual_avail, &actual_total, place_.device);
size_t allocated = total - avail;
std::string err_msg;
if (is_limited) {
auto limit_size = (total >> 20);
err_msg = string::Sprintf(
"Or set environment variable `FLAGS_gpu_memory_limit_mb` to a larger "
"value. Currently `FLAGS_gpu_memory_limit_mb` is %d, so the maximum "
"GPU memory usage is limited to %d MB.\n"
" The command is `export FLAGS_gpu_memory_limit_mb=xxx`.",
limit_size,
limit_size);
}
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, %s memory has been allocated and "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please decrease the batch size of your model. %s\n",
place_.device,
string::HumanReadableSize(size),
place_.device,
string::HumanReadableSize(allocated),
string::HumanReadableSize(avail),
place_.device,
err_msg));
}
gpuStream_t CUDAMallocAsyncAllocator::GetDefaultStream() const {
return default_stream_;
}
void CUDAMallocAsyncAllocator::SetDefaultStream(gpuStream_t stream) {
default_stream_ = stream;
}
} // namespace paddle::memory::allocation
#endif
@@ -0,0 +1,137 @@
// Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mutex> // NOLINT
#include <unordered_set>
#include "paddle/phi/backends/gpu/cuda/cuda_graph.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/platform/device/gpu/gpu_types.h"
namespace paddle {
namespace memory {
namespace allocation {
class CUDAMallocAsyncAllocation;
class CUDAMallocAsyncAllocator;
#ifdef PADDLE_WITH_CUDA
// TODO(eee4017): It may be beneficial to introduce an abstract class named
// `StreamAllocator` in future developments. This class would serve as a central
// entity for methods specifically related to stream management, such as
// `RecordStream` and `EraseStream`. The introduction of `StreamAllocator` would
// enable both `StreamSafeCUDAAllocator` and `CUDAMallocAsyncAllocator` to
// inherit directly from it,
// The `CUDAMallocAsyncAllocation` class extends `Allocation` and is used for
// managing memory allocations with CUDA async malloc. It includes methods to
// handle stream associations and to query the owning stream of the allocation.
class CUDAMallocAsyncAllocation : public Allocation {
public:
CUDAMallocAsyncAllocation(void* ptr,
size_t size,
Place place,
gpuStream_t malloc_stream,
gpuStream_t free_stream)
: Allocation(ptr, size, place),
malloc_stream_(malloc_stream),
free_stream_(free_stream) {}
gpuStream_t GetOwningStream() const { return malloc_stream_; }
bool RecordStream(gpuStream_t stream);
void EraseStream(gpuStream_t stream);
size_t Free();
private:
static thread_local std::once_flag once_flag_;
gpuStream_t malloc_stream_;
gpuStream_t free_stream_;
SpinLock recorded_streams_lock_;
std::unordered_set<gpuStream_t> recorded_streams_;
};
// The `CUDAMallocAsyncAllocator` class extends `Allocator` and is specialized
// for asynchronous memory allocation in CUDA. It offers thread-safe allocation
// and incorporates a default stream for memory operations.
class CUDAMallocAsyncAllocator : public Allocator {
public:
explicit CUDAMallocAsyncAllocator(
std::shared_ptr<Allocator> underlying_allocator,
const GPUPlace& place,
gpuStream_t default_stream);
bool IsAllocThreadSafe() const override { return true; }
gpuStream_t GetDefaultStream() const;
void SetDefaultStream(gpuStream_t stream);
void ClearFreeStream(bool sync = false);
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
uint64_t ReleaseImpl(const Place& place) override;
private:
void LazyInitializeCudaFreeStream();
void MallocThrottling();
void FreeAllocation(CUDAMallocAsyncAllocation* allocation);
std::shared_ptr<Allocator> underlying_allocator_;
GPUPlace place_; // Specifies the CUDA device context.
cudaMemPool_t mempool_;
gpuStream_t default_stream_; // Default stream for memory operations.
// we create a `free stream` for each allocator (each device should have a
// unique allocator) if an allocation is recorded on other stream than default
// stream, we release the allocation on `free stream`
gpuStream_t free_stream_;
size_t current_allocated_size_;
size_t pending_release_size_;
size_t max_size_;
double memory_throttle_ratio_;
std::once_flag once_flag_;
/*
* Life cycle management of graph_owned_allocations_:
*
* Each element within `graph_owned_allocations_` is initialized at
* `AllocateImpl`. However, there are two distinct ways of deconstruction.
*
* (A.) Deallocating occurs within `FreeImpl`.
* This implies that the allocation is initialized and disposed of during a
* graph capture, as in scenario (1.)
*
* (B.) Deallocation takes place in the callback after the graph is
* destructed. Meaning, the allocation is initialized during a graph capture
* but disposed of outside that context, as in scenario (2.)
*/
std::unordered_map<CUDAMallocAsyncAllocation*, CUDAGraphID>
graph_owned_allocations_;
SpinLock graph_owned_allocations_lock_;
};
#endif
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,95 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/cuda_managed_allocator.h"
#ifdef PADDLE_WITH_CUDA
#include <cuda.h>
#include <cuda_runtime.h>
#endif
#ifdef PADDLE_WITH_HIP
#include <hip/hip_runtime.h>
#endif
#include <string>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
bool CUDAManagedAllocator::IsAllocThreadSafe() const { return true; }
void CUDAManagedAllocator::FreeImpl(phi::Allocation* allocation) {
PADDLE_ENFORCE_EQ(
allocation->place(),
place_,
common::errors::PermissionDenied(
"GPU memory is freed in incorrect device. This may be a bug"));
platform::RecordedGpuFree(
allocation->ptr(), allocation->size(), place_.device);
delete allocation;
}
phi::Allocation* CUDAManagedAllocator::AllocateImpl(size_t size) {
std::call_once(once_flag_, [this] { platform::SetDeviceId(place_.device); });
int dev_id = place_.device; // NOLINT
void* ptr;
auto result = platform::RecordedGpuMalloc(&ptr,
size,
dev_id,
/* malloc_managed_memory = */ true);
if (LIKELY(result == gpuSuccess)) {
return new Allocation(ptr, size, Place(place_));
}
uint64_t limit_size = platform::RecordedGpuLimitSize(dev_id);
uint64_t malloc_size = platform::RecordedGpuMallocSize(dev_id);
bool is_limited =
platform::IsGpuMallocRecorded(dev_id) && malloc_size + size > limit_size;
std::string err_msg;
if (UNLIKELY(is_limited)) {
int64_t limit_size_mb = limit_size >> 20; // NOLINT
err_msg = string::Sprintf(
"Or set environment variable `FLAGS_gpu_memory_limit_mb` to a larger "
"value. Currently `FLAGS_gpu_memory_limit_mb` is %d, so the maximum "
"GPU memory usage is limited to %d MB.\n"
" The command is `export FLAGS_gpu_memory_limit_mb=xxx`.",
limit_size_mb,
limit_size_mb);
}
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s CUDA managed memory on GPU %d, %s memory has been "
"allocated.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please decrease the batch size of your model. %s\n\n",
dev_id,
string::HumanReadableSize(size),
dev_id,
string::HumanReadableSize(malloc_size),
dev_id,
err_msg));
}
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,40 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class CUDAManagedAllocator : public Allocator {
public:
explicit CUDAManagedAllocator(const GPUPlace& place) : place_(place) {}
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
GPUPlace place_;
std::once_flag once_flag_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,265 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifdef PADDLE_WITH_CUDA
#include <cuda.h>
#include <cuda_runtime.h>
#endif
#include <string>
#include "glog/logging.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/cuda_virtual_mem_allocator.h"
#include "paddle/phi/core/memory/stats.h"
#ifdef PADDLE_WITH_CUDA
#include "paddle/phi/backends/dynload/cuda_driver.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle::memory::allocation {
constexpr size_t kVirtualAddressSpaceSizeMultiplier = 2;
std::mutex CUDAVirtualMemAllocator::base_ptr_handle_mu_;
std::unordered_map<void*, CUmemGenericAllocationHandle>
CUDAVirtualMemAllocator::base_ptr_handle_map_;
CUDAVirtualMemAllocator::CUDAVirtualMemAllocator(const GPUPlace& place)
: place_(place), virtual_mem_base_(0), prop_{} {
CUmemAllocationProp prop = {};
// Setup the properties common for all the chunks
// The allocations will be device pinned memory.
// This property structure describes the physical location where the memory
// will be allocated via cuMemCreate along with additional properties In this
// case, the allocation will be pinned device memory local to a given device.
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
prop.location.id = place.device; // NOLINT
#if defined(_WIN32)
prop.requestedHandleTypes = CU_MEM_HANDLE_TYPE_NONE;
#else
prop.requestedHandleTypes = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
#endif
prop_ = prop;
// Prepare the access descriptor array indicating where and how the backings
// should be visible.
access_desc_.clear();
{
CUmemAccessDesc self = {};
self.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
self.location.id = place.device;
self.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
access_desc_.push_back(self);
}
}
void CUDAVirtualMemAllocator::InitOnce() {
std::call_once(init_flag_, [this] {
platform::SetDeviceId(place_.device);
paddle::platform::CUDADeviceGuard guard(place_.device);
// Get the minimum granularity.
size_t granularity = 0;
PADDLE_ENFORCE_GPU_SUCCESS(phi::dynload::cuMemGetAllocationGranularity(
&granularity, &prop_, CU_MEM_ALLOC_GRANULARITY_MINIMUM));
granularity_ = granularity;
// total size & VA size
size_t actual_avail, actual_total;
PADDLE_ENFORCE_GPU_SUCCESS(cudaMemGetInfo(&actual_avail, &actual_total));
VLOG(1) << "VMM InitOnce dev " << place_.device << " actual_avail: "
<< static_cast<double>(actual_avail) / (1 << 20) << " MB, "
<< "actual_total: " << static_cast<double>(actual_total) / (1 << 20)
<< " MB";
virtual_mem_size_ = AlignedSize(
actual_total * kVirtualAddressSpaceSizeMultiplier, granularity_);
// Reserve the required contiguous virtual address space for the allocations
// Reserving a larger VA range does not allocate physical memory up front.
// It leaves room for VMM pool fragmentation before physical memory is
// exhausted.
PADDLE_ENFORCE_GPU_SUCCESS(phi::dynload::cuMemAddressReserve(
&virtual_mem_base_, virtual_mem_size_, 0, 0, 0));
virtual_mem_alloced_offset_ = 0;
});
}
bool CUDAVirtualMemAllocator::IsAllocThreadSafe() const { return false; }
void CUDAVirtualMemAllocator::FreeImpl(phi::Allocation* allocation) {
PADDLE_ENFORCE_EQ(
allocation->place(),
place_,
common::errors::PermissionDenied(
"GPU memory is freed in incorrect device. This may be a bug"));
auto iter = virtual_2_physical_map_.find(
reinterpret_cast<CUdeviceptr>(allocation->ptr()));
if (iter == virtual_2_physical_map_.end()) {
PADDLE_THROW(common::errors::InvalidArgument(
"Can not find virtual memory address at %s", allocation->ptr()));
}
int prev_id;
cudaGetDevice(&prev_id);
if (prev_id != place_.device) {
cudaSetDevice(place_.device);
}
auto result = phi::dynload::cuMemUnmap(iter->first, iter->second.second);
if (result != CUDA_ERROR_DEINITIALIZED) {
PADDLE_ENFORCE_GPU_SUCCESS(result);
}
if (result != CUDA_ERROR_DEINITIALIZED) {
PADDLE_ENFORCE_GPU_SUCCESS(platform::RecordedGpuMemRelease(
iter->second.first, iter->second.second, place_.device));
}
if (prev_id != place_.device) {
cudaSetDevice(prev_id);
}
UnregisterHandle(allocation->ptr());
virtual_2_physical_map_.erase(iter);
delete allocation;
}
phi::Allocation* CUDAVirtualMemAllocator::AllocateImpl(size_t size) {
InitOnce();
size = AlignedSize(size, granularity_);
CUdeviceptr ptr = virtual_mem_base_ + virtual_mem_alloced_offset_;
if (ptr + size > virtual_mem_base_ + virtual_mem_size_) {
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU Virtual Memory %d. "
"Cannot allocate %s memory on GPU Virtual Memory %d, %s memory has "
"been allocated and "
"available memory is only %s.\n\n"
"Please decrease the batch size of your model.\n\n",
place_.device,
string::HumanReadableSize(size),
place_.device,
string::HumanReadableSize(virtual_mem_alloced_offset_),
string::HumanReadableSize(virtual_mem_size_ -
virtual_mem_alloced_offset_)));
return nullptr;
}
CUmemGenericAllocationHandle handle;
paddle::platform::CUDADeviceGuard guard(place_.device);
// Create physical memory backing allocation.
auto result =
platform::RecordedGpuMemCreate(&handle, size, &prop_, 0, place_.device);
if (result != CUDA_SUCCESS) {
if (result == CUDA_ERROR_OUT_OF_MEMORY) {
size_t actual_avail, actual_total;
PADDLE_ENFORCE_GPU_SUCCESS(cudaMemGetInfo(&actual_avail, &actual_total));
size_t actual_allocated = actual_total - actual_avail;
size_t actual_allocated_memory =
paddle::memory::DeviceMemoryStatCurrentValue("Allocated",
place_.device);
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, %s memory has been "
"allocated(actual using allocated memory %s) and "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please decrease the batch size of your model.\n\n",
place_.device,
string::HumanReadableSize(size),
place_.device,
string::HumanReadableSize(actual_allocated),
string::HumanReadableSize(actual_allocated_memory),
string::HumanReadableSize(actual_avail),
place_.device));
} else {
PADDLE_ENFORCE_GPU_SUCCESS(result);
}
return nullptr;
}
// Assign the chunk to the appropriate VA range and release the handle.
// After mapping the memory, it can be referenced by virtual address.
// The allocation will be kept live until it is unmapped.
result = phi::dynload::cuMemMap(ptr, size, 0, handle, 0);
if (result != CUDA_SUCCESS) {
platform::RecordedGpuMemRelease(handle, size, place_.device);
PADDLE_ENFORCE_GPU_SUCCESS(result);
return nullptr;
}
// Apply the access descriptors to the whole VA range.
result = phi::dynload::cuMemSetAccess(
ptr, size, access_desc_.data(), access_desc_.size());
if (result != CUDA_SUCCESS) {
phi::dynload::cuMemUnmap(ptr, size);
platform::RecordedGpuMemRelease(handle, size, place_.device);
PADDLE_ENFORCE_GPU_SUCCESS(result);
return nullptr;
}
virtual_2_physical_map_.emplace(ptr, std::make_pair(handle, size));
virtual_mem_alloced_offset_ += size;
VLOG(10) << "AllocateImpl chunk handle: " << static_cast<int64_t>(handle)
<< ", size=" << size
<< ", device=" << static_cast<int>(place_.device);
RegisterHandle(reinterpret_cast<void*>(ptr), handle);
return new Allocation(
reinterpret_cast<void*>(ptr), size, Place(place_)); // NOLINT
}
CUmemGenericAllocationHandle CUDAVirtualMemAllocator::GetHandleFromBasePtr(
void* base_ptr) {
std::lock_guard<std::mutex> guard(base_ptr_handle_mu_);
auto it = base_ptr_handle_map_.find(base_ptr);
if (it == base_ptr_handle_map_.end()) {
return 0;
}
return it->second;
}
void CUDAVirtualMemAllocator::RegisterHandle(
void* base_ptr, CUmemGenericAllocationHandle handle) {
std::lock_guard<std::mutex> guard(base_ptr_handle_mu_);
base_ptr_handle_map_.emplace(base_ptr, handle);
}
void CUDAVirtualMemAllocator::UnregisterHandle(void* base_ptr) {
std::lock_guard<std::mutex> guard(base_ptr_handle_mu_);
base_ptr_handle_map_.erase(base_ptr);
}
} // namespace paddle::memory::allocation
#endif
@@ -0,0 +1,78 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifdef PADDLE_WITH_CUDA
#include <cuda_runtime.h>
#include "paddle/phi/backends/dynload/cuda_driver.h"
#include "paddle/phi/core/memory/allocation/vmm_ipc_allocation.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#endif
#include <mutex> // NOLINT
#include <unordered_map>
#include <vector>
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#ifdef PADDLE_WITH_CUDA
namespace paddle {
namespace memory {
namespace allocation {
// Allocate memory using NVIDIA's virtual memory management technology
class CUDAVirtualMemAllocator : public Allocator {
public:
explicit CUDAVirtualMemAllocator(const GPUPlace& place);
bool IsAllocThreadSafe() const override;
static CUmemGenericAllocationHandle GetHandleFromBasePtr(void* base_ptr);
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
GPUPlace place_;
std::once_flag init_flag_;
CUdeviceptr virtual_mem_base_;
size_t virtual_mem_size_;
size_t virtual_mem_alloced_offset_;
size_t granularity_;
CUmemAllocationProp prop_;
std::vector<CUmemAccessDesc> access_desc_;
std::map<CUdeviceptr, std::pair<CUmemGenericAllocationHandle, size_t>>
virtual_2_physical_map_;
static std::mutex base_ptr_handle_mu_;
static std::unordered_map<void*, CUmemGenericAllocationHandle>
base_ptr_handle_map_;
void InitOnce();
static void RegisterHandle(void* base_ptr, CUmemGenericAllocationHandle h);
static void UnregisterHandle(void* base_ptr);
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,459 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/cuda_virtual_mem_allocator_v2.h"
#if defined(PADDLE_WITH_CUDA)
#include <algorithm>
#include <limits>
#include <utility>
#include "glog/logging.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
#include "paddle/phi/core/scope_guard.h"
namespace paddle {
namespace memory {
namespace allocation {
namespace {
constexpr size_t kVMMSetAccessChunkSize = 64UL << 20;
bool IsCudaDeinitialized(CUresult result) {
return result == CUDA_ERROR_DEINITIALIZED;
}
size_t GetPoolVAMultiplier(PoolType pool_type) {
switch (pool_type) {
case PoolType::kSmall:
return 1;
case PoolType::kLarge:
return 4;
}
return 1;
}
struct SetAccessResult {
CUresult status{CUDA_SUCCESS};
size_t failed_offset{0};
size_t failed_size{0};
};
SetAccessResult SetAccessInChunks(VMMDevicePtr ptr,
size_t size,
size_t handle_size,
const std::vector<CUmemAccessDesc>& desc) {
const size_t chunk_size =
std::max(handle_size, AlignedSize(kVMMSetAccessChunkSize, handle_size));
size_t offset = 0;
while (offset < size) {
const size_t remaining = size - offset;
const size_t current_size = std::min(chunk_size, remaining);
auto status = phi::dynload::cuMemSetAccess(
ptr + offset, current_size, desc.data(), desc.size());
if (status != CUDA_SUCCESS) {
return {status, offset, current_size};
}
offset += current_size;
}
return {};
}
template <typename Map, typename Key, typename Value>
void EmplaceOrEnforce(Map* map,
Key&& key,
Value&& value,
const char* map_name) {
const bool inserted =
map->try_emplace(std::forward<Key>(key), std::forward<Value>(value))
.second;
PADDLE_ENFORCE_EQ(
inserted,
true,
common::errors::AlreadyExists(
"Duplicate key inserted into %s, allocator state is inconsistent.",
map_name));
}
} // namespace
void CUDAVirtualMemAllocatorV2::AllocationLayoutRegistry::Add(
void* ptr, const HandleLayout& layout) {
std::lock_guard<SpinLock> guard(spinlock_);
EmplaceOrEnforce(&layouts_, ptr, layout, "allocation_layout_map_");
}
bool CUDAVirtualMemAllocatorV2::AllocationLayoutRegistry::Lookup(
void* ptr, HandleLayout* layout) const {
std::lock_guard<SpinLock> guard(spinlock_);
auto it = layouts_.find(ptr);
if (it == layouts_.end()) {
return false;
}
if (layout != nullptr) {
*layout = it->second;
}
return true;
}
void CUDAVirtualMemAllocatorV2::AllocationLayoutRegistry::Remove(void* ptr) {
std::lock_guard<SpinLock> guard(spinlock_);
layouts_.erase(ptr);
}
CUDAVirtualMemAllocatorV2::CUDAVirtualMemAllocatorV2(const GPUPlace& place,
size_t handle_size,
PoolType pool)
: place_(place), handle_size_(handle_size), pool_type_(pool) {}
bool CUDAVirtualMemAllocatorV2::IsAllocThreadSafe() const { return false; }
void CUDAVirtualMemAllocatorV2::RollbackCreatedHandles(
const HandleLayout& layout) const {
for (const auto& meta : layout) {
if (meta == nullptr) {
continue;
}
phi::dynload::cuMemUnmap(meta->base(), meta->size());
platform::RecordedGpuMemRelease(
meta->handle(), meta->size(), place_.device);
}
}
void CUDAVirtualMemAllocatorV2::MarkLayoutMapped(const HandleLayout& layout) {
for (const auto& meta : layout) {
backing_map_.MarkMapped(meta->base(), meta, meta->size());
}
}
void CUDAVirtualMemAllocatorV2::InitOnce() {
std::call_once(init_flag_, [this] {
platform::CUDADeviceGuard guard(place_.device);
prop_.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop_.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
prop_.location.id = place_.device;
#if defined(_WIN32)
prop_.requestedHandleTypes = CU_MEM_HANDLE_TYPE_NONE;
#else
prop_.requestedHandleTypes = CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR;
#endif
PADDLE_ENFORCE_GPU_SUCCESS(phi::dynload::cuMemGetAllocationGranularity(
&granularity_, &prop_, CU_MEM_ALLOC_GRANULARITY_MINIMUM));
// V2 uses a per-pool fixed handle size. Unlike V1, the allocator rounds
// user input up to the device granularity so upper layers can treat every
// handle in one HandleLayout as a stable fixed-size building block.
handle_size_ =
AlignedSize(std::max(handle_size_, granularity_), granularity_);
size_t actual_avail = 0;
size_t actual_total = 0;
PADDLE_ENFORCE_GPU_SUCCESS(cudaMemGetInfo(&actual_avail, &actual_total));
const size_t va_multiplier = GetPoolVAMultiplier(pool_type_);
PADDLE_ENFORCE_LE(va_multiplier,
std::numeric_limits<size_t>::max() / actual_total,
common::errors::InvalidArgument(
"VA multiplier %d for pool %d overflows size_t.",
va_multiplier,
static_cast<int>(pool_type_)));
// Reserve VA by pool to leave room for later split and in-place reuse.
virtual_mem_size_ = AlignedSize(actual_total * va_multiplier, granularity_);
PADDLE_ENFORCE_GPU_SUCCESS(phi::dynload::cuMemAddressReserve(
&virtual_mem_base_, virtual_mem_size_, 0, 0, 0));
backing_map_.Configure(
virtual_mem_base_, virtual_mem_size_, handle_size_, place_.device);
CUmemAccessDesc self = {};
self.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
self.location.id = place_.device;
self.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
access_desc_.push_back(self);
});
}
phi::Allocation* CUDAVirtualMemAllocatorV2::AllocateImpl(size_t size) {
return AppendWithLayout(size).allocation.release();
}
CUDAVirtualMemAllocatorV2::AllocationWithLayout
CUDAVirtualMemAllocatorV2::AppendWithLayout(size_t size) {
InitOnce();
size_t aligned = AlignedSize(size, handle_size_);
PADDLE_ENFORCE_LE(
virtual_mem_alloced_offset_,
virtual_mem_size_,
common::errors::InvalidArgument(
"VMMAllocatorV2 tail offset exceeds reserved VA space."));
PADDLE_ENFORCE_LE(
aligned,
virtual_mem_size_ - virtual_mem_alloced_offset_,
common::errors::ResourceExhausted("VMMAllocatorV2 virtual address space "
"is exhausted for place %s.",
place_));
VMMDevicePtr ptr = virtual_mem_base_ + virtual_mem_alloced_offset_;
auto layout = CreateMappedHandleLayout(ptr, aligned, "AppendWithLayout");
MarkLayoutMapped(layout);
return WrapTrackedAllocation(ptr, aligned, std::move(layout), true);
}
CUDAVirtualMemAllocatorV2::AllocationWithBlock
CUDAVirtualMemAllocatorV2::AppendWithBlock(size_t size) {
return BuildAllocationWithBlock(AppendWithLayout(size));
}
CUDAVirtualMemAllocatorV2::AllocationWithLayout
CUDAVirtualMemAllocatorV2::PlaceAtVAWithLayout(VMMDevicePtr ptr, size_t size) {
InitOnce();
const size_t aligned = AlignedSize(size, handle_size_);
const size_t num_handles = aligned / handle_size_;
PADDLE_ENFORCE_EQ(virtual_mem_base_ + virtual_mem_size_ < virtual_mem_base_,
false,
common::errors::InvalidArgument(
"VMMAllocatorV2 reserved VA range overflows."));
PADDLE_ENFORCE_GE(
ptr,
virtual_mem_base_,
common::errors::InvalidArgument(
"VMMAllocatorV2 PlaceAtVA ptr is before reserved VA range."));
PADDLE_ENFORCE_LT(
ptr,
virtual_mem_base_ + virtual_mem_size_,
common::errors::InvalidArgument(
"VMMAllocatorV2 PlaceAtVA ptr is outside reserved VA range."));
PADDLE_ENFORCE_EQ(
(ptr - virtual_mem_base_) % handle_size_,
0UL,
common::errors::InvalidArgument(
"VMMAllocatorV2 PlaceAtVA requires handle-aligned VA, ptr=%p.",
reinterpret_cast<void*>(ptr)));
PADDLE_ENFORCE_LE(
aligned,
virtual_mem_base_ + virtual_mem_size_ - ptr,
common::errors::ResourceExhausted(
"VMMAllocatorV2 PlaceAtVA range exceeds reserved VA space."));
VLOG(6) << "VMM V2 PlaceAtVA(AllocateAtVA) ptr="
<< reinterpret_cast<void*>(ptr) << " requested=" << size
<< " aligned=" << aligned << " handle_count=" << num_handles
<< " tail_offset=" << virtual_mem_alloced_offset_;
auto layout = CreateMappedHandleLayout(ptr, aligned, "PlaceAtVAWithLayout");
MarkLayoutMapped(layout);
return WrapTrackedAllocation(ptr, aligned, std::move(layout), false);
}
CUDAVirtualMemAllocatorV2::AllocationWithBlock
CUDAVirtualMemAllocatorV2::PlaceAtVAWithBlock(VMMDevicePtr ptr, size_t size) {
return BuildAllocationWithBlock(PlaceAtVAWithLayout(ptr, size));
}
HandleLayout CUDAVirtualMemAllocatorV2::CreateMappedHandleLayout(
VMMDevicePtr ptr, size_t aligned_size, const char* context) {
platform::CUDADeviceGuard guard(place_.device);
const size_t num_handles = aligned_size / handle_size_;
HandleLayout layout;
layout.reserve(num_handles);
for (size_t i = 0; i < num_handles; ++i) {
VMMAllocHandle handle;
auto ce = platform::RecordedGpuMemCreate(
&handle, handle_size_, &prop_, 0, place_.device);
if (ce != CUDA_SUCCESS) {
RollbackCreatedHandles(layout);
if (ce == CUDA_ERROR_OUT_OF_MEMORY) {
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"%s cuMemCreate failed: out of GPU memory at handle %zu/%zu "
"(handle_size=%zu).",
context,
i,
num_handles,
handle_size_));
}
PADDLE_ENFORCE_GPU_SUCCESS(ce);
}
const VMMDevicePtr dst = ptr + i * handle_size_;
auto me = phi::dynload::cuMemMap(dst, handle_size_, 0, handle, 0);
if (me != CUDA_SUCCESS) {
platform::RecordedGpuMemRelease(handle, handle_size_, place_.device);
RollbackCreatedHandles(layout);
PADDLE_THROW(common::errors::External(
"%s cuMemMap failed at handle %zu/%zu.", context, i, num_handles));
}
layout.push_back(std::make_shared<VMMHandleMeta>(
VMMHandleMeta{dst, handle_size_, handle, place_.device}));
}
try {
SetAccessOrThrow(ptr, aligned_size, num_handles, context);
} catch (...) {
RollbackCreatedHandles(layout);
throw;
}
return layout;
}
void CUDAVirtualMemAllocatorV2::SetAccessOrThrow(VMMDevicePtr ptr,
size_t aligned_size,
size_t num_handles,
const char* context) {
auto access_result =
SetAccessInChunks(ptr, aligned_size, handle_size_, access_desc_);
if (access_result.status == CUDA_SUCCESS) {
return;
}
size_t actual_avail = 0;
size_t actual_total = 0;
PADDLE_ENFORCE_GPU_SUCCESS(cudaMemGetInfo(&actual_avail, &actual_total));
if (access_result.status == CUDA_ERROR_OUT_OF_MEMORY) {
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"%s cuMemSetAccess failed: out of GPU memory at offset %zu/%zu "
"(failed_size=%zu, handle_size=%zu, handle_count=%zu, "
"actual_avail=%zu, actual_total=%zu).",
context,
access_result.failed_offset,
aligned_size,
access_result.failed_size,
handle_size_,
num_handles,
actual_avail,
actual_total));
}
PADDLE_THROW(common::errors::External(
"%s cuMemSetAccess failed at offset %zu/%zu (failed_size=%zu, "
"handle_size=%zu, handle_count=%zu, status=%d, actual_avail=%zu, "
"actual_total=%zu).",
context,
access_result.failed_offset,
aligned_size,
access_result.failed_size,
handle_size_,
num_handles,
static_cast<int>(access_result.status),
actual_avail,
actual_total));
}
bool CUDAVirtualMemAllocatorV2::CollectAllocationHandleLayout(
void* ptr, HandleLayout* layout) const {
return allocation_layouts_.Lookup(ptr, layout);
}
void CUDAVirtualMemAllocatorV2::FreeImpl(phi::Allocation* allocation) {
auto* ptr = allocation->ptr();
HandleLayout layout = RequireHandleLayout(ptr);
int prev_id = -1;
bool restore_device = false;
if (cudaGetDevice(&prev_id) == cudaSuccess && prev_id != place_.device) {
restore_device = cudaSetDevice(place_.device) == cudaSuccess;
}
DEFINE_PADDLE_SCOPE_GUARD([&] {
if (restore_device) {
cudaSetDevice(prev_id);
}
});
for (const auto& handle : layout) {
auto result = phi::dynload::cuMemUnmap(handle->base(), handle->size());
if (IsCudaDeinitialized(result)) {
continue;
}
PADDLE_ENFORCE_GPU_SUCCESS(result);
backing_map_.MarkUnmapped(handle->base(), handle->size());
result = platform::RecordedGpuMemRelease(
handle->handle(), handle->size(), place_.device);
if (IsCudaDeinitialized(result)) {
continue;
}
PADDLE_ENFORCE_GPU_SUCCESS(result);
backing_map_.MarkReleased(handle->base(), handle->handle(), handle->size());
}
UnregisterHandleLayout(ptr);
delete allocation;
}
CUDAVirtualMemAllocatorV2::AllocationWithLayout
CUDAVirtualMemAllocatorV2::WrapTrackedAllocation(VMMDevicePtr ptr,
size_t size,
HandleLayout layout,
bool advance_tail) {
if (advance_tail) {
AdvanceTailOffset(size);
}
AllocationWithLayout result;
auto* alloc = CreateTrackedAllocation(ptr, size, layout);
CUDAVirtualMemAllocatorV2* self = this;
result.layout = std::move(layout);
result.allocation = DecoratedAllocationPtr(alloc, [self](phi::Allocation* a) {
self->FreeImpl(static_cast<Allocation*>(a));
});
return result;
}
CUDAVirtualMemAllocatorV2::AllocationWithBlock
CUDAVirtualMemAllocatorV2::BuildAllocationWithBlock(
AllocationWithLayout allocation_with_layout) {
AllocationWithBlock result;
result.block =
BlockV2::MakeMappedBlock(BlockType::kFree,
allocation_with_layout.allocation->ptr(),
allocation_with_layout.allocation->size(),
pool_type_);
result.allocation = std::move(allocation_with_layout.allocation);
return result;
}
Allocation* CUDAVirtualMemAllocatorV2::CreateTrackedAllocation(
VMMDevicePtr ptr, size_t size, const HandleLayout& layout) {
RegisterHandleLayout(reinterpret_cast<void*>(ptr), layout);
return new Allocation(reinterpret_cast<void*>(ptr), size, place_); // NOLINT
}
void CUDAVirtualMemAllocatorV2::RegisterHandleLayout(
void* ptr, const HandleLayout& layout) {
allocation_layouts_.Add(ptr, layout);
if (!backing_map_.ValidateLayout(layout, "RegisterHandleLayout")) {
VLOG(0) << "VMM V2 BackingMap validation failed while registering layout "
<< ptr;
}
}
HandleLayout CUDAVirtualMemAllocatorV2::RequireHandleLayout(void* ptr) const {
HandleLayout layout;
const bool found = allocation_layouts_.Lookup(ptr, &layout);
PADDLE_ENFORCE_EQ(
found,
true,
common::errors::NotFound(
"No VMMAllocatorV2 handle layout found for allocation %p.", ptr));
return layout;
}
void CUDAVirtualMemAllocatorV2::UnregisterHandleLayout(void* ptr) {
allocation_layouts_.Remove(ptr);
}
bool CUDAVirtualMemAllocatorV2::IsRangeReleasable(VMMDevicePtr ptr,
size_t size) const {
return backing_map_.IsRangeReleasable(ptr, size);
}
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,141 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#if defined(PADDLE_WITH_CUDA)
#include <unordered_map>
#include <vector>
#include "paddle/phi/backends/dynload/cuda_driver.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/allocation/vmm_allocator_v2_types.h"
#include "paddle/phi/core/memory/allocation/vmm_backing_map.h"
namespace paddle {
namespace memory {
namespace allocation {
// Compared with CUDAVirtualMemAllocator, V2 does not expose a single
// VA<->handle mapping per allocation. It keeps the handle layout registered in
// the bottom allocator and hands upper layers either allocation-level layout
// snapshots or materialized mapped-free BlockV2 views.
class CUDAVirtualMemAllocatorV2 : public Allocator {
public:
struct AllocationWithLayout {
DecoratedAllocationPtr allocation;
HandleLayout layout;
};
struct AllocationWithBlock {
bool HasAllocation() const { return allocation != nullptr; }
BlockV2 TakeBlock() { return std::move(block); }
DecoratedAllocationPtr TakeAllocation() { return std::move(allocation); }
DecoratedAllocationPtr allocation;
BlockV2 block;
};
struct AllocationLayoutRegistry {
void Add(void* ptr, const HandleLayout& layout);
bool Lookup(void* ptr, HandleLayout* layout) const;
void Remove(void* ptr);
private:
std::unordered_map<void*, HandleLayout> layouts_;
mutable SpinLock spinlock_;
};
// Standalone use defaults to the large pool. Upper layers may also choose
// explicit small/large pool types.
CUDAVirtualMemAllocatorV2(const GPUPlace& place,
size_t handle_size,
PoolType pool = PoolType::kLarge);
bool IsAllocThreadSafe() const override;
size_t handle_size() const { return handle_size_; }
PoolType pool_type() const { return pool_type_; }
VMMDevicePtr virtual_mem_base() const { return virtual_mem_base_; }
size_t virtual_mem_size() const { return virtual_mem_size_; }
size_t tail_offset() const { return virtual_mem_alloced_offset_; }
// Best-fit layers may consume VA from the reserved range incrementally. V2
// keeps this as an explicit cursor instead of reusing V1's
// virtual_2_physical_map_ bookkeeping.
void AdvanceTailOffset(size_t bytes) { virtual_mem_alloced_offset_ += bytes; }
// Retreat the tail cursor after upper layers release tail-end backing.
void SetTailOffset(size_t offset) { virtual_mem_alloced_offset_ = offset; }
const GPUPlace& place() const { return place_; }
AllocationWithBlock AppendWithBlock(size_t size);
// Create fresh physical backing and map it at an existing reserved VA range.
// This is used by upper layers to reuse unmapped-free VA space in place.
AllocationWithBlock PlaceAtVAWithBlock(VMMDevicePtr ptr, size_t size);
bool IsRangeReleasable(VMMDevicePtr ptr, size_t size) const;
protected:
phi::Allocation* AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation* allocation) override;
private:
void InitOnce();
void RollbackCreatedHandles(const HandleLayout& layout) const;
void MarkLayoutMapped(const HandleLayout& layout);
AllocationWithLayout AppendWithLayout(size_t size);
AllocationWithLayout PlaceAtVAWithLayout(VMMDevicePtr ptr, size_t size);
HandleLayout CreateMappedHandleLayout(VMMDevicePtr ptr,
size_t aligned_size,
const char* context);
void SetAccessOrThrow(VMMDevicePtr ptr,
size_t aligned_size,
size_t num_handles,
const char* context);
bool CollectAllocationHandleLayout(void* ptr, HandleLayout* layout) const;
AllocationWithLayout WrapTrackedAllocation(VMMDevicePtr ptr,
size_t size,
HandleLayout layout,
bool advance_tail);
AllocationWithBlock BuildAllocationWithBlock(
AllocationWithLayout allocation_with_layout);
Allocation* CreateTrackedAllocation(VMMDevicePtr ptr,
size_t size,
const HandleLayout& layout);
void RegisterHandleLayout(void* ptr, const HandleLayout& layout);
HandleLayout RequireHandleLayout(void* ptr) const;
void UnregisterHandleLayout(void* ptr);
GPUPlace place_;
size_t handle_size_;
PoolType pool_type_;
std::once_flag init_flag_;
VMMDevicePtr virtual_mem_base_{0};
size_t virtual_mem_size_{0};
size_t virtual_mem_alloced_offset_{0};
size_t granularity_{0};
CUmemAllocationProp prop_{};
std::vector<CUmemAccessDesc> access_desc_;
AllocationLayoutRegistry allocation_layouts_;
VMMBackingMap backing_map_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,86 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/custom_allocator.h"
#include "paddle/phi/api/profiler/trace_event.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/stats.h"
#include "paddle/phi/core/platform/device/device_wrapper.h"
#include "paddle/phi/core/platform/profiler.h"
namespace paddle {
namespace memory {
namespace allocation {
bool CustomAllocator::IsAllocThreadSafe() const { return true; }
void CustomAllocator::FreeImpl(phi::Allocation* allocation) {
PADDLE_ENFORCE_EQ(
allocation->place(),
place_,
common::errors::PermissionDenied("CustomDevice memory is "
"freed in incorrect device. "
"This may be a bug"));
if (phi::DeviceManager::HasDeviceType(place_.GetDeviceType())) {
phi::DeviceManager::GetDeviceWithPlace(place_)->MemoryDeallocate(
allocation->ptr(), allocation->size());
}
DEVICE_MEMORY_STAT_UPDATE(
Reserved, place_.GetDeviceId(), -allocation->size());
platform::RecordMemEvent(allocation->ptr(),
place_,
allocation->size(),
phi::TracerMemEventType::ReservedFree);
delete allocation;
}
phi::Allocation* CustomAllocator::AllocateImpl(size_t size) {
std::call_once(once_flag_, [this] { phi::DeviceManager::SetDevice(place_); });
void* ptr =
phi::DeviceManager::GetDeviceWithPlace(place_)->MemoryAllocate(size);
if (LIKELY(ptr)) {
DEVICE_MEMORY_STAT_UPDATE(Reserved, place_.GetDeviceId(), size);
platform::RecordMemEvent(
ptr, place_, size, phi::TracerMemEventType::ReservedAllocate);
return new Allocation(ptr, size, place_);
}
size_t avail, total;
phi::DeviceManager::MemoryStats(place_, &total, &avail);
auto dev_type = phi::PlaceHelper::GetDeviceType(place_);
auto dev_id = phi::PlaceHelper::GetDeviceId(place_);
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on %s:%d. "
"Cannot allocate %s memory on %s:%d, "
"available memory is only %s.\n\n"
"Please check whether there is any other process using %s:%d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another %s.\n"
"2. If no, please decrease the batch size of your model.\n\n",
dev_type,
dev_id,
string::HumanReadableSize(size),
dev_type,
dev_id,
string::HumanReadableSize(avail),
dev_type,
dev_id,
dev_type));
}
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,42 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mutex> // NOLINT
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class CustomAllocator : public Allocator {
public:
explicit CustomAllocator(const CustomPlace& place) : place_(place) {}
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
Place place_;
std::once_flag once_flag_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,72 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstdint>
#include <vector>
#include "paddle/phi/core/enforce.h"
namespace paddle {
namespace memory {
namespace allocation {
template <typename T, size_t N>
class InlinedVector {
static_assert(N > 0, "N must be larger than 0");
public:
inline InlinedVector() { len_ = 0; }
inline size_t size() const { return len_; }
inline T& operator[](size_t i) { return i < N ? head_[i] : tail_[i - N]; }
inline const T& operator[](size_t i) const {
return i < N ? head_[i] : tail_[i - N];
}
inline void emplace_back(const T& item) {
if (LIKELY(len_ < N)) {
head_[len_++] = item;
} else {
tail_.emplace_back(item);
++len_;
}
}
inline void pop_back() {
if (UNLIKELY(len_ > N)) {
tail_.pop_back();
}
--len_;
}
inline T& back() {
if (LIKELY(len_ <= N)) {
return head_[len_ - 1];
} else {
return tail_.back();
}
}
private:
T head_[N];
size_t len_;
std::vector<T> tail_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,155 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include "paddle/phi/core/memory/allocation/memory_block.h"
#include "paddle/phi/core/enforce.h"
namespace paddle::memory::detail {
void MemoryBlock::Init(MetadataCache* cache,
Type t,
size_t index,
size_t size,
void* left_buddy,
void* right_buddy) {
cache->Save(this,
MemoryBlock::Desc(t,
index,
size - sizeof(MemoryBlock::Desc),
size,
static_cast<MemoryBlock*>(left_buddy),
static_cast<MemoryBlock*>(right_buddy)));
}
MemoryBlock* MemoryBlock::GetLeftBuddy(MetadataCache* cache) {
return cache->LoadDesc(this)->left_buddy;
}
MemoryBlock* MemoryBlock::GetRightBuddy(MetadataCache* cache) {
return cache->LoadDesc(this)->right_buddy;
}
void MemoryBlock::Split(MetadataCache* cache,
size_t size,
size_t extra_padding_size) {
auto desc = cache->LoadDesc(this);
// make sure the split fits
PADDLE_ENFORCE_GE(desc->total_size,
size,
common::errors::InvalidArgument(
"The size of memory block (%d) to split is "
"not larger than size of request memory (%d)",
desc->total_size,
size));
size_t pay_load_size = sizeof(MemoryBlock::Desc) + extra_padding_size;
// bail out if there is no room for another partition
if (desc->total_size - size <= pay_load_size) {
return;
}
// find the position of the split
void* right_partition = reinterpret_cast<uint8_t*>(this) + size;
size_t remaining_size = desc->total_size - size;
// Add the new block as a buddy
// Write the metadata for the new block
auto new_block_right_buddy = desc->right_buddy;
cache->Save(static_cast<MemoryBlock*>(right_partition),
MemoryBlock::Desc(FREE_CHUNK,
desc->index,
remaining_size - pay_load_size,
remaining_size,
this,
new_block_right_buddy));
desc->right_buddy = static_cast<MemoryBlock*>(right_partition);
desc->size = size - pay_load_size;
desc->total_size = size;
desc->UpdateGuards();
// Write metadata for the new block's right buddy
if (new_block_right_buddy != nullptr) {
auto buddy_desc = cache->LoadDesc(new_block_right_buddy);
buddy_desc->left_buddy = static_cast<MemoryBlock*>(right_partition);
buddy_desc->UpdateGuards();
}
}
void MemoryBlock::Merge(MetadataCache* cache, MemoryBlock* right_buddy) {
// only free blocks can be merged
auto desc = cache->LoadDesc(this);
auto rb_desc = cache->LoadDesc(right_buddy);
PADDLE_ENFORCE_EQ(desc->type,
FREE_CHUNK,
common::errors::PreconditionNotMet(
"The destination chunk to merge is not free"));
PADDLE_ENFORCE_EQ(rb_desc->type,
FREE_CHUNK,
common::errors::PreconditionNotMet(
"The source chunk to merge is not free"));
// link this->buddy's buddy
desc->right_buddy = rb_desc->right_buddy;
// link buddy's buddy -> this
if (desc->right_buddy != nullptr) {
auto buddy_metadata = cache->LoadDesc(desc->right_buddy);
buddy_metadata->left_buddy = this;
buddy_metadata->UpdateGuards();
}
desc->size += rb_desc->total_size;
desc->total_size += rb_desc->total_size;
desc->UpdateGuards();
cache->Save(right_buddy,
MemoryBlock::Desc(INVALID_CHUNK, 0, 0, 0, nullptr, nullptr));
}
void MemoryBlock::MarkAsFree(MetadataCache* cache) {
// check for double free or corruption
auto desc = cache->LoadDesc(this);
PADDLE_ENFORCE_NE(desc->type,
FREE_CHUNK,
common::errors::PreconditionNotMet(
"The chunk to mark as free is free already"));
PADDLE_ENFORCE_NE(desc->type,
INVALID_CHUNK,
common::errors::PreconditionNotMet(
"The chunk to mark as free is invalid"));
desc->type = FREE_CHUNK;
desc->UpdateGuards();
}
void* MemoryBlock::Data() const {
return const_cast<MemoryBlock::Desc*>(
reinterpret_cast<const MemoryBlock::Desc*>(this)) +
1;
}
MemoryBlock* MemoryBlock::Metadata() const {
return const_cast<MemoryBlock*>(reinterpret_cast<const MemoryBlock*>(
reinterpret_cast<const MemoryBlock::Desc*>(this) - 1));
}
} // namespace paddle::memory::detail
@@ -0,0 +1,145 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#pragma once
#include <cstddef>
#include <cstdint>
#include <unordered_map>
namespace paddle {
namespace memory {
namespace detail {
// Forward declaration.
class MetadataCache;
// MemoryBlock represents Each allocated memory block, which contains
// MemoryBlock::Desc and the payload.
struct MemoryBlock {
enum Type {
FREE_CHUNK, // memory is free and idle
ARENA_CHUNK, // memory is being occupied
HUGE_CHUNK, // memory is out of management
INVALID_CHUNK // memory is invalid
};
// init saves the MemoryBlock::Desc of the memory block in a MetadataCache.
// If it is a CPU memory block, the MetadataCache writes the
// MemoryBlock::Desc to the beginning of the block; or, if it is a GPU memory
// block, the MetadataCache writes the Metadata to a std::map in
// the CPU.
void Init(MetadataCache* cache,
Type t,
size_t index,
size_t size,
void* left_buddy,
void* right_buddy);
MemoryBlock* GetLeftBuddy(MetadataCache* cache);
MemoryBlock* GetRightBuddy(MetadataCache* cache);
// Split the allocation into left/right blocks.
void Split(MetadataCache* cache, size_t size, size_t extra_padding_size = 0);
// Merge left and right blocks together.
void Merge(MetadataCache* cache, MemoryBlock* right_buddy);
// Mark the allocation as free.
void MarkAsFree(MetadataCache* cache);
void* Data() const;
MemoryBlock* Metadata() const;
// MemoryBlock::Desc describes a MemoryBlock.
struct Desc {
Desc(MemoryBlock::Type t,
size_t i,
size_t s,
size_t ts,
MemoryBlock* l,
MemoryBlock* r);
Desc();
// mutator for type
inline void set_type(const MemoryBlock::Type& type) {
this->type = type;
this->UpdateGuards();
}
// accessor for type
inline const MemoryBlock::Type& get_type() const { return this->type; }
// accessor for index
inline const size_t& get_index() const { return this->index; }
// accessor for size
inline const size_t& get_size() const { return this->size; }
// accessor for total_size
inline const size_t& get_total_size() const { return this->total_size; }
// Updates guard_begin and guard_end by hashes of the Metadata object.
void UpdateGuards();
// Checks that guard_begin and guard_end are hashes of the Metadata object.
bool CheckGuards() const;
// TODO(gangliao): compress this
size_t guard_begin = 0;
MemoryBlock::Type type = MemoryBlock::INVALID_CHUNK;
size_t index = 0;
size_t size = 0;
size_t total_size = 0;
MemoryBlock* left_buddy = nullptr;
MemoryBlock* right_buddy = nullptr;
size_t guard_end = 0;
};
};
// A cache for accessing memory block meta-data that may be expensive
// to access directly. This class exists to unify the
// MemoryBlock::Desc format between GPU and CPU allocations. It should
// be removed when the CPU can access all GPU allocations directly via
// UVM.
class MetadataCache {
public:
explicit MetadataCache(bool uses_gpu);
// Disable copying and assignment.
MetadataCache(const MetadataCache&) = delete;
MetadataCache& operator=(const MetadataCache&) = delete;
// Returns the MemoryBlock::Desc for a memory block. When MetadataCache is
// used to manage CPU memory, the MemoryBlock::Desc resides at the beginning
// of the memory block; when used to manage GPU memory, the
// Metadata resides in CPU memory indexed by cache_.
MemoryBlock::Desc* LoadDesc(MemoryBlock* memory_block);
// Saves the MemoryBlock::Desc of a memory block into the cache. For CPU
// memory block, writes the MemoryBlock::Desc to the beginning of the memory
// block; whereas for GPU memory, writes it to cache_.
void Save(MemoryBlock* memory_block, const MemoryBlock::Desc& meta_data);
// For GPU memory block, erases its MemoryBlock::Desc from cache_.
void Invalidate(MemoryBlock* memory_block);
private:
typedef std::unordered_map<const MemoryBlock*, MemoryBlock::Desc> MetadataMap;
MetadataMap cache_;
bool uses_gpu_;
};
} // namespace detail
} // namespace memory
} // namespace paddle
@@ -0,0 +1,75 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include <cstddef>
#include <functional>
#include "paddle/phi/core/memory/allocation/memory_block.h"
namespace paddle::memory::detail {
MemoryBlock::Desc::Desc(MemoryBlock::Type t,
size_t i,
size_t s,
size_t ts,
MemoryBlock* l,
MemoryBlock* r)
: type(t),
index(i),
size(s),
total_size(ts),
left_buddy(l),
right_buddy(r) {}
MemoryBlock::Desc::Desc()
: type(MemoryBlock::INVALID_CHUNK),
index(0),
size(0),
total_size(0),
left_buddy(nullptr),
right_buddy(nullptr) {}
namespace {
template <class T>
inline void hash_combine(std::size_t* seed, const T& v) {
std::hash<T> hasher;
(*seed) ^= hasher(v) + 0x9e3779b9 + ((*seed) << 6) + ((*seed) >> 2);
}
inline size_t hash(const MemoryBlock::Desc& metadata, size_t initial_seed) {
size_t seed = initial_seed;
hash_combine(&seed, static_cast<size_t>(metadata.type));
hash_combine(&seed, metadata.index);
hash_combine(&seed, metadata.size);
hash_combine(&seed, metadata.total_size);
hash_combine(&seed, metadata.left_buddy);
hash_combine(&seed, metadata.right_buddy);
return seed;
}
} // namespace
void MemoryBlock::Desc::UpdateGuards() {
guard_begin = hash(*this, 1);
guard_end = hash(*this, 2);
}
bool MemoryBlock::Desc::CheckGuards() const {
return guard_begin == hash(*this, 1) && guard_end == hash(*this, 2);
}
} // namespace paddle::memory::detail
@@ -0,0 +1,65 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include "glog/logging.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/memory_block.h"
namespace paddle::memory::detail {
MetadataCache::MetadataCache(bool uses_gpu) : uses_gpu_(uses_gpu) {}
MemoryBlock::Desc* MetadataCache::LoadDesc(MemoryBlock* block) {
if (uses_gpu_) {
auto iter = cache_.find(block);
PADDLE_ENFORCE_NE(
iter,
cache_.end(),
common::errors::NotFound("The memory block is not found in cache"));
auto* desc = &(iter->second);
PADDLE_ENFORCE_EQ(
desc->CheckGuards(),
true,
common::errors::InvalidArgument("Invalid CPU memory access"));
return desc;
} else {
auto* desc = reinterpret_cast<MemoryBlock::Desc*>(block);
VLOG(10) << "Load MemoryBlock::Desc type=" << desc->type;
PADDLE_ENFORCE_EQ(
desc->CheckGuards(),
true,
common::errors::InvalidArgument("Invalid CPU memory access"));
return reinterpret_cast<MemoryBlock::Desc*>(block);
}
}
void MetadataCache::Save(MemoryBlock* block,
const MemoryBlock::Desc& original_desc) {
auto desc = original_desc;
desc.UpdateGuards();
if (uses_gpu_) {
cache_[block] = desc;
} else {
*reinterpret_cast<MemoryBlock::Desc*>(block) = desc;
}
}
void MetadataCache::Invalidate(MemoryBlock* block) {
if (uses_gpu_) {
cache_.erase(block);
}
}
} // namespace paddle::memory::detail
@@ -0,0 +1,420 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
#include "paddle/phi/core/memory/allocation/mmap_allocator.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <cstdlib>
#include <atomic>
#include <random>
#include <string>
#include "glog/logging.h"
#include "paddle/common/flags.h"
#include "paddle/phi/core/enforce.h"
COMMON_DECLARE_bool(use_shm_cache);
namespace paddle::memory::allocation {
std::string GetIPCName() {
static std::random_device rd;
static std::atomic<uint64_t> counter{0};
std::string handle = "/paddle_";
#ifdef _WIN32
handle += std::to_string(GetCurrentProcessId());
#else
handle += std::to_string(getpid());
#endif
handle += "_";
handle += std::to_string(counter.fetch_add(1));
handle += "_";
handle += std::to_string(rd());
return handle;
}
struct CountInfo {
std::atomic<int> refcount;
};
void AllocateMemoryMap(std::string filename,
int *shared_fd,
int flags,
size_t size,
void **map_ptr_) {
// TODO(@ZHUI): support win32
int file_flags = 0;
int fd = *shared_fd;
if (flags & MAPPED_SHAREDMEM) {
file_flags = O_RDWR | O_CREAT;
} else {
file_flags = O_RDONLY;
}
if (flags & MAPPED_EXCLUSIVE) {
file_flags |= O_EXCL;
}
if (flags & MAPPED_NOCREATE) {
file_flags &= ~O_CREAT;
}
if (!(flags & MAPPED_FROMFD) && fd == -1) {
if (flags & MAPPED_SHAREDMEM) {
fd = shm_open(filename.c_str(), file_flags, (mode_t)0600);
PADDLE_ENFORCE_NE(
fd,
-1,
common::errors::Unavailable(
"File descriptor %s open failed, unable in read-write mode",
filename.c_str()));
VLOG(6) << "shm_open: " << filename;
MemoryMapFdSet::Instance().Insert(filename);
}
}
PADDLE_ENFORCE_EQ(ftruncate(fd, size),
0,
common::errors::Unavailable(
"Truncate a file to a specified length failed!"));
if (flags & MAPPED_SHAREDMEM) {
*map_ptr_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
} else {
*map_ptr_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
}
if (flags & MAPPED_UNLINK) {
VLOG(6) << "shm_unlink: " << filename;
shm_unlink(filename.c_str());
}
PADDLE_ENFORCE_NE(*map_ptr_,
MAP_FAILED,
common::errors::Unavailable(
"Memory map failed when create shared memory."));
if (flags & MAPPED_KEEPFD) {
*shared_fd = fd;
VLOG(6) << "keep fd: " << *shared_fd;
} else {
PADDLE_ENFORCE_NE(::close(fd),
-1,
common::errors::Unavailable(
"Error closing memory mapped file %s", filename));
*shared_fd = -1;
}
}
std::shared_ptr<RefcountedMemoryMapAllocation>
AllocateRefcountedMemoryMapAllocation(std::string filename,
int shared_fd,
int flags,
size_t size,
int buffer_id) {
int fd = shared_fd;
void *base_ptr = nullptr;
if (buffer_id == -1) {
AllocateMemoryMap(filename, &fd, flags, size + mmap_alignment, &base_ptr);
VLOG(4) << "Create and mmap a new shm: " << filename;
} else {
base_ptr = MemoryMapAllocationPool::Instance().GetById(buffer_id).mmap_ptr_;
VLOG(4) << "Get a cached shm " << filename;
}
void *aligned_base_ptr =
static_cast<void *>(static_cast<char *>(base_ptr) + mmap_alignment);
return std::make_shared<RefcountedMemoryMapAllocation>(
aligned_base_ptr, size, filename, fd, flags, buffer_id);
}
RefcountedMemoryMapAllocation::RefcountedMemoryMapAllocation(
void *ptr,
size_t size,
std::string ipc_name,
int fd,
int flags,
int buffer_id)
: MemoryMapAllocation(ptr, size, ipc_name, fd, flags) {
// must reset base ptr first.
buffer_id_ = buffer_id;
fd_ = fd;
flags_ = flags;
resetBaseptr();
initializeRefercount();
}
void MemoryMapAllocation::close() {
if (!closed_fd_) {
closed_fd_ = true;
if (flags_ & MAPPED_KEEPFD) {
PADDLE_ENFORCE_NE(::close(fd_),
-1,
common::errors::Unavailable(
"Error closing file descriptor <%d>", fd_));
}
}
if (closed_) {
return;
}
closed_ = true;
}
MemoryMapAllocation::~MemoryMapAllocation() { close(); } // NOLINT
void RefcountedMemoryMapAllocation::incref() {
CountInfo *info = static_cast<CountInfo *>(map_ptr_);
++info->refcount;
}
int RefcountedMemoryMapAllocation::decref() {
CountInfo *info = static_cast<CountInfo *>(map_ptr_);
return --info->refcount == 0;
}
void RefcountedMemoryMapAllocation::resetBaseptr() {
map_ptr_ =
static_cast<void *>(static_cast<char *>(map_ptr_) - mmap_alignment);
map_size_ = map_size_ + mmap_alignment;
}
void RefcountedMemoryMapAllocation::initializeRefercount() {
CountInfo *info = reinterpret_cast<CountInfo *>(map_ptr_);
if (flags_ & MAPPED_EXCLUSIVE) {
new (&info->refcount) std::atomic<int>(1);
} else {
info->refcount++;
}
}
void RefcountedMemoryMapAllocation::close() {
VLOG(4) << "Close a RefcountedMemoryMapAllocation: " << ipc_name_;
if (closed_) {
return;
}
closed_ = true;
void *data = map_ptr_;
CountInfo *info = reinterpret_cast<CountInfo *>(data);
--info->refcount;
if (flags_ & MAPPED_KEEPFD) {
closed_fd_ = true;
PADDLE_ENFORCE_NE(
::close(fd_),
-1,
common::errors::Unavailable("Error closing file descriptor <%d>", fd_));
VLOG(6) << "close fd: " << fd_;
}
if (FLAGS_use_shm_cache && buffer_id_ != -1) {
return;
} else {
if (FLAGS_use_shm_cache &&
MemoryMapAllocationPool::Instance().BufferSize() <
static_cast<size_t>(
MemoryMapAllocationPool::Instance().MaxPoolSize())) {
MemoryMapAllocationPool::Instance().Insert(MemoryMapInfo(
flags_, map_size_ - mmap_alignment, ipc_name_, map_ptr_));
} else {
if (info->refcount == 0) {
shm_unlink(ipc_name_.c_str());
VLOG(6) << "shm_unlink file: " << ipc_name_;
}
PADDLE_ENFORCE_NE(munmap(map_ptr_, map_size_),
-1,
common::errors::Unavailable(
"could not unmap the shared memory file: %s (%d)",
strerror(errno),
errno));
}
}
}
MemoryMapWriterAllocation::~MemoryMapWriterAllocation() {
if (munmap(this->ptr(), this->size()) == -1) {
common::errors::Unavailable("could not unmap the shared memory file %s",
this->ipc_name());
}
}
MemoryMapReaderAllocation::~MemoryMapReaderAllocation() {
if (munmap(this->ptr(), this->size()) == -1) {
common::errors::Unavailable("could not unmap the shared memory file %s",
this->ipc_name());
}
/* Here we do not pay attention to the result of shm_unlink,
because the memory mapped file may have been cleared due to the
MemoryMapFdSet::Clear() */
// Code of DataLoader subprocess:
//
// core._array_to_share_memory_tensor(b)
// out_queue.put((idx, tensor_list, structure))
// core._remove_tensor_list_mmap_fds(tensor_list)
/* If the tensor in already in the send queue, the tensor will be
* deconstructed by the function. If the tensor not send yet, it
* will be cleared by MemoryMapFdSet::Clear().
* If the `_remove_tensor_list_mmap_fds` have be interrupted, the
* tensor will be cleared by both methods.
* */
shm_unlink(this->ipc_name().c_str());
MemoryMapFdSet::Instance().Remove(this->ipc_name());
VLOG(3) << "~MemoryMapReaderAllocation: " << this->ipc_name();
}
std::shared_ptr<MemoryMapWriterAllocation> AllocateMemoryMapWriterAllocation(
size_t size) {
const std::string &ipc_name = GetIPCName();
int flags = O_RDWR | O_CREAT;
int fd = shm_open(ipc_name.c_str(), flags, 0600);
PADDLE_ENFORCE_NE(fd,
-1,
common::errors::Unavailable(
"File descriptor %s open failed", ipc_name.c_str()));
PADDLE_ENFORCE_EQ(ftruncate(fd, size),
0,
common::errors::Unavailable(
"Truncate a file to a specified length failed!"));
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
PADDLE_ENFORCE_NE(ptr,
MAP_FAILED,
common::errors::Unavailable(
"Memory map failed when create shared memory."));
close(fd);
return std::make_shared<MemoryMapWriterAllocation>(ptr, size, ipc_name);
}
std::shared_ptr<MemoryMapReaderAllocation> RebuildMemoryMapReaderAllocation(
const std::string &ipc_name, size_t size) {
int flags = O_RDWR | O_CREAT;
flags &= ~O_CREAT;
int fd = shm_open(ipc_name.c_str(), flags, 0600);
PADDLE_ENFORCE_NE(fd,
-1,
common::errors::Unavailable(
"File descriptor %s open failed", ipc_name.c_str()));
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
PADDLE_ENFORCE_NE(ptr,
MAP_FAILED,
common::errors::Unavailable(
"Memory map failed when rebuild shared memory."));
close(fd);
return std::make_shared<MemoryMapReaderAllocation>(ptr, size, ipc_name);
}
MemoryMapFdSet &MemoryMapFdSet::Instance() { // NOLINT
static MemoryMapFdSet set;
return set;
}
void MemoryMapFdSet::Insert(const std::string &ipc_name) {
std::lock_guard<std::mutex> guard(mtx_);
fd_set_.emplace(ipc_name);
VLOG(3) << "PID: " << getpid() << ", MemoryMapFdSet: insert " << ipc_name
<< ", set size: " << fd_set_.size();
}
void MemoryMapFdSet::Remove(const std::string &ipc_name) {
std::lock_guard<std::mutex> guard(mtx_);
fd_set_.erase(ipc_name);
VLOG(3) << "PID: " << getpid() << ", MemoryMapFdSet: erase " << ipc_name
<< ", set size: " << fd_set_.size();
}
void MemoryMapFdSet::Clear() {
VLOG(7) << "PID: " << getpid() << ", MemoryMapFdSet: set size - "
<< fd_set_.size();
std::lock_guard<std::mutex> guard(mtx_);
for (auto const &fd : fd_set_) {
int rlt = shm_unlink(fd.c_str());
if (rlt == 0) {
VLOG(7) << "PID: " << getpid() << ", MemoryMapFdSet: clear " << fd;
}
}
fd_set_.clear();
}
MemoryMapFdSet::~MemoryMapFdSet() { Clear(); }
MemoryMapAllocationPool *MemoryMapAllocationPool::pool_ = nullptr;
void MemoryMapAllocationPool::Insert(const MemoryMapInfo &memory_map) {
std::lock_guard<std::mutex> guard(mtx_);
memory_map_allocations_.push_back(memory_map);
VLOG(4) << this << "Insert a new shm: " << memory_map.file_name_;
}
int MemoryMapAllocationPool::FindFromCache(const int &flag,
const size_t &data_size,
const std::string &file_name,
bool check_refcount) {
std::lock_guard<std::mutex> guard(mtx_);
for (int idx = 0; idx < static_cast<int>(memory_map_allocations_.size());
idx++) {
if (memory_map_allocations_.at(idx).flags_ == flag &&
memory_map_allocations_.at(idx).data_size_ == data_size) {
if (file_name.empty() ||
memory_map_allocations_.at(idx).file_name_ == file_name) {
if (!check_refcount || reinterpret_cast<CountInfo *>(
memory_map_allocations_.at(idx).mmap_ptr_)
->refcount == 0) {
VLOG(4) << "Match at: " << idx;
return idx;
}
}
}
}
return -1;
}
const MemoryMapInfo &MemoryMapAllocationPool::GetById(int id) {
std::lock_guard<std::mutex> guard(mtx_);
return memory_map_allocations_.at(id);
}
void MemoryMapAllocationPool::SetMaxPoolSize(const int &size) {
max_pool_size_ = size;
VLOG(4) << this << "Set max pool size is: " << max_pool_size_;
}
void MemoryMapAllocationPool::Clear() {
std::lock_guard<std::mutex> guard(mtx_);
for (auto const &mmap : memory_map_allocations_) {
int rlt = shm_unlink(mmap.file_name_.c_str());
if (rlt == 0) {
VLOG(4) << "MemoryMapAllocationPool: clear " << mmap.file_name_;
}
PADDLE_ENFORCE_NE(munmap(mmap.mmap_ptr_, mmap.data_size_ + mmap_alignment),
-1,
common::errors::Unavailable(
"could not unmap the shared memory file: %s (%d)",
strerror(errno),
errno));
}
memory_map_allocations_.clear();
}
MemoryMapAllocationPool::~MemoryMapAllocationPool() { Clear(); } // NOLINT
} // namespace paddle::memory::allocation
#endif
@@ -0,0 +1,240 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#ifndef _WIN32
#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_set>
#include <utility>
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
std::string GetIPCName();
static constexpr int64_t mmap_alignment = 64;
enum MappedModes {
MAPPED_SHAREDMEM = 1,
MAPPED_EXCLUSIVE = 2,
MAPPED_NOCREATE = 4,
MAPPED_KEEPFD = 8,
MAPPED_FROMFD = 16,
MAPPED_UNLINK = 32
};
class MemoryMapAllocation : public Allocation {
public:
explicit MemoryMapAllocation(void *ptr,
size_t size,
std::string ipc_name,
int fd)
: Allocation(ptr, size, CPUPlace()),
ipc_name_(std::move(ipc_name)),
fd_(fd),
map_ptr_(ptr),
map_size_(size) {}
explicit MemoryMapAllocation(
void *ptr, size_t size, std::string ipc_name, int fd, int flags)
: Allocation(ptr, size, CPUPlace()),
ipc_name_(std::move(ipc_name)),
fd_(fd),
flags_(flags),
map_ptr_(ptr),
map_size_(size) {}
inline const std::string &ipc_name() const { return ipc_name_; }
inline int shared_fd() const { return fd_; }
virtual void close();
~MemoryMapAllocation() override;
protected:
std::string ipc_name_;
int fd_ = -1;
int flags_ = 0;
void *map_ptr_ = nullptr;
size_t map_size_ = 0;
bool closed_ = false;
bool closed_fd_ = false;
};
class RefcountedMemoryMapAllocation : public MemoryMapAllocation {
public:
RefcountedMemoryMapAllocation(void *ptr,
size_t size,
std::string ipc_name,
int flags,
int fd,
int buffer_id = -1);
void incref();
int decref();
void close() override;
virtual ~RefcountedMemoryMapAllocation() { close(); }
protected:
int buffer_id_ = -1;
void initializeRefercount();
void resetBaseptr();
};
void AllocateMemoryMap(std::string filename,
int *shared_fd,
int flags,
size_t size,
void **base_ptr_);
std::shared_ptr<RefcountedMemoryMapAllocation>
AllocateRefcountedMemoryMapAllocation(std::string filename,
int shared_fd,
int flags,
size_t size,
int buffer_id = -1);
class MemoryMapWriterAllocation : public Allocation {
public:
explicit MemoryMapWriterAllocation(void *ptr,
size_t size,
std::string ipc_name)
: Allocation(ptr, size, CPUPlace()), ipc_name_(std::move(ipc_name)) {}
inline const std::string &ipc_name() const { return ipc_name_; }
inline int shared_fd() const { return fd_; }
~MemoryMapWriterAllocation() override;
private:
std::string ipc_name_;
int fd_ = -1;
};
class MemoryMapReaderAllocation : public Allocation {
public:
explicit MemoryMapReaderAllocation(void *ptr,
size_t size,
std::string ipc_name)
: Allocation(ptr, size, CPUPlace()), ipc_name_(std::move(ipc_name)) {}
inline const std::string &ipc_name() const { return ipc_name_; }
inline int shared_fd() const { return fd_; }
~MemoryMapReaderAllocation() override;
private:
std::string ipc_name_;
int fd_ = -1;
};
std::shared_ptr<MemoryMapWriterAllocation> AllocateMemoryMapWriterAllocation(
size_t size);
std::shared_ptr<MemoryMapReaderAllocation> RebuildMemoryMapReaderAllocation(
const std::string &ipc_name, size_t size);
class MemoryMapFdSet {
public:
static MemoryMapFdSet &Instance(); // NOLINT
void Insert(const std::string &ipc_name);
void Remove(const std::string &ipc_name);
void Clear();
~MemoryMapFdSet();
private:
MemoryMapFdSet() = default;
std::unordered_set<std::string> fd_set_;
std::mutex mtx_;
};
class MemoryMapInfo {
public:
explicit MemoryMapInfo(int flags,
size_t data_size,
std::string file_name,
void *mmap_ptr)
: flags_(flags),
data_size_(data_size),
file_name_(file_name),
mmap_ptr_(mmap_ptr) {}
int flags_ = 0;
size_t data_size_ = 0;
std::string file_name_;
void *mmap_ptr_ = nullptr;
};
/* Note(zhangbo):
MemoryMapAllocationPool is used to cache and reuse shm, thus reducing munmap in
dataloader. The munmap(shm_mmap_ptr) instruction in
RefcountedMemoryMapAllocation::close() function may block other threads of the
process. Therefore, the logic of shm cache and reuse is designed: the shm
created by the _share_filename process will be cached and reused according to
the data_size of shm, thus eliminating the problem of munmap blocking other
threads
*/
class MemoryMapAllocationPool {
public:
static MemoryMapAllocationPool &Instance() {
if (pool_ == nullptr) {
pool_ = new MemoryMapAllocationPool();
}
return *pool_;
}
void Insert(const MemoryMapInfo &memory_map);
int FindFromCache(const int &flag,
const size_t &data_size,
const std::string &file_name = "",
bool check_refcount = true);
const MemoryMapInfo &GetById(int id);
size_t BufferSize() { return memory_map_allocations_.size(); }
void Clear();
void SetMaxPoolSize(const int &size);
int MaxPoolSize() { return max_pool_size_; }
~MemoryMapAllocationPool();
private:
MemoryMapAllocationPool() = default;
static MemoryMapAllocationPool *pool_;
std::vector<MemoryMapInfo> memory_map_allocations_;
int max_pool_size_ = 0;
std::mutex mtx_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,716 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/naive_best_fit_allocator.h"
#include <mutex>
#include "glog/logging.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/buddy_allocator.h"
#include "paddle/phi/core/memory/allocation/system_allocator.h"
#include "paddle/phi/core/platform/device/device_wrapper.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
#include "paddle/phi/core/platform/profiler.h"
#include "paddle/phi/core/utils/visit_place.h"
#include "paddle/utils/string/printf.h"
#include "paddle/utils/string/split.h"
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/phi/core/platform/cuda_device_guard.h"
#endif
#include "paddle/common/flags.h"
PHI_DEFINE_EXPORTED_bool(
init_allocated_mem,
false,
"It is a mistake that the values of the memory allocated by "
"BuddyAllocator are always zeroed in some op's implementation. "
"To find this error in time, we use init_allocated_mem to indicate "
"that initializing the allocated memory with a small value "
"during unit testing.");
COMMON_DECLARE_double(fraction_of_gpu_memory_to_use);
COMMON_DECLARE_uint64(initial_gpu_memory_in_mb);
COMMON_DECLARE_uint64(reallocate_gpu_memory_in_mb);
COMMON_DECLARE_bool(benchmark);
namespace paddle::memory::legacy {
template <typename Place>
void *Alloc(const Place &place, size_t size);
template <typename Place>
void Free(const Place &place, void *p, size_t size);
template <typename Place>
uint64_t Release(const Place &place);
template <typename Place>
size_t Used(const Place &place);
struct Usage {
size_t operator()(const CPUPlace &cpu) const;
size_t operator()(const GPUPlace &gpu) const;
size_t operator()(const GPUPinnedPlace &cuda_pinned) const;
size_t operator()(const XPUPlace &xpu) const;
size_t operator()(const XPUPinnedPlace &xpu_pinned) const;
};
size_t memory_usage(const Place &p);
using BuddyAllocator = detail::BuddyAllocator;
BuddyAllocator *GetCPUBuddyAllocator() {
// We tried thread_local for inference::RNN1 model, but that not works much
// for multi-thread test.
static std::once_flag init_flag;
static detail::BuddyAllocator *a = nullptr;
std::call_once(init_flag, []() {
a = new detail::BuddyAllocator(
std::unique_ptr<detail::SystemAllocator>(new detail::CPUAllocator),
phi::backends::cpu::CpuMinChunkSize(),
phi::backends::cpu::CpuMaxChunkSize());
});
return a;
}
template <>
void *Alloc<CPUPlace>(const CPUPlace &place, size_t size) {
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
void *p = GetCPUBuddyAllocator()->Alloc(size);
if (FLAGS_init_allocated_mem) {
memset(p, 0xEF, size);
}
VLOG(10) << " pointer=" << p;
return p;
}
template <>
void Free<CPUPlace>(const CPUPlace &place, void *p, size_t size) {
VLOG(10) << "Free pointer=" << p << " on " << Place(place);
GetCPUBuddyAllocator()->Free(p);
}
template <>
uint64_t Release<CPUPlace>(const CPUPlace &place) {
return GetCPUBuddyAllocator()->Release();
}
template <>
size_t Used<CPUPlace>(const CPUPlace &place) {
return GetCPUBuddyAllocator()->Used();
}
// For Graphcore IPU
template <>
void *Alloc<IPUPlace>(const IPUPlace &place, size_t size) {
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
VLOG(10) << "IPUPlace, Allocate on cpu.";
void *p = GetCPUBuddyAllocator()->Alloc(size);
if (FLAGS_init_allocated_mem) {
memset(p, 0xEF, size);
}
VLOG(10) << " pointer=" << p;
return p;
}
template <>
void Free<IPUPlace>(const IPUPlace &place, void *p, size_t size) {
VLOG(10) << "Free pointer=" << p << " on " << Place(place);
GetCPUBuddyAllocator()->Free(p);
}
template <>
uint64_t Release<IPUPlace>(const IPUPlace &place) {
return GetCPUBuddyAllocator()->Release();
}
template <>
size_t Used<IPUPlace>(const IPUPlace &place) {
return GetCPUBuddyAllocator()->Used();
}
// For kunlun XPU
template <>
void *Alloc<XPUPlace>(const XPUPlace &place, size_t size) {
#ifdef PADDLE_WITH_XPU
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
void *p = nullptr;
phi::backends::xpu::XPUDeviceGuard guard(place.device);
int ret = xpu_malloc(reinterpret_cast<void **>(&p), size);
if (ret != XPU_SUCCESS) {
VLOG(10) << "xpu memory malloc(" << size << ") failed, try again";
xpu_wait();
ret = xpu_malloc(reinterpret_cast<void **>(&p), size);
}
PADDLE_ENFORCE_EQ(
ret,
XPU_SUCCESS,
common::errors::External(
"XPU API return wrong value[%d], no enough memory", ret));
if (FLAGS_init_allocated_mem) {
PADDLE_THROW(common::errors::Unimplemented(
"xpu memory FLAGS_init_allocated_mem is not implemented."));
}
VLOG(10) << " pointer=" << p;
return p;
#else
PADDLE_THROW(
common::errors::PermissionDenied("'XPUPlace' is not supported."));
return nullptr;
#endif
}
template <>
void Free<XPUPlace>(const XPUPlace &place, void *p, size_t size) {
#ifdef PADDLE_WITH_XPU
VLOG(10) << "Free " << size << " bytes on " << Place(place);
VLOG(10) << "Free pointer=" << p << " on " << Place(place);
phi::backends::xpu::XPUDeviceGuard guard(place.device);
xpu_free(p);
#else
PADDLE_THROW(
common::errors::PermissionDenied("'XPUPlace' is not supported."));
#endif
}
template <>
uint64_t Release<XPUPlace>(const XPUPlace &place) {
#ifdef PADDLE_WITH_XPU
LOG(WARNING) << "Release XPU pool is not supported now, no action here.";
#else
PADDLE_THROW(
common::errors::PermissionDenied("'XPUPlace' is not supported."));
#endif
return -1;
}
template <>
size_t Used<XPUPlace>(const XPUPlace &place) {
#ifdef PADDLE_WITH_XPU
printf("Used func return 0 for XPUPlace\n");
return 0;
#else
PADDLE_THROW(
common::errors::PermissionDenied("'XPUPlace' is not supported."));
#endif
}
// For CUDA
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
class GPUBuddyAllocatorList {
private:
GPUBuddyAllocatorList()
: devices_(platform::GetSelectedDevices()), init_flags_(), allocators_() {
auto gpu_num = devices_.size();
allocators_.resize(gpu_num);
init_flags_.reserve(gpu_num);
for (size_t i = 0; i < gpu_num; ++i) {
init_flags_.emplace_back(new std::once_flag());
}
}
static GPUBuddyAllocatorList *CreateNewInstance() {
return new GPUBuddyAllocatorList();
}
public:
static GPUBuddyAllocatorList *Instance() {
static auto *instance = CreateNewInstance();
return instance;
}
BuddyAllocator *Get(int gpu_id) {
auto pos = std::distance(
devices_.begin(), std::find(devices_.begin(), devices_.end(), gpu_id));
PADDLE_ENFORCE_LT(pos,
devices_.size(),
common::errors::OutOfRange(
"The index exceeds the size of devices, the size of "
"devices is %d, the index is %d",
devices_.size(),
pos));
std::call_once(*init_flags_[pos], [this, pos] {
platform::SetDeviceId(devices_[pos]);
allocators_[pos] = std::make_unique<BuddyAllocator>(
std::unique_ptr<detail::SystemAllocator>(
new detail::GPUAllocator(devices_[pos])),
platform::GpuMinChunkSize(),
platform::GpuMaxChunkSize());
VLOG(10) << "\n\nNOTE:\n"
<< "You can set GFlags environment variable "
<< "'FLAGS_fraction_of_gpu_memory_to_use' "
<< "or 'FLAGS_initial_gpu_memory_in_mb' "
<< "or 'FLAGS_reallocate_gpu_memory_in_mb' "
<< "to change the memory size for GPU usage.\n"
<< "Current 'FLAGS_fraction_of_gpu_memory_to_use' value is "
<< FLAGS_fraction_of_gpu_memory_to_use
<< ". Current 'FLAGS_initial_gpu_memory_in_mb' value is "
<< FLAGS_initial_gpu_memory_in_mb
<< ". Current 'FLAGS_reallocate_gpu_memory_in_mb' value is "
<< FLAGS_reallocate_gpu_memory_in_mb << "\n\n";
});
return allocators_[pos].get();
}
private:
std::vector<int> devices_;
std::vector<std::unique_ptr<std::once_flag>> init_flags_;
std::vector<std::unique_ptr<BuddyAllocator>> allocators_;
};
BuddyAllocator *GetGPUBuddyAllocator(int gpu_id) {
return GPUBuddyAllocatorList::Instance()->Get(gpu_id);
}
#endif
template <>
size_t Used<GPUPlace>(const GPUPlace &place) {
#if (defined PADDLE_WITH_CUDA || defined PADDLE_WITH_HIP)
return GetGPUBuddyAllocator(place.device)->Used();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPlace' is not supported in CPU only device."));
#endif
}
template <>
void *Alloc<GPUPlace>(const GPUPlace &place, size_t size) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
auto *buddy_allocator = GetGPUBuddyAllocator(place.device);
auto *ptr = buddy_allocator->Alloc(size);
if (ptr == nullptr) {
platform::CUDADeviceGuard guard(place.device);
size_t avail, total;
platform::GpuMemoryUsage(&avail, &total);
PADDLE_THROW(common::errors::ResourceExhausted(
"Cannot allocate %s in GPU %d, available %s, total %s, GpuMinChunkSize "
"%s, GpuMaxChunkSize %s, GPU memory used: %s.",
string::HumanReadableSize(size),
place.device,
string::HumanReadableSize(avail),
string::HumanReadableSize(total),
string::HumanReadableSize(buddy_allocator->GetMinChunkSize()),
string::HumanReadableSize(buddy_allocator->GetMaxChunkSize()),
string::HumanReadableSize(Used<GPUPlace>(place))));
} else {
if (FLAGS_init_allocated_mem) {
#ifdef PADDLE_WITH_HIP
hipMemset(ptr, 0xEF, size);
#else
cudaMemset(ptr, 0xEF, size);
#endif
}
}
return ptr;
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPlace' is not supported in CPU only device."));
#endif
}
template <>
void Free<GPUPlace>(const GPUPlace &place, void *p, size_t size) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
GetGPUBuddyAllocator(place.device)->Free(p);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPlace' is not supported in CPU only device."));
#endif
}
template <>
uint64_t Release<GPUPlace>(const GPUPlace &place) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
return GetGPUBuddyAllocator(place.device)->Release();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPlace' is not supported in CPU only device."));
#endif
}
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
BuddyAllocator *GetCUDAPinnedBuddyAllocator() {
static std::once_flag init_flag;
static BuddyAllocator *ba = nullptr;
std::call_once(init_flag, []() {
ba = new BuddyAllocator(std::unique_ptr<detail::SystemAllocator>(
new detail::CUDAPinnedAllocator),
phi::backends::cpu::CUDAPinnedMinChunkSize(),
phi::backends::cpu::CUDAPinnedMaxChunkSize());
});
return ba;
}
#endif
template <>
size_t Used<GPUPinnedPlace>(const GPUPinnedPlace &place) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
return GetCUDAPinnedBuddyAllocator()->Used();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
void *Alloc<GPUPinnedPlace>(const GPUPinnedPlace &place, size_t size) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
auto *buddy_allocator = GetCUDAPinnedBuddyAllocator();
void *ptr = buddy_allocator->Alloc(size);
if (ptr == nullptr) {
LOG(WARNING) << "cudaHostAlloc Cannot allocate " << size
<< " bytes in CUDAPinnedPlace";
} else if (FLAGS_init_allocated_mem) {
memset(ptr, 0xEF, size);
}
return ptr;
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
void Free<GPUPinnedPlace>(const GPUPinnedPlace &place, void *p, size_t size) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
VLOG(10) << "Free " << size << " bytes on " << Place(place);
GetCUDAPinnedBuddyAllocator()->Free(p);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
uint64_t Release<GPUPinnedPlace>(const GPUPinnedPlace &place) {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
VLOG(10) << "Release on " << Place(place);
return GetCUDAPinnedBuddyAllocator()->Release();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPinnedPlace' is not supported in CPU only device."));
#endif
}
// For XPUPinnedPlace
#if defined(PADDLE_WITH_XPU)
BuddyAllocator *GetXPUPinnedBuddyAllocator() {
static std::once_flag init_flag;
static BuddyAllocator *ba = nullptr;
std::call_once(init_flag, []() {
ba = new BuddyAllocator(std::unique_ptr<detail::SystemAllocator>(
new detail::XPUPinnedAllocator),
phi::backends::cpu::CUDAPinnedMinChunkSize(),
phi::backends::cpu::CUDAPinnedMaxChunkSize());
});
return ba;
}
#endif
template <>
size_t Used<XPUPinnedPlace>(const XPUPinnedPlace &place) {
#if defined(PADDLE_WITH_XPU)
return GetXPUPinnedBuddyAllocator()->Used();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
void *Alloc<XPUPinnedPlace>(const XPUPinnedPlace &place, size_t size) {
#if defined(PADDLE_WITH_XPU)
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
auto *buddy_allocator = GetXPUPinnedBuddyAllocator();
void *ptr = buddy_allocator->Alloc(size);
if (ptr == nullptr) {
LOG(WARNING) << "cudaHostAlloc Cannot allocate " << size
<< " bytes in XPUPinnedPlace";
} else if (FLAGS_init_allocated_mem) {
memset(ptr, 0xEF, size);
}
return ptr;
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
void Free<XPUPinnedPlace>(const XPUPinnedPlace &place, void *p, size_t size) {
#if defined(PADDLE_WITH_XPU)
VLOG(10) << "Free " << size << " bytes on " << Place(place);
GetXPUPinnedBuddyAllocator()->Free(p);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported in CPU only device."));
#endif
}
template <>
uint64_t Release<XPUPinnedPlace>(const XPUPinnedPlace &place) {
#if defined(PADDLE_WITH_XPU)
VLOG(10) << "Release on " << Place(place);
return GetXPUPinnedBuddyAllocator()->Release();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported in CPU only device."));
#endif
}
// For CustomDevice
#ifdef PADDLE_WITH_CUSTOM_DEVICE
class BuddyAllocatorList {
private:
explicit BuddyAllocatorList(const std::string &device_type)
: device_type_(device_type) {
auto devices = phi::DeviceManager::GetSelectedDeviceList(device_type);
for (auto dev_id : devices) {
init_flags_[dev_id] = std::make_unique<std::once_flag>();
}
}
static BuddyAllocatorList *CreateNewInstance(const std::string &device_type) {
return new BuddyAllocatorList(device_type);
}
public:
static BuddyAllocatorList *Instance(const std::string &device_type) {
// DeviceType -> AllocatorList
static std::unordered_map<std::string, BuddyAllocatorList *> pool;
if (pool.find(device_type) == pool.end()) {
pool[device_type] = CreateNewInstance(device_type);
}
return pool[device_type];
}
BuddyAllocator *Get(int dev_id) {
PADDLE_ENFORCE_NE(init_flags_.find(dev_id),
init_flags_.end(),
common::errors::OutOfRange(
"Cannot find %s %d, please check visible devices.",
device_type_,
dev_id));
std::call_once(*init_flags_[dev_id], [this, dev_id] {
phi::DeviceManager::SetDevice(device_type_, dev_id);
CustomPlace place(device_type_, dev_id);
VLOG(10) << "Init BuddyAllocator on " << place
<< " with GetExtraPaddingSize "
<< phi::DeviceManager::GetExtraPaddingSize(place);
allocators_[dev_id] = std::make_unique<BuddyAllocator>(
std::unique_ptr<detail::SystemAllocator>(
new detail::CustomAllocator(device_type_, dev_id)),
phi::DeviceManager::GetMinChunkSize(place),
phi::DeviceManager::GetMaxChunkSize(place),
phi::DeviceManager::GetExtraPaddingSize(place),
device_type_);
});
return allocators_[dev_id].get();
}
private:
std::string device_type_;
std::unordered_map<size_t, std::unique_ptr<std::once_flag>> init_flags_;
std::unordered_map<size_t, std::unique_ptr<BuddyAllocator>> allocators_;
};
BuddyAllocator *GetBuddyAllocator(const Place &place) {
VLOG(10) << "GetBuddyAllocator place = " << place;
if (phi::is_custom_place(place)) {
return BuddyAllocatorList::Instance(phi::PlaceHelper::GetDeviceType(place))
->Get(phi::PlaceHelper::GetDeviceId(place));
} else {
PADDLE_THROW(common::errors::InvalidArgument("place must be CustomPlace"));
}
}
#endif
template <>
void *Alloc<CustomPlace>(const CustomPlace &place, size_t size) {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
VLOG(10) << "Allocate " << size << " bytes on " << Place(place);
auto *buddy_allocator = GetBuddyAllocator(place);
auto *ptr = buddy_allocator->Alloc(size);
if (ptr == nullptr) {
phi::DeviceGuard guard(place);
size_t avail, total;
phi::DeviceManager::MemoryStats(place, &total, &avail);
PADDLE_THROW(common::errors::ResourceExhausted(
"Cannot allocate %s in %s:%d, available %s, total %s, used "
"%s. ",
string::HumanReadableSize(size),
place.GetDeviceType(),
place.device,
string::HumanReadableSize(avail),
string::HumanReadableSize(total),
string::HumanReadableSize(total - avail)));
} else {
if (FLAGS_init_allocated_mem) {
phi::DeviceManager::GetDeviceWithPlace(place)->MemorySet(ptr, 0xEF, size);
}
}
VLOG(10) << " pointer=" << ptr;
return ptr;
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CustomPlace' is not supported in CPU only device."));
#endif
}
template <>
void Free<CustomPlace>(const CustomPlace &place, void *p, size_t size) {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
VLOG(10) << "Free pointer=" << p << " on " << Place(place);
if (phi::DeviceManager::HasDeviceType(place.GetDeviceType())) {
GetBuddyAllocator(place)->Free(p);
}
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CustomPlace' is not supported in CPU only device."));
#endif
}
template <>
uint64_t Release<CustomPlace>(const CustomPlace &place) {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
return GetBuddyAllocator(place)->Release();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CustomPlace' is not supported in CPU only device."));
#endif
}
template <>
size_t Used<CustomPlace>(const CustomPlace &place) {
#ifdef PADDLE_WITH_CUSTOM_DEVICE
return GetBuddyAllocator(place)->Used();
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CustomPlace' is not supported in CPU only device."));
#endif
}
struct AllocVisitor {
using argument_type = const Place;
using result_type = void *;
inline explicit AllocVisitor(size_t size) : size_(size) {}
template <typename Place>
inline void *operator()(const Place &place) const {
return Alloc<Place>(place, size_);
}
private:
size_t size_;
};
struct FreeVisitor {
using argument_type = const Place;
using result_type = void;
inline explicit FreeVisitor(void *ptr, size_t size)
: ptr_(ptr), size_(size) {}
template <typename Place>
inline void operator()(const Place &place) const {
Free<Place>(place, ptr_, size_);
}
private:
void *ptr_;
size_t size_;
};
struct ReleaseVisitor {
using argument_type = const Place;
using result_type = uint64_t;
template <typename Place>
inline uint64_t operator()(const Place &place) const {
return Release<Place>(place);
}
};
size_t Usage::operator()(const CPUPlace &cpu) const { return Used(cpu); }
size_t Usage::operator()(const GPUPlace &gpu) const {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
return Used(gpu);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPlace' is not supported in CPU only device."));
#endif
}
size_t Usage::operator()(const GPUPinnedPlace &cuda_pinned) const {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
return Used(cuda_pinned);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'CUDAPinnedPlace' is not supported in CPU only device."));
#endif
}
size_t Usage::operator()(const XPUPlace &xpu) const {
#if defined(PADDLE_WITH_XPU)
return Used(xpu);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPlace' is not supported in CPU only device."));
#endif
}
size_t Usage::operator()(const XPUPinnedPlace &xpu_pinned) const {
#if defined(PADDLE_WITH_XPU)
return Used(xpu_pinned);
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported in CPU only device."));
#endif
}
} // namespace paddle::memory::legacy
namespace paddle::memory::allocation {
phi::Allocation *NaiveBestFitAllocator::AllocateImpl(size_t size) {
VLOG(10) << "NaiveBestFitAllocator::AllocateImpl: place_ = " << place_;
void *ptr = phi::VisitPlace(place_, legacy::AllocVisitor(size));
auto *tmp_alloc = new Allocation(ptr, size, place_);
return tmp_alloc;
}
void NaiveBestFitAllocator::FreeImpl(phi::Allocation *allocation) {
phi::VisitPlace(allocation->place(),
legacy::FreeVisitor(allocation->ptr(), allocation->size()));
delete allocation;
}
uint64_t NaiveBestFitAllocator::ReleaseImpl(const Place &place) {
return phi::VisitPlace(place, legacy::ReleaseVisitor());
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,48 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <algorithm>
#include <mutex> // NOLINT
#include <unordered_map>
#include <utility>
#include <vector>
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class PADDLE_API NaiveBestFitAllocator : public Allocator {
public:
explicit NaiveBestFitAllocator(const Place &p) : place_(p) {}
bool IsAllocThreadSafe() const override { return true; }
protected:
phi::Allocation *AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation *allocation) override;
uint64_t ReleaseImpl(const Place &place) override;
private:
Place place_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,48 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/pinned_allocator.h"
#include "paddle/phi/core/memory/stats.h"
#include "paddle/phi/core/platform/profiler/mem_tracing.h"
namespace paddle::memory::allocation {
bool CPUPinnedAllocator::IsAllocThreadSafe() const { return true; }
void CPUPinnedAllocator::FreeImpl(phi::Allocation *allocation) {
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(hipHostFree(allocation->ptr()));
#else
PADDLE_ENFORCE_GPU_SUCCESS(cudaFreeHost(allocation->ptr()));
#endif
VLOG(10) << "cudaFreeHost " << allocation->ptr();
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -allocation->size());
platform::RecordMemEvent(allocation->ptr(),
allocation->place(),
allocation->size(),
phi::TracerMemEventType::ReservedFree);
delete allocation;
}
phi::Allocation *CPUPinnedAllocator::AllocateImpl(size_t size) {
void *ptr;
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(hipHostMalloc(&ptr, size, hipHostMallocPortable));
#else
PADDLE_ENFORCE_GPU_SUCCESS(cudaHostAlloc(&ptr, size, cudaHostAllocPortable));
#endif
VLOG(10) << "cudaHostAlloc " << size << " " << ptr;
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
platform::RecordMemEvent(
ptr, GPUPinnedPlace(), size, phi::TracerMemEventType::ReservedAllocate);
return new Allocation(ptr, size, GPUPinnedPlace());
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,34 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
// Allocator uses `cudaHostAlloc`
class CPUPinnedAllocator : public Allocator {
public:
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation *allocation) override;
phi::Allocation *AllocateImpl(size_t size) override;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,119 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/retry_allocator.h"
#include "paddle/common/flags.h"
#include "glog/logging.h"
COMMON_DECLARE_int64(offload_retry_times);
namespace paddle::memory::allocation {
static std::function<size_t(Place, size_t)> g_oom_callback;
void RegisterOOMCallback(std::function<size_t(Place, size_t)> callback) {
g_oom_callback = std::move(callback);
}
class WaitedAllocateSizeGuard {
public:
WaitedAllocateSizeGuard(std::atomic<size_t>* waited_size,
size_t requested_size)
: waited_size_(waited_size), requested_size_(requested_size) {
waited_size_->fetch_add(requested_size_, std::memory_order_relaxed);
}
~WaitedAllocateSizeGuard() {
waited_size_->fetch_sub(requested_size_, std::memory_order_relaxed);
}
private:
std::atomic<size_t>* waited_size_;
size_t requested_size_;
};
void RetryAllocator::FreeImpl(phi::Allocation* allocation) {
// Delete underlying allocation first.
size_t size = allocation->size();
underlying_allocator_->Free(allocation);
if (UNLIKELY(waited_allocate_size_)) {
VLOG(10) << "Free " << size
<< " bytes and notify all waited threads, "
"where waited_allocate_size_ = "
<< waited_allocate_size_;
cv_.notify_all();
}
}
phi::Allocation* RetryAllocator::AllocateImpl(size_t size) {
auto alloc_func = [&, this]() {
return underlying_allocator_->Allocate(size).release();
};
// In fact, we can unify the code of allocation success and failure
// But it would add lock even when allocation success at the first time
try {
if (FLAGS_offload_retry_times <= 0 || g_oom_callback == nullptr) {
return alloc_func();
} else {
bool has_offloaded = true;
for (int64_t i = 0; i < FLAGS_offload_retry_times && has_offloaded; ++i) {
try {
return alloc_func();
} catch (BadAlloc&) {
VLOG(10) << "Allocation " << size << " on " << place_
<< " failed, try to run OOM callback " << i;
has_offloaded = (g_oom_callback(place_, size) > 0);
}
}
return alloc_func();
}
} catch (BadAlloc&) {
{
WaitedAllocateSizeGuard guard(&waited_allocate_size_, size);
VLOG(10) << "Allocation failed when allocating " << size
<< " bytes, waited_allocate_size_ = " << waited_allocate_size_;
// We can just write allocation retry inside the predicate function of
// wait_until. But it needs to acquire the lock when executing predicate
// function. For better performance, we use loop here
auto end_time = std::chrono::high_resolution_clock::now() + retry_time_;
auto wait_until = [&, this] {
std::unique_lock<std::mutex> lock(mutex_);
return cv_.wait_until(lock, end_time);
};
size_t retry_time = 0;
while (wait_until() != std::cv_status::timeout) {
try {
return alloc_func();
} catch (BadAlloc&) {
// do nothing when it is not timeout
++retry_time;
VLOG(10) << "Allocation failed when retrying " << retry_time
<< " times when allocating " << size
<< " bytes. Wait still.";
} catch (...) {
throw;
}
}
}
VLOG(10) << "Allocation failed because of timeout when allocating " << size
<< " bytes.";
return alloc_func(); // If timeout, try last allocation request.
} catch (...) {
throw;
}
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,82 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <atomic> // NOLINT
#include <chrono> // NOLINT
#include <condition_variable> // NOLINT
#include <memory>
#include <mutex> // NOLINT
#include <utility>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/mem_visitor.h"
namespace paddle {
namespace memory {
namespace allocation {
PADDLE_API void RegisterOOMCallback(
std::function<size_t(Place, size_t)> callback);
class PADDLE_API RetryAllocator : public Allocator {
public:
RetryAllocator(std::shared_ptr<Allocator> allocator,
Place place,
size_t retry_ms)
: underlying_allocator_(std::move(allocator)),
place_(place),
retry_time_(retry_ms) {
PADDLE_ENFORCE_NOT_NULL(
underlying_allocator_,
common::errors::InvalidArgument(
"Underlying allocator of RetryAllocator is NULL"));
PADDLE_ENFORCE_EQ(
underlying_allocator_->IsAllocThreadSafe(),
true,
common::errors::PreconditionNotMet(
"Underlying allocator of RetryAllocator is not thread-safe"));
}
std::shared_ptr<Allocator>& GetUnderLyingAllocator() {
return underlying_allocator_;
}
void Accept(AllocatorVisitor* visitor) override { visitor->Visit(this); }
bool IsAllocThreadSafe() const override { return true; }
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
uint64_t ReleaseImpl(const Place& place) override {
return underlying_allocator_->Release(place);
}
size_t CompactImpl(const Place& place) override {
return underlying_allocator_->Compact(place);
}
private:
std::shared_ptr<Allocator> underlying_allocator_;
Place place_;
std::chrono::milliseconds retry_time_;
std::mutex mutex_;
std::condition_variable cv_;
std::atomic<size_t> waited_allocate_size_{0};
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,67 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <atomic>
#if defined(_M_X64) || defined(__x86_64__) || defined(_M_IX86) || \
defined(__i386__)
#define __PADDLE_x86__
#include <immintrin.h>
#endif
#include <thread>
#include "paddle/common/macros.h"
namespace paddle {
namespace memory {
static inline void CpuRelax() {
#if defined(__PADDLE_x86__)
_mm_pause();
#endif
}
class SpinLock {
public:
SpinLock() : mlock_(false) {}
void lock() {
for (;;) {
if (!mlock_.exchange(true, std::memory_order_acquire)) {
break;
}
constexpr int kMaxLoop = 32;
for (int loop = 1; mlock_.load(std::memory_order_relaxed);) {
if (loop <= kMaxLoop) {
for (int i = 1; i <= loop; ++i) {
CpuRelax();
}
loop *= 2;
} else {
std::this_thread::yield();
}
}
}
}
void unlock() { mlock_.store(false, std::memory_order_release); }
DISABLE_COPY_AND_ASSIGN(SpinLock);
private:
std::atomic<bool> mlock_;
};
} // namespace memory
} // namespace paddle
@@ -0,0 +1,87 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/mem_visitor.h"
#include "paddle/phi/core/memory/stats.h"
#include "paddle/phi/core/platform/profiler/mem_tracing.h"
namespace paddle {
namespace memory {
namespace allocation {
class StatAllocator : public Allocator {
public:
explicit StatAllocator(std::shared_ptr<Allocator> underlying_allocator)
: underlying_allocator_(std::move(underlying_allocator)) {}
bool IsAllocThreadSafe() const override { return true; }
void Accept(AllocatorVisitor* visitor) override { visitor->Visit(this); }
std::shared_ptr<Allocator>& GetUnderLyingAllocator() {
return underlying_allocator_;
}
protected:
void FreeImpl(phi::Allocation* allocation) override {
if (phi::is_cpu_place(allocation->place()) ||
phi::is_pinned_place(allocation->place())) {
HOST_MEMORY_STAT_UPDATE(
Allocated, allocation->place().GetDeviceId(), -allocation->size());
} else {
DEVICE_MEMORY_STAT_UPDATE(
Allocated, allocation->place().GetDeviceId(), -allocation->size());
}
platform::RecordMemEvent(allocation->ptr(),
allocation->place(),
allocation->size(),
phi::TracerMemEventType::Free);
underlying_allocator_->Free(allocation);
}
phi::Allocation* AllocateImpl(size_t size) override {
phi::Allocator::AllocationPtr allocation =
underlying_allocator_->Allocate(size);
const Place& place = allocation->place();
if (phi::is_cpu_place(place) || phi::is_pinned_place(place)) {
HOST_MEMORY_STAT_UPDATE(
Allocated, place.GetDeviceId(), allocation->size());
} else {
DEVICE_MEMORY_STAT_UPDATE(
Allocated, place.GetDeviceId(), allocation->size());
}
platform::RecordMemEvent(allocation->ptr(),
allocation->place(),
allocation->size(),
phi::TracerMemEventType::Allocate);
return allocation.release();
}
uint64_t ReleaseImpl(const Place& place) override {
return underlying_allocator_->Release(place);
}
size_t CompactImpl(const Place& place) override {
return underlying_allocator_->Compact(place);
}
private:
std::shared_ptr<Allocator> underlying_allocator_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,304 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/stream_safe_cuda_allocator.h"
#include <thread>
#include "glog/logging.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/backends/gpu/gpu_info.h"
#if defined(PADDLE_WITH_CUDA)
#include "paddle/phi/backends/gpu/cuda/cuda_graph.h"
#elif defined(PADDLE_WITH_HIP)
#include "paddle/phi/backends/gpu/rocm/hip_graph.h"
#endif
namespace paddle::memory::allocation {
StreamSafeCUDAAllocation::StreamSafeCUDAAllocation(
DecoratedAllocationPtr underlying_allocation,
gpuStream_t owning_stream,
StreamSafeCUDAAllocator* allocator)
: Allocation(underlying_allocation->ptr(),
underlying_allocation->base_ptr(),
underlying_allocation->size(),
underlying_allocation->place()),
underlying_allocation_(std::move(underlying_allocation)),
owning_stream_(owning_stream),
allocator_(allocator->shared_from_this()) {}
bool StreamSafeCUDAAllocation::RecordStream(gpuStream_t stream) {
VLOG(8) << "Try record stream " << stream << " for address " << ptr();
if (stream == owning_stream_) {
return false;
}
std::call_once(once_flag_,
[this] { phi::backends::gpu::SetDeviceId(place_.device); });
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
graph_capturing_stream_set_.insert(stream);
return true;
}
#endif
RecordStreamWithNoGraphCapturing(stream);
RecordGraphCapturingStreams();
return true;
}
void StreamSafeCUDAAllocation::EraseStream(gpuStream_t stream) {
VLOG(8) << "Try remove stream " << stream << " for address " << ptr();
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
auto it = outstanding_event_map_.find(stream);
if (it == outstanding_event_map_.end()) {
return;
}
#ifdef PADDLE_WITH_CUDA
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventDestroy(it->second));
#else
PADDLE_ENFORCE_GPU_SUCCESS(hipEventDestroy(it->second));
#endif
outstanding_event_map_.erase(it);
}
bool StreamSafeCUDAAllocation::CanBeFreed() {
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
if (UNLIKELY(phi::backends::gpu::CUDAGraph::IsThisThreadCapturing())) {
return graph_capturing_stream_set_.empty() &&
outstanding_event_map_.empty();
}
#endif
std::call_once(once_flag_,
[this] { phi::backends::gpu::SetDeviceId(place_.device); });
RecordGraphCapturingStreams();
for (auto it = outstanding_event_map_.begin();
it != outstanding_event_map_.end();
++it) {
gpuEvent_t& event = it->second;
#ifdef PADDLE_WITH_CUDA
gpuError_t err = cudaEventQuery(event);
if (err == cudaErrorNotReady) {
VLOG(9) << "Event " << event << " for " << ptr() << " is not completed";
// Erase the completed event before "it"
outstanding_event_map_.erase(outstanding_event_map_.begin(), it);
return false;
}
PADDLE_ENFORCE_GPU_SUCCESS(err);
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventDestroy(event));
#else
gpuError_t err = hipEventQuery(event);
if (err == hipErrorNotReady) {
VLOG(9) << "Event " << event << " for " << ptr() << " is not completed";
// Erase the completed event before "it"
outstanding_event_map_.erase(outstanding_event_map_.begin(), it);
return false;
}
PADDLE_ENFORCE_GPU_SUCCESS(err);
PADDLE_ENFORCE_GPU_SUCCESS(hipEventDestroy(event));
#endif
VLOG(8) << "Destroy event " << event;
}
return true;
}
gpuStream_t StreamSafeCUDAAllocation::GetOwningStream() const {
return owning_stream_;
}
void StreamSafeCUDAAllocation::RecordGraphCapturingStreams() {
for (gpuStream_t stream : graph_capturing_stream_set_) {
RecordStreamWithNoGraphCapturing(stream);
}
graph_capturing_stream_set_.clear();
}
void StreamSafeCUDAAllocation::RecordStreamWithNoGraphCapturing(
gpuStream_t stream) {
gpuEvent_t record_event;
auto it = outstanding_event_map_.find(stream);
if (it == outstanding_event_map_.end()) {
gpuEvent_t new_event;
#ifdef PADDLE_WITH_CUDA
PADDLE_ENFORCE_GPU_SUCCESS(
cudaEventCreateWithFlags(&new_event, cudaEventDisableTiming));
#else
PADDLE_ENFORCE_GPU_SUCCESS(
hipEventCreateWithFlags(&new_event, hipEventDisableTiming));
#endif
outstanding_event_map_[stream] = new_event;
record_event = new_event;
VLOG(9) << "Create a new event " << new_event;
} else {
record_event = it->second;
VLOG(9) << "Reuse event " << record_event;
}
#ifdef PADDLE_WITH_CUDA
PADDLE_ENFORCE_GPU_SUCCESS(cudaEventRecord(record_event, stream));
#else
PADDLE_ENFORCE_GPU_SUCCESS(hipEventRecord(record_event, stream));
#endif
VLOG(8) << "Record event " << record_event << " to stream " << stream;
}
StreamSafeCUDAAllocator::StreamSafeCUDAAllocator(
std::shared_ptr<Allocator> underlying_allocator,
GPUPlace place,
gpuStream_t default_stream,
bool in_cuda_graph_capturing)
: underlying_allocator_(std::move(underlying_allocator)),
place_(place),
default_stream_(default_stream),
in_cuda_graph_capturing_(in_cuda_graph_capturing) {
if (LIKELY(!in_cuda_graph_capturing)) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
allocator_map_[place].emplace_back(this);
}
}
StreamSafeCUDAAllocator::~StreamSafeCUDAAllocator() {
if (LIKELY(!in_cuda_graph_capturing_)) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeCUDAAllocator*>& allocators = allocator_map_[place_];
allocators.erase(std::remove(allocators.begin(), allocators.end(), this),
allocators.end());
}
}
bool StreamSafeCUDAAllocator::IsAllocThreadSafe() const { return true; }
gpuStream_t StreamSafeCUDAAllocator::GetDefaultStream() const {
return default_stream_;
}
void StreamSafeCUDAAllocator::SetDefaultStream(gpuStream_t stream) {
default_stream_ = stream;
}
phi::Allocation* StreamSafeCUDAAllocator::AllocateImpl(size_t size) {
phi::RecordEvent record("StreamSafeCUDAAllocator::Allocate",
phi::TracerEventType::UserDefined,
9 /*level*/);
ProcessUnfreedAllocations();
VLOG(8) << "Try allocate " << size << " bytes";
AllocationPtr underlying_allocation;
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (BadAlloc&) {
VLOG(4) << "Allocation failed when allocating " << size << " bytes";
ReleaseImpl(place_);
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (...) {
VLOG(3)
<< "Still allocation failed after release memory from all streams";
throw;
}
} catch (...) {
throw;
}
StreamSafeCUDAAllocation* allocation = new StreamSafeCUDAAllocation(
static_unique_ptr_cast<Allocation>(std::move(underlying_allocation)),
default_stream_,
this);
VLOG(8) << "Thread " << std::this_thread::get_id() << " Allocate "
<< allocation->size() << " bytes at address " << allocation->ptr()
<< " , stream: " << default_stream_;
return allocation;
}
void StreamSafeCUDAAllocator::FreeImpl(phi::Allocation* allocation) {
phi::RecordEvent record("StreamSafeCUDAAllocator::Free",
phi::TracerEventType::UserDefined,
9 /*level*/);
StreamSafeCUDAAllocation* stream_safe_cuda_allocation =
static_cast<StreamSafeCUDAAllocation*>(allocation);
VLOG(8) << "Try free allocation " << stream_safe_cuda_allocation->ptr();
if (stream_safe_cuda_allocation->CanBeFreed()) {
VLOG(9) << "Directly delete allocation";
delete stream_safe_cuda_allocation;
} else {
VLOG(9) << "Put into unfreed_allocation list";
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
unfreed_allocations_.emplace_back(stream_safe_cuda_allocation);
}
}
uint64_t StreamSafeCUDAAllocator::ReleaseImpl(const Place& place) {
if (UNLIKELY(in_cuda_graph_capturing_)) {
VLOG(7) << "Memory release forbidden in CUDA Graph Capturing";
return 0;
}
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeCUDAAllocator*>& allocators = allocator_map_[place];
uint64_t released_size = 0;
for (StreamSafeCUDAAllocator* allocator : allocators) {
released_size += allocator->ProcessUnfreedAllocationsAndRelease();
}
VLOG(8) << "Release " << released_size << " bytes memory from all streams";
return released_size;
}
size_t StreamSafeCUDAAllocator::CompactImpl(const Place& place) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
VLOG(4) << "enter StreamSafeCUDAAllocator compact!!";
std::vector<StreamSafeCUDAAllocator*>& allocators = allocator_map_[place];
size_t compact_free_size = 0;
for (StreamSafeCUDAAllocator* allocator : allocators) {
compact_free_size += allocator->underlying_allocator_->Compact(place_);
}
return compact_free_size;
}
void StreamSafeCUDAAllocator::ProcessUnfreedAllocations() {
// NOTE(Ruibiao): This condition is to reduce lock completion. It does not
// need to be thread-safe since here occasional misjudgments are permissible.
if (unfreed_allocations_.empty()) {
return;
}
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
for (auto it = unfreed_allocations_.begin();
it != unfreed_allocations_.end();) {
if ((*it)->CanBeFreed()) {
delete *it;
it = unfreed_allocations_.erase(it);
} else {
++it;
}
}
}
uint64_t StreamSafeCUDAAllocator::ProcessUnfreedAllocationsAndRelease() {
ProcessUnfreedAllocations();
return underlying_allocator_->Release(place_);
}
thread_local std::once_flag StreamSafeCUDAAllocation::once_flag_;
std::map<Place, std::vector<StreamSafeCUDAAllocator*>>
StreamSafeCUDAAllocator::allocator_map_;
SpinLock StreamSafeCUDAAllocator::allocator_map_lock_;
} // namespace paddle::memory::allocation
@@ -0,0 +1,115 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <list>
#include <map>
#include <set>
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/mem_visitor.h"
#ifdef PADDLE_WITH_CUDA
#include <cuda_runtime.h>
#else
#include <hip/hip_runtime.h>
#endif
namespace paddle {
namespace memory {
namespace allocation {
class StreamSafeCUDAAllocator;
class StreamSafeCUDAAllocation : public Allocation {
public:
StreamSafeCUDAAllocation(DecoratedAllocationPtr underlying_allocation,
gpuStream_t owning_stream,
StreamSafeCUDAAllocator *allocator);
bool RecordStream(gpuStream_t stream);
void EraseStream(gpuStream_t stream);
bool CanBeFreed();
gpuStream_t GetOwningStream() const;
void *ptr() const noexcept override { return underlying_allocation_->ptr(); }
size_t size() const noexcept override {
return underlying_allocation_->size();
}
const Place &place() const noexcept override {
return underlying_allocation_->place();
}
private:
thread_local static std::once_flag once_flag_;
void RecordGraphCapturingStreams();
void RecordStreamWithNoGraphCapturing(gpuStream_t stream);
DecoratedAllocationPtr underlying_allocation_;
std::set<gpuStream_t> graph_capturing_stream_set_;
std::map<gpuStream_t, gpuEvent_t> outstanding_event_map_;
gpuStream_t owning_stream_;
SpinLock outstanding_event_map_lock_;
// To compatible with CUDA Graph, hold the allocator shared_ptr so that
// Allocator will not deconstruct before Allocation
std::shared_ptr<Allocator> allocator_;
};
class StreamSafeCUDAAllocator
: public Allocator,
public std::enable_shared_from_this<StreamSafeCUDAAllocator> {
public:
StreamSafeCUDAAllocator(std::shared_ptr<Allocator> underlying_allocator,
GPUPlace place,
gpuStream_t default_stream,
bool in_cuda_graph_capturing = false);
~StreamSafeCUDAAllocator();
std::shared_ptr<Allocator> &GetUnderLyingAllocator() {
return underlying_allocator_;
}
std::vector<StreamSafeCUDAAllocator *> &GetAllocatorByPlace() {
return allocator_map_[place_];
}
bool IsAllocThreadSafe() const override;
gpuStream_t GetDefaultStream() const;
void SetDefaultStream(gpuStream_t stream);
void Accept(AllocatorVisitor *visitor) override { visitor->Visit(this); }
protected:
phi::Allocation *AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation *allocation) override;
uint64_t ReleaseImpl(const Place &place) override;
size_t CompactImpl(const Place &place) override;
private:
void ProcessUnfreedAllocations();
uint64_t ProcessUnfreedAllocationsAndRelease();
static std::map<Place, std::vector<StreamSafeCUDAAllocator *>> allocator_map_;
static SpinLock allocator_map_lock_;
std::shared_ptr<Allocator> underlying_allocator_;
GPUPlace place_;
gpuStream_t default_stream_;
std::list<StreamSafeCUDAAllocation *> unfreed_allocations_;
SpinLock unfreed_allocation_lock_;
bool in_cuda_graph_capturing_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,239 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <thread>
#include "glog/logging.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/backends/context_pool.h"
#include "paddle/phi/core/memory/allocation/stream_safe_custom_device_allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
StreamSafeCustomDeviceAllocation::StreamSafeCustomDeviceAllocation(
DecoratedAllocationPtr underlying_allocation,
phi::stream::stream_t owning_stream,
StreamSafeCustomDeviceAllocator* allocator)
: Allocation(underlying_allocation->ptr(),
underlying_allocation->base_ptr(),
underlying_allocation->size(),
underlying_allocation->place()),
underlying_allocation_(std::move(underlying_allocation)),
owning_stream_(std::move(owning_stream)),
allocator_(allocator->shared_from_this()) {}
bool StreamSafeCustomDeviceAllocation::RecordStream(
phi::stream::stream_t stream) {
VLOG(8) << "Try record stream " << stream << " for address " << ptr();
if (stream == owning_stream_) {
return false;
}
std::call_once(once_flag_, [this] { phi::DeviceManager::SetDevice(place_); });
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
auto it = outstanding_event_map_.find(stream);
if (it == outstanding_event_map_.end()) {
outstanding_event_map_.insert(
{stream, std::make_shared<phi::event::Event>()});
outstanding_event_map_[stream]->Init(place());
VLOG(9) << "Create a new event "
<< outstanding_event_map_[stream]->raw_event();
}
auto stream_wrapper = phi::stream::Stream(place(), stream);
VLOG(8) << "Record event " << outstanding_event_map_[stream]->raw_event()
<< " to stream " << stream;
outstanding_event_map_[stream]->Record(&stream_wrapper);
return true;
}
void StreamSafeCustomDeviceAllocation::EraseStream(
phi::stream::stream_t stream) {
VLOG(8) << "Try remove stream " << stream << " for address " << ptr();
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
auto it = outstanding_event_map_.find(stream);
if (it == outstanding_event_map_.end()) {
return;
}
it->second->Destroy();
outstanding_event_map_.erase(it);
}
bool StreamSafeCustomDeviceAllocation::CanBeFreed() {
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
if (!phi::DeviceManager::HasDeviceType(place_.GetDeviceType())) {
return true;
}
std::call_once(once_flag_, [this] { phi::DeviceManager::SetDevice(place_); });
for (auto it = outstanding_event_map_.begin();
it != outstanding_event_map_.end();) {
auto& event = it->second;
if (!event->Query()) {
VLOG(9) << "Event " << event->raw_event() << " for " << ptr()
<< " is not completed";
return false;
}
VLOG(8) << "Destroy event " << event->raw_event();
event->Destroy();
it = outstanding_event_map_.erase(it);
}
outstanding_event_map_.clear();
return true;
}
phi::stream::stream_t StreamSafeCustomDeviceAllocation::GetOwningStream()
const {
return owning_stream_;
}
void StreamSafeCustomDeviceAllocation::SetOwningStream(
phi::stream::stream_t s) {
owning_stream_ = s;
}
StreamSafeCustomDeviceAllocator::StreamSafeCustomDeviceAllocator(
std::shared_ptr<Allocator> underlying_allocator,
CustomPlace place,
phi::stream::stream_t default_stream)
: underlying_allocator_(std::move(underlying_allocator)),
place_(std::move(place)),
default_stream_(std::move(default_stream)) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
allocator_map_[place_].emplace_back(this);
}
StreamSafeCustomDeviceAllocator::~StreamSafeCustomDeviceAllocator() {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeCustomDeviceAllocator*>& allocators =
allocator_map_[place_];
allocators.erase(std::remove(allocators.begin(), allocators.end(), this),
allocators.end());
}
phi::stream::stream_t StreamSafeCustomDeviceAllocator::GetDefaultStream()
const {
return default_stream_;
}
void StreamSafeCustomDeviceAllocator::SetDefaultStream(
phi::stream::stream_t stream) {
default_stream_ = stream;
}
phi::Allocation* StreamSafeCustomDeviceAllocator::AllocateImpl(size_t size) {
phi::RecordEvent record("StreamSafeCustomDeviceAllocator::Allocate",
phi::TracerEventType::UserDefined,
9 /*level*/);
ProcessUnfreedAllocations();
VLOG(8) << "Try allocate " << size << " bytes";
AllocationPtr underlying_allocation;
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (BadAlloc&) {
VLOG(4) << "Allocation failed when allocating " << size << " bytes";
ReleaseImpl(place_);
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (...) {
VLOG(3)
<< "Still allocation failed after release memory from all streams";
throw;
}
} catch (...) {
throw;
}
StreamSafeCustomDeviceAllocation* allocation =
new StreamSafeCustomDeviceAllocation(
static_unique_ptr_cast<Allocation>(std::move(underlying_allocation)),
default_stream_,
this);
VLOG(8) << "Thread " << std::this_thread::get_id() << " Allocate "
<< allocation->size() << " bytes at address " << allocation->ptr()
<< " , stream: " << default_stream_;
return allocation;
}
void StreamSafeCustomDeviceAllocator::FreeImpl(phi::Allocation* allocation) {
phi::RecordEvent record("StreamSafeCustomDeviceAllocator::Free",
phi::TracerEventType::UserDefined,
9 /*level*/);
StreamSafeCustomDeviceAllocation* stream_safe_cuda_allocation =
static_cast<StreamSafeCustomDeviceAllocation*>(allocation);
VLOG(8) << "Try free allocation " << stream_safe_cuda_allocation->ptr();
if (!stream_safe_cuda_allocation->GetOwningStream()) {
stream_safe_cuda_allocation->SetOwningStream(
default_stream_ ? default_stream_
: reinterpret_cast<phi::CustomContext*>(
phi::DeviceContextPool::Instance().Get(place_))
->stream());
}
if (stream_safe_cuda_allocation->CanBeFreed()) {
VLOG(9) << "Directly delete allocation";
delete stream_safe_cuda_allocation;
} else {
VLOG(9) << "Put into unfreed_allocation list";
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
unfreed_allocations_.emplace_back(stream_safe_cuda_allocation);
}
}
uint64_t StreamSafeCustomDeviceAllocator::ReleaseImpl(const Place& place) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeCustomDeviceAllocator*>& allocators =
allocator_map_[place];
uint64_t released_size = 0;
for (StreamSafeCustomDeviceAllocator* allocator : allocators) {
released_size += allocator->ProcessUnfreedAllocationsAndRelease();
}
VLOG(8) << "Release " << released_size << " bytes memory from all streams";
return released_size;
}
void StreamSafeCustomDeviceAllocator::ProcessUnfreedAllocations() {
// NOTE(Ruibiao): This condition is to reduce lock completion. It does not
// need to be thread-safe since here occasional misjudgments are
// permissible.
if (unfreed_allocations_.empty()) {
return;
}
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
for (auto it = unfreed_allocations_.begin();
it != unfreed_allocations_.end();) {
if ((*it)->CanBeFreed()) {
delete *it;
it = unfreed_allocations_.erase(it);
} else {
++it;
}
}
}
uint64_t
StreamSafeCustomDeviceAllocator::ProcessUnfreedAllocationsAndRelease() {
ProcessUnfreedAllocations();
return underlying_allocator_->Release(place_);
}
thread_local std::once_flag StreamSafeCustomDeviceAllocation::once_flag_;
std::map<Place, std::vector<StreamSafeCustomDeviceAllocator*>>
StreamSafeCustomDeviceAllocator::allocator_map_;
SpinLock StreamSafeCustomDeviceAllocator::allocator_map_lock_;
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,91 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <list>
#include <map>
#include <set>
#include "paddle/phi/backends/device_manager.h"
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
namespace paddle {
namespace memory {
namespace allocation {
class StreamSafeCustomDeviceAllocator;
class StreamSafeCustomDeviceAllocation : public Allocation {
public:
StreamSafeCustomDeviceAllocation(DecoratedAllocationPtr underlying_allocation,
phi::stream::stream_t owning_stream,
StreamSafeCustomDeviceAllocator *allocator);
bool RecordStream(phi::stream::stream_t stream);
void EraseStream(phi::stream::stream_t stream);
bool CanBeFreed();
phi::stream::stream_t GetOwningStream() const;
void SetOwningStream(phi::stream::stream_t s);
private:
thread_local static std::once_flag once_flag_;
DecoratedAllocationPtr underlying_allocation_;
std::map<phi::stream::stream_t, std::shared_ptr<phi::event::Event>>
outstanding_event_map_;
phi::stream::stream_t owning_stream_;
SpinLock outstanding_event_map_lock_;
std::shared_ptr<Allocator> allocator_;
bool will_be_freed_{false};
};
class StreamSafeCustomDeviceAllocator
: public Allocator,
public std::enable_shared_from_this<StreamSafeCustomDeviceAllocator> {
public:
StreamSafeCustomDeviceAllocator(
std::shared_ptr<Allocator> underlying_allocator,
CustomPlace place,
phi::stream::stream_t default_stream);
~StreamSafeCustomDeviceAllocator();
bool IsAllocThreadSafe() const override { return true; }
phi::stream::stream_t GetDefaultStream() const;
void SetDefaultStream(phi::stream::stream_t stream);
protected:
phi::Allocation *AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation *allocation) override;
uint64_t ReleaseImpl(const Place &place) override;
private:
void ProcessUnfreedAllocations();
uint64_t ProcessUnfreedAllocationsAndRelease();
static std::map<Place, std::vector<StreamSafeCustomDeviceAllocator *>>
allocator_map_;
static SpinLock allocator_map_lock_;
std::shared_ptr<Allocator> underlying_allocator_;
CustomPlace place_;
phi::stream::stream_t default_stream_;
std::list<StreamSafeCustomDeviceAllocation *> unfreed_allocations_;
SpinLock unfreed_allocation_lock_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,219 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/stream_safe_xpu_allocator.h"
#include <thread>
#include "glog/logging.h"
#include "paddle/phi/api/profiler/event_tracing.h"
#include "paddle/phi/backends/xpu/enforce_xpu.h"
#include "paddle/phi/backends/xpu/xpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
StreamSafeXPUAllocation::StreamSafeXPUAllocation(
DecoratedAllocationPtr underlying_allocation,
XPUStream owning_stream,
StreamSafeXPUAllocator* allocator)
: Allocation(underlying_allocation->ptr(),
underlying_allocation->base_ptr(),
underlying_allocation->size(),
underlying_allocation->place()),
underlying_allocation_(std::move(underlying_allocation)),
owning_stream_(std::move(owning_stream)),
allocator_(allocator->shared_from_this()) {}
bool StreamSafeXPUAllocation::RecordStream(XPUStream stream) {
VLOG(8) << "Try record stream " << stream << " for address " << ptr();
if (stream == owning_stream_) {
VLOG(8) << "stream " << stream << " is the same as owning stream "
<< owning_stream_;
VLOG(8) << "Skip recording the same stream " << stream << " for address "
<< ptr();
return false;
}
std::call_once(once_flag_,
[this] { phi::backends::xpu::SetXPUDeviceId(place_.device); });
std::lock_guard<SpinLock> lock_guard(outstanding_event_map_lock_);
RecordStreamPrivate(stream);
return true;
}
bool StreamSafeXPUAllocation::CanBeFreed() {
std::call_once(once_flag_,
[this] { phi::backends::xpu::SetXPUDeviceId(place_.device); });
for (auto it = outstanding_event_map_.begin();
it != outstanding_event_map_.end();
++it) {
XPUEvent& event = it->second;
if (xpu_event_query(event) == XPU_SUCCESS) {
PADDLE_ENFORCE_XRE_SUCCESS(xpu_event_destroy(event));
VLOG(8) << "Destroy event " << event;
} else {
outstanding_event_map_.erase(outstanding_event_map_.begin(), it);
return false;
}
}
return true;
}
XPUStream StreamSafeXPUAllocation::GetOwningStream() const {
return owning_stream_;
}
void StreamSafeXPUAllocation::RecordStreamPrivate(XPUStream stream) {
XPUEvent record_event;
auto it = outstanding_event_map_.find(stream);
if (it == outstanding_event_map_.end()) {
XPUEvent new_event;
PADDLE_ENFORCE_XRE_SUCCESS(xpu_event_create(&new_event));
outstanding_event_map_[stream] = new_event;
record_event = new_event;
VLOG(9) << "Create a new event " << new_event;
} else {
record_event = it->second;
VLOG(9) << "Reuse event " << record_event;
}
PADDLE_ENFORCE_XRE_SUCCESS(xpu_event_record(record_event, stream));
VLOG(8) << "Record event " << record_event << " to stream " << stream;
}
StreamSafeXPUAllocator::StreamSafeXPUAllocator(
std::shared_ptr<Allocator> underlying_allocator,
XPUPlace place,
XPUStream default_stream)
: underlying_allocator_(std::move(underlying_allocator)),
place_(std::move(place)),
default_stream_(std::move(default_stream)) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
allocator_map_[place].emplace_back(this);
}
StreamSafeXPUAllocator::~StreamSafeXPUAllocator() {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeXPUAllocator*>& allocators = allocator_map_[place_];
allocators.erase(std::remove(allocators.begin(), allocators.end(), this),
allocators.end());
}
bool StreamSafeXPUAllocator::IsAllocThreadSafe() const { return true; }
XPUStream StreamSafeXPUAllocator::GetDefaultStream() const {
return default_stream_;
}
void StreamSafeXPUAllocator::SetDefaultStream(XPUStream stream) {
default_stream_ = stream;
}
phi::Allocation* StreamSafeXPUAllocator::AllocateImpl(size_t size) {
phi::RecordEvent record("StreamSafeXPUAllocator::Allocate",
phi::TracerEventType::UserDefined,
9 /*level*/);
ProcessUnfreedAllocations();
VLOG(8) << "Try allocate " << size << " bytes";
AllocationPtr underlying_allocation;
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (BadAlloc&) {
VLOG(4) << "Allocation failed when allocating " << size << " bytes";
ReleaseImpl(place_);
try {
underlying_allocation = underlying_allocator_->Allocate(size);
} catch (...) {
VLOG(3)
<< "Still allocation failed after release memory from all streams";
throw;
}
} catch (...) {
throw;
}
StreamSafeXPUAllocation* allocation = new StreamSafeXPUAllocation(
static_unique_ptr_cast<Allocation>(std::move(underlying_allocation)),
default_stream_,
this);
VLOG(8) << "Thread " << std::this_thread::get_id() << " Allocate "
<< allocation->size() << " bytes at address " << allocation->ptr()
<< " , stream: " << default_stream_;
return allocation;
}
void StreamSafeXPUAllocator::FreeImpl(phi::Allocation* allocation) {
phi::RecordEvent record("StreamSafeXPUAllocator::Free",
phi::TracerEventType::UserDefined,
9 /*level*/);
StreamSafeXPUAllocation* stream_safe_xpu_allocation =
static_cast<StreamSafeXPUAllocation*>(allocation);
VLOG(8) << "Try free allocation " << stream_safe_xpu_allocation->ptr();
if (stream_safe_xpu_allocation->CanBeFreed()) {
VLOG(9) << "Directly delete allocation";
delete stream_safe_xpu_allocation;
} else {
VLOG(9) << "Put into unfreed_allocation list";
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
unfreed_allocations_.emplace_back(stream_safe_xpu_allocation);
}
}
uint64_t StreamSafeXPUAllocator::ReleaseImpl(const Place& place) {
std::lock_guard<SpinLock> lock_guard(allocator_map_lock_);
std::vector<StreamSafeXPUAllocator*>& allocators = allocator_map_[place];
uint64_t released_size = 0;
for (StreamSafeXPUAllocator* allocator : allocators) {
released_size += allocator->ProcessUnfreedAllocationsAndRelease();
}
VLOG(8) << "Release " << released_size << " bytes memory from all streams";
return released_size;
}
void StreamSafeXPUAllocator::ProcessUnfreedAllocations() {
// NOTE(Ruibiao): This condition is to reduce lock completion. It does not
// need to be thread-safe since here occasional misjudgments are permissible.
if (unfreed_allocations_.empty()) {
return;
}
std::lock_guard<SpinLock> lock_guard(unfreed_allocation_lock_);
for (auto it = unfreed_allocations_.begin();
it != unfreed_allocations_.end();) {
if ((*it)->CanBeFreed()) {
delete *it;
it = unfreed_allocations_.erase(it);
} else {
++it;
}
}
}
uint64_t StreamSafeXPUAllocator::ProcessUnfreedAllocationsAndRelease() {
ProcessUnfreedAllocations();
return underlying_allocator_->Release(place_);
}
thread_local std::once_flag StreamSafeXPUAllocation::once_flag_;
std::map<Place, std::vector<StreamSafeXPUAllocator*>>
StreamSafeXPUAllocator::allocator_map_;
SpinLock StreamSafeXPUAllocator::allocator_map_lock_;
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,90 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <list>
#include <map>
#include <set>
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/backends/xpu/xpu_context.h"
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
class StreamSafeXPUAllocator;
class StreamSafeXPUAllocation : public Allocation {
public:
StreamSafeXPUAllocation(DecoratedAllocationPtr underlying_allocation,
XPUStream owning_stream,
StreamSafeXPUAllocator *allocator);
bool RecordStream(XPUStream stream);
void EraseStream(XPUStream stream);
bool CanBeFreed();
XPUStream GetOwningStream() const;
private:
thread_local static std::once_flag once_flag_;
void RecordStreamPrivate(XPUStream stream);
DecoratedAllocationPtr underlying_allocation_;
std::map<XPUStream, XPUEvent> outstanding_event_map_;
XPUStream owning_stream_;
SpinLock outstanding_event_map_lock_;
std::shared_ptr<Allocator> allocator_;
};
class StreamSafeXPUAllocator
: public Allocator,
public std::enable_shared_from_this<StreamSafeXPUAllocator> {
public:
StreamSafeXPUAllocator(std::shared_ptr<Allocator> underlying_allocator,
XPUPlace place,
XPUStream default_stream);
~StreamSafeXPUAllocator();
bool IsAllocThreadSafe() const override;
XPUStream GetDefaultStream() const;
void SetDefaultStream(XPUStream stream);
protected:
phi::Allocation *AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation *allocation) override;
uint64_t ReleaseImpl(const Place &place) override;
private:
void ProcessUnfreedAllocations();
uint64_t ProcessUnfreedAllocationsAndRelease();
static std::map<Place, std::vector<StreamSafeXPUAllocator *>> allocator_map_;
static SpinLock allocator_map_lock_;
std::shared_ptr<Allocator> underlying_allocator_;
XPUPlace place_;
XPUStream default_stream_;
std::list<StreamSafeXPUAllocation *> unfreed_allocations_;
SpinLock unfreed_allocation_lock_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,442 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include "paddle/phi/core/memory/allocation/system_allocator.h"
#include "paddle/phi/core/memory/stats.h"
#ifdef _WIN32
#include <malloc.h>
#ifndef NOMINMAX
#define NOMINMAX // msvc max/min macro conflict with std::min/max
#endif
#include <windows.h> // VirtualLock/VirtualUnlock
#else
#include <sys/mman.h> // for mlock and munlock
#endif
#include "paddle/common/flags.h"
#include "paddle/phi/backends/cpu/cpu_info.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/phi/core/platform/cuda_device_guard.h"
#endif
#ifdef PADDLE_WITH_XPU
#include <cuda.h>
#include <cuda_runtime.h>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#endif
#include "paddle/phi/core/platform/device/device_wrapper.h"
#include "paddle/phi/core/platform/profiler/mem_tracing.h"
COMMON_DECLARE_bool(use_pinned_memory);
COMMON_DECLARE_double(fraction_of_gpu_memory_to_use);
COMMON_DECLARE_uint64(initial_gpu_memory_in_mb);
COMMON_DECLARE_uint64(reallocate_gpu_memory_in_mb);
namespace paddle::memory::detail {
void* AlignedMalloc(size_t size) {
void* p = nullptr;
size_t alignment = 32ul;
#ifdef PADDLE_WITH_DNNL
// refer to https://github.com/uxlfoundation/oneDNN/blob/main/include/dnnl.hpp
// memory alignment
alignment = 4096ul;
#endif
#ifdef _WIN32
p = _aligned_malloc(size, alignment);
#else
int error = posix_memalign(&p, alignment, size);
PADDLE_ENFORCE_EQ(
error,
0,
common::errors::ResourceExhausted(
"Fail to alloc memory of %ld size, error code is %d.", size, error));
#endif
PADDLE_ENFORCE_NOT_NULL(p,
common::errors::ResourceExhausted(
"Fail to alloc memory of %ld size.", size));
return p;
}
void* CPUAllocator::Alloc(size_t* index, size_t size) {
// According to http://www.cplusplus.com/reference/cstdlib/malloc/,
// malloc might not return nullptr if size is zero, but the returned
// pointer shall not be dereferenced -- so we make it nullptr.
if (size <= 0) return nullptr;
*index = 0; // unlock memory
void* p = AlignedMalloc(size);
if (p != nullptr) {
if (FLAGS_use_pinned_memory) {
*index = 1;
#ifdef _WIN32
VirtualLock(p, size);
#else
mlock(p, size); // lock memory
#endif
}
}
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedAllocate);
return p;
}
void CPUAllocator::Free(void* p, size_t size, size_t index) {
if (p != nullptr && index == 1) {
#ifdef _WIN32
VirtualUnlock(p, size);
#else
munlock(p, size);
#endif
}
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedFree);
#ifdef _WIN32
_aligned_free(p);
#else
free(p); // NOLINT
#endif
}
bool CPUAllocator::UseGpu() const { return false; }
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
void* GPUAllocator::Alloc(size_t* index, size_t size) {
// CUDA documentation doesn't explain if cudaMalloc returns nullptr
// if size is 0. We just make sure it does.
if (size <= 0) return nullptr;
void* p;
auto result = platform::RecordedGpuMalloc(&p, size, gpu_id_);
if (result == gpuSuccess) {
*index = 0;
gpu_alloc_size_ += size;
return p;
} else {
size_t avail, total, actual_avail, actual_total;
bool is_limited = platform::RecordedGpuMemGetInfo(
&avail, &total, &actual_avail, &actual_total, gpu_id_);
size_t allocated = total - avail;
std::string err_msg;
if (is_limited) {
auto limit_size = (total >> 20);
err_msg = string::Sprintf(
"\n 3) Set environment variable `FLAGS_gpu_memory_limit_mb` to a "
"larger value. Currently `FLAGS_gpu_memory_limit_mb` is %d, so the "
"maximum GPU memory usage is limited to %d MB.\n"
" The command is `export FLAGS_gpu_memory_limit_mb=xxx`.",
limit_size,
limit_size);
}
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on GPU %d. "
"Cannot allocate %s memory on GPU %d, %s memory has been allocated and "
"available memory is only %s.\n\n"
"Please check whether there is any other process using GPU %d.\n"
"1. If yes, please stop them, or start PaddlePaddle on another GPU.\n"
"2. If no, please try one of the following suggestions:\n"
" 1) Decrease the batch size of your model.\n"
" 2) FLAGS_fraction_of_gpu_memory_to_use is %.2lf now, "
"please set it to a higher value but less than 1.0.\n"
" The command is "
"`export FLAGS_fraction_of_gpu_memory_to_use=xxx`.%s\n\n",
gpu_id_,
string::HumanReadableSize(size),
gpu_id_,
string::HumanReadableSize(allocated),
string::HumanReadableSize(avail),
gpu_id_,
FLAGS_fraction_of_gpu_memory_to_use,
err_msg));
}
}
void GPUAllocator::Free(void* p, size_t size, size_t index) {
PADDLE_ENFORCE_EQ(index,
0,
common::errors::InvalidArgument(
"The index should be 0, index is %d", index));
PADDLE_ENFORCE_GE(gpu_alloc_size_,
size,
common::errors::InvalidArgument(
"The size of memory (%d) to free exceeds the size of "
"allocated gpu memory (%d)",
size,
gpu_alloc_size_));
gpu_alloc_size_ -= size;
platform::RecordedGpuFree(p, size, gpu_id_);
}
bool GPUAllocator::UseGpu() const { return true; }
// PINNED memory allows direct DMA transfers by the GPU to and from system
// memory. It's locked to a physical address.
void* CUDAPinnedAllocator::Alloc(size_t* index, size_t size) {
if (size <= 0) return nullptr;
// NOTE: here, we use CUDAPinnedMaxAllocSize as the maximum memory size
// of host pinned allocation. Allocates too much would reduce
// the amount of memory available to the underlying system for paging.
size_t usable =
phi::backends::cpu::CUDAPinnedMaxAllocSize() - cuda_pinnd_alloc_size_;
if (size > usable) {
LOG(WARNING) << "Cannot malloc " << size / 1024.0 / 1024.0
<< " MB pinned memory."
<< ", available " << usable / 1024.0 / 1024.0
<< " MB"; // NOLINT
return nullptr;
}
void* p;
// PINNED memory is visible to all CUDA contexts.
#ifdef PADDLE_WITH_HIP
hipError_t result = hipHostMalloc(&p, size, hipHostMallocPortable);
#else
cudaError_t result = cudaHostAlloc(&p, size, cudaHostAllocPortable);
#endif
if (result == gpuSuccess) {
*index = 1; // PINNED memory
cuda_pinnd_alloc_size_ += size;
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedAllocate);
return p;
} else {
LOG(WARNING) << "cudaHostAlloc failed.";
return nullptr;
}
return nullptr;
}
void CUDAPinnedAllocator::Free(void* p, size_t size, size_t index) {
gpuError_t err;
PADDLE_ENFORCE_EQ(index,
1,
common::errors::InvalidArgument(
"The index should be 1, but got %d", index));
PADDLE_ENFORCE_GE(cuda_pinnd_alloc_size_,
size,
common::errors::InvalidArgument(
"The size of memory (%d) to free exceeds the size of "
"allocated cuda pinned memory (%d)",
size,
cuda_pinnd_alloc_size_));
cuda_pinnd_alloc_size_ -= size;
#ifdef PADDLE_WITH_HIP
err = hipHostFree(p);
if (err != hipErrorDeinitialized) {
PADDLE_ENFORCE_EQ(
err,
hipSuccess,
common::errors::Fatal(
"hipFreeHost failed in GPUPinnedAllocator, error code is %d", err));
}
#else
err = cudaFreeHost(p);
// Purposefully allow cudaErrorCudartUnloading, because
// that is returned if you ever call cudaFreeHost after the
// driver has already shutdown. This happens only if the
// process is terminating, in which case we don't care if
// cudaFreeHost succeeds.
if (err != cudaErrorCudartUnloading) {
PADDLE_ENFORCE_EQ(
err,
0,
common::errors::Fatal(
"cudaFreeHost failed in GPUPinnedAllocator, error code is %d",
err));
}
#endif
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedFree);
}
bool CUDAPinnedAllocator::UseGpu() const { return false; }
#endif
#ifdef PADDLE_WITH_XPU
// XPU PINNED memory allows direct DMA transfers by the XPU to and from system
// memory. Its locked to a physical address.
void* XPUPinnedAllocator::Alloc(size_t* index, size_t size) {
VLOG(6) << "Alloc start, requested size: " << size << " bytes";
if (size <= 0) {
VLOG(6) << "Requested size <= 0, returning nullptr";
return nullptr;
}
// NOTE: here, we use CUDAPinnedMaxAllocSize as the maximum memory size
// of host pinned allocation. Allocates too much would reduce
// the amount of memory available to the underlying system for paging.
size_t usable =
phi::backends::cpu::CUDAPinnedMaxAllocSize() - xpu_pinned_alloc_size_;
VLOG(6) << "Usable pinned memory: " << usable << " bytes";
if (size > usable) {
VLOG(6) << "Requested size (" << size
<< " bytes) exceeds usable pinned memory (" << usable << " bytes)";
LOG(WARNING) << "Cannot malloc " << size / 1024.0 / 1024.0
<< " MB pinned memory."
<< ", available " << usable / 1024.0 / 1024.0
<< " MB"; // NOLINT
return nullptr;
}
void* p = nullptr;
VLOG(6) << "Calling cudaHostAlloc for " << size << " bytes";
// PINNED memory is visible to all CUDA contexts.
// FIXME(yangjianbang): XPU does not support cudaHostAllocPortable with
// multiple cuda contexts yet.
cudaError_t result = cudaHostAlloc(&p, size, cudaHostAllocDefault);
VLOG(6) << "cudaHostAlloc returned: " << result;
if (result == cudaSuccess) {
*index = 1; // PINNED memory
xpu_pinned_alloc_size_ += size;
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedAllocate);
VLOG(6) << "cudaHostAlloc succeeded. Allocated pointer: " << p;
return p;
} else {
VLOG(6) << "cudaHostAlloc failed.";
LOG(WARNING) << "cudaHostAlloc failed.";
return nullptr;
}
}
void XPUPinnedAllocator::Free(void* p, size_t size, size_t index) {
cudaError_t err;
PADDLE_ENFORCE_EQ(index,
1,
common::errors::InvalidArgument(
"The index should be 1, but got %d", index));
PADDLE_ENFORCE_GE(xpu_pinned_alloc_size_,
size,
common::errors::InvalidArgument(
"The size of memory (%d) to free exceeds the size of "
"allocated cuda pinned memory (%d)",
size,
xpu_pinned_alloc_size_));
xpu_pinned_alloc_size_ -= size;
err = cudaFreeHost(p);
// Purposefully allow cudaErrorCudartUnloading, because
// that is returned if you ever call cudaFreeHost after the
// driver has already shutdown. This happens only if the
// process is terminating, in which case we don't care if
// cudaFreeHost succeeds.
if (err != cudaErrorCudartUnloading) {
PADDLE_ENFORCE_EQ(
err,
0,
common::errors::Fatal(
"cudaFreeHost failed in XPUPinnedAllocator, error code is %d",
err));
}
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -size);
platform::RecordMemEvent(
p, CPUPlace(), size, phi::TracerMemEventType::ReservedFree);
}
bool XPUPinnedAllocator::UseGpu() const { return false; }
#endif
#ifdef PADDLE_WITH_CUSTOM_DEVICE
void* CustomAllocator::Alloc(size_t* index, size_t size) {
if (size <= 0) return nullptr;
void* p;
auto place = CustomPlace(dev_type_, dev_id_);
auto device = phi::DeviceManager::GetDeviceWithPlace(place);
p = device->MemoryAllocate(size);
if (LIKELY(p)) {
VLOG(4) << "CustomAllocator::Alloc " << p << " size " << size;
*index = 0;
plug_alloc_size += size;
DEVICE_MEMORY_STAT_UPDATE(Reserved, dev_id_, size);
platform::RecordMemEvent(
p, place, size, phi::TracerMemEventType::ReservedAllocate);
} else {
size_t avail, total;
phi::DeviceManager::MemoryStats(place, &total, &avail);
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on %s %d. "
"total memory is %s, used memory is %s, "
"available memory is only %s.\n\n",
dev_type_,
dev_id_,
string::HumanReadableSize(total),
string::HumanReadableSize(total - avail),
string::HumanReadableSize(avail)));
}
return p;
}
void CustomAllocator::Free(void* p, size_t size, size_t index) {
VLOG(4) << "CustomAllocator::Free " << p << " size " << size;
PADDLE_ENFORCE_EQ(index,
0,
common::errors::InvalidArgument(
"The index should be 0, index is %d", index));
PADDLE_ENFORCE_GE(plug_alloc_size,
size,
common::errors::InvalidArgument(
"The size of memory (%d) to free exceeds the size of "
"allocated gpu memory (%d)",
size,
plug_alloc_size));
plug_alloc_size -= size;
auto place = CustomPlace(dev_type_, dev_id_);
auto device = phi::DeviceManager::GetDeviceWithPlace(place);
device->MemoryDeallocate(p, size);
DEVICE_MEMORY_STAT_UPDATE(Reserved, dev_id_, size);
platform::RecordMemEvent(
p, place, size, phi::TracerMemEventType::ReservedFree);
}
bool CustomAllocator::UseGpu() const { return true; }
#endif
} // namespace paddle::memory::detail
@@ -0,0 +1,103 @@
/* Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#pragma once
#include <stddef.h> // for size_t
#include <string>
#include "paddle/common/macros.h"
namespace paddle {
namespace memory {
namespace detail {
/**
* \brief SystemAllocator is the parent class of CPUAllocator,
* CUDAPinnedAllocator and GPUAllocator. A BuddyAllocator
* object uses a SystemAllocator* pointing to the
* underlying system allocator.
*/
class SystemAllocator {
public:
virtual ~SystemAllocator() {}
virtual void* Alloc(size_t* index, size_t size) = 0;
virtual void Free(void* p, size_t size, size_t index) = 0;
virtual bool UseGpu() const = 0;
};
class PADDLE_API CPUAllocator : public SystemAllocator {
public:
virtual void* Alloc(size_t* index, size_t size);
virtual void Free(void* p, size_t size, size_t index);
virtual bool UseGpu() const;
};
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
class PADDLE_API GPUAllocator : public SystemAllocator {
public:
explicit GPUAllocator(int gpu_id) : gpu_id_(gpu_id) {}
virtual void* Alloc(size_t* index, size_t size);
virtual void Free(void* p, size_t size, size_t index);
virtual bool UseGpu() const;
private:
size_t gpu_alloc_size_ = 0;
int gpu_id_;
};
class PADDLE_API CUDAPinnedAllocator : public SystemAllocator {
public:
virtual void* Alloc(size_t* index, size_t size);
virtual void Free(void* p, size_t size, size_t index);
virtual bool UseGpu() const;
private:
size_t cuda_pinnd_alloc_size_ = 0;
};
#endif
#if defined(PADDLE_WITH_XPU)
class XPUPinnedAllocator : public SystemAllocator {
public:
virtual void* Alloc(size_t* index, size_t size);
virtual void Free(void* p, size_t size, size_t index);
virtual bool UseGpu() const;
private:
size_t xpu_pinned_alloc_size_ = 0;
};
#endif
#ifdef PADDLE_WITH_CUSTOM_DEVICE
class CustomAllocator : public SystemAllocator {
public:
explicit CustomAllocator(const std::string& device_type, size_t dev_id)
: dev_type_(device_type), dev_id_(dev_id) {}
virtual void* Alloc(size_t* index, size_t size);
virtual void Free(void* p, size_t size, size_t index);
virtual bool UseGpu() const;
private:
size_t plug_alloc_size = 0;
std::string dev_type_;
size_t dev_id_;
};
#endif
} // namespace detail
} // namespace memory
} // namespace paddle
@@ -0,0 +1,78 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/thread_local_allocator.h"
#include "glog/logging.h"
namespace paddle::memory::allocation {
ThreadLocalAllocatorImpl::ThreadLocalAllocatorImpl(const Place& p) : place_(p) {
if (phi::is_gpu_place(place_)) {
buddy_allocator_ = std::make_unique<memory::detail::BuddyAllocator>(
std::unique_ptr<memory::detail::SystemAllocator>(
new memory::detail::GPUAllocator(place_.device)),
platform::GpuMinChunkSize(),
platform::GpuMaxChunkSize());
} else {
PADDLE_THROW(common::errors::Unavailable(
"Thread local allocator only supports CUDAPlace now."));
}
}
std::shared_ptr<ThreadLocalAllocatorImpl> ThreadLocalCUDAAllocatorPool::Get(
int gpu_id) {
auto pos = std::distance(devices_.begin(),
std::find(devices_.begin(), devices_.end(), gpu_id));
PADDLE_ENFORCE_LT(
pos,
devices_.size(),
common::errors::InvalidArgument(
"The position of device should be less than the size of devices."));
std::call_once(*init_flags_[pos], [this, pos, gpu_id] {
platform::SetDeviceId(devices_[pos]);
allocators_[pos].reset(new ThreadLocalAllocatorImpl(GPUPlace(gpu_id)));
});
return allocators_[pos];
}
ThreadLocalCUDAAllocatorPool::ThreadLocalCUDAAllocatorPool()
: devices_(platform::GetSelectedDevices()) {
auto gpu_num = devices_.size();
allocators_.resize(gpu_num);
init_flags_.reserve(gpu_num);
for (size_t i = 0; i < gpu_num; ++i) {
init_flags_.emplace_back(new std::once_flag());
}
}
ThreadLocalAllocation* ThreadLocalAllocatorImpl::AllocateImpl(size_t size) {
VLOG(10) << "ThreadLocalAllocatorImpl::AllocateImpl " << size;
void* ptr = buddy_allocator_->Alloc(size);
auto* tl_allocation = new ThreadLocalAllocation(ptr, size, place_);
tl_allocation->SetThreadLocalAllocatorImpl(shared_from_this());
return tl_allocation;
}
void ThreadLocalAllocatorImpl::FreeImpl(ThreadLocalAllocation* allocation) {
VLOG(10) << "ThreadLocalAllocatorImpl::FreeImpl " << allocation;
buddy_allocator_->Free(allocation->ptr());
delete allocation;
}
uint64_t ThreadLocalAllocatorImpl::ReleaseImpl() {
return buddy_allocator_->Release();
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,104 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <memory>
#include <vector>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/buddy_allocator.h"
#include "paddle/phi/core/memory/allocation/system_allocator.h"
#include "paddle/phi/core/platform/device/gpu/gpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
class ThreadLocalAllocatorImpl;
class ThreadLocalAllocation : public Allocation {
public:
ThreadLocalAllocation(void* ptr, size_t size, Place place)
: Allocation(ptr, size, place) {}
void SetThreadLocalAllocatorImpl(
std::shared_ptr<ThreadLocalAllocatorImpl> allocator) {
allocator_ = allocator;
}
std::shared_ptr<ThreadLocalAllocatorImpl> GetAllocator() {
return allocator_;
}
private:
std::shared_ptr<ThreadLocalAllocatorImpl> allocator_;
};
class ThreadLocalAllocatorImpl
: public std::enable_shared_from_this<ThreadLocalAllocatorImpl> {
public:
explicit ThreadLocalAllocatorImpl(const Place& p);
ThreadLocalAllocation* AllocateImpl(size_t size);
void FreeImpl(ThreadLocalAllocation* allocation);
uint64_t ReleaseImpl();
private:
std::unique_ptr<memory::detail::BuddyAllocator> buddy_allocator_;
Place place_;
};
class ThreadLocalCUDAAllocatorPool {
public:
static ThreadLocalCUDAAllocatorPool& Instance() {
static thread_local ThreadLocalCUDAAllocatorPool pool;
return pool;
}
PADDLE_API std::shared_ptr<ThreadLocalAllocatorImpl> Get(int gpu_id);
private:
PADDLE_API ThreadLocalCUDAAllocatorPool();
std::vector<int> devices_;
std::vector<std::unique_ptr<std::once_flag>> init_flags_;
std::vector<std::shared_ptr<ThreadLocalAllocatorImpl>> allocators_;
};
class ThreadLocalCUDAAllocator : public Allocator {
public:
explicit ThreadLocalCUDAAllocator(const GPUPlace& p) : gpu_id_(p.device) {}
bool IsAllocThreadSafe() const override { return true; }
protected:
phi::Allocation* AllocateImpl(size_t size) override {
return ThreadLocalCUDAAllocatorPool::Instance().Get(gpu_id_)->AllocateImpl(
size);
}
void FreeImpl(phi::Allocation* allocation) override {
auto* tl_allocation = static_cast<ThreadLocalAllocation*>(allocation);
auto allocator_impl = tl_allocation->GetAllocator();
allocator_impl->FreeImpl(tl_allocation);
}
uint64_t ReleaseImpl(const Place& p) override {
return ThreadLocalCUDAAllocatorPool::Instance().Get(gpu_id_)->ReleaseImpl();
}
private:
int gpu_id_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,584 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/virtual_memory_auto_growth_best_fit_allocator.h"
#include <limits>
#include <mutex>
#include "glog/logging.h"
#include "paddle/common/flags.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/memory/allocation/aligned_allocator.h"
#include "paddle/phi/core/memory/allocation/cuda_virtual_mem_allocator.h"
PHI_DEFINE_EXPORTED_uint64(
vmm_small_pool_size_in_mb,
1,
"Threshold (MiB) separating the small and large pools. "
"0 disables the small pool and enables single-pool mode "
"(all requests go to the large pool). When > 0, requests "
"<= threshold use the small pool; larger requests use the "
"large pool. Default: 0.");
PHI_DEFINE_EXPORTED_uint64(vmm_small_pool_min_growth_size_in_mb,
0,
"The minimal chunk size for the small pool in MiB. "
"If small_pool_size_in_mb > 0, this overrides "
"the constructor-provided global growth size "
"(FLAGS_auto_growth_chunk_size_in_mb).");
PHI_DEFINE_EXPORTED_uint64(vmm_large_pool_min_growth_size_in_mb,
0,
"The minimal chunk size for the large pool in MiB. "
"If small_pool_size_in_mb > 0, this overrides "
"the constructor-provided global growth size "
"(FLAGS_auto_growth_chunk_size_in_mb).");
PHI_DEFINE_EXPORTED_uint64(
vmm_large_pool_pre_alloc_in_mb,
0,
"Pre-reserve this many MiB in the large pool. 0 disables pre-allocation.");
PHI_DEFINE_EXPORTED_uint64(
vmm_small_pool_pre_alloc_in_mb,
0,
"Pre-reserve this many MiB in the small pool. 0 disables pre-allocation.");
PHI_DEFINE_EXPORTED_uint64(
vmm_pre_alloc_in_mb,
0,
"Pre-reserve this many MiB in the small pool. 0 disables pre-allocation.");
PHI_DEFINE_EXPORTED_bool(
dump_vmm_allocation_info,
false,
"dump VirtualMemoryAutoGrowthBestFitAllocator's allocation info");
PHI_DEFINE_EXPORTED_bool(native_compact,
false,
"native_compact means compact memory after OOM, The "
"algorithm still needs to be upgraded.");
namespace paddle {
namespace memory {
namespace allocation {
bool NeedSplit(size_t block_size, size_t alignment, size_t alloc_size) {
return block_size > (alloc_size * 2) || (block_size - alloc_size) > alignment;
}
static BlockPart MakeBlockPart(void *ptr, size_t size, int device) {
auto chunk = std::make_shared<VmmChunkMeta>();
chunk->base = reinterpret_cast<VmmDevicePtr>(ptr);
chunk->size = size;
#ifdef PADDLE_WITH_CUDA
auto handle = CUDAVirtualMemAllocator::GetHandleFromBasePtr(ptr);
PADDLE_ENFORCE_NE(
handle,
0,
common::errors::InvalidArgument(
"Allocation returned by underlying allocator is not VMM allocation"));
chunk->handle = handle;
#else
PADDLE_THROW(common::errors::Unavailable(
"Virtual memory auto-growth allocator requires CUDA support."));
#endif
chunk->device = device;
return BlockPart{chunk, 0, size};
}
VirtualMemoryAutoGrowthBestFitAllocator::
VirtualMemoryAutoGrowthBestFitAllocator(
const std::shared_ptr<Allocator> &underlying_allocator,
size_t alignment,
const GPUPlace &place)
: underlying_allocator_(
std::make_shared<AlignedAllocator>(underlying_allocator, alignment)),
alignment_(alignment),
place_(place) {
// NOTE(liujinnan): Only support TotalMemoryCompactor strategy for now.
memory_compactor_ = std::make_unique<TotalMemoryCompactor>();
}
phi::Allocation *VirtualMemoryAutoGrowthBestFitAllocator::AllocateImpl(
size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
size = AlignedSize(size, alignment_);
auto result = AllocFromFreeBlocks(size);
if (!result) {
ExtendOrCompact(size);
result = AllocFromFreeBlocks(size);
}
return result;
}
void VirtualMemoryAutoGrowthBestFitAllocator::FreeImpl(
phi::Allocation *allocation) {
std::lock_guard<SpinLock> guard(spinlock_);
auto block_it = static_cast<BlockAllocation *>(allocation)->block_it_;
TryMergeBlock2Blocks(block_it);
delete allocation;
}
bool VirtualMemoryAutoGrowthBestFitAllocator::CollectTensorParts(
void *ptr, size_t size, std::vector<BlockPart> *parts) {
std::lock_guard<SpinLock> guard(spinlock_);
auto target_begin = reinterpret_cast<uintptr_t>(ptr);
PADDLE_ENFORCE_LE(
size,
std::numeric_limits<uintptr_t>::max() - target_begin,
common::errors::InvalidArgument(
"Invalid VMM tensor range: ptr %p plus size %zu overflows.",
ptr,
size));
auto target_end = target_begin + size;
for (const auto &block : all_blocks_) {
if (block.is_free_) {
continue;
}
auto block_begin = reinterpret_cast<uintptr_t>(block.ptr_);
auto block_end = block_begin + block.size_;
if (target_begin >= block_begin && target_end <= block_end) {
if (parts) {
*parts = SliceBlockPartsForRange(
block.parts_, target_begin - block_begin, size);
}
return true;
}
}
return false;
}
void VirtualMemoryAutoGrowthBestFitAllocator::TryMergeBlock2Blocks(
std::list<Block>::iterator block) {
if (block->ptr_ == all_blocks_.front().ptr_ &&
block->ptr_ == all_blocks_.back().ptr_) {
block->is_free_ = true;
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
} else if (block->ptr_ == all_blocks_.front().ptr_) {
auto next = std::next(block);
if (next->is_free_ &&
reinterpret_cast<uint8_t *>(block->ptr_) + block->size_ == next->ptr_) {
// merge with next
AppendBlockPartsTail(&block->parts_, &next->parts_);
block->size_ += next->size_;
block->is_free_ = true;
free_blocks_.erase(std::make_pair(next->size_, next->ptr_));
all_blocks_.erase(next);
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
} else {
block->is_free_ = true;
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
}
} else if (block->ptr_ == all_blocks_.back().ptr_) {
auto pre = std::prev(block);
if (pre->is_free_ &&
reinterpret_cast<uint8_t *>(pre->ptr_) + pre->size_ == block->ptr_) {
// merge with pre
free_blocks_.erase(std::make_pair(pre->size_, pre->ptr_));
AppendBlockPartsTail(&pre->parts_, &block->parts_);
pre->size_ += block->size_;
all_blocks_.erase(block);
free_blocks_.emplace(std::make_pair(pre->size_, pre->ptr_), pre);
} else {
block->is_free_ = true;
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
}
} else {
auto pre = std::prev(block);
auto next = std::next(block);
if (pre->is_free_ &&
reinterpret_cast<uint8_t *>(pre->ptr_) + pre->size_ == block->ptr_ &&
!(next->is_free_ &&
reinterpret_cast<uint8_t *>(block->ptr_) + block->size_ ==
next->ptr_)) {
// merge with pre
free_blocks_.erase(std::make_pair(pre->size_, pre->ptr_));
AppendBlockPartsTail(&pre->parts_, &block->parts_);
pre->size_ += block->size_;
all_blocks_.erase(block);
free_blocks_.emplace(std::make_pair(pre->size_, pre->ptr_), pre);
} else if (next->is_free_ &&
reinterpret_cast<uint8_t *>(block->ptr_) + block->size_ ==
next->ptr_ &&
!(pre->is_free_ &&
reinterpret_cast<uint8_t *>(pre->ptr_) + pre->size_ ==
block->ptr_)) {
// merge with next
block->size_ += next->size_;
block->is_free_ = true;
AppendBlockPartsTail(&block->parts_, &next->parts_);
free_blocks_.erase(std::make_pair(next->size_, next->ptr_));
all_blocks_.erase(next);
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
} else if (pre->is_free_ &&
reinterpret_cast<uint8_t *>(pre->ptr_) + pre->size_ ==
block->ptr_ &&
next->is_free_ &&
reinterpret_cast<uint8_t *>(block->ptr_) + block->size_ ==
next->ptr_) {
// merge with pre and next
free_blocks_.erase(std::make_pair(pre->size_, pre->ptr_));
free_blocks_.erase(std::make_pair(next->size_, next->ptr_));
AppendBlockPartsTail(&pre->parts_, &block->parts_);
AppendBlockPartsTail(&pre->parts_, &next->parts_);
pre->size_ += (block->size_ + next->size_);
all_blocks_.erase(block);
all_blocks_.erase(next);
free_blocks_.emplace(std::make_pair(pre->size_, pre->ptr_), pre);
} else {
block->is_free_ = true;
free_blocks_.emplace(std::make_pair(block->size_, block->ptr_), block);
}
}
}
std::optional<AllocationPtr>
VirtualMemoryAutoGrowthBestFitAllocator::AllocateOrCompact(size_t size) {
AllocationPtr allocateptr = nullptr;
// Just Allocate, no compact.
if (!FLAGS_native_compact) {
if (all_blocks_.empty()) {
allocateptr = std::move(underlying_allocator_->Allocate(size));
} else {
auto free_block = std::prev(all_blocks_.end());
if (free_block->is_free_) {
assert(free_block->size_ < size);
auto remain_size = size - free_block->size_;
VLOG(4) << " Tail free block size {" << free_block->size_
<< "} is smaller than allocate size {" << size
<< "} after compact, re-alloc {" << remain_size << "}";
allocateptr = std::move(underlying_allocator_->Allocate(remain_size));
} else {
VLOG(4) << "Tail block is not free, just allocate {" << size << "}";
allocateptr = std::move(underlying_allocator_->Allocate(size));
}
}
return allocateptr;
}
// Compact branch, try allocate and compact.
try {
allocateptr = std::move(underlying_allocator_->Allocate(size));
} catch (const paddle::memory::allocation::BadAlloc &e) {
VLOG(4) << "Do Memory Compact allocate size and compact " << size;
size_t compact_free_size = memory_compactor_->Compact(
all_blocks_, all_blocks_.front().ptr_, all_blocks_.back().ptr_);
VLOG(4) << "Memory Compacted Size: " << compact_free_size;
auto free_block = std::prev(all_blocks_.end());
if (free_block->is_free_ && free_block->size_ < size) {
auto realloc_size = size - free_block->size_;
VLOG(4) << "Free block size {" << free_block->size_
<< "} is smaller than allocate size {" << size
<< "} after compact, re-alloc {" << realloc_size << "}";
try {
auto realloc_ptr =
underlying_allocator_->Allocate(size - free_block->size_);
VLOG(4) << "Re-alloc size {" << realloc_ptr->size() << "} success";
std::vector<BlockPart> realloc_parts;
realloc_parts.emplace_back(MakeBlockPart(
realloc_ptr->ptr(), realloc_ptr->size(), place_.device));
AppendBlockPartsTail(&free_block->parts_, &realloc_parts);
free_block->size_ += realloc_ptr->size();
allocations_.push_back(std::move(realloc_ptr)); // hold allocation
} catch (const paddle::memory::allocation::BadAlloc &e) {
VLOG(4) << "Re-alloc size {" << realloc_size << "} failed";
throw;
}
}
return std::nullopt;
}
return allocateptr;
}
void VirtualMemoryAutoGrowthBestFitAllocator::ExtendOrCompact(size_t size) {
void *alloc_ptr = nullptr;
size_t alloc_size = 0;
if (FLAGS_dump_vmm_allocation_info) {
DumpInfo("===== Before ExtendOrCompact ===== request size: " +
std::to_string(size));
}
std::optional<AllocationPtr> allocate_result;
try {
allocate_result = AllocateOrCompact(size);
} catch (const paddle::memory::allocation::BadAlloc &e) {
auto stats = SumLargestFreeBlockSizes(free_blocks_.size());
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"%s\n"
"VMM allocator stats (pool): total_free=%s, max_free=%s.\n",
e.what(),
string::HumanReadableSize(stats.second),
string::HumanReadableSize(stats.first)));
}
AllocationPtr allocateptr = nullptr;
if (allocate_result.has_value()) {
allocateptr = std::move(allocate_result.value());
}
if (!allocateptr) {
// Allocate failed and Compact success branch.
free_blocks_.clear();
auto free_block = std::prev(all_blocks_.end());
if (free_block->is_free_) {
free_blocks_.emplace(std::make_pair(free_block->size_, free_block->ptr_),
free_block);
} else {
LOG(INFO) << "Dont have free block after memory compact";
}
if (FLAGS_dump_vmm_allocation_info) {
DumpInfo("===== After ExtendOrCompact do compact =====");
}
// After compact, Merge is not needed. just return.
return;
}
alloc_ptr = allocateptr->ptr();
alloc_size = allocateptr->size();
allocations_.push_back(std::move(allocateptr)); // hold allocation
std::vector<BlockPart> new_parts;
new_parts.emplace_back(MakeBlockPart(alloc_ptr, alloc_size, place_.device));
if (all_blocks_.empty()) {
all_blocks_.emplace_back(alloc_ptr, alloc_size, true);
auto it = all_blocks_.begin();
it->parts_ = std::move(new_parts);
free_blocks_.emplace(std::make_pair(alloc_size, alloc_ptr), it);
return;
}
// insert to back
auto block_it = all_blocks_.end();
block_it--;
if (block_it->is_free_ &&
reinterpret_cast<uint8_t *>(block_it->ptr_) + block_it->size_ ==
alloc_ptr) {
// merge with pre
free_blocks_.erase(std::make_pair(block_it->size_, block_it->ptr_));
block_it->size_ += alloc_size;
AppendBlockPartsTail(&block_it->parts_, &new_parts);
free_blocks_.emplace(std::make_pair(block_it->size_, block_it->ptr_),
block_it);
} else {
// do not merge
all_blocks_.emplace_back(alloc_ptr, alloc_size, true);
auto block_it = all_blocks_.end();
block_it--;
block_it->parts_ = std::move(new_parts);
free_blocks_.emplace(std::make_pair(alloc_size, alloc_ptr), block_it);
}
if (FLAGS_dump_vmm_allocation_info) {
DumpInfo("===== After ExtendOrCompact ===== request size: " +
std::to_string(size) +
" alloc size: " + std::to_string(alloc_size));
}
}
phi::Allocation *VirtualMemoryAutoGrowthBestFitAllocator::AllocFromFreeBlocks(
size_t size) {
auto iter = free_blocks_.lower_bound(std::make_pair(size, nullptr));
if (iter != free_blocks_.end()) {
std::list<Block>::iterator block_it = iter->second;
free_blocks_.erase(iter);
if (NeedSplit(block_it->size_, alignment_, size)) {
void *remaining_ptr = reinterpret_cast<uint8_t *>(block_it->ptr_) + size;
size_t remaining_size = block_it->size_ - size;
std::vector<BlockPart> alloc_parts =
SliceBlockPartsForRange(block_it->parts_, 0, size);
std::vector<BlockPart> remaining_parts =
SliceBlockPartsForRange(block_it->parts_, size, remaining_size);
block_it->size_ = size;
block_it->is_free_ = false;
block_it->parts_.swap(alloc_parts);
auto remaining_free_block = all_blocks_.insert(
std::next(block_it), Block(remaining_ptr, remaining_size, true));
remaining_free_block->parts_ = std::move(remaining_parts);
free_blocks_.emplace(std::make_pair(remaining_size, remaining_ptr),
remaining_free_block);
} else {
block_it->is_free_ = false;
}
return new BlockAllocation(block_it, place_);
}
return nullptr;
}
size_t VirtualMemoryAutoGrowthBestFitAllocator::CompactImpl(
const Place &place) {
VLOG(1) << "Do Memory Compact Manual";
size_t compact_free_size = memory_compactor_->Compact(
all_blocks_, all_blocks_.front().ptr_, all_blocks_.back().ptr_);
VLOG(1) << "Memory Compact Manual Finish Compact size: " << compact_free_size;
if (compact_free_size > 0) {
auto free_block = std::prev(all_blocks_.end());
assert(free_block->is_free_);
// remove all old free blocks and put new free block into free_blocks_.
free_blocks_.clear();
free_blocks_.emplace(std::make_pair(free_block->size_, free_block->ptr_),
free_block);
}
return compact_free_size;
}
bool VirtualMemoryAutoGrowthBestFitAllocator::TryAllocateBatch(
const std::vector<size_t> &sizes) {
auto SimulateAlloc =
[&](size_t size,
std::map<std::pair<size_t, void *>, size_t> &shadow_blocks) {
auto iter = shadow_blocks.lower_bound(std::make_pair(size, nullptr));
if (iter != shadow_blocks.end()) {
size_t block_size = iter->first.first;
void *block_ptr = iter->first.second;
shadow_blocks.erase(iter);
if (NeedSplit(block_size, alignment_, size)) {
size_t remaining_size = block_size - size;
void *remaining_ptr = reinterpret_cast<uint8_t *>(block_ptr) + size;
shadow_blocks.emplace(std::make_pair(remaining_size, remaining_ptr),
remaining_size);
}
return true;
}
return false;
};
std::lock_guard<SpinLock> guard(spinlock_);
// copy large N free_blocks_ to shadow_blocks_.
std::map<std::pair<size_t, void *>, size_t> shadow_blocks;
auto it = free_blocks_.rbegin();
for (int i = 0; i < sizes.size() && it != free_blocks_.rend(); ++i, ++it) {
shadow_blocks.emplace(it->first, it->first.first);
}
for (size_t size : sizes) {
size_t aligned_size = AlignedSize(size, alignment_);
if (!SimulateAlloc(aligned_size, shadow_blocks)) return false;
}
return true;
}
std::pair<size_t, size_t>
VirtualMemoryAutoGrowthBestFitAllocator::SumLargestFreeBlockSizes(
size_t n) const {
if (n == 0 || free_blocks_.empty()) return std::make_pair(0, 0);
size_t large_size = free_blocks_.rbegin()->first.first;
size_t total_size = 0;
size_t count = 0;
for (auto it = free_blocks_.rbegin(); it != free_blocks_.rend() && count < n;
++it, ++count) {
total_size += it->first.first;
}
return std::make_pair(large_size, total_size);
}
void VirtualMemoryAutoGrowthBestFitAllocator::DumpInfo(
std::string phase) const {
size_t total = 0, free = 0, used = 0;
std::cout << phase << std::endl;
std::cout << "All_blocks_:" << std::endl;
for (auto block = all_blocks_.begin(); block != all_blocks_.end(); ++block) {
std::ostringstream oss_used;
std::ostringstream oss_free;
if (block->is_free_) {
free += block->size_;
oss_free << "(" << block->size_ << "," << block->ptr_ << ")";
} else {
used += block->size_;
oss_used << "(" << block->size_ << "," << block->ptr_ << ","
<< block->allocation_->ptr() << ")";
}
std::cout << "is_free? " << block->is_free_ << "[" << oss_used.str()
<< "]\t[" << oss_free.str() << "]" << std::endl;
}
std::cout << total << "\t" << used << "\t" << free << std::endl;
std::cout << "Free_blocks_:" << std::endl;
for (const auto &[key, list_iter] : free_blocks_) {
auto [size, ptr] = key;
std::cout << "Size: " << size << ", Ptr: " << ptr << "\t" << list_iter->ptr_
<< std::endl;
}
}
void VirtualMemoryAutoGrowthBestFitAllocator::PreAlloc() {
auto pre_alloc_size = FLAGS_vmm_pre_alloc_in_mb << 20;
VLOG(4)
<< "Begin PreAllocate in VirtualMemoryAutoGrowthBestFitAllocator size "
<< pre_alloc_size;
PreAllocate(pre_alloc_size);
VLOG(4)
<< "Finish PreAllocate in VirtualMemoryAutoGrowthBestFitAllocator size "
<< pre_alloc_size;
}
void VirtualMemoryAutoGrowthBestFitAllocator::PreAllocate(size_t size) {
if (size <= 0) return;
ExtendOrCompact(size);
}
bool VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator::IsSmallRequest(
size_t size) {
const size_t routed_size = AlignedSize(size, alignment_);
const size_t small_pool_size = FLAGS_vmm_small_pool_size_in_mb << 20;
return routed_size < small_pool_size;
}
void VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator::PreAlloc() {
auto small_allocator =
std::dynamic_pointer_cast<VirtualMemoryAutoGrowthBestFitAllocator>(
GetSmallAllocator());
auto large_allocator =
std::dynamic_pointer_cast<VirtualMemoryAutoGrowthBestFitAllocator>(
GetLargeAllocator());
auto vmm_small_pool_pre_alloc = FLAGS_vmm_small_pool_pre_alloc_in_mb << 20;
auto vmm_large_pool_pre_alloc = FLAGS_vmm_large_pool_pre_alloc_in_mb << 20;
if (vmm_small_pool_pre_alloc > 0 && small_allocator) {
VLOG(4) << "Begin Small Pool PreAllocate in "
"VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator size "
<< vmm_small_pool_pre_alloc;
small_allocator->PreAllocate(vmm_small_pool_pre_alloc);
VLOG(4) << "Finish Small Pool PreAllocate in "
"VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator size "
<< vmm_small_pool_pre_alloc;
}
if (vmm_large_pool_pre_alloc > 0 && large_allocator) {
VLOG(4) << "Begin Large Pool PreAllocate in "
"VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator size "
<< vmm_large_pool_pre_alloc;
large_allocator->PreAllocate(vmm_large_pool_pre_alloc);
VLOG(4) << "Finish Large Pool PreAllocate in "
"VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator size "
<< vmm_large_pool_pre_alloc;
}
}
size_t VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator::CompactImpl(
const Place &place) {
auto large_allocator =
std::dynamic_pointer_cast<VirtualMemoryAutoGrowthBestFitAllocator>(
GetLargeAllocator());
VLOG(1) << "Do Memory Compact Large Pool Manual";
size_t compact_free_size = large_allocator->Compact(place);
VLOG(1) << "Memory Compact Large Pool Manual Finish Compact size: "
<< compact_free_size;
compact_size_.emplace_back(compact_free_size);
return compact_free_size;
}
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,135 @@
// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <vector>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/allocation/vmm_ipc_allocation.h"
#include "paddle/phi/core/memory/mem_utils.h"
#include "paddle/phi/core/memory/mem_visitor.h"
namespace paddle {
namespace memory {
namespace allocation {
/**
* Like AutoGrowthBestFitAllocator, VirtualMemoryAutoGrowthBestFitAllocator will
* gradually apply to GPU for video memory as the model uses more video memory.
* However, the difference is that VirtualMemoryAutoGrowthBestFitAllocator uses
* NVIDIA's virtual memory management technology and obtains the virtual memory
* address. If the video memory applied for twice is continuous, we can combine
* the two video memories later. This combination can greatly reduce
* fragmentation.
*/
class VirtualMemoryAutoGrowthBestFitAllocator : public Allocator {
public:
VirtualMemoryAutoGrowthBestFitAllocator(
const std::shared_ptr<Allocator> &underlying_allocator,
size_t alignment,
const GPUPlace &place);
std::shared_ptr<Allocator> &GetUnderLyingAllocator() {
return underlying_allocator_;
}
const std::map<std::pair<size_t, void *>, std::list<Block>::iterator>
&GetFreeBlocks() const {
return free_blocks_;
}
const std::list<Block> &GetAllBlocks() const { return all_blocks_; }
std::pair<size_t, size_t> SumLargestFreeBlockSizes(size_t n) const;
void Accept(AllocatorVisitor *visitor) override { visitor->Visit(this); }
bool IsAllocThreadSafe() const override { return true; }
void PreAlloc() override;
void PreAllocate(size_t size);
// Try to simulate an allocation, simulating a request for vector<size>.
bool TryAllocateBatch(const std::vector<size_t> &sizes);
bool CollectTensorParts(void *ptr,
size_t size,
std::vector<BlockPart> *parts);
protected:
phi::Allocation *AllocateImpl(size_t size) override;
size_t CompactImpl(const Place &place) override;
void FreeImpl(phi::Allocation *allocation) override;
private:
// AllocateOrCompact will try to allocate memory from free blocks first, if
// OOM happens, it will try to compact memory.
std::optional<AllocationPtr> AllocateOrCompact(size_t size);
phi::Allocation *AllocFromFreeBlocks(size_t size);
void ExtendOrCompact(size_t size);
void TryMergeBlock2Blocks(std::list<Block>::iterator iter);
void DumpInfo(std::string phase) const;
std::shared_ptr<Allocator> underlying_allocator_;
std::unique_ptr<MemoryCompactionStrategy> memory_compactor_;
size_t alignment_;
std::map<std::pair<size_t, void *>, std::list<Block>::iterator> free_blocks_;
std::list<Block> all_blocks_;
std::list<AllocationPtr> allocations_;
Place place_;
SpinLock spinlock_;
};
/**
* VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator is a multi-scale
* allocator that combines the virtual memory management technology of
* VirtualMemoryAutoGrowthBestFitAllocator and the multi-scale pooling strategy
* of MultiScalePoolAllocator.
*/
class VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator
: public MultiScalePoolAllocator {
public:
VirtualMemoryAutoGrowthBestFitMultiScalePoolAllocator(
const std::shared_ptr<VirtualMemoryAutoGrowthBestFitAllocator>
&small_allocator,
const std::shared_ptr<VirtualMemoryAutoGrowthBestFitAllocator>
&large_allocator,
size_t alignment,
const GPUPlace &place)
: MultiScalePoolAllocator(
small_allocator, large_allocator, alignment, place),
alignment_(alignment) {}
bool IsAllocThreadSafe() const override { return true; }
void PreAlloc() override;
void Accept(AllocatorVisitor *visitor) override { visitor->Visit(this); }
bool IsSmallRequest(size_t size) override;
std::vector<size_t> GetCompactSize() const { return compact_size_; }
protected:
size_t CompactImpl(const Place &place) override;
private:
size_t alignment_;
std::vector<size_t> compact_size_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,159 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <cstdint>
#include <memory>
#include <vector>
#if defined(PADDLE_WITH_CUDA)
#include "paddle/phi/backends/dynload/cuda_driver.h"
#endif
#include "paddle/phi/core/enforce.h"
#if defined(PADDLE_WITH_CUDA)
#include "paddle/phi/core/platform/device/gpu/gpu_types.h"
#endif
namespace paddle {
namespace memory {
namespace allocation {
#if defined(PADDLE_WITH_CUDA)
using VMMDevicePtr = CUdeviceptr;
using VMMAllocHandle = CUmemGenericAllocationHandle;
#else
using VMMDevicePtr = uintptr_t;
using VMMAllocHandle = uint64_t;
#endif
// V2 keeps the bottom-layer shared types independent from the best-fit layer
// so that CUDAVirtualMemAllocatorV2 can be reviewed and compiled separately.
enum class PoolType : uint8_t {
kSmall = 0,
kLarge = 1,
};
// Fixed-size handle metadata returned by the bottom VMM provider. Upper layers
// may later reference these handles from block-level views.
struct VMMHandleMeta {
VMMHandleMeta() = default;
VMMHandleMeta(VMMDevicePtr base,
size_t size,
VMMAllocHandle handle,
int device)
: base_(base), size_(size), handle_(handle), device_(device) {}
VMMDevicePtr base() const { return base_; }
size_t size() const { return size_; }
VMMAllocHandle handle() const { return handle_; }
int device() const { return device_; }
private:
VMMDevicePtr base_{0};
size_t size_{0};
VMMAllocHandle handle_{0};
int device_{0};
};
// HandleLayout is a lightweight allocation-level handle list returned by the
// bottom VMM provider. It is used to bootstrap upper-layer block state.
using HandleLayout = std::vector<std::shared_ptr<VMMHandleMeta>>;
enum class BlockType : uint8_t {
kActive = 0,
kFree = 1,
kUnmappedFree = 2,
};
struct BlockV2 {
static BlockV2 MakeMappedBlock(BlockType type,
void* ptr,
size_t size,
PoolType pool_type) {
BlockV2 block;
block.Reset(ptr, size, type, pool_type);
return block;
}
static BlockV2 MakeUnmappedFreeBlock(void* ptr,
size_t size,
PoolType pool_type) {
BlockV2 block;
block.Reset(ptr, size, BlockType::kUnmappedFree, pool_type);
return block;
}
bool IsActive() const { return type_ == BlockType::kActive; }
bool IsFree() const { return type_ == BlockType::kFree; }
bool IsMappedFree() const { return IsFree(); }
bool IsUnmappedFree() const { return type_ == BlockType::kUnmappedFree; }
void* ptr() const { return ptr_; }
size_t size() const { return size_; }
uint8_t* begin_ptr() const { return reinterpret_cast<uint8_t*>(ptr_); }
uint8_t* end_ptr() const { return begin_ptr() + size_; }
VMMDevicePtr begin_va() const {
return reinterpret_cast<VMMDevicePtr>(begin_ptr());
}
VMMDevicePtr end_va() const { return begin_va() + size_; }
bool IsAdjacentBefore(const BlockV2& next) const {
return end_ptr() == next.begin_ptr();
}
bool CanMergeAdjacentFreeBlock(const BlockV2& next) const {
return IsFree() && next.IsFree() && IsAdjacentBefore(next);
}
bool CanMergeAdjacentUnmappedFreeBlock(const BlockV2& next) const {
return IsUnmappedFree() && next.IsUnmappedFree() && IsAdjacentBefore(next);
}
BlockV2 MakeMappedFreeSubBlock(size_t offset, size_t len) const {
return MakeMappedBlock(
BlockType::kFree, begin_ptr() + offset, len, pool_type_);
}
BlockV2 MakeMappedActiveSubBlock(size_t offset, size_t len) const {
return MakeMappedBlock(
BlockType::kActive, begin_ptr() + offset, len, pool_type_);
}
BlockV2 MakeUnmappedFreeSubBlock(size_t offset, size_t len) const {
return MakeUnmappedFreeBlock(begin_ptr() + offset, len, pool_type_);
}
void MarkActive() { type_ = BlockType::kActive; }
void MarkFree() { type_ = BlockType::kFree; }
void Reset(void* ptr, size_t size, BlockType type, PoolType pool_type) {
ptr_ = ptr;
size_ = size;
type_ = type;
pool_type_ = pool_type;
}
void TrimToPrefix(size_t keep) { size_ = keep; }
void TrimToSuffix(size_t trim, size_t keep) {
ptr_ = reinterpret_cast<uint8_t*>(ptr_) + trim;
size_ = keep;
}
void MergeAdjacentBlock(const BlockV2& src) { size_ += src.size_; }
void MergeAdjacentUnmappedFreeBlock(const BlockV2& src) {
size_ += src.size_;
}
void* ptr_{nullptr};
size_t size_{0};
BlockType type_{BlockType::kUnmappedFree};
PoolType pool_type_{PoolType::kLarge};
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,668 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/vmm_auto_growth_best_fit_allocator_v2.h"
#if defined(PADDLE_WITH_CUDA)
#include <exception>
#include "glog/logging.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/cuda_device_guard.h"
namespace paddle {
namespace memory {
namespace allocation {
namespace {
template <typename Map, typename Key, typename Value>
void EmplaceOrEnforce(Map* map,
Key&& key,
Value&& value,
const char* map_name) {
const bool inserted =
map->try_emplace(std::forward<Key>(key), std::forward<Value>(value))
.second;
PADDLE_ENFORCE_EQ(
inserted,
true,
common::errors::AlreadyExists(
"Duplicate key inserted into %s, allocator state is inconsistent.",
map_name));
}
} // namespace
void VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::Add(
DecoratedAllocationPtr allocation) {
allocations_.emplace_back(std::move(allocation));
auto it = std::prev(allocations_.end());
auto* begin = Begin(*it);
PADDLE_ENFORCE_EQ(
allocations_by_ptr_.emplace(begin, it).second,
true,
common::errors::AlreadyExists(
"Duplicate underlying allocation base %p in VMM V2 registry.",
begin));
}
namespace {
bool RangesOverlap(void* lhs_ptr,
size_t lhs_size,
void* rhs_ptr,
size_t rhs_size) {
const auto* lhs_begin = reinterpret_cast<const uint8_t*>(lhs_ptr);
const auto* lhs_end = lhs_begin + lhs_size;
const auto* rhs_begin = reinterpret_cast<const uint8_t*>(rhs_ptr);
const auto* rhs_end = rhs_begin + rhs_size;
return lhs_end > rhs_begin && rhs_end > lhs_begin;
}
} // namespace
uint8_t* VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::Begin(
const DecoratedAllocationPtr& allocation) {
return reinterpret_cast<uint8_t*>(allocation->ptr());
}
uint8_t* VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::End(
const DecoratedAllocationPtr& allocation) {
return Begin(allocation) + allocation->size();
}
bool VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::HasOverlap(
void* ptr, size_t size) const {
auto* begin = reinterpret_cast<uint8_t*>(ptr);
auto* end = begin + size;
auto it = allocations_by_ptr_.lower_bound(begin);
if (it != allocations_by_ptr_.begin()) {
auto prev = std::prev(it);
if (End(*prev->second) > begin) {
return true;
}
}
return it != allocations_by_ptr_.end() && it->first < end;
}
bool VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::Overlaps(
void* ptr, size_t size) const {
return HasOverlap(ptr, size);
}
VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::iterator
VMMAutoGrowthBestFitAllocatorV2::UnderlyingAllocationRegistry::Erase(
iterator it) {
allocations_by_ptr_.erase(Begin(*it));
return allocations_.erase(it);
}
VMMAutoGrowthBestFitAllocatorV2::VMMAutoGrowthBestFitAllocatorV2(
const std::shared_ptr<CUDAVirtualMemAllocatorV2>& underlying_allocator,
size_t alignment,
const GPUPlace& place,
PoolType pool_type)
: underlying_allocator_(underlying_allocator),
alignment_(alignment),
place_(place),
pool_type_(pool_type) {}
phi::Allocation* VMMAutoGrowthBestFitAllocatorV2::AllocateImpl(size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
const size_t requested_size = AlignedSize(size, alignment_);
if (auto* allocation = AllocFromFreeBlocks(requested_size)) {
return allocation;
}
if (auto* allocation = AllocFromUnmappedFreeBlocks(requested_size)) {
return allocation;
}
// Tail reuse: if the last block in the address space is FREE, detach it
// and only request the difference from the underlying allocator. The
// underlying VMM provider maps new handles at a monotonically increasing
// VA cursor, so the new allocation is guaranteed to be contiguous with
// the tail FREE block.
bool has_tail_reuse = false;
size_t tail_reuse_size = 0;
BlockV2 combined_free_block;
if (!all_blocks_.empty()) {
auto tail_it = std::prev(all_blocks_.end());
if (CanIndexFreeBlock(*tail_it)) {
has_tail_reuse = true;
tail_reuse_size = tail_it->size_;
EraseFreeBlock(tail_it);
combined_free_block = std::move(*tail_it);
all_blocks_.erase(tail_it);
}
}
const size_t grow_size = (requested_size > tail_reuse_size)
? (requested_size - tail_reuse_size)
: 0;
auto restore_tail_free_block = [&] {
if (has_tail_reuse) {
auto restored_it =
all_blocks_.insert(all_blocks_.end(), std::move(combined_free_block));
InsertFreeBlock(restored_it);
}
};
// Grow: obtain a new raw allocation from the bottom VMM provider.
// If cuMemCreate fails due to physical memory exhaustion (CU error 2),
// the driver-level allocator throws EnforceNotMet. Convert it to BadAlloc so
// the outer retry path can handle it.
CUDAVirtualMemAllocatorV2::AllocationWithBlock grow_alloc;
if (grow_size > 0) {
try {
grow_alloc = underlying_allocator_->AppendWithBlock(grow_size);
} catch (const BadAlloc& bad_alloc) {
restore_tail_free_block();
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"VMM V2 best-fit allocator (pool %d) failed to grow by %zu bytes.\n"
"Underlying VMM allocation failure:\n%s",
static_cast<int>(pool_type_),
grow_size,
bad_alloc.what()));
} catch (const std::exception& e) {
restore_tail_free_block();
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"VMM V2 best-fit allocator (pool %d) failed to grow by %zu bytes.\n"
"Underlying VMM allocation exception:\n%s",
static_cast<int>(pool_type_),
grow_size,
e.what()));
} catch (...) {
restore_tail_free_block();
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"VMM V2 best-fit allocator (pool %d) failed to grow by %zu bytes "
"with an unknown underlying VMM allocation exception.",
static_cast<int>(pool_type_),
grow_size));
}
}
size_t total_new_size = tail_reuse_size;
if (grow_alloc.HasAllocation()) {
BlockV2 grow_block = AdoptBackingBlock(&grow_alloc);
total_new_size += grow_block.size_;
if (has_tail_reuse) {
combined_free_block.MergeAdjacentBlock(grow_block);
} else {
combined_free_block = std::move(grow_block);
}
}
const size_t remaining_size = total_new_size - requested_size;
BlockV2 block =
combined_free_block.MakeMappedActiveSubBlock(0, requested_size);
auto it = all_blocks_.insert(all_blocks_.end(), std::move(block));
if (remaining_size > 0) {
BlockV2 remaining_block = combined_free_block.MakeMappedFreeSubBlock(
requested_size, remaining_size);
auto remain_it =
all_blocks_.insert(std::next(it), std::move(remaining_block));
InsertFreeBlock(remain_it);
}
return new VMMAutoGrowthBestFitBlockAllocationV2(it, place_, this); // NOLINT
}
void VMMAutoGrowthBestFitAllocatorV2::FreeImpl(phi::Allocation* allocation) {
std::lock_guard<SpinLock> guard(spinlock_);
auto* wrapped_allocation =
static_cast<VMMAutoGrowthBestFitBlockAllocationV2*>(allocation);
auto it = wrapped_allocation->block_it();
PADDLE_ENFORCE_NE(
it,
all_blocks_.end(),
common::errors::NotFound("Can not find active block for allocation %p in "
"VMMAutoGrowthBestFitAllocatorV2.",
allocation->ptr()));
it->MarkFree();
TryMerge(it);
delete allocation;
}
phi::Allocation* VMMAutoGrowthBestFitAllocatorV2::AllocFromFreeBlocks(
size_t size) {
auto it = free_blocks_.lower_bound({size, nullptr});
if (it == free_blocks_.end()) {
return nullptr;
}
auto block_it = it->second;
PADDLE_ENFORCE_EQ(
CanIndexFreeBlock(*block_it),
true,
common::errors::PreconditionNotMet(
"VMM V2 free block index points to a non-reusable block."));
free_blocks_.erase(it);
if (block_it->size_ > size) {
const size_t remaining_size = block_it->size_ - size;
BlockV2 remaining_block =
block_it->MakeMappedFreeSubBlock(size, remaining_size);
block_it->TrimToPrefix(size);
auto remain_it =
all_blocks_.insert(std::next(block_it), std::move(remaining_block));
InsertFreeBlock(remain_it);
}
block_it->MarkActive();
return new // NOLINT
VMMAutoGrowthBestFitBlockAllocationV2(block_it, place_, this);
}
phi::Allocation* VMMAutoGrowthBestFitAllocatorV2::AllocFromUnmappedFreeBlocks(
size_t size) {
const size_t backing_size =
AlignedSize(size, underlying_allocator_->handle_size());
BlockListIt best = all_blocks_.end();
for (auto iter = unmapped_free_blocks_.lower_bound({backing_size, nullptr});
iter != unmapped_free_blocks_.end();) {
auto it = iter->second;
if (!it->IsUnmappedFree()) {
iter = unmapped_free_blocks_.erase(iter);
continue;
}
if (RangeOverlapsUnderlying(it->ptr_, backing_size)) {
VLOG(6) << "VMM V2 AllocFromUnmappedFreeBlocks skip ownership-overlapped "
"unmapped-free ptr="
<< it->ptr_ << " backing_size=" << backing_size
<< " block_size=" << it->size_;
++iter;
continue;
}
best = it;
break;
}
if (best == all_blocks_.end()) {
return nullptr;
}
const auto unmapped_free_ptr = best->begin_va();
VLOG(6) << "VMM V2 AllocFromUnmappedFreeBlocks ptr="
<< reinterpret_cast<void*>(unmapped_free_ptr) << " requested=" << size
<< " backing_size=" << backing_size
<< " original_unmapped_free_size=" << best->size_
<< " tail_offset=" << underlying_allocator_->tail_offset();
CUDAVirtualMemAllocatorV2::AllocationWithBlock unmapped_free_alloc;
try {
unmapped_free_alloc = underlying_allocator_->PlaceAtVAWithBlock(
unmapped_free_ptr, backing_size);
} catch (const BadAlloc&) {
// Do not mutate the allocation view if backing cannot be created in this
// unmapped-free range due to physical memory pressure. The normal grow
// path will surface the allocation failure if needed. Other exceptions
// indicate allocator state bugs and must not be hidden as a cache miss.
return nullptr;
}
BlockV2 mapped_block = AdoptBackingBlock(&unmapped_free_alloc);
PADDLE_ENFORCE_EQ(
mapped_block.size_,
backing_size,
common::errors::InvalidArgument(
"Unexpected unmapped-free backing size: got %zu, expected %zu.",
mapped_block.size_,
backing_size));
const size_t original_unmapped_free_size = best->size_;
const PoolType original_pool_type = best->pool_type_;
EraseUnmappedFreeBlock(best);
*best = mapped_block.MakeMappedActiveSubBlock(0, size);
auto insert_pos = std::next(best);
if (backing_size > size) {
BlockV2 mapped_remain =
mapped_block.MakeMappedFreeSubBlock(size, backing_size - size);
auto free_it = all_blocks_.insert(insert_pos, std::move(mapped_remain));
InsertFreeBlock(free_it);
insert_pos = std::next(free_it);
}
if (original_unmapped_free_size > backing_size) {
BlockV2 tail_unmapped_free = BlockV2::MakeUnmappedFreeBlock(
reinterpret_cast<uint8_t*>(best->ptr_) + backing_size,
original_unmapped_free_size - backing_size,
original_pool_type);
auto tail_it =
all_blocks_.insert(insert_pos, std::move(tail_unmapped_free));
InsertUnmappedFreeBlock(tail_it);
}
return new // NOLINT
VMMAutoGrowthBestFitBlockAllocationV2(best, place_, this);
}
void VMMAutoGrowthBestFitAllocatorV2::TrackUnderlyingAllocation(
DecoratedAllocationPtr allocation) {
underlying_allocations_.Add(std::move(allocation));
}
BlockV2 VMMAutoGrowthBestFitAllocatorV2::AdoptBackingBlock(
CUDAVirtualMemAllocatorV2::AllocationWithBlock* allocation_with_block) {
PADDLE_ENFORCE_NOT_NULL(
allocation_with_block,
common::errors::InvalidArgument(
"AllocationWithBlock must not be null when adopting block."));
BlockV2 block = allocation_with_block->TakeBlock();
auto allocation = static_unique_ptr_cast<Allocation>(
allocation_with_block->TakeAllocation());
TrackUnderlyingAllocation(std::move(allocation));
return block;
}
bool VMMAutoGrowthBestFitAllocatorV2::RangeOverlapsUnderlying(
void* ptr, size_t size) const {
return underlying_allocations_.Overlaps(ptr, size);
}
bool VMMAutoGrowthBestFitAllocatorV2::HasReleasableIdleUnderlying() const {
for (const auto& allocation : underlying_allocations_) {
auto* base = reinterpret_cast<uint8_t*>(allocation->ptr());
if (CanReleaseIdleUnderlying(base, allocation->size())) {
return true;
}
}
return false;
}
bool VMMAutoGrowthBestFitAllocatorV2::CanReleaseIdleUnderlying(
uint8_t* base, size_t size) const {
if (!IsRangeEntirelyFree(base, size)) {
return false;
}
return underlying_allocator_->IsRangeReleasable(
reinterpret_cast<VMMDevicePtr>(base), size);
}
bool VMMAutoGrowthBestFitAllocatorV2::TryReleaseIdleUnderlying(
UnderlyingAllocationRegistry::iterator* alloc_it, uint64_t* released) {
auto* allocation = (**alloc_it).get();
auto* base = reinterpret_cast<uint8_t*>(allocation->ptr());
const size_t alloc_size = allocation->size();
if (!CanReleaseIdleUnderlying(base, alloc_size)) {
return false;
}
ReplaceRangeWithUnmappedFree(base, alloc_size);
*released += alloc_size;
VLOG(5) << "VMM V2 pool " << static_cast<int>(pool_type_)
<< " released idle chunk: " << alloc_size << " bytes";
*alloc_it = underlying_allocations_.Erase(*alloc_it);
return true;
}
bool VMMAutoGrowthBestFitAllocatorV2::CanIndexFreeBlock(
const BlockV2& block) const {
return block.IsMappedFree();
}
void VMMAutoGrowthBestFitAllocatorV2::InsertFreeBlock(BlockListIt it) {
if (!CanIndexFreeBlock(*it)) {
return;
}
EmplaceOrEnforce(
&free_blocks_, std::make_pair(it->size_, it->ptr_), it, "free_blocks_");
}
void VMMAutoGrowthBestFitAllocatorV2::EraseFreeBlock(BlockListIt it) {
free_blocks_.erase({it->size_, it->ptr_});
}
void VMMAutoGrowthBestFitAllocatorV2::InsertUnmappedFreeBlock(BlockListIt it) {
if (!it->IsUnmappedFree()) {
return;
}
EmplaceOrEnforce(&unmapped_free_blocks_,
std::make_pair(it->size_, it->ptr_),
it,
"unmapped_free_blocks_");
}
void VMMAutoGrowthBestFitAllocatorV2::EraseUnmappedFreeBlock(BlockListIt it) {
unmapped_free_blocks_.erase({it->size_, it->ptr_});
}
void VMMAutoGrowthBestFitAllocatorV2::TryMerge(BlockListIt it) {
// Only adjacent FREE blocks are merged here. ACTIVE blocks are never touched,
// and unmapped-free blocks remain as explicit holes for later reuse.
// all_blocks_ is the full VA-ordered block list, so adjacency is checked
// against neighboring entries in that list.
if (it != all_blocks_.begin()) {
auto prev = std::prev(it);
if (prev->CanMergeAdjacentFreeBlock(*it)) {
EraseFreeBlock(prev);
prev->MergeAdjacentBlock(*it);
all_blocks_.erase(it);
it = prev;
}
}
auto next = std::next(it);
if (next != all_blocks_.end() && it->CanMergeAdjacentFreeBlock(*next)) {
EraseFreeBlock(next);
it->MergeAdjacentBlock(*next);
all_blocks_.erase(next);
}
InsertFreeBlock(it);
}
void VMMAutoGrowthBestFitAllocatorV2::TryMergeUnmappedFree(BlockListIt it) {
if (it == all_blocks_.end() || !it->IsUnmappedFree()) {
return;
}
if (it != all_blocks_.begin()) {
auto prev = std::prev(it);
if (prev->CanMergeAdjacentUnmappedFreeBlock(*it)) {
EraseUnmappedFreeBlock(prev);
EraseUnmappedFreeBlock(it);
prev->MergeAdjacentUnmappedFreeBlock(*it);
all_blocks_.erase(it);
it = prev;
InsertUnmappedFreeBlock(it);
}
}
auto next = std::next(it);
if (next != all_blocks_.end() &&
it->CanMergeAdjacentUnmappedFreeBlock(*next)) {
EraseUnmappedFreeBlock(it);
EraseUnmappedFreeBlock(next);
it->MergeAdjacentUnmappedFreeBlock(*next);
all_blocks_.erase(next);
InsertUnmappedFreeBlock(it);
}
}
// ---------------------------------------------------------------------------
// ReleaseImpl / FreeIdleChunks: release underlying allocations whose entire
// VA range is covered by FREE blocks back to the CUDA VMM driver.
//
// Because TryMerge may have merged FREE blocks across allocation boundaries,
// we must split the spanning block at the allocation edges, release the
// backing, and keep the released VA range as explicit unmapped-free space for
// later reuse.
// ---------------------------------------------------------------------------
uint64_t VMMAutoGrowthBestFitAllocatorV2::ReleaseImpl(
const Place& place UNUSED) {
std::lock_guard<SpinLock> guard(spinlock_);
if (!HasReleasableIdleUnderlying()) {
return 0;
}
// FreeIdleChunks may release CUDA VMM mappings and physical handles. Those
// driver calls are not ordered by the stream-safe wrapper, so wait before
// making any previously returned VA range invalid.
platform::CUDADeviceGuard device_guard(place_.device);
PADDLE_ENFORCE_GPU_SUCCESS(cudaDeviceSynchronize());
return FreeIdleChunks();
}
uint64_t VMMAutoGrowthBestFitAllocatorV2::FreeIdleChunks() {
uint64_t released = 0;
for (auto alloc_it = underlying_allocations_.begin();
alloc_it != underlying_allocations_.end();) {
if (!TryReleaseIdleUnderlying(&alloc_it, &released)) {
++alloc_it;
}
}
TrimTrailingUnmappedFreeBlocks();
underlying_allocator_->SetTailOffset(ComputeTailOffset());
return released;
}
void VMMAutoGrowthBestFitAllocatorV2::TrimTrailingUnmappedFreeBlocks() {
while (!all_blocks_.empty()) {
auto tail_it = std::prev(all_blocks_.end());
if (!tail_it->IsUnmappedFree() ||
underlying_allocations_.Overlaps(tail_it->ptr_, tail_it->size_)) {
break;
}
EraseUnmappedFreeBlock(tail_it);
all_blocks_.erase(tail_it);
}
}
size_t VMMAutoGrowthBestFitAllocatorV2::ComputeTailOffset() const {
for (auto it = all_blocks_.rbegin(); it != all_blocks_.rend(); ++it) {
if (it->IsUnmappedFree() &&
!underlying_allocations_.Overlaps(it->ptr_, it->size_)) {
continue;
}
return static_cast<size_t>(it->end_va() -
underlying_allocator_->virtual_mem_base());
}
return 0;
}
bool VMMAutoGrowthBestFitAllocatorV2::IsRangeEntirelyFree(uint8_t* base,
size_t size) const {
auto* end = base + size;
for (const auto& block : all_blocks_) {
auto* bptr = block.begin_ptr();
auto* bend = block.end_ptr();
if (bend <= base) continue;
if (bptr >= end) break;
if (block.IsActive()) {
return false;
}
}
// Return true when the range contains only FREE/unmapped-free blocks, or
// when blocks have already been removed by a prior FreeIdleChunks pass.
return true;
}
void VMMAutoGrowthBestFitAllocatorV2::ReplaceRangeWithUnmappedFree(
uint8_t* base, size_t size) {
auto* end = base + size;
auto erase_free_index = [this](BlockList::iterator it) {
if (it->IsUnmappedFree()) {
EraseUnmappedFreeBlock(it);
} else {
EraseFreeBlock(it);
}
};
auto insert_free_index = [this](BlockList::iterator it) {
if (it->IsUnmappedFree()) {
InsertUnmappedFreeBlock(it);
} else {
InsertFreeBlock(it);
}
};
for (auto it = all_blocks_.begin(); it != all_blocks_.end();) {
auto* bptr = it->begin_ptr();
auto* bend = it->end_ptr();
if (bend <= base) {
++it;
continue;
}
if (bptr >= end) break;
// Case 1: block entirely within [base, end): remove it.
if (bptr >= base && bend <= end) {
erase_free_index(it);
it = all_blocks_.erase(it);
continue;
}
// Case 2: block straddles left boundary only: keep left remnant.
if (bptr < base && bend <= end) {
const size_t keep = static_cast<size_t>(base - bptr);
erase_free_index(it);
it->TrimToPrefix(keep);
insert_free_index(it);
++it;
continue;
}
// Case 3: block straddles right boundary only: keep right remnant.
if (bptr >= base && bend > end) {
const size_t trim = static_cast<size_t>(end - bptr);
const size_t keep = it->size_ - trim;
erase_free_index(it);
it->TrimToSuffix(trim, keep);
insert_free_index(it);
break; // nothing more in range
}
// Case 4: block fully encompasses [base, end): split into two.
if (bptr < base && bend > end) {
const size_t left_size = static_cast<size_t>(base - bptr);
const size_t right_offset = static_cast<size_t>(end - bptr);
const size_t right_size = it->size_ - right_offset;
BlockV2 right =
it->IsUnmappedFree()
? it->MakeUnmappedFreeSubBlock(right_offset, right_size)
: it->MakeMappedFreeSubBlock(right_offset, right_size);
erase_free_index(it);
it->TrimToPrefix(left_size);
insert_free_index(it);
auto right_it = all_blocks_.insert(std::next(it), std::move(right));
insert_free_index(right_it);
break; // done
}
++it;
}
auto insert_pos = all_blocks_.begin();
while (insert_pos != all_blocks_.end() && insert_pos->begin_ptr() < base) {
++insert_pos;
}
auto unmapped_it = all_blocks_.insert(
insert_pos, BlockV2::MakeUnmappedFreeBlock(base, size, pool_type_));
InsertUnmappedFreeBlock(unmapped_it);
TryMergeUnmappedFree(unmapped_it);
}
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,142 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <functional>
#include <list>
#include <map>
#include <memory>
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/memory/allocation/cuda_virtual_mem_allocator_v2.h"
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/allocation/vmm_allocator_v2_types.h"
#include "paddle/phi/core/memory/mem_visitor.h"
#if defined(PADDLE_WITH_CUDA)
namespace paddle {
namespace memory {
namespace allocation {
using BlockList = std::list<BlockV2>;
using BlockListIt = BlockList::iterator;
class VMMAutoGrowthBestFitAllocatorV2;
class VMMAutoGrowthBestFitBlockAllocationV2 : public Allocation {
public:
VMMAutoGrowthBestFitBlockAllocationV2(BlockListIt block_it,
const Place& place,
VMMAutoGrowthBestFitAllocatorV2* owner)
: Allocation(block_it->ptr_, block_it->ptr_, block_it->size_, place),
block_it_(block_it),
owner_(owner) {}
BlockListIt block_it() const { return block_it_; }
private:
BlockListIt block_it_;
VMMAutoGrowthBestFitAllocatorV2* owner_;
};
class VMMAutoGrowthBestFitAllocatorV2 : public Allocator {
public:
VMMAutoGrowthBestFitAllocatorV2(
const std::shared_ptr<CUDAVirtualMemAllocatorV2>& underlying_allocator,
size_t alignment,
const GPUPlace& place,
PoolType pool_type);
bool IsAllocThreadSafe() const override { return true; }
void Accept(AllocatorVisitor* visitor) override { visitor->Visit(this); }
const BlockList& all_blocks() const { return all_blocks_; }
PoolType pool_type() const { return pool_type_; }
size_t alignment() const { return alignment_; }
protected:
phi::Allocation* AllocateImpl(size_t size) override;
void FreeImpl(phi::Allocation* allocation) override;
uint64_t ReleaseImpl(const Place& place) override;
private:
struct UnderlyingAllocationRegistry {
using List = std::list<DecoratedAllocationPtr>;
using iterator = List::iterator;
using OverlapPredicate = std::function<bool(const DecoratedAllocationPtr&)>;
void Add(DecoratedAllocationPtr allocation);
bool Overlaps(void* ptr, size_t size) const;
iterator begin() { return allocations_.begin(); }
iterator end() { return allocations_.end(); }
List::const_iterator begin() const { return allocations_.begin(); }
List::const_iterator end() const { return allocations_.end(); }
iterator Erase(iterator it);
private:
using Index = std::map<uint8_t*, iterator>;
static uint8_t* Begin(const DecoratedAllocationPtr& allocation);
static uint8_t* End(const DecoratedAllocationPtr& allocation);
bool HasOverlap(void* ptr, size_t size) const;
List allocations_;
Index allocations_by_ptr_;
};
phi::Allocation* AllocFromFreeBlocks(size_t size);
phi::Allocation* AllocFromUnmappedFreeBlocks(size_t size);
BlockV2 AdoptBackingBlock(
CUDAVirtualMemAllocatorV2::AllocationWithBlock* allocation_with_block);
void TrackUnderlyingAllocation(DecoratedAllocationPtr allocation);
bool RangeOverlapsUnderlying(void* ptr, size_t size) const;
bool HasReleasableIdleUnderlying() const;
bool CanReleaseIdleUnderlying(uint8_t* base, size_t size) const;
bool TryReleaseIdleUnderlying(
UnderlyingAllocationRegistry::iterator* alloc_it, uint64_t* released);
bool CanIndexFreeBlock(const BlockV2& block) const;
void InsertFreeBlock(BlockListIt it);
void EraseFreeBlock(BlockListIt it);
void InsertUnmappedFreeBlock(BlockListIt it);
void EraseUnmappedFreeBlock(BlockListIt it);
void TryMerge(BlockListIt it);
void TryMergeUnmappedFree(BlockListIt it);
uint64_t FreeIdleChunks();
void TrimTrailingUnmappedFreeBlocks();
size_t ComputeTailOffset() const;
bool IsRangeEntirelyFree(uint8_t* base, size_t size) const;
void ReplaceRangeWithUnmappedFree(uint8_t* base, size_t size);
// Best-fit V2 only grows from the fixed-handle CUDA VMM provider. The
// bottom allocator returns mapped-free BlockV2 views, while best-fit owns
// allocation/free-list policy over those block views.
std::shared_ptr<CUDAVirtualMemAllocatorV2> underlying_allocator_;
size_t alignment_;
GPUPlace place_;
PoolType pool_type_;
UnderlyingAllocationRegistry underlying_allocations_;
// Full block list ordered by VA address. This is the source of truth and
// contains ACTIVE/FREE/UNMAPPED-FREE blocks together.
BlockList all_blocks_;
std::map<std::pair<size_t, void*>, BlockListIt> free_blocks_;
std::map<std::pair<size_t, void*>, BlockListIt> unmapped_free_blocks_;
mutable SpinLock spinlock_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,330 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/vmm_backing_map.h"
#if defined(PADDLE_WITH_CUDA)
#include <algorithm>
#include <mutex>
#include "glog/logging.h"
#include "paddle/phi/core/enforce.h"
namespace paddle {
namespace memory {
namespace allocation {
namespace {
bool AddOverflow(VMMDevicePtr base, size_t size) { return base + size < base; }
bool ComputeOverlappedPages(VMMDevicePtr base,
size_t backing_size,
size_t page_size,
VMMDevicePtr va,
size_t size,
const char* context,
size_t* start,
size_t* count) {
if (size == 0 || page_size == 0 || AddOverflow(base, backing_size) ||
va < base || va + size < va || va + size > base + backing_size) {
VLOG(0) << "VMM V2 BackingMap invalid overlap range in " << context
<< ": va=" << reinterpret_cast<void*>(va) << " size=" << size
<< " base=" << reinterpret_cast<void*>(base)
<< " backing_size=" << backing_size << " page_size=" << page_size;
return false;
}
const size_t begin_offset = va - base;
const size_t end_offset = va + size - base;
*start = begin_offset / page_size;
const size_t end_page = (end_offset + page_size - 1) / page_size;
*count = end_page - *start;
return true;
}
} // namespace
void VMMBackingMap::Configure(VMMDevicePtr base,
size_t size,
size_t page_size,
int device) {
std::lock_guard<SpinLock> guard(spinlock_);
PADDLE_ENFORCE_GT(page_size,
0UL,
common::errors::InvalidArgument(
"VMM V2 BackingMap page_size must be positive."));
PADDLE_ENFORCE_EQ(
size % page_size,
0UL,
common::errors::InvalidArgument(
"VMM V2 BackingMap size %zu must be page-aligned by page_size %zu.",
size,
page_size));
if (configured_) {
if (base_ != base || size_ != size || page_size_ != page_size ||
device_ != device) {
VLOG(0) << "VMM V2 BackingMap reconfigure mismatch: old_base="
<< reinterpret_cast<void*>(base_)
<< " new_base=" << reinterpret_cast<void*>(base)
<< " old_size=" << size_ << " new_size=" << size
<< " old_page_size=" << page_size_
<< " new_page_size=" << page_size << " old_device=" << device_
<< " new_device=" << device;
}
return;
}
base_ = base;
size_ = size;
page_size_ = page_size;
device_ = device;
configured_ = true;
pages_.resize(size_ / page_size_);
mapped_page_count_ = 0;
}
bool VMMBackingMap::CheckRangeLocked(VMMDevicePtr va,
size_t size,
const char* context,
size_t* start,
size_t* count) const {
if (!configured_) {
VLOG(0) << "VMM V2 BackingMap " << context
<< " before Configure, va=" << reinterpret_cast<void*>(va)
<< " size=" << size;
return false;
}
if (size == 0 || page_size_ == 0 || size % page_size_ != 0 ||
AddOverflow(base_, size_) || va < base_ || va + size < va ||
va + size > base_ + size_ || (va - base_) % page_size_ != 0) {
VLOG(0) << "VMM V2 BackingMap invalid range in " << context
<< ": va=" << reinterpret_cast<void*>(va) << " size=" << size
<< " base=" << reinterpret_cast<void*>(base_)
<< " backing_size=" << size_ << " page_size=" << page_size_;
return false;
}
*start = (va - base_) / page_size_;
*count = size / page_size_;
return true;
}
void VMMBackingMap::MarkPageMappedLocked(
Page* page,
VMMDevicePtr page_va,
VMMAllocHandle handle,
const std::shared_ptr<VMMHandleMeta>& meta) {
PADDLE_ENFORCE_EQ(
page->mapped && handle != 0 && page->handle != handle,
false,
common::errors::PreconditionNotMet(
"VMM V2 BackingMap cannot overwrite mapped page at %p from "
"handle %p to %p.",
reinterpret_cast<void*>(page_va),
reinterpret_cast<void*>(page->handle),
reinterpret_cast<void*>(handle)));
if (!page->mapped) {
mapped_page_count_++;
}
page->handle = handle;
page->meta = meta;
page->mapped = true;
page->epoch++;
}
void VMMBackingMap::ResetPageToUnmappedLocked(Page* page) {
if (page->mapped && mapped_page_count_ > 0) {
mapped_page_count_--;
}
page->handle = 0;
page->meta.reset();
page->mapped = false;
page->epoch++;
}
void VMMBackingMap::MarkMapped(VMMDevicePtr va,
VMMAllocHandle handle,
size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "MarkMapped(handle)", &start, &count)) {
return;
}
for (size_t i = 0; i < count; ++i) {
auto& page = pages_[start + i];
MarkPageMappedLocked(
&page, va + i * page_size_, handle, std::shared_ptr<VMMHandleMeta>());
}
}
void VMMBackingMap::MarkMapped(VMMDevicePtr va,
const std::shared_ptr<VMMHandleMeta>& meta,
size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "MarkMapped", &start, &count)) {
return;
}
const VMMAllocHandle handle =
meta == nullptr ? static_cast<VMMAllocHandle>(0) : meta->handle();
for (size_t i = 0; i < count; ++i) {
auto& page = pages_[start + i];
MarkPageMappedLocked(&page, va + i * page_size_, handle, meta);
}
}
void VMMBackingMap::MarkUnmapped(VMMDevicePtr va, size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "MarkUnmapped", &start, &count)) {
return;
}
for (size_t i = 0; i < count; ++i) {
auto& page = pages_[start + i];
if (!page.mapped) {
VLOG(5) << "VMM V2 BackingMap unmapping already-unmapped page at "
<< reinterpret_cast<void*>(va + i * page_size_);
}
ResetPageToUnmappedLocked(&page);
}
}
void VMMBackingMap::MarkReleased(VMMDevicePtr va,
VMMAllocHandle handle,
size_t size) {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "MarkReleased", &start, &count)) {
return;
}
for (size_t i = 0; i < count; ++i) {
auto& page = pages_[start + i];
if (handle != 0 && page.handle != 0 && page.handle != handle) {
VLOG(0) << "VMM V2 BackingMap release handle mismatch at "
<< reinterpret_cast<void*>(va + i * page_size_)
<< " tracked=" << reinterpret_cast<void*>(page.handle)
<< " released=" << reinterpret_cast<void*>(handle);
}
ResetPageToUnmappedLocked(&page);
}
}
bool VMMBackingMap::ValidateLayout(const HandleLayout& layout,
const char* context) const {
std::lock_guard<SpinLock> guard(spinlock_);
bool ok = true;
for (const auto& meta : layout) {
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(
meta->base(), meta->size(), context, &start, &count)) {
ok = false;
continue;
}
for (size_t i = 0; i < count; ++i) {
const auto& page = pages_[start + i];
if (!page.mapped) {
VLOG(0) << "VMM V2 BackingMap mapped-state mismatch in " << context
<< " va="
<< reinterpret_cast<void*>(meta->base() + i * page_size_)
<< " tracked_mapped=" << page.mapped;
ok = false;
}
if (page.mapped && page.handle != meta->handle()) {
VLOG(0) << "VMM V2 BackingMap handle mismatch in " << context << " va="
<< reinterpret_cast<void*>(meta->base() + i * page_size_)
<< " tracked=" << reinterpret_cast<void*>(page.handle)
<< " meta=" << reinterpret_cast<void*>(meta->handle());
ok = false;
}
}
}
return ok;
}
bool VMMBackingMap::IsRangeMapped(VMMDevicePtr va, size_t size) const {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "IsRangeMapped", &start, &count)) {
return false;
}
for (size_t i = 0; i < count; ++i) {
if (!pages_[start + i].mapped) {
return false;
}
}
return true;
}
bool VMMBackingMap::IsRangeUnmapped(VMMDevicePtr va, size_t size) const {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!CheckRangeLocked(va, size, "IsRangeUnmapped", &start, &count)) {
return false;
}
for (size_t i = 0; i < count; ++i) {
if (pages_[start + i].mapped) {
return false;
}
}
return true;
}
bool VMMBackingMap::IsRangeReleasable(VMMDevicePtr va, size_t size) const {
std::lock_guard<SpinLock> guard(spinlock_);
size_t start = 0;
size_t count = 0;
if (!ComputeOverlappedPages(base_,
size_,
page_size_,
va,
size,
"IsRangeReleasable",
&start,
&count)) {
return false;
}
for (size_t i = 0; i < count; ++i) {
if (!PageCanUseBackingLocked(&pages_[start + i], "IsRangeReleasable")) {
return false;
}
}
return true;
}
bool VMMBackingMap::PageCanUseBackingLocked(Page* page,
const char* context) const {
if (page == nullptr || !page->mapped || page->meta == nullptr) {
VLOG(6) << "VMM V2 BackingMap page cannot use backing in " << context;
return false;
}
return true;
}
size_t VMMBackingMap::total_mapped_bytes() const {
std::lock_guard<SpinLock> guard(spinlock_);
return mapped_page_count_ * page_size_;
}
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,96 @@
// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#if defined(PADDLE_WITH_CUDA)
#include <cstddef>
#include <cstdint>
#include <utility>
#include <vector>
#include "paddle/phi/core/memory/allocation/spin_lock.h"
#include "paddle/phi/core/memory/allocation/vmm_allocator_v2_types.h"
namespace paddle {
namespace memory {
namespace allocation {
// Page-granular backing state for VMM V2. Allocation blocks keep only logical
// VA layout; backing ownership and release safety are decided from this map.
class VMMBackingMap {
public:
struct MappedPage {
VMMDevicePtr va{0};
VMMAllocHandle handle{0};
std::shared_ptr<VMMHandleMeta> meta;
uint64_t epoch{0};
};
struct UnmappedPage {
VMMDevicePtr va{0};
uint64_t epoch{0};
};
void Configure(VMMDevicePtr base, size_t size, size_t page_size, int device);
bool IsConfigured() const { return configured_; }
void MarkMapped(VMMDevicePtr va, VMMAllocHandle handle, size_t size);
void MarkMapped(VMMDevicePtr va,
const std::shared_ptr<VMMHandleMeta>& meta,
size_t size);
void MarkUnmapped(VMMDevicePtr va, size_t size);
void MarkReleased(VMMDevicePtr va, VMMAllocHandle handle, size_t size);
bool ValidateLayout(const HandleLayout& layout, const char* context) const;
bool IsRangeMapped(VMMDevicePtr va, size_t size) const;
bool IsRangeUnmapped(VMMDevicePtr va, size_t size) const;
bool IsRangeReleasable(VMMDevicePtr va, size_t size) const;
size_t total_mapped_bytes() const;
private:
struct Page {
VMMAllocHandle handle{0};
std::shared_ptr<VMMHandleMeta> meta;
bool mapped{false};
uint64_t epoch{0};
};
bool CheckRangeLocked(VMMDevicePtr va,
size_t size,
const char* context,
size_t* start,
size_t* count) const;
void MarkPageMappedLocked(Page* page,
VMMDevicePtr page_va,
VMMAllocHandle handle,
const std::shared_ptr<VMMHandleMeta>& meta);
void ResetPageToUnmappedLocked(Page* page);
bool PageCanUseBackingLocked(Page* page, const char* context) const;
VMMDevicePtr base_{0};
size_t size_{0};
size_t page_size_{0};
int device_{-1};
bool configured_{false};
mutable std::vector<Page> pages_;
size_t mapped_page_count_{0};
mutable SpinLock spinlock_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif
@@ -0,0 +1,221 @@
// Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
#if defined(PADDLE_WITH_CUDA)
#include "paddle/phi/backends/dynload/cuda_driver.h"
using VmmDevicePtr = CUdeviceptr;
using VmmAllocHandle = CUmemGenericAllocationHandle;
#else
using VmmDevicePtr = uintptr_t;
using VmmAllocHandle = uint64_t;
#endif
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
struct ImportedVmmMulti {
VmmDevicePtr base{0};
size_t reserved_size{0};
std::vector<VmmAllocHandle> hs;
#if defined(PADDLE_WITH_CUDA)
~ImportedVmmMulti() {
if (base && reserved_size) {
phi::dynload::cuMemUnmap(base, reserved_size);
}
for (auto h : hs) {
if (h) phi::dynload::cuMemRelease(h);
}
if (base && reserved_size) {
phi::dynload::cuMemAddressFree(base, reserved_size);
}
}
#else
~ImportedVmmMulti() = default;
#endif
};
class VmmImportedAllocation : public phi::Allocation {
public:
VmmImportedAllocation(void* ptr,
size_t bytes,
Place place,
std::shared_ptr<ImportedVmmMulti> keep)
: Allocation(ptr, bytes, place), keep_(std::move(keep)) {}
private:
std::shared_ptr<ImportedVmmMulti> keep_;
};
struct VmmChunkMeta {
VmmDevicePtr base;
size_t size;
VmmAllocHandle handle;
int device;
};
struct BlockPart {
std::shared_ptr<VmmChunkMeta> chunk;
size_t chunk_rel_off;
size_t len;
};
inline bool TryConcatAdjacentBlockPart(BlockPart* a, const BlockPart& b) {
if (!a) return false;
if (a->chunk.get() != b.chunk.get()) return false;
if (a->chunk_rel_off + a->len != b.chunk_rel_off) return false;
a->len += b.len;
return true;
}
inline std::vector<BlockPart> SliceBlockPartsForRange(
const std::vector<BlockPart>& parts,
size_t range_offset,
size_t range_len) {
// parts describes one logical block as an ordered list of VMM chunk slices.
// The target range is expressed in that logical block address space.
std::vector<BlockPart> sliced_parts;
if (range_len == 0 || parts.empty()) {
return sliced_parts;
}
PADDLE_ENFORCE_LE(
range_offset,
std::numeric_limits<size_t>::max() - range_len,
common::errors::InvalidArgument(
"Invalid VMM block-part slice range: offset %zu plus length %zu "
"overflows.",
range_offset,
range_len));
if (parts.size() == 1) {
const auto& part = parts.front();
PADDLE_ENFORCE_LE(
range_offset,
part.len,
common::errors::InvalidArgument(
"Invalid VMM block-part slice offset %zu for part length %zu.",
range_offset,
part.len));
PADDLE_ENFORCE_LE(
range_len,
part.len - range_offset,
common::errors::InvalidArgument(
"Invalid VMM block-part slice length %zu at offset %zu for part "
"length %zu.",
range_len,
range_offset,
part.len));
return {
BlockPart{part.chunk, part.chunk_rel_off + range_offset, range_len}};
}
sliced_parts.reserve(parts.size());
const size_t range_end = range_offset + range_len;
size_t cursor = 0;
size_t sliced_len = 0;
for (const auto& part : parts) {
const size_t part_block_begin = cursor;
const size_t part_block_end = cursor + part.len;
cursor = part_block_end;
if (part_block_end <= range_offset) {
continue;
}
if (part_block_begin >= range_end) {
break;
}
const size_t slice_begin = std::max(part_block_begin, range_offset);
const size_t slice_end = std::min(part_block_end, range_end);
BlockPart slice{part.chunk,
part.chunk_rel_off + (slice_begin - part_block_begin),
slice_end - slice_begin};
if (!sliced_parts.empty() &&
TryConcatAdjacentBlockPart(&sliced_parts.back(), slice)) {
sliced_len += slice.len;
continue;
}
sliced_parts.push_back(std::move(slice));
sliced_len += sliced_parts.back().len;
}
PADDLE_ENFORCE_EQ(
sliced_len,
range_len,
common::errors::InvalidArgument(
"Invalid VMM block-part slice range: requested %zu bytes at offset "
"%zu, but only sliced %zu bytes from %zu parts.",
range_len,
range_offset,
sliced_len,
parts.size()));
return sliced_parts;
}
inline void AppendBlockPartsTail(std::vector<BlockPart>* dst,
std::vector<BlockPart>* src) {
if (src->empty()) return;
dst->reserve(dst->size() + src->size());
auto begin = src->begin();
if (!dst->empty() && TryConcatAdjacentBlockPart(&dst->back(), src->front())) {
++begin;
}
dst->insert(dst->end(),
std::make_move_iterator(begin),
std::make_move_iterator(src->end()));
}
#pragma pack(push, 1)
struct VmmIpcHeader {
uint8_t version;
uint16_t flags;
uint32_t pid;
uint32_t num_entries;
uint64_t alloc_size;
uint64_t offset;
uint64_t reserved_size;
};
struct VmmIpcEntry {
uint8_t handle_type;
uint8_t reserved[7];
uint64_t rel_offset;
uint64_t chunk_size;
uint64_t chunk_rel_off;
};
#pragma pack(pop)
static_assert(sizeof(VmmIpcHeader) == 35, "VmmIpcHeader size changed");
static_assert(sizeof(VmmIpcEntry) == 32, "VmmIpcEntry size changed");
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,57 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/xpu_allocator.h"
#include <string>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
bool XPUAllocator::IsAllocThreadSafe() const { return true; }
void XPUAllocator::FreeImpl(phi::Allocation* allocation) {
PADDLE_ENFORCE_EQ(
allocation->place(),
place_,
common::errors::PermissionDenied(
"XPU memory is freed in incorrect device. This may be a bug"));
platform::RecordedXPUFree(
allocation->ptr(), allocation->size(), place_.device);
delete allocation;
}
phi::Allocation* XPUAllocator::AllocateImpl(size_t size) {
std::call_once(once_flag_,
[this] { platform::SetXPUDeviceId(place_.device); });
void* ptr;
auto result = platform::RecordedXPUMalloc(&ptr, size, place_.device);
if (LIKELY(result == XPU_SUCCESS)) {
return new Allocation(ptr, size, Place(place_));
}
PADDLE_THROW_BAD_ALLOC(common::errors::ResourceExhausted(
"\n\nOut of memory error on XPU %d. "
"Cannot allocate %s memory on XPU %d.\n\n",
place_.device,
string::HumanReadableSize(size),
place_.device));
}
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,42 @@
// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mutex> // NOLINT
#include "paddle/phi/common/place.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
class XPUAllocator : public Allocator {
public:
explicit XPUAllocator(const XPUPlace& place) : place_(place) {}
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
private:
XPUPlace place_;
std::once_flag once_flag_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
@@ -0,0 +1,109 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
#include "paddle/phi/core/memory/allocation/xpu_ipc_allocator.h"
#include <cuda.h>
#include <cuda_runtime.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstdlib>
#include <random>
#include <string>
#include "glog/logging.h"
#include "paddle/phi/backends/xpu/enforce_xpu.h"
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
namespace paddle::memory::allocation {
namespace {
// Mutex to protect IPC handle cache.
std::mutex ipc_mutex_;
// Cache mapping from handle string to a weak pointer of the opened IPC memory.
std::unordered_map<std::string, std::weak_ptr<void>> ipc_handle_to_baseptr_;
} // namespace
std::shared_ptr<void> GetIpcBasePtr(std::string handle) {
std::lock_guard<std::mutex> lock(ipc_mutex_);
// Get the current device ID.
int device_id = platform::GetXPUCurrentDeviceId();
paddle::platform::SetXPUDeviceId(device_id);
auto iter = ipc_handle_to_baseptr_.find(handle);
if (iter != ipc_handle_to_baseptr_.end()) {
if (auto baseptr = iter->second.lock()) {
return baseptr;
}
}
// The IPC memory handle can only be opened once for the same handle,
// so we cache the opened pointer.
void *baseptr = nullptr;
// Interpret the provided handle string as an XPU IPC memory handle.
auto ipc_handle =
reinterpret_cast<const cudaIpcMemHandle_t *>(handle.c_str());
// PADDLE_ENFORCE_XPU_SUCCESS(cudaIpcOpenMemHandle(&baseptr, *ipc_handle,
// cudaIpcMemLazyEnablePeerAccess));
int ret = cudaIpcOpenMemHandle(
&baseptr, *ipc_handle, cudaIpcMemLazyEnablePeerAccess);
PADDLE_ENFORCE_XPU_SUCCESS(ret);
// Create a shared_ptr with a custom deleter that will close the IPC handle.
auto sp = std::shared_ptr<void>(baseptr, [handle, device_id](void *ptr) {
platform::XPUDeviceGuard guard(device_id);
std::lock_guard<std::mutex> lock(ipc_mutex_);
PADDLE_ENFORCE_XPU_SUCCESS(cudaIpcCloseMemHandle(ptr));
ipc_handle_to_baseptr_.erase(handle);
VLOG(6) << "cudaIpcCloseMemHandle for ptr:"
<< "\t" << ptr;
});
std::weak_ptr<void> wp = sp;
ipc_handle_to_baseptr_.insert({handle, wp});
return sp;
}
void IpcCollect() {
std::lock_guard<std::mutex> lock(ipc_mutex_);
size_t before = ipc_handle_to_baseptr_.size();
VLOG(6) << "The number of IPC handles before collection:" << before;
for (auto it = ipc_handle_to_baseptr_.begin();
it != ipc_handle_to_baseptr_.end();) {
if (it->second.expired()) {
it = ipc_handle_to_baseptr_.erase(it);
} else {
VLOG(6) << " Valid ipc handle is not expired";
++it;
}
}
size_t after = ipc_handle_to_baseptr_.size();
size_t collected = before - after;
VLOG(1) << "IpcCollect: collected " << collected << " expired IPC handles"
<< "out of " << before << " total handles";
}
XpuIpcAllocation::~XpuIpcAllocation() {
// Release the underlying IPC resource.
shared_ptr_.reset();
VLOG(6) << "tensor deleted cudaIpcCloseMemHandle for ptr:"
<< "\t" << this->ptr();
}
} // namespace paddle::memory::allocation
#endif // _WIN32
@@ -0,0 +1,59 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef _WIN32
#pragma once
#include <memory>
#include <mutex> // NOLINT
#include <string>
#include <unordered_set>
#include <utility>
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/memory/allocation/allocator.h"
#include "paddle/phi/core/platform/device/xpu/xpu_info.h"
namespace paddle {
namespace memory {
namespace allocation {
// Returns a shared pointer that holds the IPC base pointer for the given
// handle.
std::shared_ptr<void> GetIpcBasePtr(std::string handle);
void IpcCollect();
class XpuIpcAllocation : public Allocation {
public:
explicit XpuIpcAllocation(void *ptr,
size_t size,
int device_id,
std::shared_ptr<void> shared_ptr)
: Allocation(ptr, size, XPUPlace(device_id)),
device_id_(device_id),
shared_ptr_(std::move(shared_ptr)) {}
inline const int &device_id() const { return device_id_; }
~XpuIpcAllocation() override;
private:
int device_id_;
std::shared_ptr<void> shared_ptr_;
};
} // namespace allocation
} // namespace memory
} // namespace paddle
#endif // _WIN32
@@ -0,0 +1,66 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "paddle/phi/core/memory/allocation/xpu_pinned_allocator.h"
#include "paddle/phi/core/memory/stats.h"
#include "paddle/phi/core/platform/profiler/mem_tracing.h"
#if defined(PADDLE_WITH_XPU)
#include <cuda.h>
#include <cuda_runtime.h>
#include "paddle/phi/backends/xpu/enforce_xpu.h"
#endif
namespace paddle::memory::allocation {
// Define the destructor so the vtable gets emitted.
XPUPinnedAllocator::~XPUPinnedAllocator() = default;
bool XPUPinnedAllocator::IsAllocThreadSafe() const { return true; }
void XPUPinnedAllocator::FreeImpl(phi::Allocation* allocation) {
#if defined(PADDLE_WITH_XPU)
PADDLE_ENFORCE_XPU_SUCCESS(cudaFreeHost(allocation->ptr()));
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported. Please re-compile with WITH_XPU."));
#endif
VLOG(10) << "cudaFreeHost " << allocation->ptr();
HOST_MEMORY_STAT_UPDATE(Reserved, 0, -allocation->size());
platform::RecordMemEvent(allocation->ptr(),
allocation->place(),
allocation->size(),
phi::TracerMemEventType::ReservedFree);
delete allocation;
}
phi::Allocation* XPUPinnedAllocator::AllocateImpl(size_t size) {
void* ptr;
#if defined(PADDLE_WITH_XPU)
PADDLE_ENFORCE_XPU_SUCCESS(cudaHostAlloc(&ptr, size, cudaHostAllocPortable));
#else
PADDLE_THROW(common::errors::PermissionDenied(
"'XPUPinnedPlace' is not supported. Please re-compile with WITH_XPU."));
#endif
VLOG(10) << "cudaHostAlloc " << size << " " << ptr;
HOST_MEMORY_STAT_UPDATE(Reserved, 0, size);
platform::RecordMemEvent(
ptr, XPUPinnedPlace(), size, phi::TracerMemEventType::ReservedAllocate);
return new Allocation(ptr, size, XPUPinnedPlace());
}
} // namespace paddle::memory::allocation
@@ -0,0 +1,37 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/core/memory/allocation/allocator.h"
namespace paddle {
namespace memory {
namespace allocation {
// Allocator uses `cudaHostAlloc`
class XPUPinnedAllocator : public Allocator {
public:
// Add an explicit virtual destructor
virtual ~XPUPinnedAllocator();
bool IsAllocThreadSafe() const override;
protected:
void FreeImpl(phi::Allocation* allocation) override;
phi::Allocation* AllocateImpl(size_t size) override;
};
} // namespace allocation
} // namespace memory
} // namespace paddle