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 <cerrno>
15 #include <cstdio>
16 #include <fstream>
17 #include <optional>
18 
19 #if defined(__SYCL_RT_OS_POSIX_SUPPORT)
20 #include <unistd.h>
21 #else
22 #include <direct.h>
23 #include <io.h>
24 #endif
25 
26 namespace sycl {
27 inline namespace _V1 {
28 namespace detail {
29 
30 /* Lock file suffix */
31 const char LockCacheItem::LockSuffix[] = ".lock";
32 
33 LockCacheItem::LockCacheItem(const std::string &Path)
34  : FileName(Path + LockSuffix) {
35  int fd;
36 
37  /* If the lock fail is not created */
38  if ((fd = open(FileName.c_str(), O_CREAT | O_EXCL, S_IWRITE)) != -1) {
39  close(fd);
40  Owned = true;
41  } else {
42  PersistentDeviceCodeCache::trace("Failed to acquire lock file: " +
43  FileName + " " + std::strerror(errno));
44  PersistentDeviceCodeCache::trace("Failed to acquire lock file: " +
45  FileName + " " + std::strerror(errno));
46  }
47 }
48 
50  if (Owned && std::remove(FileName.c_str()))
51  PersistentDeviceCodeCache::trace("Failed to release lock file: " +
52  FileName);
53 }
54 
55 // Returns true if the specified format is either SPIRV or a native binary.
56 static bool
58  return Format == PI_DEVICE_BINARY_TYPE_SPIRV ||
60 }
61 
62 /* Returns true if specified image should be cached on disk. It checks if
63  * cache is enabled, image has supported format and matches thresholds. */
64 bool PersistentDeviceCodeCache::isImageCached(const RTDeviceBinaryImage &Img) {
65  // Cache should be enabled and image type is one of the supported formats.
66  if (!isEnabled() || !IsSupportedImageFormat(Img.getFormat()))
67  return false;
68 
69  // Disable cache for ITT-profiled images.
71  return false;
72  }
73 
74  // TODO: Move parsing logic and caching to specializations of SYCLConfig.
75  static auto MaxImgSize = getNumParam<SYCL_CACHE_MAX_DEVICE_IMAGE_SIZE>(
76  DEFAULT_MAX_DEVICE_IMAGE_SIZE);
77  static auto MinImgSize = getNumParam<SYCL_CACHE_MIN_DEVICE_IMAGE_SIZE>(
78  DEFAULT_MIN_DEVICE_IMAGE_SIZE);
79 
80  // Make sure that image size is between caching thresholds if they are set.
81  // Zero values for threshold is treated as disabled threshold.
82  if ((MaxImgSize && (Img.getSize() > MaxImgSize)) ||
83  (MinImgSize && (Img.getSize() < MinImgSize)))
84  return false;
85 
86  return true;
87 }
88 
89 /* Stores built program in persisten cache
90  */
92  const device &Device, const RTDeviceBinaryImage &Img,
93  const SerializedObj &SpecConsts, const std::string &BuildOptionsString,
94  const sycl::detail::pi::PiProgram &NativePrg) {
95 
96  if (!isImageCached(Img))
97  return;
98 
99  std::string DirName =
100  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
101 
102  if (DirName.empty())
103  return;
104 
105  auto Plugin = detail::getSyclObjImpl(Device)->getPlugin();
106 
107  unsigned int DeviceNum = 0;
108 
109  Plugin->call<PiApiKind::piProgramGetInfo>(
110  NativePrg, PI_PROGRAM_INFO_NUM_DEVICES, sizeof(DeviceNum), &DeviceNum,
111  nullptr);
112 
113  std::vector<size_t> BinarySizes(DeviceNum);
114  Plugin->call<PiApiKind::piProgramGetInfo>(
115  NativePrg, PI_PROGRAM_INFO_BINARY_SIZES,
116  sizeof(size_t) * BinarySizes.size(), BinarySizes.data(), nullptr);
117 
118  std::vector<std::vector<char>> Result;
119  std::vector<char *> Pointers;
120  for (size_t I = 0; I < BinarySizes.size(); ++I) {
121  Result.emplace_back(BinarySizes[I]);
122  Pointers.push_back(Result[I].data());
123  }
124 
125  Plugin->call<PiApiKind::piProgramGetInfo>(NativePrg, PI_PROGRAM_INFO_BINARIES,
126  sizeof(char *) * Pointers.size(),
127  Pointers.data(), nullptr);
128  size_t i = 0;
129  std::string FileName;
130  do {
131  FileName = DirName + "/" + std::to_string(i++);
132  } while (OSUtil::isPathPresent(FileName + ".bin") ||
133  OSUtil::isPathPresent(FileName + ".lock"));
134 
135  try {
136  OSUtil::makeDir(DirName.c_str());
137  LockCacheItem Lock{FileName};
138  if (Lock.isOwned()) {
139  std::string FullFileName = FileName + ".bin";
140  writeBinaryDataToFile(FullFileName, Result);
141  trace("device binary has been cached: " + FullFileName);
142  writeSourceItem(FileName + ".src", Device, Img, SpecConsts,
143  BuildOptionsString);
144  } else {
145  PersistentDeviceCodeCache::trace("cache lock not owned " + FileName);
146  }
147  } catch (std::exception &e) {
149  std::string("exception encountered making persistent cache: ") +
150  e.what());
151  } catch (...) {
153  std::string("error outputting persistent cache: ") +
154  std::strerror(errno));
155  }
156 }
157 
158 /* Program binaries built for one or more devices are read from persistent
159  * cache and returned in form of vector of programs. Each binary program is
160  * stored in vector of chars.
161  */
162 std::vector<std::vector<char>> PersistentDeviceCodeCache::getItemFromDisc(
163  const device &Device, const RTDeviceBinaryImage &Img,
164  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
165 
166  if (!isImageCached(Img))
167  return {};
168 
169  std::string Path =
170  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
171 
172  if (Path.empty() || !OSUtil::isPathPresent(Path))
173  return {};
174 
175  int i = 0;
176 
177  std::string FileName{Path + "/" + std::to_string(i)};
178  while (OSUtil::isPathPresent(FileName + ".bin") ||
179  OSUtil::isPathPresent(FileName + ".src")) {
180 
181  if (!LockCacheItem::isLocked(FileName) &&
182  isCacheItemSrcEqual(FileName + ".src", Device, Img, SpecConsts,
183  BuildOptionsString)) {
184  try {
185  std::string FullFileName = FileName + ".bin";
186  std::vector<std::vector<char>> res =
187  readBinaryDataFromFile(FullFileName);
188  trace("using cached device binary: " + FullFileName);
189  return res; // subject for NRVO
190  } catch (...) {
191  // If read was unsuccessfull try the next item
192  }
193  }
194  FileName = Path + "/" + std::to_string(++i);
195  }
196  return {};
197 }
198 
199 /* Returns string value which can be used to identify different device
200  */
201 std::string PersistentDeviceCodeCache::getDeviceIDString(const device &Device) {
202  return Device.get_platform().get_info<sycl::info::platform::name>() + "/" +
203  Device.get_info<sycl::info::device::name>() + "/" +
204  Device.get_info<sycl::info::device::version>() + "/" +
205  Device.get_info<sycl::info::device::driver_version>();
206 }
207 
208 /* Write built binary to persistent cache
209  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
210  * Return on first unsuccessfull file operation
211  */
212 void PersistentDeviceCodeCache::writeBinaryDataToFile(
213  const std::string &FileName, const std::vector<std::vector<char>> &Data) {
214  std::ofstream FileStream{FileName, std::ios::binary};
215 
216  size_t Size = Data.size();
217  FileStream.write((char *)&Size, sizeof(Size));
218 
219  for (size_t i = 0; i < Data.size(); ++i) {
220  Size = Data[i].size();
221  FileStream.write((char *)&Size, sizeof(Size));
222  FileStream.write(Data[i].data(), Size);
223  }
224  FileStream.close();
225  if (FileStream.fail())
226  trace("Failed to write binary file " + FileName);
227 }
228 
229 /* Read built binary to persistent cache
230  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
231  */
232 std::vector<std::vector<char>>
233 PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) {
234  std::ifstream FileStream{FileName, std::ios::binary};
235  size_t ImgNum = 0, ImgSize = 0;
236  FileStream.read((char *)&ImgNum, sizeof(ImgNum));
237 
238  std::vector<std::vector<char>> Res(ImgNum);
239  for (size_t i = 0; i < ImgNum; ++i) {
240  FileStream.read((char *)&ImgSize, sizeof(ImgSize));
241 
242  std::vector<char> ImgData(ImgSize);
243  FileStream.read(ImgData.data(), ImgSize);
244 
245  Res[i] = std::move(ImgData);
246  }
247  FileStream.close();
248 
249  if (FileStream.fail()) {
250  trace("Failed to read binary file from " + FileName);
251  return {};
252  }
253 
254  return Res;
255 }
256 
257 /* Writing cache item key sources to be used for reliable identification
258  * Format: Four pairs of [size, value] for device, build options, specialization
259  * constant values, device code SPIR-V image.
260  */
261 void PersistentDeviceCodeCache::writeSourceItem(
262  const std::string &FileName, const device &Device,
263  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
264  const std::string &BuildOptionsString) {
265  std::ofstream FileStream{FileName, std::ios::binary};
266 
267  std::string DeviceString{getDeviceIDString(Device)};
268  size_t Size = DeviceString.size();
269  FileStream.write((char *)&Size, sizeof(Size));
270  FileStream.write(DeviceString.data(), Size);
271 
272  Size = BuildOptionsString.size();
273  FileStream.write((char *)&Size, sizeof(Size));
274  FileStream.write(BuildOptionsString.data(), Size);
275 
276  Size = SpecConsts.size();
277  FileStream.write((char *)&Size, sizeof(Size));
278  FileStream.write((const char *)SpecConsts.data(), Size);
279 
280  Size = Img.getSize();
281  FileStream.write((char *)&Size, sizeof(Size));
282  FileStream.write((const char *)Img.getRawData().BinaryStart, Size);
283  FileStream.close();
284 
285  if (FileStream.fail()) {
286  trace("Failed to write source file to " + FileName);
287  }
288 }
289 
290 /* Check that cache item key sources are equal to the current program.
291  * If file read operations fail cache item is treated as not equal.
292  */
293 bool PersistentDeviceCodeCache::isCacheItemSrcEqual(
294  const std::string &FileName, const device &Device,
295  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
296  const std::string &BuildOptionsString) {
297  std::ifstream FileStream{FileName, std::ios::binary};
298 
299  std::string ImgString{(const char *)Img.getRawData().BinaryStart,
300  Img.getSize()};
301  std::string SpecConstsString{(const char *)SpecConsts.data(),
302  SpecConsts.size()};
303 
304  size_t Size = 0;
305  FileStream.read((char *)&Size, sizeof(Size));
306  std::string res(Size, '\0');
307  FileStream.read(&res[0], Size);
308  if (getDeviceIDString(Device).compare(res))
309  return false;
310 
311  FileStream.read((char *)&Size, sizeof(Size));
312  res.resize(Size);
313  FileStream.read(&res[0], Size);
314  if (BuildOptionsString.compare(res))
315  return false;
316 
317  FileStream.read((char *)&Size, sizeof(Size));
318  res.resize(Size);
319  FileStream.read(&res[0], Size);
320  if (SpecConstsString.compare(res))
321  return false;
322 
323  FileStream.read((char *)&Size, sizeof(Size));
324  res.resize(Size);
325  FileStream.read(&res[0], Size);
326  if (ImgString.compare(res))
327  return false;
328 
329  FileStream.close();
330 
331  if (FileStream.fail()) {
332  trace("Failed to read source file from " + FileName);
333  }
334 
335  return true;
336 }
337 
338 /* Returns directory name to store specific kernel image for specified
339  * device, build options and specialization constants values.
340  */
342  const device &Device, const RTDeviceBinaryImage &Img,
343  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
344  std::string cache_root{getRootDir()};
345  if (cache_root.empty()) {
346  trace("Disable persistent cache due to unconfigured cache root.");
347  return {};
348  }
349 
350  std::string ImgString = "";
351  if (Img.getRawData().BinaryStart)
352  ImgString.assign((const char *)Img.getRawData().BinaryStart, Img.getSize());
353 
354  std::string DeviceString{getDeviceIDString(Device)};
355  std::string SpecConstsString{(const char *)SpecConsts.data(),
356  SpecConsts.size()};
357  std::hash<std::string> StringHasher{};
358 
359  return cache_root + "/" + std::to_string(StringHasher(DeviceString)) + "/" +
360  std::to_string(StringHasher(ImgString)) + "/" +
361  std::to_string(StringHasher(SpecConstsString)) + "/" +
362  std::to_string(StringHasher(BuildOptionsString));
363 }
364 
365 /* Returns true if persistent cache is enabled.
366  */
367 bool PersistentDeviceCodeCache::isEnabled() {
368  bool CacheIsEnabled = SYCLConfig<SYCL_CACHE_PERSISTENT>::get();
369  static bool FirstCheck = true;
370  if (FirstCheck) {
371  PersistentDeviceCodeCache::trace(CacheIsEnabled ? "enabled" : "disabled");
372  FirstCheck = false;
373  }
374  return CacheIsEnabled;
375 }
376 
377 /* Returns path for device code cache root directory
378  */
379 std::string PersistentDeviceCodeCache::getRootDir() {
381 }
382 
383 } // namespace detail
384 } // namespace _V1
385 } // namespace sycl
static bool isLocked(const std::string &Path)
static int makeDir(const char *Dir)
Make all directories on the path, throws on error.
Definition: os_util.cpp:242
static bool isPathPresent(const std::string &Path)
Checks if specified path is present.
Definition: os_util.hpp:72
static std::vector< std::vector< char > > getItemFromDisc(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
static std::string getCacheItemPath(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
static void putItemToDisc(const device &Device, const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts, const std::string &BuildOptionsString, const sycl::detail::pi::PiProgram &NativePrg)
const pi_device_binary_struct & getRawData() const
static const char * get()
Definition: config.hpp:115
The SYCL device class encapsulates a single SYCL device on which kernels may be executed.
Definition: device.hpp:64
detail::is_device_info_desc< Param >::return_type get_info() const
Queries this SYCL device for information requested by the template parameter param.
Definition: device.hpp:223
platform get_platform() const
Get associated SYCL platform.
Definition: device.cpp:85
detail::is_platform_info_desc< Param >::return_type get_info() const
Queries this SYCL platform for info.
Definition: platform.hpp:184
::pi_device_binary_type PiDeviceBinaryType
Definition: pi.hpp:134
static bool IsSupportedImageFormat(sycl::detail::pi::PiDeviceBinaryType Format)
decltype(Obj::impl) getSyclObjImpl(const Obj &SyclObject)
Definition: impl_utils.hpp:30
std::vector< unsigned char > SerializedObj
Definition: util.hpp:69
Definition: access.hpp:18
std::enable_if_t< std::is_same_v< std::invoke_result_t< BinaryOperation, ValueT, ValueT >, bool >, bool > compare(const ValueT a, const ValueT b, const BinaryOperation binary_op)
Performs comparison.
Definition: math.hpp:188
static constexpr pi_device_binary_type PI_DEVICE_BINARY_TYPE_NATIVE
Definition: pi.h:943
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_cuda.cpp:272
static constexpr pi_device_binary_type PI_DEVICE_BINARY_TYPE_SPIRV
Definition: pi.h:946
@ PI_PROGRAM_INFO_NUM_DEVICES
Definition: pi.h:470
@ PI_PROGRAM_INFO_BINARY_SIZES
Definition: pi.h:473
@ PI_PROGRAM_INFO_BINARIES
Definition: pi.h:474
const unsigned char * BinaryStart
Pointer to the target code start.
Definition: pi.h:1057