1919#include < cstdlib>
2020#include < float.h>
2121#include < limits>
22+ #include < optional>
2223#include < stdint.h>
2324#include < stdio.h>
2425#include < vector>
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;
7076int g_ggml_sycl_disable_optimize = 0 ;
7177int g_ggml_sycl_disable_graph = 0 ;
7278int g_ggml_sycl_disable_dnn = 0 ;
79+ int g_ggml_sycl_enable_vmm = 1 ;
7380int g_ggml_sycl_prioritize_dmmv = 0 ;
7481int g_ggml_sycl_use_async_mem_op = 0 ;
7582int 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
756791static 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
11791214static 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+
14651615struct 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
15441694std::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+
15521704std::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
15601709typedef void (*ggml_sycl_op_mul_mat_t )(
15611710 ggml_backend_sycl_context & ctx,
0 commit comments