-
Notifications
You must be signed in to change notification settings - Fork 796
[SYCL][Clang] Add support for device image compression #15124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 40 commits
ef323f7
bdab2f0
45f1e99
34978f8
195e961
cd64225
d89f41b
fb643e3
151e70a
2983fab
054984c
4493984
dbb96a7
6c26a42
7d7edc6
f0aca25
dbda582
758723c
7282eec
eda727e
94a98c9
6cc23ec
4297d5f
71abfce
fae7906
9272d65
b4a2c0c
fa9459b
63389bb
6487867
5099ee9
687db23
ff53221
7fb726e
84f0864
7fdbd5e
312bd38
75f28ac
288ed5b
e3576a6
b6f1f7a
7262fd1
6ff1a37
022a7ef
2eb0c25
44b41dd
1d81813
9936bab
969b520
32e4868
fd0f1e3
07c119e
eb7588c
58f9939
575efc6
970ad35
c1a2c13
966e3dd
946a738
ce1d0f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // REQUIRES: zstd && (system-windows || system-linux) | ||
|
|
||
| // clang-offload-wrapper compression test: checks that the wrapper can compress the device images. | ||
| // Checks the '--offload-compress', '--offload-compression-level', and '--offload-compression-threshold' | ||
| // CLI options. | ||
|
|
||
| // --- Prepare test data by creating the debice binary image. | ||
| // RUN: echo -e -n 'device binary image1\n' > %t.bin | ||
| // RUN: echo -e -n '[Category1]\nint_prop1=1|10\n[Category2]\nint_prop2=1|20\n' > %t.props | ||
| // RUN: echo -e -n 'kernel1\nkernel2\n' > %t.sym | ||
| // RUN: echo -e -n 'Manifest file - arbitrary data generated by the toolchain\n' > %t.mnf | ||
| // RUN: echo '[Code|Properties|Symbols|Manifest]' > %t.img1 | ||
| // RUN: echo %t.bin"|"%t.props"|"%t.sym"|"%t.mnf >> %t.img1 | ||
|
|
||
| /////////////////////////////////////////////////////// | ||
| // Compress the test image using clang-offload-wrapper. | ||
| /////////////////////////////////////////////////////// | ||
|
|
||
| // RUN: clang-offload-wrapper -kind=sycl -target=TARGET -batch %t.img1 -o %t.wrapped.bc -v \ | ||
| // RUN: --offload-compress --offload-compression-level=9 --offload-compression-threshold=0 \ | ||
| // RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-COMPRESS | ||
|
|
||
| // CHECK-COMPRESS: [Compression] Original image size: | ||
| // CHECK-COMPRESS: [Compression] Compressed image size: | ||
| // CHECK-COMPRESS: [Compression] Compression level used: 9 | ||
|
|
||
| /////////////////////////////////////////////////////////// | ||
| // Check that there is no compression when the threshold is set to a value higher than the image size | ||
| // or '--offload-compress' is not set. | ||
| /////////////////////////////////////////////////////////// | ||
|
|
||
| // RUN: clang-offload-wrapper -kind=sycl -target=TARGET -batch %t.img1 -o %t.wrapped.bc -v \ | ||
| // RUN: --offload-compress --offload-compression-level=3 --offload-compression-threshold=1000 \ | ||
| // RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-NO-COMPRESS | ||
|
|
||
| // RUN: clang-offload-wrapper -kind=sycl -target=TARGET -batch %t.img1 -o %t.wrapped.bc -v \ | ||
| // RUN: --offload-compression-level=3 --offload-compression-threshold=0 \ | ||
| // RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-NO-COMPRESS | ||
|
|
||
| // CHECK-NO-COMPRESS-NOT: [Compression] Original image size: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,9 @@ | |
| #include <string> | ||
| #include <tuple> | ||
|
|
||
| // For device image compression. | ||
| #include <llvm/Support/Compression.h> | ||
|
|
||
| #define OPENMP_OFFLOAD_IMAGE_VERSION "1.0" | ||
|
|
||
| using namespace llvm; | ||
|
|
@@ -139,15 +142,35 @@ static cl::list<std::string> Inputs(cl::Positional, cl::OneOrMore, | |
| cl::desc("<input files>"), | ||
| cl::cat(ClangOffloadWrapperCategory)); | ||
|
|
||
| // CLI options for device image compression. | ||
| static cl::opt<bool> OffloadCompressDevImgs( | ||
| "offload-compress", cl::init(false), cl::Optional, | ||
| cl::desc("Enable device image compression using ZSTD."), | ||
| cl::cat(ClangOffloadWrapperCategory)); | ||
|
|
||
| static cl::opt<int> | ||
| OffloadCompressLevel("offload-compression-level", cl::init(10), | ||
| cl::Optional, | ||
| cl::desc("ZSTD Compression level. Default: 10"), | ||
| cl::cat(ClangOffloadWrapperCategory)); | ||
|
|
||
| static cl::opt<int> | ||
| OffloadCompressThreshold("offload-compression-threshold", cl::init(512), | ||
| cl::Optional, | ||
| cl::desc("Threshold (in bytes) over which to " | ||
| "compress images. Default: 512"), | ||
| cl::cat(ClangOffloadWrapperCategory)); | ||
|
|
||
| // Binary image formats supported by this tool. The support basically means | ||
| // mapping string representation given at the command line to a value from this | ||
| // enum. No format checking is performed. | ||
| enum BinaryImageFormat { | ||
| none, // image kind is not determined | ||
| native, // image kind is native | ||
| // portable image kinds go next | ||
| spirv, // SPIR-V | ||
| llvmbc // LLVM bitcode | ||
| spirv, // SPIR-V | ||
| llvmbc, // LLVM bitcode | ||
| compressed_none // compressed image with unknown format | ||
| }; | ||
|
|
||
| /// Sets offload kind. | ||
|
|
@@ -265,6 +288,8 @@ static StringRef formatToString(BinaryImageFormat Fmt) { | |
| return "llvmbc"; | ||
| case BinaryImageFormat::native: | ||
| return "native"; | ||
| case BinaryImageFormat::compressed_none: | ||
| return "compressed_none"; | ||
| } | ||
| llvm_unreachable("bad format"); | ||
|
|
||
|
|
@@ -1083,10 +1108,55 @@ class BinaryWrapper { | |
| return FBinOrErr.takeError(); | ||
| Fbin = *FBinOrErr; | ||
| } else { | ||
| Fbin = addDeviceImageToModule( | ||
| ArrayRef<char>(Bin->getBufferStart(), Bin->getBufferSize()), | ||
| Twine(OffloadKindTag) + Twine(ImgId) + Twine(".data"), Kind, | ||
| Img.Tgt); | ||
|
|
||
| // If '--offload-compress' option is specified and zstd is not available | ||
| // then warn the user that the image will not be compressed. | ||
| if (OffloadCompressDevImgs && !llvm::compression::zstd::isAvailable()) { | ||
| WithColor::warning(errs(), ToolName) | ||
| << "'--offload-compress' option is specified but zstd is not " | ||
| "available. The device image will not be compressed.\n"; | ||
| } | ||
uditagarwal97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Don't compress if the user explicitly specifies the binary image | ||
| // format or if the image is smaller than OffloadCompressThreshold | ||
| // bytes. | ||
| if (Kind != OffloadKind::SYCL || !OffloadCompressDevImgs || | ||
| Img.Fmt != BinaryImageFormat::none || | ||
| !llvm::compression::zstd::isAvailable() || | ||
| static_cast<int>(Bin->getBufferSize()) < OffloadCompressThreshold) { | ||
| Fbin = addDeviceImageToModule( | ||
| ArrayRef<char>(Bin->getBufferStart(), Bin->getBufferSize()), | ||
| Twine(OffloadKindTag) + Twine(ImgId) + Twine(".data"), Kind, | ||
| Img.Tgt); | ||
| } else { | ||
|
|
||
| // Compress the image using zstd. | ||
| SmallVector<uint8_t, 512> CompressedBuffer; | ||
| llvm::compression::zstd::compress( | ||
| ArrayRef<unsigned char>( | ||
| (const unsigned char *)(Bin->getBufferStart()), | ||
| Bin->getBufferSize()), | ||
| CompressedBuffer, OffloadCompressLevel); | ||
|
||
|
|
||
| if (Verbose) | ||
| errs() << "[Compression] Original image size: " | ||
| << Bin->getBufferSize() << "\n" | ||
| << "[Compression] Compressed image size: " | ||
| << CompressedBuffer.size() << "\n" | ||
| << "[Compression] Compression level used: " | ||
| << OffloadCompressLevel << "\n"; | ||
|
|
||
| // Add the compressed image to the module. | ||
| Fbin = addDeviceImageToModule( | ||
| ArrayRef<char>((const char *)CompressedBuffer.data(), | ||
| CompressedBuffer.size()), | ||
| Twine(OffloadKindTag) + Twine(ImgId) + Twine(".data"), Kind, | ||
| Img.Tgt); | ||
|
|
||
| // Change image format to compressed_non. | ||
uditagarwal97 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Ffmt = ConstantInt::get(Type::getInt8Ty(C), | ||
| BinaryImageFormat::compressed_none); | ||
| } | ||
| } | ||
|
|
||
| if (Kind == OffloadKind::SYCL) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,11 +16,12 @@ else() | |
| set(zstd_STATIC_LIBRARY_SUFFIX "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") | ||
| endif() | ||
|
|
||
| find_path(zstd_INCLUDE_DIR NAMES zstd.h) | ||
| find_library(zstd_LIBRARY NAMES zstd zstd_static) | ||
| find_path(zstd_INCLUDE_DIR NAMES zstd.h HINTS $ENV{ZSTD_ROOT}/include) | ||
|
||
| find_library(zstd_LIBRARY NAMES zstd zstd_static HINTS $ENV{ZSTD_ROOT}/lib) | ||
| find_library(zstd_STATIC_LIBRARY NAMES | ||
| zstd_static | ||
| "${CMAKE_STATIC_LIBRARY_PREFIX}zstd${CMAKE_STATIC_LIBRARY_SUFFIX}") | ||
| "${CMAKE_STATIC_LIBRARY_PREFIX}zstd${CMAKE_STATIC_LIBRARY_SUFFIX}" | ||
| HINTS $ENV{ZSTD_ROOT}/lib) | ||
|
|
||
| include(FindPackageHandleStandardArgs) | ||
| find_package_handle_standard_args( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -69,6 +69,15 @@ function(add_sycl_rt_library LIB_NAME LIB_OBJ_NAME) | |
| target_link_libraries(${LIB_NAME} PRIVATE ${ARG_XPTI_LIB}) | ||
| endif() | ||
|
|
||
| # TODO: Remove debug print. Need this to figure out why zstd is not | ||
| # being found on Windows CI machines. | ||
| set(CMAKE_FIND_DEBUG_MODE 1) | ||
|
|
||
| # Need zstd for device image compression. | ||
| find_package(zstd REQUIRED) | ||
|
||
| target_link_libraries(${LIB_NAME} PRIVATE ${zstd_STATIC_LIBRARY}) | ||
| target_include_directories(${LIB_OBJ_NAME} PRIVATE ${zstd_INCLUDE_DIR}) | ||
|
|
||
| target_include_directories(${LIB_OBJ_NAME} PRIVATE ${BOOST_UNORDERED_INCLUDE_DIRS}) | ||
|
|
||
| # ur_win_proxy_loader | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| //==---------- compression.hpp --------------------------------------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| #pragma once | ||
|
|
||
| #include <iostream> | ||
| #include <memory> | ||
| #include <zstd.h> | ||
|
|
||
| #define ZSTD_CONTENTSIZE_UNKNOWN (0ULL - 1) | ||
| #define ZSTD_CONTENTSIZE_ERROR (0ULL - 2) | ||
|
|
||
| namespace sycl { | ||
| inline namespace _V1 { | ||
| namespace detail { | ||
|
|
||
| // Singleton class to handle ZSTD compression and decompression. | ||
| class ZSTDCompressor { | ||
| private: | ||
| // Initialize ZSTD context and error code. | ||
| ZSTDCompressor() { | ||
| m_ZSTD_compression_ctx = static_cast<void *>(ZSTD_createCCtx()); | ||
uditagarwal97 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| m_ZSTD_decompression_ctx = static_cast<void *>(ZSTD_createDCtx()); | ||
|
|
||
| if (!m_ZSTD_compression_ctx || !m_ZSTD_decompression_ctx) { | ||
| std::cerr << "Error creating ZSTD contexts. \n"; | ||
| } | ||
|
|
||
| m_lastError = 0; | ||
| } | ||
|
|
||
| // Free ZSTD contexts. | ||
| ~ZSTDCompressor() { | ||
| ZSTD_freeCCtx(static_cast<ZSTD_CCtx *>(m_ZSTD_compression_ctx)); | ||
| ZSTD_freeDCtx(static_cast<ZSTD_DCtx *>(m_ZSTD_decompression_ctx)); | ||
| } | ||
|
|
||
| ZSTDCompressor(const ZSTDCompressor &) = delete; | ||
| ZSTDCompressor &operator=(const ZSTDCompressor &) = delete; | ||
|
|
||
| // Get the singleton instance of the ZSTDCompressor class. | ||
| static ZSTDCompressor &GetSingletonInstance() { | ||
| static ZSTDCompressor instance; | ||
| return instance; | ||
| } | ||
|
|
||
| // Public APIs | ||
| public: | ||
| // Return 0 is last (de)compression was successful, otherwise return error | ||
| // code. | ||
| static int GetLastError() { return GetSingletonInstance().m_lastError; } | ||
|
|
||
| // Returns a string representation of the error code. | ||
| // If the error code is 0, it returns "No error detected". | ||
| static std::string GetErrorString(int code) { | ||
| return ZSTD_getErrorName(code); | ||
| } | ||
|
|
||
| // Blob (de)compression do not assume format/structure of the input buffer. | ||
| static std::unique_ptr<char> CompressBlob(const char *src, size_t srcSize, | ||
uditagarwal97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| size_t &dstSize, int level) { | ||
| auto &instance = GetSingletonInstance(); | ||
|
|
||
| // Get maximum size of the compressed buffer and allocate it. | ||
| auto dstBufferSize = ZSTD_compressBound(srcSize); | ||
| auto dstBuffer = std::unique_ptr<char>(new char[dstBufferSize]); | ||
|
|
||
| // Compress the input buffer. | ||
| dstSize = ZSTD_compressCCtx( | ||
| static_cast<ZSTD_CCtx *>(instance.m_ZSTD_compression_ctx), | ||
| static_cast<void *>(dstBuffer.get()), dstBufferSize, | ||
| static_cast<const void *>(src), srcSize, level); | ||
|
|
||
| // Store the error code if compression failed. | ||
| if (ZSTD_isError(dstSize)) | ||
| instance.m_lastError = dstSize; | ||
| else | ||
| instance.m_lastError = 0; | ||
|
|
||
| // Pass ownership of the buffer to the caller. | ||
| return dstBuffer; | ||
| } | ||
|
|
||
| static std::unique_ptr<unsigned char> | ||
uditagarwal97 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| DecompressBlob(const char *src, size_t srcSize, size_t &dstSize) { | ||
| auto &instance = GetSingletonInstance(); | ||
|
|
||
| // Size of decompressed image can be larger than what we can allocate | ||
| // on heap. In that case, we need to use streaming decompression. | ||
| // TODO: Throw if the decompression size is too large. | ||
| auto dstBufferSize = ZSTD_getFrameContentSize(src, srcSize); | ||
|
|
||
| if (dstBufferSize == ZSTD_CONTENTSIZE_UNKNOWN || | ||
| dstBufferSize == ZSTD_CONTENTSIZE_ERROR) { | ||
|
|
||
| std::cerr << "Error determining size of uncompressed data\n"; | ||
| dstSize = 0; | ||
| instance.m_lastError = dstBufferSize; | ||
| return nullptr; | ||
| } | ||
|
|
||
| // Allocate buffer for decompressed data. | ||
| auto dstBuffer = | ||
| std::unique_ptr<unsigned char>(new unsigned char[dstBufferSize]); | ||
|
|
||
| dstSize = ZSTD_decompressDCtx( | ||
| static_cast<ZSTD_DCtx *>(instance.m_ZSTD_decompression_ctx), | ||
| static_cast<void *>(dstBuffer.get()), dstBufferSize, | ||
| static_cast<const void *>(src), srcSize); | ||
|
|
||
| // In case of decompression error, return the error message and set dstSize | ||
| // to 0. | ||
| if (ZSTD_isError(dstSize)) { | ||
| instance.m_lastError = dstSize; | ||
| dstSize = 0; | ||
| } | ||
|
|
||
| // Pass ownership of the buffer to the caller. | ||
| return dstBuffer; | ||
| } | ||
|
|
||
| // Data fields | ||
| private: | ||
| int m_lastError; | ||
| // ZSTD context. Reusing ZSTD context speeds up subsequent (de)compression. | ||
| // Storing as void* to avoid including ZSTD headers in this file. | ||
| void *m_ZSTD_compression_ctx; | ||
uditagarwal97 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| void *m_ZSTD_decompression_ctx; | ||
| }; | ||
| } // namespace detail | ||
| } // namespace _V1 | ||
| } // namespace sycl | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we shouldn't turn on these by default? We should just turn on them in
if args.ci_defaults:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea behind turning them on by default is to build the compiler with device image compression support if the user has zstd-dev package installed. If zstd is not found, there shouldn't be any build error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are configurate failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CMake Error at lib/Support/CMakeLists.txt:327 (get_property):
get_property could not find TARGET zstd::libzstd_static. Perhaps it has
not yet been created.
CMake Error at lib/Support/CMakeLists.txt:330 (get_property):
get_property could not find TARGET zstd::libzstd_static. Perhaps it has
not yet been created.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah local build is cooked for me too, we need to revert the on by zstd default part at least
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR to temporarily turn off zstd by default: #15833