DPC++ Runtime
Runtime libraries for oneAPI DPC++
persistent_device_code_cache.cpp
Go to the documentation of this file.
1 //==---------- persistent_device_code_cache.cpp -----------------*- C++-*---==//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include <detail/device_impl.hpp>
11 #include <detail/plugin.hpp>
13 
14 #include <cstdio>
15 #include <optional>
16 
17 #if defined(__SYCL_RT_OS_LINUX)
18 #include <unistd.h>
19 #else
20 #include <direct.h>
21 #include <io.h>
22 #endif
23 
25 namespace sycl {
26 namespace detail {
27 
28 /* Lock file suffix */
29 const char LockCacheItem::LockSuffix[] = ".lock";
30 
31 LockCacheItem::LockCacheItem(const std::string &Path)
32  : FileName(Path + LockSuffix) {
33  int fd;
34 
35  /* If the lock fail is not created */
36  if ((fd = open(FileName.c_str(), O_CREAT | O_EXCL, S_IWRITE)) != -1) {
37  close(fd);
38  Owned = true;
39  } else {
40  PersistentDeviceCodeCache::trace("Failed to aquire lock file: " + FileName);
41  }
42 }
43 
45  if (Owned && std::remove(FileName.c_str()))
46  PersistentDeviceCodeCache::trace("Failed to release lock file: " +
47  FileName);
48 }
49 
50 /* Returns true if specified image should be cached on disk. It checks if
51  * cache is enabled, image has SPIRV type and matches thresholds. */
52 bool PersistentDeviceCodeCache::isImageCached(const RTDeviceBinaryImage &Img) {
53  // Cache shoould be enabled and image type should be SPIR-V
54  if (!isEnabled() || Img.getFormat() != PI_DEVICE_BINARY_TYPE_SPIRV)
55  return false;
56 
57  // Disable cache for ITT-profiled images.
59  return false;
60  }
61 
62  static auto MaxImgSize = getNumParam<SYCL_CACHE_MAX_DEVICE_IMAGE_SIZE>(
63  DEFAULT_MAX_DEVICE_IMAGE_SIZE);
64  static auto MinImgSize = getNumParam<SYCL_CACHE_MIN_DEVICE_IMAGE_SIZE>(
65  DEFAULT_MIN_DEVICE_IMAGE_SIZE);
66 
67  // Make sure that image size is between caching thresholds if they are set.
68  // Zero values for threshold is treated as disabled threshold.
69  if ((MaxImgSize && (Img.getSize() > MaxImgSize)) ||
70  (MinImgSize && (Img.getSize() < MinImgSize)))
71  return false;
72 
73  return true;
74 }
75 
76 /* Stores built program in persisten cache
77  */
79  const device &Device, const RTDeviceBinaryImage &Img,
80  const SerializedObj &SpecConsts, const std::string &BuildOptionsString,
81  const RT::PiProgram &NativePrg) {
82 
83  if (!isImageCached(Img))
84  return;
85 
86  std::string DirName =
87  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
88 
89  if (DirName.empty())
90  return;
91 
92  auto Plugin = detail::getSyclObjImpl(Device)->getPlugin();
93 
94  size_t i = 0;
95  std::string FileName;
96  do {
97  FileName = DirName + "/" + std::to_string(i++);
98  } while (OSUtil::isPathPresent(FileName + ".bin"));
99 
100  unsigned int DeviceNum = 0;
101 
102  Plugin.call<PiApiKind::piProgramGetInfo>(
103  NativePrg, PI_PROGRAM_INFO_NUM_DEVICES, sizeof(DeviceNum), &DeviceNum,
104  nullptr);
105 
106  std::vector<size_t> BinarySizes(DeviceNum);
107  Plugin.call<PiApiKind::piProgramGetInfo>(
108  NativePrg, PI_PROGRAM_INFO_BINARY_SIZES,
109  sizeof(size_t) * BinarySizes.size(), BinarySizes.data(), nullptr);
110 
111  std::vector<std::vector<char>> Result;
112  std::vector<char *> Pointers;
113  for (size_t I = 0; I < BinarySizes.size(); ++I) {
114  Result.emplace_back(BinarySizes[I]);
115  Pointers.push_back(Result[I].data());
116  }
117 
119  sizeof(char *) * Pointers.size(),
120  Pointers.data(), nullptr);
121 
122  try {
123  OSUtil::makeDir(DirName.c_str());
124  LockCacheItem Lock{FileName};
125  if (Lock.isOwned()) {
126  std::string FullFileName = FileName + ".bin";
127  writeBinaryDataToFile(FullFileName, Result);
128  trace("device binary has been cached: " + FullFileName);
129  writeSourceItem(FileName + ".src", Device, Img, SpecConsts,
130  BuildOptionsString);
131  }
132  } catch (...) {
133  // If a problem happens on storing cache item, do nothing
134  }
135 }
136 
137 /* Program binaries built for one or more devices are read from persistent
138  * cache and returned in form of vector of programs. Each binary program is
139  * stored in vector of chars.
140  */
141 std::vector<std::vector<char>> PersistentDeviceCodeCache::getItemFromDisc(
142  const device &Device, const RTDeviceBinaryImage &Img,
143  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
144 
145  if (!isImageCached(Img))
146  return {};
147 
148  std::string Path =
149  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
150 
151  if (Path.empty() || !OSUtil::isPathPresent(Path))
152  return {};
153 
154  int i = 0;
155 
156  std::string FileName{Path + "/" + std::to_string(i)};
157  while (OSUtil::isPathPresent(FileName + ".bin") ||
158  OSUtil::isPathPresent(FileName + ".src")) {
159 
160  if (!LockCacheItem::isLocked(FileName) &&
161  isCacheItemSrcEqual(FileName + ".src", Device, Img, SpecConsts,
162  BuildOptionsString)) {
163  try {
164  std::string FullFileName = FileName + ".bin";
165  std::vector<std::vector<char>> res =
166  readBinaryDataFromFile(FullFileName);
167  trace("using cached device binary: " + FullFileName);
168  return res; // subject for NRVO
169  } catch (...) {
170  // If read was unsuccessfull try the next item
171  }
172  }
173  FileName = Path + "/" + std::to_string(++i);
174  }
175  return {};
176 }
177 
178 /* Returns string value which can be used to identify different device
179  */
180 std::string PersistentDeviceCodeCache::getDeviceIDString(const device &Device) {
181  return Device.get_platform().get_info<sycl::info::platform::name>() + "/" +
182  Device.get_info<sycl::info::device::name>() + "/" +
183  Device.get_info<sycl::info::device::version>() + "/" +
185 }
186 
187 /* Write built binary to persistent cache
188  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
189  * Return on first unsuccessfull file operation
190  */
191 void PersistentDeviceCodeCache::writeBinaryDataToFile(
192  const std::string &FileName, const std::vector<std::vector<char>> &Data) {
193  std::ofstream FileStream{FileName, std::ios::binary};
194 
195  size_t Size = Data.size();
196  FileStream.write((char *)&Size, sizeof(Size));
197 
198  for (size_t i = 0; i < Data.size(); ++i) {
199  Size = Data[i].size();
200  FileStream.write((char *)&Size, sizeof(Size));
201  FileStream.write(Data[i].data(), Size);
202  }
203  FileStream.close();
204  if (FileStream.fail())
205  trace("Failed to write binary file " + FileName);
206 }
207 
208 /* Read built binary to persistent cache
209  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
210  */
211 std::vector<std::vector<char>>
212 PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) {
213  std::ifstream FileStream{FileName, std::ios::binary};
214  size_t ImgNum = 0, ImgSize = 0;
215  FileStream.read((char *)&ImgNum, sizeof(ImgNum));
216 
217  std::vector<std::vector<char>> Res(ImgNum);
218  for (size_t i = 0; i < ImgNum; ++i) {
219  FileStream.read((char *)&ImgSize, sizeof(ImgSize));
220 
221  std::vector<char> ImgData(ImgSize);
222  FileStream.read(ImgData.data(), ImgSize);
223 
224  Res[i] = std::move(ImgData);
225  }
226  FileStream.close();
227 
228  if (FileStream.fail()) {
229  trace("Failed to read binary file from " + FileName);
230  return {};
231  }
232 
233  return Res;
234 }
235 
236 /* Writing cache item key sources to be used for reliable identification
237  * Format: Four pairs of [size, value] for device, build options, specialization
238  * constant values, device code SPIR-V image.
239  */
240 void PersistentDeviceCodeCache::writeSourceItem(
241  const std::string &FileName, const device &Device,
242  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
243  const std::string &BuildOptionsString) {
244  std::ofstream FileStream{FileName, std::ios::binary};
245 
246  std::string DeviceString{getDeviceIDString(Device)};
247  size_t Size = DeviceString.size();
248  FileStream.write((char *)&Size, sizeof(Size));
249  FileStream.write(DeviceString.data(), Size);
250 
251  Size = BuildOptionsString.size();
252  FileStream.write((char *)&Size, sizeof(Size));
253  FileStream.write(BuildOptionsString.data(), Size);
254 
255  Size = SpecConsts.size();
256  FileStream.write((char *)&Size, sizeof(Size));
257  FileStream.write((const char *)SpecConsts.data(), Size);
258 
259  Size = Img.getSize();
260  FileStream.write((char *)&Size, sizeof(Size));
261  FileStream.write((const char *)Img.getRawData().BinaryStart, Size);
262  FileStream.close();
263 
264  if (FileStream.fail()) {
265  trace("Failed to write source file to " + FileName);
266  }
267 }
268 
269 /* Check that cache item key sources are equal to the current program.
270  * If file read operations fail cache item is treated as not equal.
271  */
272 bool PersistentDeviceCodeCache::isCacheItemSrcEqual(
273  const std::string &FileName, const device &Device,
274  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
275  const std::string &BuildOptionsString) {
276  std::ifstream FileStream{FileName, std::ios::binary};
277 
278  std::string ImgString{(const char *)Img.getRawData().BinaryStart,
279  Img.getSize()};
280  std::string SpecConstsString{(const char *)SpecConsts.data(),
281  SpecConsts.size()};
282 
283  size_t Size = 0;
284  FileStream.read((char *)&Size, sizeof(Size));
285  std::string res(Size, '\0');
286  FileStream.read(&res[0], Size);
287  if (getDeviceIDString(Device).compare(res))
288  return false;
289 
290  FileStream.read((char *)&Size, sizeof(Size));
291  res.resize(Size);
292  FileStream.read(&res[0], Size);
293  if (BuildOptionsString.compare(res))
294  return false;
295 
296  FileStream.read((char *)&Size, sizeof(Size));
297  res.resize(Size);
298  FileStream.read(&res[0], Size);
299  if (SpecConstsString.compare(res))
300  return false;
301 
302  FileStream.read((char *)&Size, sizeof(Size));
303  res.resize(Size);
304  FileStream.read(&res[0], Size);
305  if (ImgString.compare(res))
306  return false;
307 
308  FileStream.close();
309 
310  if (FileStream.fail()) {
311  trace("Failed to read source file from " + FileName);
312  }
313 
314  return true;
315 }
316 
317 /* Returns directory name to store specific kernel image for specified
318  * device, build options and specialization constants values.
319  */
321  const device &Device, const RTDeviceBinaryImage &Img,
322  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
323  static std::string cache_root{getRootDir()};
324  if (cache_root.empty()) {
325  trace("Disable persistent cache due to unconfigured cache root.");
326  return {};
327  }
328 
329  std::string ImgString{(const char *)Img.getRawData().BinaryStart,
330  Img.getSize()};
331  std::string DeviceString{getDeviceIDString(Device)};
332  std::string SpecConstsString{(const char *)SpecConsts.data(),
333  SpecConsts.size()};
334  std::hash<std::string> StringHasher{};
335 
336  return cache_root + "/" + std::to_string(StringHasher(DeviceString)) + "/" +
337  std::to_string(StringHasher(ImgString)) + "/" +
338  std::to_string(StringHasher(SpecConstsString)) + "/" +
339  std::to_string(StringHasher(BuildOptionsString));
340 }
341 
342 // TODO Currently parsing configuration variables and error reporting is not
343 // centralized, and is basically re-implemented (with different level of
344 // reliability) for each particular variable. As a variant, this can go into
345 // the SYCLConfigBase class, which can be templated by value type, default value
346 // and value parser (combined with error checker). It can also have typed get()
347 // function returning one-time parsed and error-checked value.
348 
349 // Parses persistent cache configuration and checks it for errors.
350 // Returns true if it is enabled, false otherwise.
352  constexpr bool Default = false; // default is disabled
353 
354  // Check if deprecated opt-out env var is used, then warn.
356  std::cerr
358  << " environment variable is deprecated "
359  << "and has no effect. By default, persistent device code caching is "
360  << (Default ? "enabled." : "disabled.") << " Use "
362  << "=1/0 to enable/disable.\n";
363  }
364  bool Ret = Default;
365  const char *RawVal = SYCLConfig<SYCL_CACHE_PERSISTENT>::get();
366 
367  if (RawVal) {
368  if (!std::strcmp(RawVal, "0")) {
369  Ret = false;
370  } else if (!std::strcmp(RawVal, "1")) {
371  Ret = true;
372  } else {
373  std::string Msg =
374  std::string{"Invalid value for bool configuration variable "} +
375  SYCLConfig<SYCL_CACHE_PERSISTENT>::getName() + std::string{": "} +
376  RawVal;
377  throw runtime_error(Msg, PI_INVALID_OPERATION);
378  }
379  }
380  PersistentDeviceCodeCache::trace(Ret ? "enabled" : "disabled");
381  return Ret;
382 }
383 
384 /* Cached static variable signalling if the persistent cache is enabled.
385  * The variable can have three values:
386  * - None : The configuration has not been parsed.
387  * - true : The persistent cache is enabled.
388  * - false : The persistent cache is disabled.
389  */
390 static std::optional<bool> CacheIsEnabled;
391 
392 /* Forces a reparsing of the information used to determine if the persistent
393  * cache is enabled. This is primarily used for unit-testing where the
394  * corresponding configuration variable is set by the individual tests.
395  */
398 }
399 
400 /* Returns true if persistent cache is enabled.
401  */
402 bool PersistentDeviceCodeCache::isEnabled() {
403  if (!CacheIsEnabled)
404  reparseConfig();
405  return *CacheIsEnabled;
406 }
407 
408 /* Returns path for device code cache root directory
409  * If environment variables are not available return an empty string to identify
410  * that cache is not available.
411  */
412 std::string PersistentDeviceCodeCache::getRootDir() {
413  static const char *RootDir = SYCLConfig<SYCL_CACHE_DIR>::get();
414  if (RootDir)
415  return RootDir;
416 
417  constexpr char DeviceCodeCacheDir[] = "/libsycl_cache";
418 
419  // Use static to calculate directory only once per program run
420 #if defined(__SYCL_RT_OS_LINUX)
421  static const char *CacheDir = std::getenv("XDG_CACHE_HOME");
422  static const char *HomeDir = std::getenv("HOME");
423  if (!CacheDir && !HomeDir)
424  return {};
425  static std::string Res{
426  std::string(CacheDir ? CacheDir : (std::string(HomeDir) + "/.cache")) +
427  DeviceCodeCacheDir};
428 #else
429  static const char *AppDataDir = std::getenv("AppData");
430  if (!AppDataDir)
431  return {};
432  static std::string Res{std::string(AppDataDir) + DeviceCodeCacheDir};
433 #endif
434  return Res;
435 }
436 
437 } // namespace detail
438 } // namespace sycl
439 } // __SYCL_INLINE_NAMESPACE(cl)
cl::sycl::detail::PersistentDeviceCodeCache::putItemToDisc
static void putItemToDisc(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString, const RT::PiProgram &NativePrg)
Definition: persistent_device_code_cache.cpp:78
cl::sycl::detail::PersistentDeviceCodeCache::getItemFromDisc
static std::vector< std::vector< char > > getItemFromDisc(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
Definition: persistent_device_code_cache.cpp:141
cl::sycl::info::device::driver_version
@ driver_version
cl::sycl::detail::SerializedObj
std::vector< unsigned char > SerializedObj
Definition: util.hpp:56
cl::sycl::detail::SYCLConfig::get
static const char * get()
Definition: config.hpp:109
cl::sycl::detail::PersistentDeviceCodeCache::trace
static void trace(const std::string &msg)
Definition: persistent_device_code_cache.hpp:194
PI_INVALID_OPERATION
@ PI_INVALID_OPERATION
Definition: pi.h:88
PI_DEVICE_BINARY_TYPE_SPIRV
static constexpr pi_device_binary_type PI_DEVICE_BINARY_TYPE_SPIRV
Definition: pi.h:727
cl::sycl::detail::RTDeviceBinaryImage
Definition: device_binary_image.hpp:20
cl::sycl::detail::pi::DeviceBinaryImage::getFormat
pi::PiDeviceBinaryType getFormat() const
Returns the format of the binary image.
Definition: pi.hpp:319
PI_PROGRAM_INFO_BINARY_SIZES
@ PI_PROGRAM_INFO_BINARY_SIZES
Definition: pi.h:334
cl::sycl::detail::CacheIsEnabled
static std::optional< bool > CacheIsEnabled
Definition: persistent_device_code_cache.cpp:390
cl::sycl::detail::PersistentDeviceCodeCache::getCacheItemPath
static std::string getCacheItemPath(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
Definition: persistent_device_code_cache.cpp:320
pi_device_binary_struct::BinaryStart
const unsigned char * BinaryStart
Pointer to the target code start.
Definition: pi.h:830
cl::sycl::detail::SYCLConfig
Definition: config.hpp:105
cl::sycl::device::get_platform
platform get_platform() const
Get associated SYCL platform.
Definition: device.cpp:110
cl::sycl::platform::get_info
info::param_traits< info::platform, param >::return_type get_info() const
Queries this SYCL platform for info.
Definition: platform.cpp:54
sycl
Definition: invoke_simd.hpp:68
cl::sycl::detail::RTDeviceBinaryImage::getRawData
const pi_device_binary_struct & getRawData() const
Definition: device_binary_image.hpp:42
device_impl.hpp
plugin.hpp
cl::sycl::detail::SYCLConfig::getName
static const char * getName()
Definition: config.hpp:113
piProgramGetInfo
pi_result piProgramGetInfo(pi_program program, pi_program_info param_name, size_t param_value_size, void *param_value, size_t *param_value_size_ret)
Definition: pi_esimd_emulator.cpp:1308
cl::sycl::detail::LockCacheItem
Definition: persistent_device_code_cache.hpp:42
cl::sycl::info::device::name
@ name
cl::sycl::detail::parsePersistentCacheConfig
static bool parsePersistentCacheConfig()
Definition: persistent_device_code_cache.cpp:351
cl::sycl::device
The SYCL device class encapsulates a single SYCL device on which kernels may be executed.
Definition: device.hpp:35
cl::sycl::detail::OSUtil::makeDir
static int makeDir(const char *Dir)
Make directory recursively and returns zero code on success.
Definition: os_util.cpp:280
cl::sycl::detail::OSUtil::isPathPresent
static bool isPathPresent(const std::string &Path)
Checks if specified path is present.
Definition: os_util.hpp:90
cl::sycl::info::device::version
@ version
cl
We provide new interfaces for matrix muliply in this patch:
Definition: access.hpp:13
cl::sycl::detail::PersistentDeviceCodeCache::reparseConfig
static void reparseConfig()
Definition: persistent_device_code_cache.cpp:396
cl::sycl::info::platform::name
@ name
_pi_program
Implementation of PI Program on CUDA Module object.
Definition: pi_cuda.hpp:569
cl::sycl::detail::pi::DeviceBinaryImage::getSize
size_t getSize() const
Definition: pi.hpp:303
program_manager.hpp
persistent_device_code_cache.hpp
cl::sycl::device::get_info
info::param_traits< info::device, param >::return_type get_info() const
Queries this SYCL device for information requested by the template parameter param.
Definition: device.cpp:147
cl::sycl::detail::LockCacheItem::isLocked
static bool isLocked(const std::string &Path)
Definition: persistent_device_code_cache.hpp:52
cl::sycl::detail::getSyclObjImpl
decltype(Obj::impl) getSyclObjImpl(const Obj &SyclObject)
Definition: common.hpp:198
PI_PROGRAM_INFO_NUM_DEVICES
@ PI_PROGRAM_INFO_NUM_DEVICES
Definition: pi.h:331
cl::sycl::detail::LockCacheItem::~LockCacheItem
~LockCacheItem()
Definition: persistent_device_code_cache.cpp:44
PI_PROGRAM_INFO_BINARIES
@ PI_PROGRAM_INFO_BINARIES
Definition: pi.h:335
__SYCL_INLINE_NAMESPACE
#define __SYCL_INLINE_NAMESPACE(X)
Definition: defines_elementary.hpp:12