Refreshing VM performance by separating verification and tweaking buffer access. (#12426)
This moves much of the interpreter checks to an ahead-of-time bytecode
verifier. This allows us to share the same verification with JITs and
disable it entirely for code size reasons using the
`-DIREE_VM_BYTECODE_VERIFICATION_ENABLE=0` compiler flag. Verification
is pretty exhaustive but may still need some additions. It's
significantly better than before, though, so even if not the final form
it's a good step. Simpler verification (and dispatch) will come with a
pending bytecode shuffling into instruction classes.
Since this required breaking binary compatibility I did some deferred
changes that had been sitting in
https://github.com/openxla/iree/projects/32, namely requirement bits and
fixing vm.buffer.fill from bytes to elements.
The requirements give us much nicer error messages by supporting
per-module and per-function bitfields indicating features required to
execute the bytecode they contain, e.g.:
```
D:\Dev\iree\runtime\src\iree\vm\bytecode\module.c:309: INVALID_ARGUMENT; required module features [EXT_F32] are not available in this runtime configuration; have [] while module requires [EXT_F32]; while invoking native function hal.executable.create; while calling import;
[ 1] native hal.executable.create:0 -
[ 0] bytecode module.__init:446 D:\Dev\iree/tests/e2e/models/unidirectional_lstm.mlir:0:0
```
Splitting out the verification from dispatch is good for code
reuse/optionality but also now lets us dispatch without verifying.
Between removing the inlined verification/register masking/etc and
streamlining buffer access we get a near 2x speedup of compute-heavy
VMVX workloads. resnet50 for example on my ryzen system (with
`--iree-vm-target-index-bits=64`, which I need to make default):
```
before:
1 core: BM_predict/process_time/real_time 343774 ms 343766 ms 1 items_per_second=2.90888m/s
8 core: BM_predict/process_time/real_time 48306 ms 361156 ms 1 items_per_second=0.0207012/s
32 core: BM_predict/process_time/real_time 18943 ms 408922 ms 2 items_per_second=0.0527891/s
after:
1 core: BM_predict/process_time/real_time 147856 ms 147859 ms 1 items_per_second=6.76332m/s
8 core: BM_predict/process_time/real_time 21569 ms 158781 ms 1 items_per_second=0.0463637/s
32 core: BM_predict/process_time/real_time 8962 ms 186276 ms 3 items_per_second=0.111579/s
```
About ~20-30% of the remaining runtime is spent in bytecode dispatch
which needs a larger op table reworking to make better. That'll also
reduce code size quite a bit as today we have a lot of duplicate
decoding work. The most expensive ops remaining are buffer loads/stores
and short of JIT or scatter/gather such as #8477 there's not much to do
besides less work. Today codegen is producing some phenomenally bad code
and we're executing ~100x+ more instructions than required
(https://gist.github.com/benvanik/e2b45891e02baf8318109b60189a1b12 for
example - that's _a lot_ of loop arithmetic, a useless fill that should
be removed or at least turned into a util.buffer.fill, and unfused
writebacks) - even without op table shuffling or microkernels we should
be well under 900ms instead of 9000ms. We've also got to parameterize
our workgroup distribution - today we don't tend to use more than 4-16
cores so we don't see the latency improvement we'd expect going 8->32
cores (16 cores is nearly identical).
These issues also impact emitc paths as the C compiler downstream of
that is dealing with all this difficult to analyze output and can't do
much. I thought an emitc resnet with inline VMVX would be a good
approximation of what a JIT could do and it's not good: 448154ms vs the
147856ms of the interpreter! It's also 2x larger in size on disk (380KB
x86_64 vs 200KB bytecode) and that's prior to optimization of the
bytecode. We should definitely be able to do 2-4x faster with a naïve
JIT - if not 10x!
Fixes #5732.
Fixes #12373.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2295e77..c34cc8c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -205,8 +205,6 @@
# Attempt to restore from cache unconditionally.
# Note: this will first try to grab a cache entry for this exact commit
# then it will fall back to the latest for any commit.
- # We're pretty limited on repository cache space (10GB) relative to cache
- # entry size (~2.5GB), so this usage is pretty optimistic.
- name: "Fetching cache (CMake/ccache)"
uses: actions/cache/restore@58c146cc91c5b9e778e71775dfe9bf1442ad9a12 # v3.2.3
with:
@@ -238,9 +236,15 @@
IREE_READ_LOCAL_CCACHE: 1
IREE_WRITE_LOCAL_CCACHE: ${{ needs.setup.outputs.write-caches }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
- # A full build is around 2.5GB, but uploads/downloads are slow and
- # we have a limit of 10GB per repository
- CCACHE_MAXSIZE: 3G
+ # Cache size and compression level settings are a delicate balance.
+ # * A full build cache is around 2-5GB depending on compression level
+ # * Upload/download is slow (double compression may or may not help)
+ # * We have a limit of 10GB across all cached files per repository
+ # * Cache misses are quite costly:
+ # * 99% cache hits -> ~5 minutes to build
+ # * 20% cache hits -> ~15-20 minutes to build
+ CCACHE_MAXSIZE: 4G
+ CCACHE_COMPRESSLEVEL: 5
run: ./build_tools/cmake/build_all.sh "${BUILD_DIR}"
- name: "Testing IREE"
run: ./build_tools/cmake/ctest_all.sh "${BUILD_DIR}"
diff --git a/build_tools/bazel/iree.bazelrc b/build_tools/bazel/iree.bazelrc
index 3bf77da..827036a 100644
--- a/build_tools/bazel/iree.bazelrc
+++ b/build_tools/bazel/iree.bazelrc
@@ -5,14 +5,6 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
###############################################################################
-# Short-hand config settings for IREE specific features.
-###############################################################################
-
-# Equivalent to CMake flag IREE_ENABLE_RUNTIME_TRACING.
-# Builds the runtime with tracing support enabled.
-build --flag_alias=iree_enable_runtime_tracing=@tracy_client//:runtime_enable
-
-###############################################################################
# Common flags that apply to all configurations.
# Use sparingly for things common to all compilers and platforms.
###############################################################################
@@ -45,6 +37,10 @@
# Build settings flag aliases. See https://bazel.build/rules/config
###############################################################################
build --flag_alias=iree_drivers=//runtime/src/iree/hal/drivers:enabled_drivers
+build --flag_alias=iree_link_compiler_shared=//compiler/src/iree/compiler/API2:link_shared
+# Equivalent to CMake flag IREE_ENABLE_RUNTIME_TRACING.
+# Builds the runtime with tracing support enabled.
+build --flag_alias=iree_enable_runtime_tracing=@tracy_client//:runtime_enable
###############################################################################
# Options for "generic_gcc" builds
diff --git a/build_tools/bazel_to_cmake/bazel_to_cmake_converter.py b/build_tools/bazel_to_cmake/bazel_to_cmake_converter.py
index 798d64f..ba805a5 100644
--- a/build_tools/bazel_to_cmake/bazel_to_cmake_converter.py
+++ b/build_tools/bazel_to_cmake/bazel_to_cmake_converter.py
@@ -49,6 +49,12 @@
}
+def _should_skip_target(tags=None, **kwargs):
+ if tags and "skip-bazel_to_cmake" in tags:
+ return True
+ return False
+
+
def _convert_timeout_arg_block(name, value):
if value is None:
return ""
@@ -221,6 +227,9 @@
def alias(self, *args, **kwargs):
pass
+ def bool_flag(self, *args, **kwargs):
+ pass
+
def load(self, *args, **kwargs):
pass
@@ -255,6 +264,8 @@
self._convert_unimplemented_function("filegroup", name)
def sh_binary(self, name, **kwargs):
+ if _should_skip_target(**kwargs):
+ return
self._convert_unimplemented_function("sh_binary", name)
def enforce_glob(self, files, **kwargs):
@@ -299,9 +310,11 @@
def platform_trampoline_deps(self, basename, path="base"):
return [f"//{path}/internal:{basename}_internal"]
- def select(self, d):
- self._convert_unimplemented_function("select", str(d))
- return d["//conditions:default"]
+ def select(self, selector):
+ default_value = selector.get("//conditions:default")
+ if default_value is None:
+ raise ValueError("bazel_to_cmake can only convert selects with a default")
+ return default_value
def cc_library(self,
name,
@@ -316,6 +329,8 @@
linkopts=None,
includes=None,
**kwargs):
+ if _should_skip_target(**kwargs):
+ return
if linkopts:
self._convert_unimplemented_function("linkopts")
name_block = _convert_string_arg_block("NAME", name, quote=False)
@@ -363,6 +378,8 @@
tags=None,
includes=None,
**kwargs):
+ if _should_skip_target(tags=tags, **kwargs):
+ return
name_block = _convert_string_arg_block("NAME", name, quote=False)
hdrs_block = _convert_string_list_block("HDRS", hdrs, sort=True)
srcs_block = _convert_srcs_block(srcs)
@@ -406,6 +423,8 @@
testonly=None,
includes=None,
**kwargs):
+ if _should_skip_target(**kwargs):
+ return
if linkopts:
self._convert_unimplemented_function("linkopts")
name_block = _convert_string_arg_block("NAME", name, quote=False)
@@ -442,6 +461,8 @@
identifier=None,
deps=None,
**kwargs):
+ if _should_skip_target(**kwargs):
+ return
name_block = _convert_string_arg_block("NAME", name, quote=False)
srcs_block = _convert_srcs_block(srcs)
c_file_output_block = _convert_string_arg_block("C_FILE_OUTPUT",
@@ -565,6 +586,8 @@
f")\n\n")
def iree_gentbl_cc_library(self, **kwargs):
+ if _should_skip_target(**kwargs):
+ return
# The bazel version of this rule adds some include directories and defs
# that are implicitly handled by the cmake version.
self.gentbl_cc_library(**kwargs)
@@ -598,6 +621,8 @@
tags=None,
timeout=None,
**kwargs):
+ if _should_skip_target(tags=tags, **kwargs):
+ return
name_block = _convert_string_arg_block("NAME", name, quote=False)
srcs_block = _convert_srcs_block(srcs)
tools_block = _convert_target_list_block("TOOLS", tools)
@@ -626,6 +651,8 @@
target_cpu_features=None,
timeout=None,
**kwargs):
+ if _should_skip_target(tags=tags, **kwargs):
+ return
name_block = _convert_string_arg_block("NAME", name, quote=False)
srcs_block = _convert_srcs_block(srcs)
target_backend_block = _convert_string_arg_block("TARGET_BACKEND",
@@ -661,6 +688,8 @@
target_cpu_features_variants=None,
timeout=None,
**kwargs):
+ if _should_skip_target(tags=tags, **kwargs):
+ return
target_backends = None
drivers = None
if target_backends_and_drivers is not None:
@@ -703,6 +732,8 @@
tags=None,
target_cpu_features_variants=None,
**kwargs):
+ if _should_skip_target(tags=tags, **kwargs):
+ return
target_backends = None
drivers = None
if target_backends_and_drivers is not None:
@@ -749,6 +780,8 @@
data=None,
tags=None,
timeout=None):
+ if _should_skip_target(tags=tags):
+ return
if data is not None:
self._convert_unimplemented_function("native_test", name + " has data")
@@ -779,7 +812,8 @@
# unused
size="small",
timeout=None):
-
+ if _should_skip_target(tags=tags):
+ return
name_block = _convert_string_arg_block("NAME", name, quote=False)
srcs_block = _convert_srcs_block(srcs)
data_block = _convert_target_list_block("DATA", data)
diff --git a/compiler/bindings/c/BUILD b/compiler/bindings/c/BUILD
index a5cdac9..6f0144a 100644
--- a/compiler/bindings/c/BUILD
+++ b/compiler/bindings/c/BUILD
@@ -33,4 +33,20 @@
],
)
-# TODO: Support loader_test in Bazel.
+cc_test(
+ name = "loader_test",
+ srcs = [
+ "iree/compiler/loader/loader_test.c",
+ ],
+ args = [
+ "lib/libIREECompiler.so",
+ ],
+ data = [
+ "//lib:libIREECompiler.so",
+ ],
+ tags = ["skip-bazel_to_cmake"],
+ deps = [
+ ":headers",
+ ":loader",
+ ],
+)
diff --git a/compiler/src/iree/compiler/API2/BUILD b/compiler/src/iree/compiler/API2/BUILD
index eabcec4..e44f131 100644
--- a/compiler/src/iree/compiler/API2/BUILD
+++ b/compiler/src/iree/compiler/API2/BUILD
@@ -4,6 +4,7 @@
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("//build_tools/bazel:build_defs.oss.bzl", "iree_compiler_cc_library")
package(
@@ -49,12 +50,30 @@
],
)
-# Bazel does not have a well-defined mechanism for managing
-# shared libraries in a cross-platform way. Therefore, we
-# only support static linking of tools. Separately, we do
-# build an actual shared library for platforms that support
-# it, but this is only done to support stub use-cases.
+iree_compiler_cc_library(
+ name = "SharedImpl",
+ srcs = [
+ "//lib:libIREECompiler.so",
+ ],
+ tags = ["skip-bazel_to_cmake"],
+)
+
+bool_flag(
+ name = "link_shared",
+ build_setting_default = False,
+)
+
+config_setting(
+ name = "link_shared_config",
+ flag_values = {
+ ":link_shared": "True",
+ },
+)
+
alias(
name = "Impl",
- actual = ":StaticImpl",
+ actual = select({
+ ":link_shared_config": ":SharedImpl",
+ "//conditions:default": ":StaticImpl",
+ }),
)
diff --git a/compiler/src/iree/compiler/Codegen/Common/BUILD b/compiler/src/iree/compiler/Codegen/Common/BUILD
index cb4597a..78bad29 100644
--- a/compiler/src/iree/compiler/Codegen/Common/BUILD
+++ b/compiler/src/iree/compiler/Codegen/Common/BUILD
@@ -132,6 +132,7 @@
"TestExecutablePreprocessing.cpp",
"TestPartitionableLoopsInterface.cpp",
"TileAndDistributeToWorkgroupsPass.cpp",
+ "TileDispatchUsingInterface.cpp",
"TypePropagationPass.cpp",
"VectorizePackUnPackOps.cpp",
"VectorizePad.cpp",
@@ -216,9 +217,6 @@
"MemrefCopyToLinalg.cpp",
"PadDynamicAlloc.cpp",
"RemoveTrivialLoops.cpp",
- "TestPartitionableLoopsInterface.cpp",
- "TileAndDistributeToWorkgroupsPass.cpp",
- "TileDispatchUsingInterface.cpp",
"UserConfig.cpp",
"VectorReductionToGPU.cpp",
"WorkGroupSwizzle.cpp",
diff --git a/compiler/src/iree/compiler/Codegen/Common/CMakeLists.txt b/compiler/src/iree/compiler/Codegen/Common/CMakeLists.txt
index e887349..1b0e4e3 100644
--- a/compiler/src/iree/compiler/Codegen/Common/CMakeLists.txt
+++ b/compiler/src/iree/compiler/Codegen/Common/CMakeLists.txt
@@ -107,6 +107,7 @@
"TestExecutablePreprocessing.cpp"
"TestPartitionableLoopsInterface.cpp"
"TileAndDistributeToWorkgroupsPass.cpp"
+ "TileDispatchUsingInterface.cpp"
"TypePropagationPass.cpp"
"VectorizePackUnPackOps.cpp"
"VectorizePad.cpp"
@@ -189,9 +190,6 @@
"MemrefCopyToLinalg.cpp"
"PadDynamicAlloc.cpp"
"RemoveTrivialLoops.cpp"
- "TestPartitionableLoopsInterface.cpp"
- "TileAndDistributeToWorkgroupsPass.cpp"
- "TileDispatchUsingInterface.cpp"
"UserConfig.cpp"
"VectorReductionToGPU.cpp"
"WorkGroupSwizzle.cpp"
diff --git a/compiler/src/iree/compiler/API2/Shlib/Posix/BUILD.bazel b/lib/BUILD
similarity index 64%
rename from compiler/src/iree/compiler/API2/Shlib/Posix/BUILD.bazel
rename to lib/BUILD
index 9923488..daf2d17 100644
--- a/compiler/src/iree/compiler/API2/Shlib/Posix/BUILD.bazel
+++ b/lib/BUILD
@@ -1,16 +1,15 @@
-# Copyright 2022 The IREE Authors
+# Copyright 2023 The IREE Authors
#
# Licensed 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
-# bazel-to-cmake: skip
+package(
+ default_visibility = ["//visibility:public"],
+ features = ["layering_check"],
+ licenses = ["notice"], # Apache 2.0
+)
-# This is problematic on anything non-Linux but Bazel has failed
-# for years to provide the flexibility to make it any better. Yolo.
-# We really only support this for some very narrow use cases that
-# need to dynamically load the compiler as a shared library, and other
-# uses are served by the cross-platform CMake build.
cc_binary(
name = "libIREECompiler.so",
srcs = [
diff --git a/runtime/src/iree/builtins/device/BUILD b/runtime/src/iree/builtins/device/BUILD
index 29e48e6..531d733 100644
--- a/runtime/src/iree/builtins/device/BUILD
+++ b/runtime/src/iree/builtins/device/BUILD
@@ -42,7 +42,7 @@
iree_cmake_extra_content(
content = """
-if(NOT IREE_BUILD_COMPILER)
+if(NOT IREE_BUILD_COMPILER OR NOT IREE_TARGET_BACKEND_LLVM_CPU)
return()
endif()
""",
diff --git a/runtime/src/iree/builtins/device/CMakeLists.txt b/runtime/src/iree/builtins/device/CMakeLists.txt
index ca2bd01..0b01c73 100644
--- a/runtime/src/iree/builtins/device/CMakeLists.txt
+++ b/runtime/src/iree/builtins/device/CMakeLists.txt
@@ -22,7 +22,7 @@
PUBLIC
)
-if(NOT IREE_BUILD_COMPILER)
+if(NOT IREE_BUILD_COMPILER OR NOT IREE_TARGET_BACKEND_LLVM_CPU)
return()
endif()