Skip to content

Commit 581d020

Browse files
sanmaiarthw
andauthored
SYCL: implement ggml_sycl_pool_vmm (ggml-org#22862)
* SYCL: implement ggml_sycl_pool_vmm * Add an option to bypass VMM with GGML_SYCL_DISABLE_VMM * Clean up debugging logging * document GGML_SYCL_DISABLE_VMM * Multi-stream MoE optimization * Revert "Multi-stream MoE optimization" This reverts commit 938929c. * Update common.hpp Co-authored-by: Neo Zhang <zhang.jianyu@outlook.com> * Flip GGML_SYCL_DISABLE_VMM to GGML_SYCL_ENABLE_VMM * add logging for GGML_SYCL_ENABLE_VMM when extension is not available (SYCL_EXT_ONEAPI_VIRTUAL_MEM macro) * Apply suggestions from code review Co-authored-by: Alexey Kopytko <alexey@kopytko.com> * Apply suggestion from @sanmai * Apply suggestion from @sanmai --------- Co-authored-by: Neo Zhang <zhang.jianyu@outlook.com>
1 parent 7623de1 commit 581d020

3 files changed

Lines changed: 165 additions & 11 deletions

File tree

docs/backend/SYCL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ use 1 SYCL GPUs: [0] with Max compute units:512
743743
| GGML_SYCL_DISABLE_GRAPH | 0 or 1 (default) | Disable running computations through SYCL Graphs feature. Disabled by default because SYCL Graph is still on development, no better performance. |
744744
| GGML_SYCL_ENABLE_LEVEL_ZERO | 1 (default) or 0 | Use Level Zero API for device memory allocation instead of SYCL. Reduces system RAM usage on Intel dGPUs by avoiding DMA-buf/TTM host memory staging. Requires GGML_SYCL_SUPPORT_LEVEL_ZERO=ON at build time. |
745745
| GGML_SYCL_DISABLE_DNN | 0 (default) or 1 | Disable running computations through oneDNN and always use oneMKL. |
746+
| GGML_SYCL_ENABLE_VMM | 0 or 1 (default) | Enable the virtual-memory device pool. |
746747
| ZES_ENABLE_SYSMAN | 0 (default) or 1 | Support to get free memory of GPU by sycl::aspect::ext_intel_free_memory.<br>Recommended to use when --split-mode = layer |
747748
| UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS | 0 (default) or 1 | Allow SYCL/Unified Runtime Level Zero device allocations larger than 4 GiB. llama.cpp's direct Level Zero allocation path requests the relaxed maximum-size limit itself when GGML_SYCL_ENABLE_LEVEL_ZERO=1. |
748749

@@ -753,6 +754,7 @@ Pass these via `CXXFLAGS` or add a one-off `#define` to enable a flag on the spo
753754
| Name | Function |
754755
|-----------------|----------------------------------------------------------------------------------|
755756
| DEBUG_SYCL_POOL | Enable device memory pool logging on teardown. Useful for profiling allocations. |
757+
| DEBUG_SYCL_MALLOC | Enable verbose per-call logging of device pool alloc/free operations. |
756758

757759
## Design Rule
758760

ggml/src/ggml-sycl/common.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ struct sycl_device_info {
224224
int max_wg_per_cu; // max work groups per compute unit - refer to
225225
// cudaOccupancyMaxActiveBlocksPerMultiprocessor
226226
bool vmm; // virtual memory support
227+
size_t vmm_granularity; // granularity of virtual memory
227228
size_t total_vram;
228229
sycl_hw_info hw_info;
229230
optimize_feature opt_feature;
@@ -244,6 +245,8 @@ struct ggml_sycl_device_info {
244245

245246
const ggml_sycl_device_info & ggml_sycl_info();
246247

248+
static constexpr size_t SYCL_BUFFER_ALIGNMENT = 128;
249+
247250
struct ggml_sycl_pool {
248251
virtual ~ggml_sycl_pool() = default;
249252

ggml/src/ggml-sycl/ggml-sycl.cpp

Lines changed: 160 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <cstdlib>
2020
#include <float.h>
2121
#include <limits>
22+
#include <optional>
2223
#include <stdint.h>
2324
#include <stdio.h>
2425
#include <vector>
@@ -37,6 +38,11 @@
3738
#if defined(GGML_SYCL_GRAPH) && SYCL_EXT_ONEAPI_ASYNC_MEMORY_ALLOC
3839
# include <sycl/ext/oneapi/experimental/async_alloc/async_alloc.hpp>
3940
#endif
41+
#if SYCL_EXT_ONEAPI_VIRTUAL_MEM
42+
# include <sycl/ext/oneapi/virtual_mem/physical_mem.hpp>
43+
# include <sycl/ext/oneapi/virtual_mem/virtual_mem.hpp>
44+
# define GGML_SYCL_USE_VMM
45+
#endif
4046
#include <sycl/half_type.hpp>
4147

4248
#include "ggml.h"
@@ -70,6 +76,7 @@ int g_ggml_sycl_debug = 0;
7076
int g_ggml_sycl_disable_optimize = 0;
7177
int g_ggml_sycl_disable_graph = 0;
7278
int g_ggml_sycl_disable_dnn = 0;
79+
int g_ggml_sycl_enable_vmm = 1;
7380
int g_ggml_sycl_prioritize_dmmv = 0;
7481
int g_ggml_sycl_use_async_mem_op = 0;
7582
int g_ggml_sycl_use_async_mem_op_requested = 1;
@@ -96,13 +103,30 @@ static ggml_sycl_device_info ggml_sycl_init() {
96103
// GGML_LOG_INFO("%s: SYCL_USE_XMX: no\n", __func__);
97104
// #endif
98105
for (int i = 0; i < info.device_count; ++i) {
99-
info.devices[i].vmm = 0;
100106
dpct::device_info prop;
101107
auto & device = dpct::dev_mgr::instance().get_device(i);
102108

103109
SYCL_CHECK(CHECK_TRY_ERROR(dpct::get_device_info(
104110
prop, device)));
105111

112+
#if !defined(GGML_SYCL_USE_VMM)
113+
info.devices[i].vmm = 0;
114+
#else
115+
info.devices[i].vmm = device.has(sycl::aspect::ext_oneapi_virtual_mem);
116+
if (info.devices[i].vmm) {
117+
// NB: SYCL's get_mem_granularity always returns the _minimum_ granularity,
118+
// but the L0 API requires a larger page size for allocs above 2 MiB and
119+
// rejects non-multiples with UR_RESULT_ERROR_INVALID_VALUE [sic].
120+
// Here we clamp it to 2 MiB for simplicity, but other devices may require
121+
// calling zeVirtualMemQueryPageSize or yet unexposed public API.
122+
const size_t physical_page = 2ull << 20; // 2 MiB
123+
info.devices[i].vmm_granularity = std::max<size_t>(
124+
sycl::ext::oneapi::experimental::get_mem_granularity(
125+
device, sycl::context(device)),
126+
physical_page);
127+
}
128+
#endif
129+
106130
info.default_tensor_split[i] = total_vram;
107131
total_vram += prop.get_global_mem_size();
108132

@@ -234,6 +258,7 @@ static void ggml_check_sycl() try {
234258
g_ggml_sycl_disable_optimize = get_sycl_env("GGML_SYCL_DISABLE_OPT", 0);
235259
g_ggml_sycl_disable_graph = get_sycl_env("GGML_SYCL_DISABLE_GRAPH", 1);
236260
g_ggml_sycl_disable_dnn = get_sycl_env("GGML_SYCL_DISABLE_DNN", 0);
261+
g_ggml_sycl_enable_vmm = get_sycl_env("GGML_SYCL_ENABLE_VMM", 1);
237262
g_ggml_sycl_prioritize_dmmv = get_sycl_env("GGML_SYCL_PRIORITIZE_DMMV", 0);
238263
#ifdef GGML_SYCL_SUPPORT_LEVEL_ZERO
239264
g_ggml_sycl_enable_level_zero = get_sycl_env("GGML_SYCL_ENABLE_LEVEL_ZERO", ggml_sycl_info().ext_oneapi_level_zero);
@@ -275,6 +300,11 @@ static void ggml_check_sycl() try {
275300
#else
276301
GGML_LOG_INFO(" GGML_SYCL_SUPPORT_LEVEL_ZERO: no\n");
277302
#endif
303+
#if defined(GGML_SYCL_USE_VMM)
304+
GGML_LOG_INFO(" GGML_SYCL_USE_VMM: yes\n");
305+
#else
306+
GGML_LOG_INFO(" GGML_SYCL_USE_VMM: no\n");
307+
#endif
278308

279309
GGML_LOG_INFO("Running with Environment Variables:\n");
280310
GGML_LOG_INFO(" GGML_SYCL_DEBUG: %d\n", g_ggml_sycl_debug);
@@ -293,6 +323,11 @@ static void ggml_check_sycl() try {
293323
GGML_LOG_INFO(" GGML_SYCL_DISABLE_DNN: %d\n", g_ggml_sycl_disable_dnn);
294324
#else
295325
GGML_LOG_INFO(" GGML_SYCL_DISABLE_DNN: DNN disabled by compile flag\n");
326+
#endif
327+
#if defined(GGML_SYCL_USE_VMM)
328+
GGML_LOG_INFO(" GGML_SYCL_ENABLE_VMM: %d\n", g_ggml_sycl_enable_vmm);
329+
#else
330+
GGML_LOG_INFO(" GGML_SYCL_ENABLE_VMM: virtual memory extension is not available\n");
296331
#endif
297332
GGML_LOG_INFO(" GGML_SYCL_PRIORITIZE_DMMV: %d\n", g_ggml_sycl_prioritize_dmmv);
298333
g_ggml_sycl_use_async_mem_op_requested = get_sycl_env("GGML_SYCL_USE_ASYNC_MEM_OP", 1);
@@ -754,7 +789,7 @@ catch (sycl::exception const &exc) {
754789
}
755790

756791
static size_t ggml_backend_sycl_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
757-
return 128;
792+
return SYCL_BUFFER_ALIGNMENT;
758793
GGML_UNUSED(buft);
759794
}
760795

@@ -1177,7 +1212,7 @@ static ggml_backend_buffer_t ggml_backend_sycl_split_buffer_type_alloc_buffer(gg
11771212
}
11781213

11791214
static size_t ggml_backend_sycl_split_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) {
1180-
return 128;
1215+
return SYCL_BUFFER_ALIGNMENT;
11811216
GGML_UNUSED(buft);
11821217
}
11831218

@@ -1462,6 +1497,121 @@ struct ggml_sycl_pool_leg : public ggml_sycl_pool {
14621497
}
14631498
};
14641499

1500+
// pool with virtual memory management
1501+
#if defined(GGML_SYCL_USE_VMM)
1502+
struct ggml_sycl_pool_vmm : public ggml_sycl_pool {
1503+
static const size_t SYCL_POOL_VMM_MAX_SIZE = 1ull << 35; // 32 GB
1504+
1505+
int device;
1506+
sycl::context ctx;
1507+
sycl::device dev;
1508+
1509+
uintptr_t pool_addr = 0;
1510+
size_t pool_used = 0;
1511+
size_t pool_size = 0;
1512+
size_t granularity;
1513+
1514+
// physical_mem owns the commits (unlike cuMemMap)
1515+
struct mapping {
1516+
sycl::ext::oneapi::experimental::physical_mem phys;
1517+
void * map_ptr;
1518+
};
1519+
std::vector<mapping> mappings;
1520+
1521+
explicit ggml_sycl_pool_vmm(queue_ptr qptr_, int device_) :
1522+
device(device_),
1523+
ctx(qptr_->get_context()),
1524+
dev(qptr_->get_device()),
1525+
granularity(ggml_sycl_info().devices[device_].vmm_granularity) {
1526+
}
1527+
1528+
~ggml_sycl_pool_vmm() {
1529+
if (pool_addr == 0) {
1530+
return;
1531+
}
1532+
1533+
// Per spec, unmap must (a) match the exact (ptr, size) of an earlier
1534+
// physical_mem::map() call and (b) precede destruction of the
1535+
// physical_mem objects (their dtors won't unmap).
1536+
for (auto & m : mappings) {
1537+
SYCL_CHECK(CHECK_TRY_ERROR(sycl::ext::oneapi::experimental::unmap(
1538+
m.map_ptr, m.phys.size(), ctx)));
1539+
}
1540+
SYCL_CHECK(CHECK_TRY_ERROR(sycl::ext::oneapi::experimental::free_virtual_mem(
1541+
pool_addr, SYCL_POOL_VMM_MAX_SIZE, ctx)));
1542+
}
1543+
1544+
void * alloc(size_t size, size_t * actual_size) override {
1545+
// round up the allocation size to the alignment to ensure that all allocations are aligned for all data types
1546+
size = GGML_PAD(size, SYCL_BUFFER_ALIGNMENT);
1547+
1548+
size_t avail = pool_size - pool_used;
1549+
1550+
if (size > avail) {
1551+
// round up to the next multiple of the granularity
1552+
size_t reserve_size = GGML_PAD(size - avail, granularity);
1553+
1554+
GGML_ASSERT(pool_size + reserve_size <= SYCL_POOL_VMM_MAX_SIZE);
1555+
1556+
// allocate more physical memory
1557+
std::optional<sycl::ext::oneapi::experimental::physical_mem> phys;
1558+
SYCL_CHECK(CHECK_TRY_ERROR(phys.emplace(dev, ctx, reserve_size)));
1559+
1560+
// reserve virtual address space (if not already reserved)
1561+
if (pool_addr == 0) {
1562+
SYCL_CHECK(CHECK_TRY_ERROR(
1563+
pool_addr = sycl::ext::oneapi::experimental::reserve_virtual_mem(
1564+
SYCL_POOL_VMM_MAX_SIZE, ctx)));
1565+
}
1566+
1567+
// map at the end of the pool
1568+
void * map_ptr = nullptr;
1569+
SYCL_CHECK(CHECK_TRY_ERROR(
1570+
map_ptr = phys->map(pool_addr + pool_size, reserve_size,
1571+
sycl::ext::oneapi::experimental::address_access_mode::read_write)));
1572+
1573+
// stash these so we could unmap this exact range in dtor
1574+
mappings.push_back({
1575+
std::move(*phys),
1576+
map_ptr,
1577+
});
1578+
1579+
// add to the pool
1580+
pool_size += reserve_size;
1581+
1582+
#ifdef DEBUG_SYCL_MALLOC
1583+
GGML_LOG_INFO("sycl pool[%d]: size increased to %llu MB (reserved %llu MB)\n",
1584+
device, (unsigned long long) (pool_size/1024/1024),
1585+
(unsigned long long) (reserve_size/1024/1024));
1586+
#endif
1587+
}
1588+
1589+
GGML_ASSERT(pool_addr != 0);
1590+
1591+
void * ptr = reinterpret_cast<void *>(pool_addr + pool_used);
1592+
*actual_size = size;
1593+
pool_used += size;
1594+
1595+
#ifdef DEBUG_SYCL_MALLOC
1596+
GGML_LOG_INFO("sycl pool[%d]: allocated %llu bytes at %p\n", device, (unsigned long long) size, ptr);
1597+
#endif
1598+
1599+
return ptr;
1600+
}
1601+
1602+
void free(void * ptr, size_t size) override {
1603+
#ifdef DEBUG_SYCL_MALLOC
1604+
GGML_LOG_INFO("sycl pool[%d]: freed %llu bytes at %p\n", device, (unsigned long long) size, ptr);
1605+
#endif
1606+
1607+
pool_used -= size;
1608+
1609+
// all deallocations must be in reverse order of the allocations
1610+
GGML_ASSERT(ptr == reinterpret_cast<void *>(pool_addr + pool_used));
1611+
}
1612+
};
1613+
#endif // defined(GGML_SYCL_USE_VMM)
1614+
14651615
struct ggml_sycl_pool_host : public ggml_sycl_pool {
14661616
queue_ptr qptr;
14671617
int device;
@@ -1542,20 +1692,19 @@ std::unique_ptr<ggml_sycl_pool> ggml_backend_sycl_context::new_pool_for_host(que
15421692
}
15431693

15441694
std::unique_ptr<ggml_sycl_pool> ggml_backend_sycl_context::new_pool_for_device(queue_ptr qptr, int device) {
1545-
// TBD: NO VMM support
1546-
// if (ggml_sycl_info().devices[device].vmm) {
1547-
// return std::unique_ptr<ggml_sycl_pool>(new ggml_sycl_pool_vmm(device));
1548-
// }
1549-
return std::unique_ptr<ggml_sycl_pool>(new ggml_sycl_pool_leg(qptr, device));
1695+
#if defined(GGML_SYCL_USE_VMM)
1696+
if (g_ggml_sycl_enable_vmm && ggml_sycl_info().devices[device].vmm) {
1697+
return std::unique_ptr<ggml_sycl_pool>(new ggml_sycl_pool_vmm(qptr, device));
1698+
}
1699+
#endif // defined(GGML_SYCL_USE_VMM)
1700+
return std::unique_ptr<ggml_sycl_pool>(new ggml_sycl_pool_leg(qptr, device));
15501701
}
15511702

1703+
15521704
std::unique_ptr<ggml_sycl_fattn_kv_buffers> ggml_backend_sycl_context::new_fattn_kv_buffers(queue_ptr qptr, int device) {
15531705
return std::unique_ptr<ggml_sycl_fattn_kv_buffers>(new ggml_sycl_fattn_kv_buffers(qptr, device));
15541706
}
15551707

1556-
// TBD pool with virtual memory management
1557-
// struct ggml_sycl_pool_vmm : public ggml_sycl_pool
1558-
15591708
/// kernels
15601709
typedef void (*ggml_sycl_op_mul_mat_t)(
15611710
ggml_backend_sycl_context & ctx,

0 commit comments

Comments
 (0)