This is an automated email from the ASF dual-hosted git repository.
junrushao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 67df20faee [Target][TOPI] Use LLVM for x86 CPU feature lookup (#15685)
67df20faee is described below
commit 67df20faeeffaf3c145698783f6cd157b8cdbd9c
Author: Balint Cristian <[email protected]>
AuthorDate: Thu Sep 14 22:06:31 2023 +0300
[Target][TOPI] Use LLVM for x86 CPU feature lookup (#15685)
This PR leverage LLVM itself for CPU features lookup, replacing hard-coded
lists.
In order to keep maintainability with X86 families & features we can rely
on LLVM.
---
Changes:
* Introduce a single ```target_has_feature(XXX)``` replacing all
```target_has_XXX()```
* PY+FFI: expose new ```llvm_x86_get_archlist```,
```llvm_x86_get_features``` & ```llvm_x86_has_feature```
* PY: expose new ```target_has_feature``` wrapper to
```_ffi.llvm_x86_has_feature```
---
There is a test unit for a comprehensive check with the old behaviour.
For better reliability, this way of feature checking can be implemented for
other arches.
---
include/tvm/target/target_kind.h | 4 +-
python/tvm/relay/qnn/op/legalizations.py | 5 +-
python/tvm/relay/qnn/op/qnn.py | 7 +-
python/tvm/target/codegen.py | 32 ++++
python/tvm/target/x86.py | 161 +++++--------------
python/tvm/topi/x86/batch_matmul.py | 16 +-
python/tvm/topi/x86/conv2d_int8.py | 10 +-
python/tvm/topi/x86/dense.py | 16 +-
python/tvm/topi/x86/dense_alter_op.py | 11 +-
python/tvm/topi/x86/tensor_intrin.py | 10 +-
.../space_generator/space_generator.cc | 23 +--
src/relay/qnn/op/requantize.cc | 9 +-
src/relay/qnn/op/requantize_config.h | 13 +-
src/target/llvm/llvm_module.cc | 156 ++++++++++++++++++
tests/python/target/test_x86_features.py | 176 +++++++++++++++++++++
15 files changed, 473 insertions(+), 176 deletions(-)
diff --git a/include/tvm/target/target_kind.h b/include/tvm/target/target_kind.h
index 86f386abb8..130aea32f8 100644
--- a/include/tvm/target/target_kind.h
+++ b/include/tvm/target/target_kind.h
@@ -130,10 +130,10 @@ class TargetKind : public ObjectRef {
*/
TVM_DLL static Optional<TargetKind> Get(const String& target_kind_name);
TVM_DEFINE_NOTNULLABLE_OBJECT_REF_METHODS(TargetKind, ObjectRef,
TargetKindNode);
-
- private:
/*! \brief Mutable access to the container class */
TargetKindNode* operator->() { return
static_cast<TargetKindNode*>(data_.get()); }
+
+ private:
TVM_DLL static const AttrRegistryMapContainerMap<TargetKind>&
GetAttrMapContainer(
const String& attr_name);
friend class TargetKindRegEntry;
diff --git a/python/tvm/relay/qnn/op/legalizations.py
b/python/tvm/relay/qnn/op/legalizations.py
index 8dc54a19b9..ce45b2e759 100644
--- a/python/tvm/relay/qnn/op/legalizations.py
+++ b/python/tvm/relay/qnn/op/legalizations.py
@@ -23,7 +23,7 @@ from tvm import relay
from tvm._ffi.base import TVMError
from tvm.relay.qnn.op.canonicalizations import create_integer_lookup_op
-from ....target.x86 import target_has_sse42
+from ....target.x86 import target_has_features
from ....topi.utils import is_target
from .. import op as reg
@@ -457,8 +457,7 @@ def helper_change_dtypes_to_be_same(attrs, inputs, types,
relay_op):
def is_fast_int8_on_intel():
"""Checks whether the hardware has support for fast Int8 arithmetic
operations."""
- target = tvm.target.Target.current(allow_none=False)
- return target_has_sse42(target.mcpu)
+ return target_has_features("sse4.2")
# Helper function to align up given value.
diff --git a/python/tvm/relay/qnn/op/qnn.py b/python/tvm/relay/qnn/op/qnn.py
index 968935f062..312fd18bf6 100644
--- a/python/tvm/relay/qnn/op/qnn.py
+++ b/python/tvm/relay/qnn/op/qnn.py
@@ -27,7 +27,7 @@ from tvm.relay.op.nn.utils import get_pad_tuple2d
from tvm.runtime import Object
from tvm.target import Target
from tvm.topi.nn.qnn import SQNN_DTYPE_TO_CODE
-from tvm.target.x86 import target_has_sse41
+from tvm.target.x86 import target_has_features
from . import _make, _requantize
@@ -54,8 +54,9 @@ class RequantizeConfig(Object):
@staticmethod
def _get_node_default_compute_dtype():
target = Target.current(True)
- if target and str(target.kind) == "llvm" and
target_has_sse41(target.mcpu):
- return "float32"
+ if target and str(target.kind) == "llvm":
+ if target_has_features("sse4.1", target):
+ return "float32"
return "int64"
diff --git a/python/tvm/target/codegen.py b/python/tvm/target/codegen.py
index eacaf45fff..5d43f4ae24 100644
--- a/python/tvm/target/codegen.py
+++ b/python/tvm/target/codegen.py
@@ -71,6 +71,38 @@ def llvm_get_intrinsic_name(intrin_id: int) -> str:
return _ffi_api.llvm_get_intrinsic_name(intrin_id)
+def llvm_x86_get_archlist(only64bit=False):
+ """Get X86 CPU name list.
+
+ Parameters
+ ----------
+ only64bit : bool
+ Filter 64bit architectures.
+
+ Returns
+ -------
+ features : list[str]
+ String list of X86 architectures.
+ """
+ return _ffi_api.llvm_x86_get_archlist(only64bit)
+
+
+def llvm_x86_get_features(cpu_name):
+ """Get X86 CPU features.
+
+ Parameters
+ ----------
+ cpu_name : string
+ X86 CPU name (e.g. "skylake").
+
+ Returns
+ -------
+ features : list[str]
+ String list of X86 CPU features.
+ """
+ return _ffi_api.llvm_x86_get_features(cpu_name)
+
+
def llvm_version_major(allow_none=False):
"""Get the major LLVM version.
diff --git a/python/tvm/target/x86.py b/python/tvm/target/x86.py
index b08f0be98c..a3dcb62e8a 100644
--- a/python/tvm/target/x86.py
+++ b/python/tvm/target/x86.py
@@ -16,127 +16,48 @@
# under the License.
"""Common x86 related utilities"""
from .._ffi import register_func
-from .target import Target
-
-
-@register_func("tvm.target.x86.target_has_sse41")
-def target_has_sse41(target):
- return (
- target_has_sse42(target)
- or target_has_avx(target)
- or target_has_avx2(target)
- or target_has_avx512(target)
- or target_has_vnni(target)
- or target
- in {
- "btver2",
- "penryn",
- }
- )
-
-
-@register_func("tvm.target.x86.target_has_sse42")
-def target_has_sse42(target):
- return (
- target_has_avx(target)
- or target_has_avx2(target)
- or target_has_avx512(target)
- or target_has_vnni(target)
- or target
- in {
- "silvermont",
- "slm",
- "goldmont",
- "goldmont-plus",
- "tremont",
- "nehalem",
- "corei7",
- "westmere",
- "bdver1",
- "bdver2",
- "bdver3",
- "x86-64-v2",
- }
- )
-
-
-@register_func("tvm.target.x86.target_has_avx")
-def target_has_avx(target):
- return (
- target_has_avx2(target)
- or target_has_avx512(target)
- or target_has_vnni(target)
- or target in {"sandybridge", "corei7-avx", "ivybridge", "core-avx-i"}
- )
-
-
-@register_func("tvm.target.x86.target_has_avx2")
-def target_has_avx2(target):
- return (
- target_has_avx512(target)
- or target_has_vnni(target)
- or target
- in {
- "haswell",
- "core-avx2",
- "broadwell",
- "skylake",
- "bdver4",
- "znver1",
- "znver2",
- "znver3",
- "x86-64-v3",
- }
- )
-
-
-@register_func("tvm.target.x86.target_has_avx512")
-def target_has_avx512(target):
- return target in {
- "skylake-avx512",
- "skx",
- "knl",
- "knm",
- "x86-64-v4",
- "cannonlake",
- # explicit enumeration of VNNI capable due to collision with alderlake
- "cascadelake",
- "icelake-client",
- "icelake-server",
- "rocketlake",
- "tigerlake",
- "cooperlake",
- "sapphirerapids",
- }
-
-
-@register_func("tvm.target.x86.target_has_vnni")
-def target_has_vnni(target):
- return target in {
- "cascadelake",
- "icelake-client",
- "icelake-server",
- "rocketlake",
- "tigerlake",
- "cooperlake",
- "sapphirerapids",
- "alderlake",
- }
-
-
-@register_func("tvm.target.x86.target_has_amx")
-def target_has_amx(target):
- return target in {
- "sapphirerapids",
- }
+from . import _ffi_api
+from ..ir.container import Array
+
+
+@register_func("tvm.target.x86.target_has_features")
+def target_has_features(features, target=None):
+ """Check X86 CPU features.
+ Parameters
+ ----------
+ features : str or Array
+ Feature(s) to check.
+ target : Target
+ Optional TVM target, default `None` use the global context target.
+ Returns
+ -------
+ has_feats : bool
+ True if feature(s) are in the target arch.
+ """
+ has_feats = True
+ assert isinstance(features, (Array, str))
+ features = [features] if isinstance(features, str) else features
+ for feat in features:
+ has_feats &= _ffi_api.llvm_x86_has_feature(feat, target)
+ return has_feats
@register_func("tvm.topi.x86.utils.get_simd_32bit_lanes")
def get_simd_32bit_lanes():
- mcpu = Target.current().mcpu
- fp32_vec_len = 4
- if target_has_avx512(mcpu):
- fp32_vec_len = 16
- elif target_has_avx2(mcpu):
- fp32_vec_len = 8
- return fp32_vec_len
+ """X86 SIMD optimal vector length lookup.
+ Parameters
+ ----------
+ Returns
+ -------
+ vec_len : int
+ The optimal vector length of CPU from the global context target.
+ """
+ vec_len = 4
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ if target_has_features(["avx512bw", "avx512f"]):
+ vec_len = 16
+ elif target_has_features("avx2"):
+ vec_len = 8
+ return vec_len
diff --git a/python/tvm/topi/x86/batch_matmul.py
b/python/tvm/topi/x86/batch_matmul.py
index 96b1433268..85accab87b 100644
--- a/python/tvm/topi/x86/batch_matmul.py
+++ b/python/tvm/topi/x86/batch_matmul.py
@@ -21,7 +21,7 @@ import tvm
from tvm import autotvm, te
from tvm.autotvm.task.space import SplitEntity
from tvm.contrib import cblas, mkl
-from tvm.target.x86 import target_has_amx, target_has_avx512
+from tvm.target.x86 import target_has_features
from .. import generic, nn
from ..transform import layout_transform
@@ -38,8 +38,10 @@ def batch_matmul_int8_compute(cfg, x, y, *_):
packed_y = layout_transform(y, "BNK", packed_y_layout)
_, n_o, _, n_i, _ = packed_y.shape
ak = te.reduce_axis((0, k), name="k")
- mcpu = tvm.target.Target.current().mcpu
- if target_has_avx512(mcpu):
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ if target_has_features(["avx512bw", "avx512f"]):
attrs_info = {"schedule_rule": "batch_matmul_int8"}
else:
attrs_info = None
@@ -233,14 +235,16 @@ def schedule_batch_matmul(cfg, outs):
def schedule_batch_matmul_int8(cfg, outs):
"""Schedule for batch_matmul_int8"""
s = te.create_schedule([x.op for x in outs])
- mcpu = tvm.target.Target.current().mcpu
def _callback(op):
if "batch_matmul_int8" in op.tag:
layout_trans = op.input_tensors[1]
- if target_has_amx(mcpu):
+ if target_has_features("amx-int8"):
batch_matmul_amx_schedule(cfg, s, op.output(0), outs[0],
layout_trans)
- elif target_has_avx512(mcpu):
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ elif target_has_features(["avx512bw", "avx512f"]):
batch_matmul_int8_schedule(cfg, s, op.output(0), outs[0],
layout_trans)
traverse_inline(s, outs[0].op, _callback)
diff --git a/python/tvm/topi/x86/conv2d_int8.py
b/python/tvm/topi/x86/conv2d_int8.py
index 9d32534352..7c01967e87 100644
--- a/python/tvm/topi/x86/conv2d_int8.py
+++ b/python/tvm/topi/x86/conv2d_int8.py
@@ -20,7 +20,7 @@
import tvm
from tvm import autotvm, te
-from tvm.target.x86 import target_has_sse42
+from tvm.target.x86 import target_has_features
from .. import nn, tag
from ..generic import conv2d as conv2d_generic
@@ -49,7 +49,10 @@ def _get_default_config_int8(
"""
if is_depthwise:
# Fallback to FP32 default config until a VNNI schedule is defined.
- wkl = _get_depthwise_conv2d_workload(data, kernel, strides, padding,
out_dtype)
+ wkl = _get_depthwise_conv2d_workload(
+ data, kernel, strides, padding, dilation, out_dtype, layout
+ )
+
from .depthwise_conv2d import _fallback_schedule
_fallback_schedule(cfg, wkl)
@@ -81,8 +84,7 @@ def is_int8_hw_support(data_dtype, kernel_dtype):
is_llvm_support = llvm_version >= 8
# 3) Check target
- mcpu = tvm.target.Target.current().mcpu
- is_target_support = target_has_sse42(mcpu)
+ is_target_support = target_has_features("sse4.2")
return is_dtype_support and is_llvm_support and is_target_support
diff --git a/python/tvm/topi/x86/dense.py b/python/tvm/topi/x86/dense.py
index a31127065e..2437b1a695 100644
--- a/python/tvm/topi/x86/dense.py
+++ b/python/tvm/topi/x86/dense.py
@@ -23,7 +23,7 @@ import tvm
from tvm import autotvm, te
from tvm.autotvm.task.space import SplitEntity
from tvm.contrib import cblas, dnnl, mkl
-from tvm.target.x86 import get_simd_32bit_lanes, target_has_amx,
target_has_avx512
+from tvm.target.x86 import get_simd_32bit_lanes, target_has_features
from .. import generic, tag
from ..utils import get_const_tuple, traverse_inline
@@ -298,13 +298,15 @@ def dense_int8(cfg, data, weight, bias=None,
out_dtype=None):
def schedule_dense_int8(cfg, outs):
"""Create a schedule for dense__int8"""
s = te.create_schedule([x.op for x in outs])
- mcpu = tvm.target.Target.current().mcpu
def _callback(op):
if "dense_int8" in op.tag:
- if target_has_amx(mcpu):
+ if target_has_features("amx-int8"):
dense_amx_int8_schedule(cfg, s, op.output(0), outs[0])
- elif target_has_avx512(mcpu):
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ elif target_has_features(["avx512bw", "avx512f"]):
dense_int8_schedule(cfg, s, op.output(0), outs[0])
traverse_inline(s, outs[0].op, _callback)
@@ -316,8 +318,10 @@ def dense_int8_compute(cfg, X, packed_w, bias=None):
m, k = X.shape
n_o, _, n_i, _ = packed_w.shape
ak = te.reduce_axis((0, k), name="k")
- mcpu = tvm.target.Target.current().mcpu
- if target_has_avx512(mcpu):
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ if target_has_features(["avx512bw", "avx512f"]):
target_attr = {"schedule_rule": "meta_schedule.x86.dense_int8"}
else:
target_attr = None
diff --git a/python/tvm/topi/x86/dense_alter_op.py
b/python/tvm/topi/x86/dense_alter_op.py
index 973f94ecb9..ef6df7dd2c 100644
--- a/python/tvm/topi/x86/dense_alter_op.py
+++ b/python/tvm/topi/x86/dense_alter_op.py
@@ -19,7 +19,7 @@
import tvm
from tvm import autotvm, relay, te
-from tvm.target.x86 import target_has_amx, target_has_avx512
+from tvm.target.x86 import target_has_features
from .. import nn
from ..nn import dense_alter_layout
@@ -28,9 +28,12 @@ from .dense import _default_dense_pack_config
def check_int8_applicable(x, y, allow_padding=False):
- mcpu = tvm.target.Target.current().mcpu
- # TODO(vvchernov): may be also target_has_avx2 or lower?
- simd_avai = target_has_avx512(mcpu) or target_has_amx(mcpu)
+ # avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ # avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ # + llvm.x86.avx512.pmaddw.d.512"
+ simd_avai = target_has_features(["avx512bw", "avx512f"])
+ simd_avai |= target_has_features("amx-int8")
+ # TODO(vvchernov): may be also target_has_features("avx2") or lower?
return (
simd_avai
and "int8" in x.dtype
diff --git a/python/tvm/topi/x86/tensor_intrin.py
b/python/tvm/topi/x86/tensor_intrin.py
index 8368f755e9..f2e84a62ec 100644
--- a/python/tvm/topi/x86/tensor_intrin.py
+++ b/python/tvm/topi/x86/tensor_intrin.py
@@ -19,15 +19,15 @@
import tvm
from tvm import te
import tvm.target.codegen
-from tvm.target.x86 import target_has_sse42, target_has_vnni,
get_simd_32bit_lanes
+from tvm.target.x86 import target_has_features, get_simd_32bit_lanes
def dot_16x1x16_uint8_int8_int32():
"""Dispatch the most optimized intrin depending on the target"""
- mcpu = tvm.target.Target.current().mcpu
-
- assert target_has_sse42(mcpu), "An old Intel machine that does not have
fast Int8 support."
- if target_has_vnni(mcpu):
+ assert target_has_features(
+ "sse4.2"
+ ), "An old Intel machine that does not have fast Int8 support."
+ if target_has_features("avx512vnni") or target_has_features("avxvnni"):
# VNNI capable platform
return dot_16x1x16_uint8_int8_int32_cascadelake()
# vpmaddubsw/vpmaddwd fallback
diff --git a/src/meta_schedule/space_generator/space_generator.cc
b/src/meta_schedule/space_generator/space_generator.cc
index c96554e6a2..4657f962f3 100644
--- a/src/meta_schedule/space_generator/space_generator.cc
+++ b/src/meta_schedule/space_generator/space_generator.cc
@@ -24,18 +24,21 @@ namespace meta_schedule {
String GetRuleKindFromTarget(const Target& target) {
if (target->kind->name == "llvm") {
- static const PackedFunc* f_check_vnni =
- runtime::Registry::Get("tvm.target.x86.target_has_vnni");
- ICHECK(f_check_vnni != nullptr) << "The `target_has_vnni` func is not in
tvm registry.";
- if (target->GetAttr<String>("mcpu") &&
- (*f_check_vnni)(target->GetAttr<String>("mcpu").value())) {
+ static const PackedFunc* llvm_x86_has_feature_fn_ptr =
+ runtime::Registry::Get("target.llvm_x86_has_feature");
+ ICHECK(llvm_x86_has_feature_fn_ptr != nullptr)
+ << "The `target.llvm_x86_has_feature` func is not in tvm registry.";
+ bool have_avx512vnni = (*llvm_x86_has_feature_fn_ptr)("avx512vnni",
target);
+ bool have_avxvnni = (*llvm_x86_has_feature_fn_ptr)("avxvnni", target);
+ if (have_avx512vnni || have_avxvnni) {
return "vnni";
} else {
- static const PackedFunc* f_check_avx512 =
- runtime::Registry::Get("tvm.target.x86.target_has_avx512");
- ICHECK(f_check_avx512 != nullptr) << "The `target_has_avx512` func is
not in tvm registry.";
- if (target->GetAttr<String>("mcpu") &&
- (*f_check_avx512)(target->GetAttr<String>("mcpu").value())) {
+ // avx512f: llvm.x86.avx512.addpd.w.512 (LLVM auto, added)
+ // avx512bw: llvm.x86.avx512.pmaddubs.w.512" (TVM required)
+ // + llvm.x86.avx512.pmaddw.d.512"
+ bool have_avx512f = (*llvm_x86_has_feature_fn_ptr)("avx512f", target);
+ bool have_avx512bw = (*llvm_x86_has_feature_fn_ptr)("avx512bw", target);
+ if (have_avx512bw && have_avx512f) {
return "avx512";
}
}
diff --git a/src/relay/qnn/op/requantize.cc b/src/relay/qnn/op/requantize.cc
index 1dd1eae9a8..b57710b266 100644
--- a/src/relay/qnn/op/requantize.cc
+++ b/src/relay/qnn/op/requantize.cc
@@ -121,12 +121,9 @@ InferCorrectLayoutOutput
RequantizeInferCorrectLayout(const Attrs& attrs,
}
bool has_current_target_sse41_support() {
- auto target = Target::Current(true);
- Optional<String> mcpu =
- target.defined() ? target->GetAttr<String>("mcpu") :
Optional<String>(nullptr);
- auto target_has_sse41_fn_ptr =
tvm::runtime::Registry::Get("tvm.target.x86.target_has_sse41");
- ICHECK(target_has_sse41_fn_ptr) << "Function tvm.target.x86.target_has_sse41
not found";
- return mcpu && (*target_has_sse41_fn_ptr)(mcpu.value());
+ auto llvm_x86_has_feature_fn_ptr =
tvm::runtime::Registry::Get("target.llvm_x86_has_feature");
+ ICHECK(llvm_x86_has_feature_fn_ptr) << "Function target.llvm_x86_has_feature
not found";
+ return (*llvm_x86_has_feature_fn_ptr)("sse4.1", Target::Current(true));
}
/*
diff --git a/src/relay/qnn/op/requantize_config.h
b/src/relay/qnn/op/requantize_config.h
index f83789592b..956bc3533b 100644
--- a/src/relay/qnn/op/requantize_config.h
+++ b/src/relay/qnn/op/requantize_config.h
@@ -61,14 +61,13 @@ class RequantizeConfigNode : public Object {
// For the x86 architecture, the float32 computation is expected to give
significant speedup,
// with little loss in the accuracy of the requantize operation.
auto target = Target::Current(true);
- auto target_has_sse41 =
tvm::runtime::Registry::Get("tvm.target.x86.target_has_sse41");
- ICHECK(target_has_sse41) << "Function tvm.target.x86.target_has_sse41 not
found";
- if (target.defined() && target->kind->name == "llvm" &&
- (target->GetAttr<String>("mcpu") &&
- (*target_has_sse41)(target->GetAttr<String>("mcpu").value()))) {
- return "float32";
+ auto llvm_x86_has_feature_fn_ptr =
tvm::runtime::Registry::Get("target.llvm_x86_has_feature");
+ ICHECK(llvm_x86_has_feature_fn_ptr) << "Function
target.llvm_x86_has_feature not found";
+ if (target.defined() && target->kind->name == "llvm") {
+ if ((*llvm_x86_has_feature_fn_ptr)("sse4.1", target)) {
+ return "float32";
+ }
}
-
return "int64";
}
diff --git a/src/target/llvm/llvm_module.cc b/src/target/llvm/llvm_module.cc
index 85750fbf14..168163c416 100644
--- a/src/target/llvm/llvm_module.cc
+++ b/src/target/llvm/llvm_module.cc
@@ -45,6 +45,16 @@
#include <llvm/Support/raw_ostream.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Target/TargetOptions.h>
+#if TVM_LLVM_VERSION < 110
+#include <llvm/MC/MCSubtargetInfo.h>
+#include <llvm/Support/TargetSelect.h>
+#else
+#if TVM_LLVM_VERSION < 170
+#include <llvm/Support/X86TargetParser.h>
+#else
+#include <llvm/TargetParser/X86TargetParser.h>
+#endif
+#endif
#include <llvm/Transforms/Utils/Cloning.h>
#include <tvm/ir/module.h>
#include <tvm/relay/runtime.h>
@@ -77,6 +87,25 @@
#include "codegen_llvm.h"
#include "llvm_instance.h"
+#if TVM_LLVM_VERSION < 110
+namespace llvm {
+// SubtargetSubTypeKV view
+template <ArrayRef<SubtargetSubTypeKV> MCSubtargetInfo::*Member>
+struct ArchViewer {
+ friend ArrayRef<SubtargetSubTypeKV>& archViewer(MCSubtargetInfo Obj) {
return Obj.*Member; }
+};
+template struct ArchViewer<&MCSubtargetInfo::ProcDesc>;
+ArrayRef<SubtargetSubTypeKV>& archViewer(MCSubtargetInfo);
+// SubtargetFeatureKV view
+template <ArrayRef<SubtargetFeatureKV> MCSubtargetInfo::*Member>
+struct FeatViewer {
+ friend ArrayRef<SubtargetFeatureKV>& featViewer(MCSubtargetInfo Obj) {
return Obj.*Member; }
+};
+template struct FeatViewer<&MCSubtargetInfo::ProcFeatures>;
+ArrayRef<SubtargetFeatureKV>& featViewer(MCSubtargetInfo);
+} // namespace llvm
+#endif
+
namespace tvm {
namespace codegen {
@@ -485,6 +514,133 @@
TVM_REGISTER_GLOBAL("target.llvm_get_intrinsic_name").set_body_typed([](int64_t
#endif
});
+#if TVM_LLVM_VERSION < 110
+static const llvm::MCSubtargetInfo* llvm_compat_get_subtargetinfo(const
std::string triple,
+ const
std::string cpu_name) {
+ std::string error;
+ llvm::InitializeAllTargets();
+ llvm::InitializeAllTargetMCs();
+ // create a LLVM x86 instance
+ auto* llvm_instance = llvm::TargetRegistry::lookupTarget(triple, error);
+ // create a target machine
+ llvm::TargetOptions target_options;
+ auto RM = llvm::Optional<llvm::Reloc::Model>();
+ auto* tm = llvm_instance->createTargetMachine(triple, cpu_name.c_str(), "",
target_options, RM);
+ // create subtarget info module
+ const llvm::MCSubtargetInfo* MCInfo = tm->getMCSubtargetInfo();
+
+ return MCInfo;
+}
+
+static const Array<String> llvm_compat_get_archlist(const std::string triple) {
+ // get the subtarget info module
+ const auto* MCInfo = llvm_compat_get_subtargetinfo(triple, "");
+ // get all X86 arches
+ llvm::ArrayRef<llvm::SubtargetSubTypeKV> x86_arches =
+ llvm::archViewer(*(llvm::MCSubtargetInfo*)MCInfo);
+ Array<String> cpu_arches;
+ for (auto& arch : x86_arches) {
+ cpu_arches.push_back(arch.Key);
+ }
+ return cpu_arches;
+}
+
+static const Array<String> llvm_compat_get_features(const std::string triple,
+ const std::string
cpu_name) {
+ // get the subtarget info module
+ const auto* MCInfo = llvm_compat_get_subtargetinfo(triple, cpu_name.c_str());
+ // get all features
+ llvm::ArrayRef<llvm::SubtargetFeatureKV> x86_features =
+ llvm::featViewer(*(llvm::MCSubtargetInfo*)MCInfo);
+ // only targeted CPU features
+ Array<String> cpu_features;
+ for (auto& feat : x86_features) {
+ if (MCInfo->checkFeatures("+" + std::string(feat.Key))) {
+ cpu_features.push_back(feat.Key);
+ }
+ }
+ return cpu_features;
+}
+#endif
+
+TVM_REGISTER_GLOBAL("target.llvm_x86_get_archlist")
+ .set_body_typed([](bool only64bit) -> Array<String> {
+ Array<String> cpu_arches;
+#if TVM_LLVM_VERSION < 110
+ cpu_arches = llvm_compat_get_archlist("x86_64--");
+#else
+ llvm::SmallVector<llvm::StringRef> x86_arches;
+ llvm::X86::fillValidCPUArchList(x86_arches, only64bit);
+ for (auto& arch : x86_arches) {
+ cpu_arches.push_back(arch.str());
+ }
+#endif
+ return cpu_arches;
+ });
+
+TVM_REGISTER_GLOBAL("target.llvm_x86_get_features")
+ .set_body_typed([](std::string cpu_name) -> Array<String> {
+ Array<String> cpu_features;
+#if TVM_LLVM_VERSION < 110
+ cpu_features = llvm_compat_get_features("x86_64--", cpu_name);
+#else
+ llvm::SmallVector<llvm::StringRef> x86_features;
+ llvm::X86::getFeaturesForCPU(cpu_name, x86_features);
+ for (auto& feat : x86_features) {
+ cpu_features.push_back(feat.str());
+ }
+#endif
+ return cpu_features;
+ });
+
+TVM_REGISTER_GLOBAL("target.llvm_x86_has_feature")
+ .set_body_typed([](String feature, const Target& target) -> bool {
+ // target argument is optional (nullptr or None)
+ // if not explicit then use the current context target
+ Optional<String> mcpu = target.defined() ?
target->GetAttr<String>("mcpu")
+ :
Target::Current(false)->GetAttr<String>("mcpu");
+ Optional<Array<String>> mattr = target.defined()
+ ?
target->GetAttr<Array<String>>("mattr")
+ :
Target::Current(false)->GetAttr<Array<String>>("mattr");
+ String name = target.defined() ? target->kind->name :
Target::Current(false)->kind->name;
+ // lookup only for `llvm` targets having -mcpu
+ if ((name != "llvm") || !mcpu) {
+ return false;
+ }
+ // lookup in -mattr flags
+ bool is_in_mattr =
+ !mattr ? false
+ : std::any_of(mattr.value().begin(), mattr.value().end(),
+ [&](const String& var) { return var == ("+" +
feature); });
+#if TVM_LLVM_VERSION < 110
+ auto x86_arches = llvm_compat_get_archlist("x86_64--");
+ // decline on invalid arch (avoid llvm assertion)
+ if (!std::any_of(x86_arches.begin(), x86_arches.end(),
+ [&](const String& var) { return var == mcpu.value();
})) {
+ return false;
+ }
+ // lookup in -mcpu llvm architecture flags
+ auto cpu_features = llvm_compat_get_features("x86_64--", mcpu.value());
+ bool has_feature = std::any_of(cpu_features.begin(), cpu_features.end(),
+ [&](const String& var) { return var ==
feature; });
+#else
+ llvm::SmallVector<llvm::StringRef> x86_arches;
+ llvm::X86::fillValidCPUArchList(x86_arches, false);
+ // decline on invalid arch (avoid llvm assertion)
+ if (!std::any_of(x86_arches.begin(), x86_arches.end(),
+ [&](const llvm::StringRef& var) { return var ==
mcpu.value().c_str(); })) {
+ return false;
+ }
+ // lookup in -mcpu llvm architecture flags
+ llvm::SmallVector<llvm::StringRef> x86_features;
+ llvm::X86::getFeaturesForCPU(mcpu.value().c_str(), x86_features);
+ bool has_feature =
+ std::any_of(x86_features.begin(), x86_features.end(),
+ [&](const llvm::StringRef& var) { return var ==
feature.c_str(); });
+#endif
+ return has_feature || is_in_mattr;
+ });
+
TVM_REGISTER_GLOBAL("target.llvm_version_major").set_body_typed([]() -> int {
return TVM_LLVM_VERSION / 10;
});
diff --git a/tests/python/target/test_x86_features.py
b/tests/python/target/test_x86_features.py
new file mode 100644
index 0000000000..31a823b504
--- /dev/null
+++ b/tests/python/target/test_x86_features.py
@@ -0,0 +1,176 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+import pytest
+
+import tvm
+from tvm.target import _ffi_api, codegen, Target
+from tvm.target.x86 import target_has_features
+
+LLVM_VERSION = codegen.llvm_version_major()
+
+min_llvm_version, tvm_target, x86_feature, is_supported =
tvm.testing.parameters(
+ # sse4.1
+ (-1, "llvm -mcpu=btver2", "sse4a", True),
+ (-1, "llvm -mcpu=penryn", "sse4.1", True),
+ (-1, "llvm -mcpu=silvermont", "sse4.2", True),
+ (11, "llvm -mcpu=slm", "sse4.2", True),
+ (-1, "llvm -mcpu=goldmont", "sse4.2", True),
+ (-1, "llvm -mcpu=goldmont-plus", "sse4.2", True),
+ (-1, "llvm -mcpu=tremont", "sse4.2", True),
+ (-1, "llvm -mcpu=nehalem", "sse4.2", True),
+ (11, "llvm -mcpu=corei7", "sse4.2", True),
+ (-1, "llvm -mcpu=westmere", "sse4.2", True),
+ (-1, "llvm -mcpu=bdver1", "sse4.2", True),
+ (-1, "llvm -mcpu=bdver2", "sse4.2", True),
+ (-1, "llvm -mcpu=bdver3", "sse4.2", True),
+ (11, "llvm -mcpu=x86-64-v2", "sse4.2", True),
+ # avx
+ (-1, "llvm -mcpu=sandybridge", "avx", True),
+ (11, "llvm -mcpu=corei7-avx", "avx", True),
+ (-1, "llvm -mcpu=ivybridge", "avx", True),
+ (11, "llvm -mcpu=core-avx-i", "avx", True),
+ # avx2
+ (-1, "llvm -mcpu=haswell", "avx2", True),
+ (11, "llvm -mcpu=core-avx2", "avx2", True),
+ (-1, "llvm -mcpu=broadwell", "avx2", True),
+ (-1, "llvm -mcpu=skylake", "avx2", True),
+ (-1, "llvm -mcpu=bdver4", "avx2", True),
+ (-1, "llvm -mcpu=znver1", "avx2", True),
+ (-1, "llvm -mcpu=znver2", "avx2", True),
+ (11, "llvm -mcpu=znver3", "avx2", True),
+ (11, "llvm -mcpu=x86-64-v3", "avx2", True),
+ # avx512bw
+ (-1, "llvm -mcpu=skylake-avx512", "avx512bw", True),
+ (11, "llvm -mcpu=skx", "avx512bw", True),
+ (11, "llvm -mcpu=knl", "avx512bw", False),
+ (-1, "llvm -mcpu=knl", "avx512f", True),
+ (11, "llvm -mcpu=knl", ["avx512bw", "avx512f"], False),
+ (11, "llvm -mcpu=knl", ("avx512bw", "avx512f"), False),
+ (-1, "llvm -mcpu=knl", "avx512cd", True),
+ (11, "llvm -mcpu=knl", ["avx512cd", "avx512f"], True),
+ (11, "llvm -mcpu=knl", ("avx512cd", "avx512f"), True),
+ (-1, "llvm -mcpu=knl", "avx512er", True),
+ (-1, "llvm -mcpu=knl", "avx512pf", True),
+ (11, "llvm -mcpu=knm", "avx512bw", False),
+ (-1, "llvm -mcpu=knm", "avx512f", True),
+ (-1, "llvm -mcpu=knm", "avx512cd", True),
+ (-1, "llvm -mcpu=knm", "avx512er", True),
+ (-1, "llvm -mcpu=knm", "avx512pf", True),
+ (11, "llvm -mcpu=x86-64-v4", "avx512bw", True),
+ (-1, "llvm -mcpu=cannonlake", "avx512bw", True),
+ # explicit enumeration of VNNI capable due to collision with alderlake
+ (11, "llvm -mcpu=alderlake", "avx512bw", False),
+ (-1, "llvm -mcpu=cascadelake", "avx512bw", True),
+ (-1, "llvm -mcpu=icelake-client", "avx512bw", True),
+ (-1, "llvm -mcpu=icelake-server", "avx512bw", True),
+ (11, "llvm -mcpu=rocketlake", "avx512bw", True),
+ (-1, "llvm -mcpu=tigerlake", "avx512bw", True),
+ (-1, "llvm -mcpu=cooperlake", "avx512bw", True),
+ (11, "llvm -mcpu=sapphirerapids", "avx512bw", True),
+ # avx512vnni
+ (11, "llvm -mcpu=alderlake", "avx512vnni", False),
+ (11, "llvm -mcpu=alderlake", "avxvnni", True),
+ (-1, "llvm -mcpu=cascadelake", "avx512vnni", True),
+ (-1, "llvm -mcpu=icelake-client", "avx512vnni", True),
+ (-1, "llvm -mcpu=icelake-server", "avx512vnni", True),
+ (11, "llvm -mcpu=rocketlake", "avx512vnni", True),
+ (-1, "llvm -mcpu=tigerlake", "avx512vnni", True),
+ (-1, "llvm -mcpu=cooperlake", "avx512vnni", True),
+ (11, "llvm -mcpu=sapphirerapids", "avx512vnni", True),
+ # amx-int8
+ (11, "llvm -mcpu=sapphirerapids", "amx-int8", True),
+ # generic CPU (no features) but with extra -mattr
+ (-1, "llvm -mcpu=x86-64 -mattr=+sse4.1,+avx2", "avx2", True),
+ (-1, "llvm -mcpu=x86-64 -mattr=+sse4.1,+avx2", "sse4.1", True),
+ (-1, "llvm -mcpu=x86-64 -mattr=+sse4.1,+avx2", "ssse3", False),
+)
+
+
+def test_x86_target_features(min_llvm_version, tvm_target, x86_feature,
is_supported):
+ """Test X86 features support for different targets.
+
+ Parameters
+ ----------
+ min_llvm_version : int
+ Minimal LLVM version.
+ tvm_target : str
+ TVM target.
+ x86_feature : str
+ X86 CPU feature.
+ is_supported : bool
+ Expected result.
+ """
+
+ ##
+ ## no context
+ ##
+
+ # check for feature via the python api (no explicit target, no context
target)
+ try:
+ assert target_has_features(x86_feature) == is_supported
+ assert False
+ except tvm.error.InternalError as e:
+ msg = str(e)
+ assert (
+ msg.find(
+ "InternalError: Check failed: (allow_not_defined) is false:
Target context required"
+ )
+ != -1
+ )
+
+ if isinstance(x86_feature, str):
+ # check for feature via the ffi llvm api (no explicit target, no
context target)
+ try:
+ assert _ffi_api.llvm_x86_has_feature(x86_feature, None) ==
is_supported
+ assert False
+ except tvm.error.InternalError as e:
+ msg = str(e)
+ assert (
+ msg.find(
+ "InternalError: Check failed: (allow_not_defined) is
false: Target context required"
+ )
+ != -1
+ )
+
+ # skip test on llvm_version
+ if LLVM_VERSION < min_llvm_version:
+ return
+
+ # check for feature via the python api (with explicit target, no context
target)
+ assert target_has_features(x86_feature, Target(tvm_target)) == is_supported
+ if isinstance(x86_feature, str):
+ # check for feature via the ffi llvm api (with explicit target, no
context target)
+ assert _ffi_api.llvm_x86_has_feature(x86_feature, Target(tvm_target))
== is_supported
+
+ ##
+ ## with context
+ ##
+
+ with Target(tvm_target):
+ mcpu = Target.current(False).mcpu
+ # check for feature via the python api (current context target)
+ assert target_has_features(x86_feature) == is_supported
+ # check for feature via the python api (with explicit target)
+ assert target_has_features(x86_feature, Target(tvm_target)) ==
is_supported
+ if isinstance(x86_feature, str):
+ # check for feature via the ffi llvm api (current context target)
+ assert _ffi_api.llvm_x86_has_feature(x86_feature, None) ==
is_supported
+ # check for feature via the ffi llvm api (with explicit target)
+ assert _ffi_api.llvm_x86_has_feature(x86_feature,
Target(tvm_target)) == is_supported
+ # check for feature in target's llvm full x86 CPU feature list
+ if not Target(tvm_target).mattr:
+ assert (x86_feature in codegen.llvm_x86_get_features(mcpu)) ==
is_supported