Skip to content

Commit

Permalink
[SYCL] Revert USM caching to pre-2021.3 state and disable Shared USM … (
Browse files Browse the repository at this point in the history
#4799)

This change a) enhances the env var controls for USM caching, b) sets caching parameters to match the previous implementation and c) disables Shared memory chunking to fix a data race issue.
  • Loading branch information
rdeodhar authored Oct 31, 2021
1 parent 55be63a commit a0342b3
Show file tree
Hide file tree
Showing 5 changed files with 631 additions and 151 deletions.
2 changes: 1 addition & 1 deletion sycl/doc/EnvironmentVariables.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ variables in production code.</span>
| Environment variable | Values | Description |
| -------------------- | ------ | ----------- |
| `SYCL_PI_LEVEL_ZERO_MAX_COMMAND_LIST_CACHE` | Positive integer | Maximum number of oneAPI Level Zero Command lists that can be allocated with no reuse before throwing an "out of resources" error. Default is 20000, threshold may be increased based on resource availabilty and workload demand. |
| `SYCL_PI_LEVEL_ZERO_USM_ALLOCATOR` | MaxPoolableSize,Capacity,MaxPoolSize | Values specified as positive integers. Defaults are 1, 4, 256. MaxPoolableSize is the maximum allocation size in MB that may be pooled. Capacity is the number of allocations in each size range that are freed by the program but retained in the pool for reallocation. Size ranges follow this pattern: 32, 48, 64, 96, 128, 192, and so on, i.e., powers of 2, with one range in between. MaxPoolSize is the maximum size of the pool in MB. |
| `SYCL_PI_LEVEL_ZERO_USM_ALLOCATOR` | EnableBuffers, MaxPoolSize [, MemType, MaxPoolableSize, Capacity, SlabMinSize]... | EnableBuffers enables pooling for SYCL buffers, default false. MaxPoolSize is the maximum size of the pool, default 0. MemType is host, device or shared. Other parameters are values specified as positive integers with optional K, M or G suffix. MaxPoolableSize is the maximum allocation size that may be pooled, default 0 for host and shared, 32KB for device. Capacity is the number of allocations in each size range freed by the program but retained in the pool for reallocation, default 0. Size ranges follow this pattern: 64, 96, 128, 192, and so on, i.e., powers of 2, with one range in between. SlabMinSize is the minimum allocation size, 64KB for host and device, 2MB for shared. |
| `SYCL_PI_LEVEL_ZERO_BATCH_SIZE` | Integer | Sets a preferred number of commands to batch into a command list before executing the command list. A value of 0 causes the batch size to be adjusted dynamically. A value greater than 0 specifies fixed size batching, with the batch size set to the specified value. The default is 0. |
| `SYCL_PI_LEVEL_ZERO_FILTER_EVENT_WAIT_LIST` | Integer | When set to 0, disables filtering of signaled events from wait lists when using the Level Zero backend. The default is 1. |
| `SYCL_PI_LEVEL_ZERO_USE_COPY_ENGINE` | Any(\*) | This environment variable enables users to control use of copy engines for copy operations. If the value is an integer, it will allow the use of copy engines, if available in the device, in Level Zero plugin to transfer SYCL buffer or image data between the host and/or device(s) and to fill SYCL buffer or image data in device or shared memory. The value of this environment variable can also be a pair of the form "lower_index:upper_index" where the indices point to copy engines in a list of all available copy engines. The default is 1. |
Expand Down
163 changes: 139 additions & 24 deletions sycl/plugins/level_zero/pi_level_zero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3032,6 +3032,74 @@ pi_result piextQueueCreateWithNativeHandle(pi_native_handle NativeHandle,
return PI_SUCCESS;
}

// If indirect access tracking is enabled then performs reference counting,
// otherwise just calls zeMemAllocDevice.
static pi_result ZeDeviceMemAllocHelper(void **ResultPtr, pi_context Context,
pi_device Device, size_t Size) {
pi_platform Plt = Device->Platform;
std::unique_lock<std::mutex> ContextsLock(Plt->ContextsMutex,
std::defer_lock);
if (IndirectAccessTrackingEnabled) {
// Lock the mutex which is guarding contexts container in the platform.
// This prevents new kernels from being submitted in any context while
// we are in the process of allocating a memory, this is needed to
// properly capture allocations by kernels with indirect access.
ContextsLock.lock();
// We are going to defer memory release if there are kernels with
// indirect access, that is why explicitly retain context to be sure
// that it is released after all memory allocations in this context are
// released.
PI_CALL(piContextRetain(Context));
}

ze_device_mem_alloc_desc_t ZeDesc = {};
ZeDesc.flags = 0;
ZeDesc.ordinal = 0;
ZE_CALL(zeMemAllocDevice,
(Context->ZeContext, &ZeDesc, Size, 1, Device->ZeDevice, ResultPtr));

if (IndirectAccessTrackingEnabled) {
// Keep track of all memory allocations in the context
Context->MemAllocs.emplace(std::piecewise_construct,
std::forward_as_tuple(*ResultPtr),
std::forward_as_tuple(Context));
}
return PI_SUCCESS;
}

// If indirect access tracking is enabled then performs reference counting,
// otherwise just calls zeMemAllocHost.
static pi_result ZeHostMemAllocHelper(void **ResultPtr, pi_context Context,
size_t Size) {
pi_platform Plt = Context->Devices[0]->Platform;
std::unique_lock<std::mutex> ContextsLock(Plt->ContextsMutex,
std::defer_lock);
if (IndirectAccessTrackingEnabled) {
// Lock the mutex which is guarding contexts container in the platform.
// This prevents new kernels from being submitted in any context while
// we are in the process of allocating a memory, this is needed to
// properly capture allocations by kernels with indirect access.
ContextsLock.lock();
// We are going to defer memory release if there are kernels with
// indirect access, that is why explicitly retain context to be sure
// that it is released after all memory allocations in this context are
// released.
PI_CALL(piContextRetain(Context));
}

ze_host_mem_alloc_desc_t ZeDesc = {};
ZeDesc.flags = 0;
ZE_CALL(zeMemAllocHost, (Context->ZeContext, &ZeDesc, Size, 1, ResultPtr));

if (IndirectAccessTrackingEnabled) {
// Keep track of all memory allocations in the context
Context->MemAllocs.emplace(std::piecewise_construct,
std::forward_as_tuple(*ResultPtr),
std::forward_as_tuple(Context));
}
return PI_SUCCESS;
}

pi_result piMemBufferCreate(pi_context Context, pi_mem_flags Flags, size_t Size,
void *HostPtr, pi_mem *RetMem,
const pi_mem_properties *properties) {
Expand Down Expand Up @@ -3091,23 +3159,35 @@ pi_result piMemBufferCreate(pi_context Context, pi_mem_flags Flags, size_t Size,

pi_result Result;
if (DeviceIsIntegrated) {
Result = piextUSMHostAlloc(&Ptr, Context, nullptr, Size, Alignment);
if (enableBufferPooling())
Result = piextUSMHostAlloc(&Ptr, Context, nullptr, Size, Alignment);
else {
ZeHostMemAllocHelper(&Ptr, Context, Size);
}
} else if (Context->SingleRootDevice) {
// If we have a single discrete device or all devices in the context are
// sub-devices of the same device then we can allocate on device
Result = piextUSMDeviceAlloc(&Ptr, Context, Context->SingleRootDevice,
nullptr, Size, Alignment);
if (enableBufferPooling())
Result = piextUSMDeviceAlloc(&Ptr, Context, Context->SingleRootDevice,
nullptr, Size, Alignment);
else {
ZeDeviceMemAllocHelper(&Ptr, Context, Context->SingleRootDevice, Size);
}
} else {
// Context with several gpu cards. Temporarily use host allocation because
// it is accessible by all devices. But it is not good in terms of
// performance.
// TODO: We need to either allow remote access to device memory using IPC,
// or do explicit memory transfers from one device to another using host
// resources as backing buffers to allow those transfers.
Result = piextUSMHostAlloc(&Ptr, Context, nullptr, Size, Alignment);
if (enableBufferPooling())
Result = piextUSMHostAlloc(&Ptr, Context, nullptr, Size, Alignment);
else {
ZeHostMemAllocHelper(&Ptr, Context, Size);
}
}

if (Result != PI_SUCCESS)
if (enableBufferPooling() && Result != PI_SUCCESS)
return Result;

if (HostPtr) {
Expand Down Expand Up @@ -3170,6 +3250,37 @@ pi_result piMemRetain(pi_mem Mem) {
return PI_SUCCESS;
}

// If indirect access tracking is not enabled then this functions just performs
// zeMemFree. If indirect access tracking is enabled then reference counting is
// performed.
static pi_result ZeMemFreeHelper(pi_context Context, void *Ptr) {
pi_platform Plt = Context->Devices[0]->Platform;
std::unique_lock<std::mutex> ContextsLock(Plt->ContextsMutex,
std::defer_lock);
if (IndirectAccessTrackingEnabled) {
ContextsLock.lock();
auto It = Context->MemAllocs.find(Ptr);
if (It == std::end(Context->MemAllocs)) {
die("All memory allocations must be tracked!");
}
if (--(It->second.RefCount) != 0) {
// Memory can't be deallocated yet.
return PI_SUCCESS;
}

// Reference count is zero, it is ok to free memory.
// We don't need to track this allocation anymore.
Context->MemAllocs.erase(It);
}

ZE_CALL(zeMemFree, (Context->ZeContext, Ptr));

if (IndirectAccessTrackingEnabled)
PI_CALL(ContextReleaseHelper(Context));

return PI_SUCCESS;
}

pi_result piMemRelease(pi_mem Mem) {
PI_ASSERT(Mem, PI_INVALID_MEM_OBJECT);

Expand All @@ -3179,7 +3290,11 @@ pi_result piMemRelease(pi_mem Mem) {
} else {
auto Buf = static_cast<_pi_buffer *>(Mem);
if (!Buf->isSubBuffer()) {
PI_CALL(piextUSMFree(Mem->Context, Mem->getZeHandle()));
if (enableBufferPooling()) {
PI_CALL(piextUSMFree(Mem->Context, Mem->getZeHandle()));
} else {
ZeMemFreeHelper(Mem->Context, Mem->getZeHandle());
}
}
}
delete Mem;
Expand Down Expand Up @@ -4998,13 +5113,7 @@ static pi_result EventRelease(pi_event Event, pi_queue LockedQueue) {
if (Event->CommandType == PI_COMMAND_TYPE_MEM_BUFFER_UNMAP &&
Event->CommandData) {
// Free the memory allocated in the piEnqueueMemBufferMap.
// TODO: always use piextUSMFree
if (IndirectAccessTrackingEnabled) {
// Use the version with reference counting
PI_CALL(piextUSMFree(Event->Context, Event->CommandData));
} else {
ZE_CALL(zeMemFree, (Event->Context->ZeContext, Event->CommandData));
}
ZeMemFreeHelper(Event->Context, Event->CommandData);
Event->CommandData = nullptr;
}
if (Event->OwnZeEvent) {
Expand Down Expand Up @@ -5795,17 +5904,7 @@ pi_result piEnqueueMemBufferMap(pi_queue Queue, pi_mem Buffer,
if (Buffer->MapHostPtr) {
*RetMap = Buffer->MapHostPtr + Offset;
} else {
// TODO: always use piextUSMHostAlloc
if (IndirectAccessTrackingEnabled) {
// Use the version with reference counting
PI_CALL(piextUSMHostAlloc(RetMap, Queue->Context, nullptr, Size, 1));
} else {
ZeStruct<ze_host_mem_alloc_desc_t> ZeDesc;
ZeDesc.flags = 0;

ZE_CALL(zeMemAllocHost,
(Queue->Context->ZeContext, &ZeDesc, Size, 1, RetMap));
}
ZeHostMemAllocHelper(RetMap, Queue->Context, Size);
}
const auto &ZeCommandList = CommandList->first;
const auto &WaitList = (*Event)->WaitList;
Expand Down Expand Up @@ -6495,6 +6594,18 @@ pi_result USMHostMemoryAlloc::allocateImpl(void **ResultPtr, size_t Size,
return USMHostAllocImpl(ResultPtr, Context, nullptr, Size, Alignment);
}

SystemMemory::MemType USMSharedMemoryAlloc::getMemTypeImpl() {
return SystemMemory::Shared;
}

SystemMemory::MemType USMDeviceMemoryAlloc::getMemTypeImpl() {
return SystemMemory::Device;
}

SystemMemory::MemType USMHostMemoryAlloc::getMemTypeImpl() {
return SystemMemory::Host;
}

void *USMMemoryAllocBase::allocate(size_t Size) {
void *Ptr = nullptr;

Expand Down Expand Up @@ -6523,6 +6634,10 @@ void USMMemoryAllocBase::deallocate(void *Ptr) {
}
}

SystemMemory::MemType USMMemoryAllocBase::getMemType() {
return getMemTypeImpl();
}

pi_result piextUSMDeviceAlloc(void **ResultPtr, pi_context Context,
pi_device Device,
pi_usm_mem_properties *Properties, size_t Size,
Expand Down
5 changes: 5 additions & 0 deletions sycl/plugins/level_zero/pi_level_zero.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,20 +265,23 @@ class USMMemoryAllocBase : public SystemMemory {
// type
virtual pi_result allocateImpl(void **ResultPtr, size_t Size,
pi_uint32 Alignment) = 0;
virtual MemType getMemTypeImpl() = 0;

public:
USMMemoryAllocBase(pi_context Ctx, pi_device Dev)
: Context{Ctx}, Device{Dev} {}
void *allocate(size_t Size) override final;
void *allocate(size_t Size, size_t Alignment) override final;
void deallocate(void *Ptr) override final;
MemType getMemType() override final;
};

// Allocation routines for shared memory type
class USMSharedMemoryAlloc : public USMMemoryAllocBase {
protected:
pi_result allocateImpl(void **ResultPtr, size_t Size,
pi_uint32 Alignment) override;
MemType getMemTypeImpl() override;

public:
USMSharedMemoryAlloc(pi_context Ctx, pi_device Dev)
Expand All @@ -290,6 +293,7 @@ class USMDeviceMemoryAlloc : public USMMemoryAllocBase {
protected:
pi_result allocateImpl(void **ResultPtr, size_t Size,
pi_uint32 Alignment) override;
MemType getMemTypeImpl() override;

public:
USMDeviceMemoryAlloc(pi_context Ctx, pi_device Dev)
Expand All @@ -301,6 +305,7 @@ class USMHostMemoryAlloc : public USMMemoryAllocBase {
protected:
pi_result allocateImpl(void **ResultPtr, size_t Size,
pi_uint32 Alignment) override;
MemType getMemTypeImpl() override;

public:
USMHostMemoryAlloc(pi_context Ctx) : USMMemoryAllocBase(Ctx, nullptr) {}
Expand Down
Loading

0 comments on commit a0342b3

Please sign in to comment.