1 // Copyright (c) 2017-2026, Lawrence Livermore National Security, LLC and other CEED contributors.
2 // All Rights Reserved. See the top-level LICENSE and NOTICE files for details.
3 //
4 // SPDX-License-Identifier: BSD-2-Clause
5 //
6 // This file is part of CEED: http://github.com/ceed
7
8 #include "ceed-sycl-compile.hpp"
9
10 #include <ceed/backend.h>
11 #include <ceed/ceed.h>
12 #include <ceed/jit-tools.h>
13 #include <level_zero/ze_api.h>
14
15 #include <map>
16 #include <sstream>
17 #include <sycl/sycl.hpp>
18
19 #include "./online_compiler.hpp"
20 #include "ceed-sycl-common.hpp"
21
22 using ByteVector_t = std::vector<unsigned char>;
23
24 //------------------------------------------------------------------------------
25 // Add defined constants at the beginning of kernel source
26 //------------------------------------------------------------------------------
CeedJitAddDefinitions_Sycl(Ceed ceed,const std::string & kernel_source,std::string & jit_source,const std::map<std::string,CeedInt> & constants={})27 static int CeedJitAddDefinitions_Sycl(Ceed ceed, const std::string &kernel_source, std::string &jit_source,
28 const std::map<std::string, CeedInt> &constants = {}) {
29 std::ostringstream oss;
30
31 const char *jit_defs_path, *jit_defs_source;
32 const char *sycl_jith_path = "ceed/jit-source/sycl/sycl-jit.h";
33
34 // Prepend defined constants
35 for (const auto &[name, value] : constants) {
36 oss << "#define " << name << " " << value << "\n";
37 }
38
39 // libCeed definitions for Sycl Backends
40 CeedCallBackend(CeedGetJitAbsolutePath(ceed, sycl_jith_path, &jit_defs_path));
41 {
42 char *source;
43
44 CeedCallBackend(CeedLoadSourceToBuffer(ceed, jit_defs_path, &source));
45 jit_defs_source = source;
46 }
47
48 oss << jit_defs_source << "\n";
49
50 CeedCallBackend(CeedFree(&jit_defs_path));
51 CeedCallBackend(CeedFree(&jit_defs_source));
52
53 // Append kernel_source
54 oss << "\n" << kernel_source;
55
56 jit_source = oss.str();
57 return CEED_ERROR_SUCCESS;
58 }
59
60 //------------------------------------------------------------------------------
61 // TODO: Add architecture flags, optimization flags
62 //------------------------------------------------------------------------------
CeedJitGetFlags_Sycl(std::vector<std::string> & flags)63 static inline int CeedJitGetFlags_Sycl(std::vector<std::string> &flags) {
64 flags = {std::string("-cl-std=CL3.0"), std::string("-Dint32_t=int"), std::string("-DCEED_RUNNING_JIT_PASS=1")};
65 return CEED_ERROR_SUCCESS;
66 }
67
68 //------------------------------------------------------------------------------
69 // Compile an OpenCL source to SPIR-V using Intel's online compiler extension
70 //------------------------------------------------------------------------------
CeedJitCompileSource_Sycl(Ceed ceed,const sycl::device & sycl_device,const std::string & opencl_source,ByteVector_t & il_binary,const std::vector<std::string> & flags={})71 static inline int CeedJitCompileSource_Sycl(Ceed ceed, const sycl::device &sycl_device, const std::string &opencl_source, ByteVector_t &il_binary,
72 const std::vector<std::string> &flags = {}) {
73 sycl::ext::libceed::online_compiler<sycl::ext::libceed::source_language::opencl_c> compiler(sycl_device);
74
75 try {
76 il_binary = compiler.compile(opencl_source, flags);
77 } catch (sycl::ext::libceed::online_compile_error &e) {
78 return CeedError((ceed), CEED_ERROR_BACKEND, e.what());
79 }
80 return CEED_ERROR_SUCCESS;
81 }
82
83 // ------------------------------------------------------------------------------
84 // Load (compile) SPIR-V source and wrap in sycl kernel_bundle
85 // ------------------------------------------------------------------------------
CeedLoadModule_Sycl(Ceed ceed,const sycl::context & sycl_context,const sycl::device & sycl_device,const ByteVector_t & il_binary,SyclModule_t ** sycl_module)86 static int CeedLoadModule_Sycl(Ceed ceed, const sycl::context &sycl_context, const sycl::device &sycl_device, const ByteVector_t &il_binary,
87 SyclModule_t **sycl_module) {
88 auto lz_context = sycl::get_native<sycl::backend::ext_oneapi_level_zero>(sycl_context);
89 auto lz_device = sycl::get_native<sycl::backend::ext_oneapi_level_zero>(sycl_device);
90
91 ze_module_desc_t lz_mod_desc = {ZE_STRUCTURE_TYPE_MODULE_DESC,
92 nullptr, // extension specific structs
93 ZE_MODULE_FORMAT_IL_SPIRV,
94 il_binary.size(),
95 il_binary.data(),
96 " -ze-opt-large-register-file", // flags
97 nullptr}; // specialization constants
98
99 ze_module_handle_t lz_module;
100 ze_module_build_log_handle_t lz_log;
101 ze_result_t lz_err = zeModuleCreate(lz_context, lz_device, &lz_mod_desc, &lz_module, &lz_log);
102
103 if (ZE_RESULT_SUCCESS != lz_err) {
104 size_t log_size = 0;
105 char *log_message;
106
107 zeModuleBuildLogGetString(lz_log, &log_size, nullptr);
108
109 CeedCallBackend(CeedCalloc(log_size, &log_message));
110 zeModuleBuildLogGetString(lz_log, &log_size, log_message);
111
112 return CeedError(ceed, CEED_ERROR_BACKEND, "Failed to compile Level Zero module:\n%s", log_message);
113 }
114
115 // sycl make_<type> only throws errors for backend mismatch--assume we have vetted this already
116 *sycl_module = new SyclModule_t(sycl::make_kernel_bundle<sycl::backend::ext_oneapi_level_zero, sycl::bundle_state::executable>(
117 {lz_module, sycl::ext::oneapi::level_zero::ownership::transfer}, sycl_context));
118 return CEED_ERROR_SUCCESS;
119 }
120
121 // ------------------------------------------------------------------------------
122 // Compile kernel source to an executable `sycl::kernel_bundle`
123 // ------------------------------------------------------------------------------
CeedBuildModule_Sycl(Ceed ceed,const std::string & kernel_source,SyclModule_t ** sycl_module,const std::map<std::string,CeedInt> & constants)124 int CeedBuildModule_Sycl(Ceed ceed, const std::string &kernel_source, SyclModule_t **sycl_module, const std::map<std::string, CeedInt> &constants) {
125 Ceed_Sycl *data;
126 std::string jit_source;
127 std::vector<std::string> flags;
128 ByteVector_t il_binary;
129
130 CeedCallBackend(CeedGetData(ceed, &data));
131 CeedCallBackend(CeedJitAddDefinitions_Sycl(ceed, kernel_source, jit_source, constants));
132 CeedCallBackend(CeedJitGetFlags_Sycl(flags));
133 CeedCallBackend(CeedJitCompileSource_Sycl(ceed, data->sycl_device, jit_source, il_binary, flags));
134 CeedCallBackend(CeedLoadModule_Sycl(ceed, data->sycl_context, data->sycl_device, il_binary, sycl_module));
135 return CEED_ERROR_SUCCESS;
136 }
137
138 // ------------------------------------------------------------------------------
139 // Get a sycl kernel from an existing kernel_bundle
140 //
141 // TODO: Error handle lz calls
142 // ------------------------------------------------------------------------------
CeedGetKernel_Sycl(Ceed ceed,const SyclModule_t * sycl_module,const std::string & kernel_name,sycl::kernel ** sycl_kernel)143 int CeedGetKernel_Sycl(Ceed ceed, const SyclModule_t *sycl_module, const std::string &kernel_name, sycl::kernel **sycl_kernel) {
144 Ceed_Sycl *data;
145
146 CeedCallBackend(CeedGetData(ceed, &data));
147
148 // sycl::get_native returns std::vector<ze_module_handle_t> for lz backend
149 // https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/supported/sycl_ext_oneapi_backend_level_zero.md
150 ze_module_handle_t lz_module = sycl::get_native<sycl::backend::ext_oneapi_level_zero>(*sycl_module).front();
151
152 ze_kernel_desc_t lz_kernel_desc = {ZE_STRUCTURE_TYPE_KERNEL_DESC, nullptr, 0, kernel_name.c_str()};
153 ze_kernel_handle_t lz_kernel;
154 ze_result_t lz_err = zeKernelCreate(lz_module, &lz_kernel_desc, &lz_kernel);
155
156 if (ZE_RESULT_SUCCESS != lz_err) {
157 return CeedError(ceed, CEED_ERROR_BACKEND, "Failed to retrieve kernel from Level Zero module");
158 }
159
160 *sycl_kernel = new sycl::kernel(sycl::make_kernel<sycl::backend::ext_oneapi_level_zero>({*sycl_module, lz_kernel,
161 sycl::ext::oneapi::level_zero::ownership::transfer},
162 data->sycl_context));
163 return CEED_ERROR_SUCCESS;
164 }
165
166 //------------------------------------------------------------------------------
167 // Run SYCL kernel for spatial dimension with shared memory
168 //------------------------------------------------------------------------------
CeedRunKernelDimSharedSycl(Ceed ceed,sycl::kernel * kernel,const int grid_size,const int block_size_x,const int block_size_y,const int block_size_z,const int shared_mem_size,void ** kernel_args)169 int CeedRunKernelDimSharedSycl(Ceed ceed, sycl::kernel *kernel, const int grid_size, const int block_size_x, const int block_size_y,
170 const int block_size_z, const int shared_mem_size, void **kernel_args) {
171 sycl::range<3> local_range(block_size_z, block_size_y, block_size_x);
172 sycl::range<3> global_range(grid_size * block_size_z, block_size_y, block_size_x);
173 sycl::nd_range<3> kernel_range(global_range, local_range);
174
175 //-----------
176 // Order queue
177 Ceed_Sycl *ceed_Sycl;
178
179 CeedCallBackend(CeedGetData(ceed, &ceed_Sycl));
180 sycl::event e = ceed_Sycl->sycl_queue.ext_oneapi_submit_barrier();
181
182 ceed_Sycl->sycl_queue.submit([&](sycl::handler &cgh) {
183 cgh.depends_on(e);
184 cgh.set_args(*kernel_args);
185 cgh.parallel_for(kernel_range, *kernel);
186 });
187 return CEED_ERROR_SUCCESS;
188 }
189