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_POSIX_SUPPORT)
18 #include <unistd.h>
19 #else
20 #include <direct.h>
21 #include <io.h>
22 #endif
23 
24 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 the specified format is either SPIRV or a native binary.
52  return Format == PI_DEVICE_BINARY_TYPE_SPIRV ||
54 }
55 
56 /* Returns true if specified image should be cached on disk. It checks if
57  * cache is enabled, image has supported format and matches thresholds. */
58 bool PersistentDeviceCodeCache::isImageCached(const RTDeviceBinaryImage &Img) {
59  // Cache should be enabled and image type is one of the supported formats.
60  if (!isEnabled() || !IsSupportedImageFormat(Img.getFormat()))
61  return false;
62 
63  // Disable cache for ITT-profiled images.
65  return false;
66  }
67 
68  // TODO: Move parsing logic and caching to specializations of SYCLConfig.
69  static auto MaxImgSize = getNumParam<SYCL_CACHE_MAX_DEVICE_IMAGE_SIZE>(
70  DEFAULT_MAX_DEVICE_IMAGE_SIZE);
71  static auto MinImgSize = getNumParam<SYCL_CACHE_MIN_DEVICE_IMAGE_SIZE>(
72  DEFAULT_MIN_DEVICE_IMAGE_SIZE);
73 
74  // Make sure that image size is between caching thresholds if they are set.
75  // Zero values for threshold is treated as disabled threshold.
76  if ((MaxImgSize && (Img.getSize() > MaxImgSize)) ||
77  (MinImgSize && (Img.getSize() < MinImgSize)))
78  return false;
79 
80  return true;
81 }
82 
83 /* Stores built program in persisten cache
84  */
86  const device &Device, const RTDeviceBinaryImage &Img,
87  const SerializedObj &SpecConsts, const std::string &BuildOptionsString,
88  const RT::PiProgram &NativePrg) {
89 
90  if (!isImageCached(Img))
91  return;
92 
93  std::string DirName =
94  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
95 
96  if (DirName.empty())
97  return;
98 
99  auto Plugin = detail::getSyclObjImpl(Device)->getPlugin();
100 
101  size_t i = 0;
102  std::string FileName;
103  do {
104  FileName = DirName + "/" + std::to_string(i++);
105  } while (OSUtil::isPathPresent(FileName + ".bin"));
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 
126  sizeof(char *) * Pointers.size(),
127  Pointers.data(), nullptr);
128 
129  try {
130  OSUtil::makeDir(DirName.c_str());
131  LockCacheItem Lock{FileName};
132  if (Lock.isOwned()) {
133  std::string FullFileName = FileName + ".bin";
134  writeBinaryDataToFile(FullFileName, Result);
135  trace("device binary has been cached: " + FullFileName);
136  writeSourceItem(FileName + ".src", Device, Img, SpecConsts,
137  BuildOptionsString);
138  }
139  } catch (...) {
140  // If a problem happens on storing cache item, do nothing
141  }
142 }
143 
144 /* Program binaries built for one or more devices are read from persistent
145  * cache and returned in form of vector of programs. Each binary program is
146  * stored in vector of chars.
147  */
148 std::vector<std::vector<char>> PersistentDeviceCodeCache::getItemFromDisc(
149  const device &Device, const RTDeviceBinaryImage &Img,
150  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
151 
152  if (!isImageCached(Img))
153  return {};
154 
155  std::string Path =
156  getCacheItemPath(Device, Img, SpecConsts, BuildOptionsString);
157 
158  if (Path.empty() || !OSUtil::isPathPresent(Path))
159  return {};
160 
161  int i = 0;
162 
163  std::string FileName{Path + "/" + std::to_string(i)};
164  while (OSUtil::isPathPresent(FileName + ".bin") ||
165  OSUtil::isPathPresent(FileName + ".src")) {
166 
167  if (!LockCacheItem::isLocked(FileName) &&
168  isCacheItemSrcEqual(FileName + ".src", Device, Img, SpecConsts,
169  BuildOptionsString)) {
170  try {
171  std::string FullFileName = FileName + ".bin";
172  std::vector<std::vector<char>> res =
173  readBinaryDataFromFile(FullFileName);
174  trace("using cached device binary: " + FullFileName);
175  return res; // subject for NRVO
176  } catch (...) {
177  // If read was unsuccessfull try the next item
178  }
179  }
180  FileName = Path + "/" + std::to_string(++i);
181  }
182  return {};
183 }
184 
185 /* Returns string value which can be used to identify different device
186  */
187 std::string PersistentDeviceCodeCache::getDeviceIDString(const device &Device) {
188  return Device.get_platform().get_info<sycl::info::platform::name>() + "/" +
189  Device.get_info<sycl::info::device::name>() + "/" +
190  Device.get_info<sycl::info::device::version>() + "/" +
191  Device.get_info<sycl::info::device::driver_version>();
192 }
193 
194 /* Write built binary to persistent cache
195  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
196  * Return on first unsuccessfull file operation
197  */
198 void PersistentDeviceCodeCache::writeBinaryDataToFile(
199  const std::string &FileName, const std::vector<std::vector<char>> &Data) {
200  std::ofstream FileStream{FileName, std::ios::binary};
201 
202  size_t Size = Data.size();
203  FileStream.write((char *)&Size, sizeof(Size));
204 
205  for (size_t i = 0; i < Data.size(); ++i) {
206  Size = Data[i].size();
207  FileStream.write((char *)&Size, sizeof(Size));
208  FileStream.write(Data[i].data(), Size);
209  }
210  FileStream.close();
211  if (FileStream.fail())
212  trace("Failed to write binary file " + FileName);
213 }
214 
215 /* Read built binary to persistent cache
216  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
217  */
218 std::vector<std::vector<char>>
219 PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) {
220  std::ifstream FileStream{FileName, std::ios::binary};
221  size_t ImgNum = 0, ImgSize = 0;
222  FileStream.read((char *)&ImgNum, sizeof(ImgNum));
223 
224  std::vector<std::vector<char>> Res(ImgNum);
225  for (size_t i = 0; i < ImgNum; ++i) {
226  FileStream.read((char *)&ImgSize, sizeof(ImgSize));
227 
228  std::vector<char> ImgData(ImgSize);
229  FileStream.read(ImgData.data(), ImgSize);
230 
231  Res[i] = std::move(ImgData);
232  }
233  FileStream.close();
234 
235  if (FileStream.fail()) {
236  trace("Failed to read binary file from " + FileName);
237  return {};
238  }
239 
240  return Res;
241 }
242 
243 /* Writing cache item key sources to be used for reliable identification
244  * Format: Four pairs of [size, value] for device, build options, specialization
245  * constant values, device code SPIR-V image.
246  */
247 void PersistentDeviceCodeCache::writeSourceItem(
248  const std::string &FileName, const device &Device,
249  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
250  const std::string &BuildOptionsString) {
251  std::ofstream FileStream{FileName, std::ios::binary};
252 
253  std::string DeviceString{getDeviceIDString(Device)};
254  size_t Size = DeviceString.size();
255  FileStream.write((char *)&Size, sizeof(Size));
256  FileStream.write(DeviceString.data(), Size);
257 
258  Size = BuildOptionsString.size();
259  FileStream.write((char *)&Size, sizeof(Size));
260  FileStream.write(BuildOptionsString.data(), Size);
261 
262  Size = SpecConsts.size();
263  FileStream.write((char *)&Size, sizeof(Size));
264  FileStream.write((const char *)SpecConsts.data(), Size);
265 
266  Size = Img.getSize();
267  FileStream.write((char *)&Size, sizeof(Size));
268  FileStream.write((const char *)Img.getRawData().BinaryStart, Size);
269  FileStream.close();
270 
271  if (FileStream.fail()) {
272  trace("Failed to write source file to " + FileName);
273  }
274 }
275 
276 /* Check that cache item key sources are equal to the current program.
277  * If file read operations fail cache item is treated as not equal.
278  */
279 bool PersistentDeviceCodeCache::isCacheItemSrcEqual(
280  const std::string &FileName, const device &Device,
281  const RTDeviceBinaryImage &Img, const SerializedObj &SpecConsts,
282  const std::string &BuildOptionsString) {
283  std::ifstream FileStream{FileName, std::ios::binary};
284 
285  std::string ImgString{(const char *)Img.getRawData().BinaryStart,
286  Img.getSize()};
287  std::string SpecConstsString{(const char *)SpecConsts.data(),
288  SpecConsts.size()};
289 
290  size_t Size = 0;
291  FileStream.read((char *)&Size, sizeof(Size));
292  std::string res(Size, '\0');
293  FileStream.read(&res[0], Size);
294  if (getDeviceIDString(Device).compare(res))
295  return false;
296 
297  FileStream.read((char *)&Size, sizeof(Size));
298  res.resize(Size);
299  FileStream.read(&res[0], Size);
300  if (BuildOptionsString.compare(res))
301  return false;
302 
303  FileStream.read((char *)&Size, sizeof(Size));
304  res.resize(Size);
305  FileStream.read(&res[0], Size);
306  if (SpecConstsString.compare(res))
307  return false;
308 
309  FileStream.read((char *)&Size, sizeof(Size));
310  res.resize(Size);
311  FileStream.read(&res[0], Size);
312  if (ImgString.compare(res))
313  return false;
314 
315  FileStream.close();
316 
317  if (FileStream.fail()) {
318  trace("Failed to read source file from " + FileName);
319  }
320 
321  return true;
322 }
323 
324 /* Returns directory name to store specific kernel image for specified
325  * device, build options and specialization constants values.
326  */
328  const device &Device, const RTDeviceBinaryImage &Img,
329  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
330  std::string cache_root{getRootDir()};
331  if (cache_root.empty()) {
332  trace("Disable persistent cache due to unconfigured cache root.");
333  return {};
334  }
335 
336  std::string ImgString = "";
337  if (Img.getRawData().BinaryStart)
338  ImgString.assign((const char *)Img.getRawData().BinaryStart, Img.getSize());
339 
340  std::string DeviceString{getDeviceIDString(Device)};
341  std::string SpecConstsString{(const char *)SpecConsts.data(),
342  SpecConsts.size()};
343  std::hash<std::string> StringHasher{};
344 
345  return cache_root + "/" + std::to_string(StringHasher(DeviceString)) + "/" +
346  std::to_string(StringHasher(ImgString)) + "/" +
347  std::to_string(StringHasher(SpecConstsString)) + "/" +
348  std::to_string(StringHasher(BuildOptionsString));
349 }
350 
351 /* Returns true if persistent cache is enabled.
352  */
353 bool PersistentDeviceCodeCache::isEnabled() {
354  bool CacheIsEnabled = SYCLConfig<SYCL_CACHE_PERSISTENT>::get();
355  static bool FirstCheck = true;
356  if (FirstCheck) {
357  PersistentDeviceCodeCache::trace(CacheIsEnabled ? "enabled" : "disabled");
358  FirstCheck = false;
359  }
360  return CacheIsEnabled;
361 }
362 
363 /* Returns path for device code cache root directory
364  */
365 std::string PersistentDeviceCodeCache::getRootDir() {
367 }
368 
369 } // namespace detail
370 } // __SYCL_INLINE_VER_NAMESPACE(_V1)
371 } // namespace sycl
sycl::_V1::detail::LockCacheItem::~LockCacheItem
~LockCacheItem()
Definition: persistent_device_code_cache.cpp:44
sycl::_V1::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:148
sycl::_V1::detail::pi::PiDeviceBinaryType
::pi_device_binary_type PiDeviceBinaryType
Definition: pi.hpp:127
sycl::_V1::detail::OSUtil::makeDir
static int makeDir(const char *Dir)
Make directory recursively and returns zero code on success.
Definition: os_util.cpp:297
sycl::_V1::detail::RTDeviceBinaryImage::getRawData
const pi_device_binary_struct & getRawData() const
Definition: device_binary_image.hpp:157
sycl::_V1::detail::RTDeviceBinaryImage::getSize
size_t getSize() const
Definition: device_binary_image.hpp:162
PI_DEVICE_BINARY_TYPE_SPIRV
static constexpr pi_device_binary_type PI_DEVICE_BINARY_TYPE_SPIRV
Definition: pi.h:767
PI_PROGRAM_INFO_BINARY_SIZES
@ PI_PROGRAM_INFO_BINARY_SIZES
Definition: pi.h:341
sycl::_V1::detail::PersistentDeviceCodeCache::trace
static void trace(const std::string &msg)
Definition: persistent_device_code_cache.hpp:188
__SYCL_INLINE_VER_NAMESPACE
#define __SYCL_INLINE_VER_NAMESPACE(X)
Definition: defines_elementary.hpp:11
sycl::_V1::detail::IsSupportedImageFormat
static bool IsSupportedImageFormat(RT::PiDeviceBinaryType Format)
Definition: persistent_device_code_cache.cpp:51
pi_device_binary_struct::BinaryStart
const unsigned char * BinaryStart
Pointer to the target code start.
Definition: pi.h:874
sycl
---— Error handling, matching OpenCL plugin semantics.
Definition: access.hpp:14
device_impl.hpp
plugin.hpp
sycl::_V1::detail::LockCacheItem
Definition: persistent_device_code_cache.hpp:42
sycl::_V1::detail::LockCacheItem::isLocked
static bool isLocked(const std::string &Path)
Definition: persistent_device_code_cache.hpp:52
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:1325
PI_DEVICE_BINARY_TYPE_NATIVE
static constexpr pi_device_binary_type PI_DEVICE_BINARY_TYPE_NATIVE
Definition: pi.h:764
sycl::_V1::detail::OSUtil::isPathPresent
static bool isPathPresent(const std::string &Path)
Checks if specified path is present.
Definition: os_util.hpp:90
sycl::_V1::detail::pi::PiProgram
::pi_program PiProgram
Definition: pi.hpp:130
sycl::_V1::device
The SYCL device class encapsulates a single SYCL device on which kernels may be executed.
Definition: device.hpp:49
sycl::_V1::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:85
program_manager.hpp
persistent_device_code_cache.hpp
sycl::_V1::detail::SerializedObj
std::vector< unsigned char > SerializedObj
Definition: util.hpp:68
sycl::_V1::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:327
PI_PROGRAM_INFO_NUM_DEVICES
@ PI_PROGRAM_INFO_NUM_DEVICES
Definition: pi.h:338
sycl::_V1::detail::RTDeviceBinaryImage
Definition: device_binary_image.hpp:82
sycl::_V1::detail::SYCLConfig::get
static const char * get()
Definition: config.hpp:114
PI_PROGRAM_INFO_BINARIES
@ PI_PROGRAM_INFO_BINARIES
Definition: pi.h:342
sycl::_V1::detail::getSyclObjImpl
decltype(Obj::impl) getSyclObjImpl(const Obj &SyclObject)
Definition: common.hpp:300