mirror of
https://github.com/tensorflow/tensorflow.git
synced 2026-10-26 00:09:51 +08:00
Merge remote-tracking branch 'upstream/master' into feat/auto-strategy
This commit is contained in:
commit
4946093114
1974 files changed
+230396
-49165
No files matched your search
@@ -219,6 +219,7 @@ python.toolchain(python_version = "3.10")
|
||||
python.toolchain(python_version = "3.11")
|
||||
python.toolchain(python_version = "3.12")
|
||||
python.toolchain(python_version = "3.13")
|
||||
python.toolchain(python_version = "3.14")
|
||||
|
||||
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
|
||||
|
||||
@@ -237,6 +238,7 @@ pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
|
||||
"3.11",
|
||||
"3.12",
|
||||
"3.13",
|
||||
"3.14",
|
||||
]]
|
||||
|
||||
use_repo(pip, "pypi_mods", pypi = "tf_pypi")
|
||||
|
||||
+1
-1
@@ -52,9 +52,9 @@ In `tensorflow/c/experimental/filesystem/filesystem_interface.h`, removed `TF_Tr
|
||||
|
||||
### Major Features and Improvements
|
||||
|
||||
* Support for Python 3.14 has been added.
|
||||
* `tf.distribute.experimental`
|
||||
* Introduces `tf.distribute.experimental.auto_strategy`, a dynamic factory API that automatically detects available hardware (TPUs, multiple GPUs, or multi-worker clusters via `TF_CONFIG`) and resolves the optimal distribution strategy, safely falling back to single device execution.
|
||||
|
||||
* `tf.lite`
|
||||
* Adds support for QUI4 (Quantized Unsigned 4-bit) in Dequantize operator.
|
||||
* Adds support for FP16 and BF16 in Unpack operator.
|
||||
|
||||
@@ -71,7 +71,6 @@ RUN if [ "${INSTALL_CMAKE_TOOLCHAIN}" = "true" ]; then \
|
||||
# Setup links for TensorFlow to compile.
|
||||
# Referenced in devel.usertools/*.bazelrc.
|
||||
# Set python3.12 as the default python version.
|
||||
# TF does not support python3.13.
|
||||
RUN ln -sf /usr/bin/python3.12 /usr/bin/python3
|
||||
RUN ln -sf /usr/bin/python3.12 /usr/bin/python
|
||||
RUN ln -sf /usr/lib/python3.12 /usr/lib/tf_python
|
||||
|
||||
@@ -11,7 +11,7 @@ pyyaml
|
||||
# For JAX
|
||||
build ~= 1.2.2
|
||||
# uv is faster than pip for installing Python packages.
|
||||
uv ~= 0.5.30
|
||||
uv ~= 0.12.23
|
||||
|
||||
# For running wheel verification script
|
||||
immutabledict ~= 4.2.1
|
||||
|
||||
@@ -764,13 +764,13 @@ typing-extensions==4.14.1 \
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
# rich
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.41.3 \
|
||||
--hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \
|
||||
|
||||
@@ -763,13 +763,13 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.41.3 \
|
||||
--hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \
|
||||
|
||||
@@ -763,13 +763,13 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.41.3 \
|
||||
--hash=sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942 \
|
||||
|
||||
@@ -1332,6 +1332,7 @@ def main():
|
||||
clang_version = retrieve_clang_version(clang_compiler_path)
|
||||
if is_s390x():
|
||||
write_to_bazelrc('build --linkopt=-rtlib=compiler-rt')
|
||||
write_to_bazelrc('build --host_linkopt=-rtlib=compiler-rt')
|
||||
disable_clang_offsetof_extension(clang_version)
|
||||
if is_windows():
|
||||
environ_cp['TF_NEED_CLANG'] = str(choose_compiler_Win(environ_cp))
|
||||
|
||||
@@ -888,13 +888,13 @@ typing-extensions==4.14.1 \
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
# rich
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -887,13 +887,13 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -887,13 +887,13 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -894,13 +894,13 @@ typing-extensions==4.14.1 \
|
||||
# via
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.6 \
|
||||
--hash=sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25 \
|
||||
--hash=sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -1128,13 +1128,13 @@ typing-extensions==4.14.1 \
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# grpcio
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.8 \
|
||||
--hash=sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50 \
|
||||
--hash=sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -1129,13 +1129,13 @@ typing-extensions==4.14.1 \
|
||||
# -r ci/official/requirements_updater/requirements.in
|
||||
# grpcio
|
||||
# optree
|
||||
urllib3==2.7.0 \
|
||||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
urllib3==2.8.0 \
|
||||
--hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
|
||||
--hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
|
||||
# via requests
|
||||
werkzeug==3.1.8 \
|
||||
--hash=sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50 \
|
||||
--hash=sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44
|
||||
werkzeug==3.1.9 \
|
||||
--hash=sha256:55ca7c70a75689be937aa27f8ff4b018f06ff4838fc73045560bf0f5a1291060 \
|
||||
--hash=sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab
|
||||
# via tb-nightly
|
||||
wheel==0.46.2 \
|
||||
--hash=sha256:33ae60725d69eaa249bc1982e739943c23b34b58d51f1cb6253453773aca6e65 \
|
||||
|
||||
@@ -34,7 +34,7 @@ class AbstractContext {
|
||||
protected:
|
||||
enum AbstractContextKind { kGraph, kMlir, kEager, kTfrt, kTape, kOpHandler };
|
||||
explicit AbstractContext(AbstractContextKind kind) : kind_(kind) {}
|
||||
virtual ~AbstractContext() {}
|
||||
virtual ~AbstractContext() = default;
|
||||
|
||||
public:
|
||||
AbstractContextKind getKind() const { return kind_; }
|
||||
|
||||
@@ -40,7 +40,7 @@ class AbstractOperation {
|
||||
kOpHandler
|
||||
};
|
||||
explicit AbstractOperation(AbstractOperationKind kind) : kind_(kind) {}
|
||||
virtual ~AbstractOperation() {}
|
||||
virtual ~AbstractOperation() = default;
|
||||
|
||||
public:
|
||||
AbstractOperationKind getKind() const { return kind_; }
|
||||
|
||||
@@ -31,7 +31,7 @@ class AbstractTensorHandle : public core::RefCounted {
|
||||
protected:
|
||||
enum AbstractTensorHandleKind { kGraph, kMlir, kEager, kTfrt, kCustomDevice };
|
||||
explicit AbstractTensorHandle(AbstractTensorHandleKind kind) : kind_(kind) {}
|
||||
~AbstractTensorHandle() override {}
|
||||
~AbstractTensorHandle() override = default;
|
||||
|
||||
public:
|
||||
// Returns tensor dtype.
|
||||
|
||||
@@ -310,7 +310,7 @@ string VariableAddFunction() {
|
||||
class GraphErrorInjectionPass : public tensorflow::GraphOptimizationPass {
|
||||
public:
|
||||
static bool enabled_;
|
||||
GraphErrorInjectionPass() {}
|
||||
GraphErrorInjectionPass() = default;
|
||||
|
||||
absl::Status Run(
|
||||
const tensorflow::GraphOptimizationPassOptions& options) override {
|
||||
|
||||
@@ -321,7 +321,7 @@ class GraphOperation : public TracingOperation {
|
||||
static bool classof(const AbstractOperation* ptr) {
|
||||
return ptr->getKind() == kGraph;
|
||||
}
|
||||
~GraphOperation() override {}
|
||||
~GraphOperation() override = default;
|
||||
|
||||
private:
|
||||
friend class GraphContext; // For access to op_.
|
||||
|
||||
@@ -58,7 +58,7 @@ class GradientFunction {
|
||||
AbstractContext* ctx,
|
||||
absl::Span<AbstractTensorHandle* const> grad_outputs,
|
||||
absl::Span<AbstractTensorHandle*> grad_inputs) = 0;
|
||||
virtual ~GradientFunction() {}
|
||||
virtual ~GradientFunction() = default;
|
||||
};
|
||||
|
||||
// Metadata from the forward operation that is made available to the
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace graph {
|
||||
GraphFunction::GraphFunction(FunctionDef fdef)
|
||||
: AbstractFunction(kGraph),
|
||||
func_record_(new FunctionRecord(std::move(fdef), {}, true)) {}
|
||||
GraphFunction::~GraphFunction() {}
|
||||
GraphFunction::~GraphFunction() = default;
|
||||
absl::Status GraphFunction::GetFunctionDef(const FunctionDef **fdef) {
|
||||
*fdef = &(func_record_->fdef());
|
||||
return absl::OkStatus();
|
||||
|
||||
@@ -272,7 +272,7 @@ class ImmediateExecutionContext : public AbstractContext {
|
||||
protected:
|
||||
explicit ImmediateExecutionContext(AbstractContextKind kind)
|
||||
: AbstractContext(kind) {}
|
||||
~ImmediateExecutionContext() override {}
|
||||
~ImmediateExecutionContext() override = default;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -33,7 +33,7 @@ class WorkerCacheInterface;
|
||||
|
||||
class ImmediateExecutionDistributedManager {
|
||||
public:
|
||||
virtual ~ImmediateExecutionDistributedManager() {}
|
||||
virtual ~ImmediateExecutionDistributedManager() = default;
|
||||
|
||||
// Set up distributed execution environment on local and remote tasks.
|
||||
// When `reset_context` is true, initialize new cluster context state based
|
||||
|
||||
@@ -83,7 +83,7 @@ class ImmediateExecutionTensorHandle : public AbstractTensorHandle {
|
||||
protected:
|
||||
explicit ImmediateExecutionTensorHandle(AbstractTensorHandleKind kind)
|
||||
: AbstractTensorHandle(kind) {}
|
||||
~ImmediateExecutionTensorHandle() override {}
|
||||
~ImmediateExecutionTensorHandle() override = default;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
@@ -94,7 +94,7 @@ using OpTape =
|
||||
template <typename Gradient, typename BackwardFunction, typename TapeTensor>
|
||||
class VSpace {
|
||||
public:
|
||||
virtual ~VSpace() {}
|
||||
virtual ~VSpace() = default;
|
||||
|
||||
// Returns the number of elements in the gradient tensor.
|
||||
virtual int64_t NumElements(Gradient* tensor) const = 0;
|
||||
|
||||
@@ -92,14 +92,14 @@ class Cleanup {
|
||||
bool is_released() const { return released_; }
|
||||
|
||||
private:
|
||||
static_assert(!std::is_reference<F>::value, "F must not be a reference");
|
||||
static_assert(!std::is_reference_v<F>, "F must not be a reference");
|
||||
|
||||
bool released_ = false;
|
||||
F f_;
|
||||
};
|
||||
|
||||
template <int&... ExplicitParameterBarrier, typename F,
|
||||
typename DecayF = typename std::decay<F>::type>
|
||||
typename DecayF = std::decay_t<F>>
|
||||
Cleanup<DecayF> MakeCleanup(F&& f) {
|
||||
return Cleanup<DecayF>(std::forward<F>(f));
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class IdentityNGradientFunction : public GradientFunction {
|
||||
}
|
||||
return absl::OkStatus();
|
||||
}
|
||||
~IdentityNGradientFunction() override {}
|
||||
~IdentityNGradientFunction() override = default;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ class SparseSoftmaxCrossEntropyWithLogitsGradientFunction
|
||||
grad_inputs[1] = nullptr;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
~SparseSoftmaxCrossEntropyWithLogitsGradientFunction() override {}
|
||||
~SparseSoftmaxCrossEntropyWithLogitsGradientFunction() override = default;
|
||||
|
||||
private:
|
||||
vector<AbstractTensorHandle*> forward_outputs_;
|
||||
@@ -146,7 +146,7 @@ class BiasAddGradientFunction : public GradientFunction {
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
~BiasAddGradientFunction() override {}
|
||||
~BiasAddGradientFunction() override = default;
|
||||
|
||||
private:
|
||||
AttrBuilder forward_attrs_;
|
||||
|
||||
@@ -150,7 +150,7 @@ TEST(StreamExecutor, UnifiedMemoryAllocateNotSet) {
|
||||
/*** StreamExecutor behavior tests ***/
|
||||
class StreamExecutorTest : public ::testing::Test {
|
||||
protected:
|
||||
StreamExecutorTest() {}
|
||||
StreamExecutorTest() = default;
|
||||
void SetUp() override {
|
||||
test_util::PopulateDefaultPlatform(&platform_, &platform_fns_);
|
||||
test_util::PopulateDefaultDeviceFns(&device_fns_);
|
||||
|
||||
@@ -61,7 +61,7 @@ ClientSession::ClientSession(const Scope& scope,
|
||||
// Define destructor here so we can forward declare `Impl` in client_session.h.
|
||||
// If we define a dtor in the header file or use the default dtor,
|
||||
// unique_ptr<Impl> needs the complete type.
|
||||
ClientSession::~ClientSession() {}
|
||||
ClientSession::~ClientSession() = default;
|
||||
|
||||
SessionOptions ClientSession::Impl::MakeDefaultSessionOptions(
|
||||
const std::string& target) {
|
||||
|
||||
@@ -69,14 +69,14 @@ SET_BASE_UNITS_FOR_TYPE(complex128, ({{1, 0}, {0, 1}}));
|
||||
// When T is complex, it assigns the real and complex values to successive rows
|
||||
// or columns in the matrix depending on the expand_by_row parameter
|
||||
template <typename T, typename JAC_T>
|
||||
typename std::enable_if<std::is_floating_point<T>::value>::type SetJacobian(
|
||||
std::enable_if_t<std::is_floating_point_v<T>> SetJacobian(
|
||||
typename TTypes<JAC_T>::Matrix* jacobian, const int row, const int col,
|
||||
const T& value, const bool expand_by_row) {
|
||||
(*jacobian)(row, col) = JAC_T{value};
|
||||
}
|
||||
|
||||
template <typename T, typename JAC_T>
|
||||
typename std::enable_if<is_complex<T>::value>::type SetJacobian(
|
||||
std::enable_if_t<is_complex<T>::value> SetJacobian(
|
||||
typename TTypes<JAC_T>::Matrix* jacobian, const int row, const int col,
|
||||
const T& value, const bool expand_by_row) {
|
||||
(*jacobian)(row, col) = JAC_T{value.real()};
|
||||
|
||||
@@ -112,9 +112,9 @@ class Input {
|
||||
struct Initializer {
|
||||
/// Construct from a scalar value of an arithmetic type or a type that can
|
||||
/// be converted to a string (eg. a string literal).
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_convertible<T, std::string>::value>::type>
|
||||
template <typename T, typename = std::enable_if_t<
|
||||
std::is_arithmetic_v<T> ||
|
||||
std::is_convertible_v<T, std::string>>>
|
||||
Initializer(const T& v) { // NOLINT(runtime/explicit)
|
||||
typedef typename RealType<T>::type RealT;
|
||||
Tensor t(DataTypeToEnum<RealT>::v(), TensorShape());
|
||||
@@ -125,9 +125,9 @@ class Input {
|
||||
Initializer(const Tensor& t) : tensor(t) {} // NOLINT(runtime/explicit)
|
||||
|
||||
/// Construct from a scalar value and an explicit shape
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_convertible<T, std::string>::value>::type>
|
||||
template <typename T, typename = std::enable_if_t<
|
||||
std::is_arithmetic_v<T> ||
|
||||
std::is_convertible_v<T, std::string>>>
|
||||
Initializer(const T& v, const TensorShape& shape) {
|
||||
typedef typename RealType<T>::type RealT;
|
||||
Tensor t(DataTypeToEnum<RealT>::v(), shape);
|
||||
@@ -138,9 +138,9 @@ class Input {
|
||||
}
|
||||
|
||||
/// Construct from a initializer list of scalars (a one-dimensional tensor).
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_convertible<T, std::string>::value>::type>
|
||||
template <typename T, typename = std::enable_if_t<
|
||||
std::is_arithmetic_v<T> ||
|
||||
std::is_convertible_v<T, std::string>>>
|
||||
Initializer(
|
||||
const std::initializer_list<T>& v) { // NOLINT(runtime/explicit)
|
||||
typedef typename RealType<T>::type RealT;
|
||||
@@ -151,9 +151,9 @@ class Input {
|
||||
}
|
||||
|
||||
/// Construct from a initializer list of scalars and an explicit shape.
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_convertible<T, std::string>::value>::type>
|
||||
template <typename T, typename = std::enable_if_t<
|
||||
std::is_arithmetic_v<T> ||
|
||||
std::is_convertible_v<T, std::string>>>
|
||||
Initializer(const std::initializer_list<T>& v, const TensorShape& shape) {
|
||||
typedef typename RealType<T>::type RealT;
|
||||
Tensor t(DataTypeToEnum<RealT>::v(), shape);
|
||||
@@ -175,7 +175,7 @@ class Input {
|
||||
Initializer(const std::initializer_list<Initializer>& v);
|
||||
|
||||
// START_SKIP_DOXYGEN
|
||||
template <typename T, bool = std::is_convertible<T, std::string>::value>
|
||||
template <typename T, bool = std::is_convertible_v<T, std::string>>
|
||||
struct RealType {
|
||||
typedef tstring type;
|
||||
};
|
||||
@@ -210,9 +210,9 @@ class Input {
|
||||
/// * A Tensor object.
|
||||
Input(const Output& o) : output_(o) {} // NOLINT(runtime/explicit)
|
||||
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_arithmetic<T>::value ||
|
||||
std::is_convertible<T, std::string>::value>::type>
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<std::is_arithmetic_v<T> ||
|
||||
std::is_convertible_v<T, std::string>>>
|
||||
Input(const T& v) // NOLINT(runtime/explicit)
|
||||
: Input(Initializer(v)) {}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Scope::Scope(Impl* impl) : impl_(impl) {}
|
||||
|
||||
Scope::Scope(const Scope& other) : impl_(new Impl(*other.impl())) {}
|
||||
|
||||
Scope::~Scope() {}
|
||||
Scope::~Scope() = default;
|
||||
|
||||
Scope& Scope::operator=(const Scope& other) {
|
||||
// We can't copy Impls because of the const members, use copy ctor instead
|
||||
|
||||
@@ -41,7 +41,7 @@ constexpr char kTestData[] = "cc/saved_model/testdata";
|
||||
|
||||
class BundleV2Test : public ::testing::Test {
|
||||
protected:
|
||||
BundleV2Test() {}
|
||||
BundleV2Test() = default;
|
||||
|
||||
void RestoreVarsAndVerify(SavedModelV2Bundle* bundle,
|
||||
std::vector<std::string> expected_names) {
|
||||
|
||||
@@ -51,7 +51,7 @@ string NonChunkedSavedModel() {
|
||||
|
||||
class ReaderTest : public ::testing::Test {
|
||||
protected:
|
||||
ReaderTest() {}
|
||||
ReaderTest() = default;
|
||||
|
||||
void CheckMetaGraphDef(const MetaGraphDef& meta_graph_def) {
|
||||
const auto& tags = meta_graph_def.meta_info_def().tags();
|
||||
|
||||
@@ -42,7 +42,7 @@ constexpr char kTestDataInitOpV2[] =
|
||||
|
||||
class LoaderTest : public ::testing::Test {
|
||||
protected:
|
||||
LoaderTest() {}
|
||||
LoaderTest() = default;
|
||||
|
||||
string MakeSerializedExample(float x) {
|
||||
tensorflow::Example example;
|
||||
|
||||
@@ -57,7 +57,7 @@ constexpr char kV2ModuleSavedModelChecksum[] = "15788619162413586750";
|
||||
|
||||
class LoaderTest : public ::testing::Test {
|
||||
protected:
|
||||
LoaderTest() {}
|
||||
LoaderTest() = default;
|
||||
|
||||
string MakeSerializedExample(float x) {
|
||||
tensorflow::Example example;
|
||||
|
||||
@@ -1643,6 +1643,7 @@ cc_library(
|
||||
hdrs = ["device_compiler_client.h"],
|
||||
visibility = [":internal"],
|
||||
deps = [
|
||||
":common",
|
||||
"//tensorflow/compiler/tf2xla:xla_compiler",
|
||||
"//tensorflow/core/util:determinism",
|
||||
"@xla//xla/client:executable_build_options",
|
||||
@@ -1653,8 +1654,11 @@ tf_cc_test(
|
||||
name = "device_compiler_client_test",
|
||||
srcs = ["device_compiler_client_test.cc"],
|
||||
deps = [
|
||||
":common",
|
||||
":device_compiler_client",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
"@xla//xla/hlo/builder:xla_computation",
|
||||
"@xla//xla/service:hlo_proto_cc",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class Predicate {
|
||||
virtual absl::Span<Predicate* const> GetOperands() const = 0;
|
||||
|
||||
virtual Kind kind() const = 0;
|
||||
virtual ~Predicate() {}
|
||||
virtual ~Predicate() = default;
|
||||
|
||||
// Invokes func on p and on all of its operands recursively. Does not invoke
|
||||
// `func` on the same Predicate instance twice. Aborts the search if `func`
|
||||
@@ -1566,7 +1566,7 @@ void DeadnessAnalysisImpl::Print() const {
|
||||
|
||||
} // namespace
|
||||
|
||||
DeadnessAnalysis::~DeadnessAnalysis() {}
|
||||
DeadnessAnalysis::~DeadnessAnalysis() = default;
|
||||
|
||||
/*static*/ absl::Status DeadnessAnalysis::Run(
|
||||
const Graph& graph, std::unique_ptr<DeadnessAnalysis>* result) {
|
||||
|
||||
@@ -32,6 +32,8 @@ const char* const kXlaInternalScopeAttr = "_XlaInternalScope";
|
||||
|
||||
const char* const kXlaClusterIdAttr = "_xla_compile_id";
|
||||
|
||||
const char* const kXlaDeterministicAttr = "_XlaDeterministic";
|
||||
|
||||
static std::atomic<bool> xla_devices_creation_required(false);
|
||||
|
||||
// Request XLA:GPU and XLA:CPU device creation. Deprecated, only used by XRT
|
||||
|
||||
@@ -38,6 +38,9 @@ extern const char* const kXlaInternalScopeAttr; // "_XlaInternalScope"
|
||||
// The id of the compiled cluster.
|
||||
extern const char* const kXlaClusterIdAttr; // "_xla_compile_id"
|
||||
|
||||
// Implies per-cluster deterministic XLA ops.
|
||||
extern const char* const kXlaDeterministicAttr; // "_XlaDeterministic"
|
||||
|
||||
[[deprecated("XLA:CPU/GPU devices are deprecated")]] void
|
||||
RequestXlaDevicesCreation();
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ limitations under the License.
|
||||
|
||||
#include "tensorflow/compiler/jit/device_compiler_client.h"
|
||||
|
||||
#include "tensorflow/compiler/jit/defs.h"
|
||||
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
|
||||
#include "tensorflow/core/util/determinism.h"
|
||||
|
||||
@@ -37,10 +38,23 @@ xla::ExecutableBuildOptions GetExecutableBuildOptions(
|
||||
build_options.set_alias_passthrough_params(options.alias_passthrough_params);
|
||||
build_options.mutable_debug_options()->set_xla_detailed_logging(
|
||||
options.detailed_logging);
|
||||
if (tensorflow::OpDeterminismRequired()) {
|
||||
bool cluster_deterministic = false;
|
||||
if (result.computation != nullptr) {
|
||||
const auto& frontend_attrs =
|
||||
result.computation->proto().frontend_attributes().map();
|
||||
auto it = frontend_attrs.find(kXlaDeterministicAttr);
|
||||
if (it != frontend_attrs.end() && it->second == "true") {
|
||||
cluster_deterministic = true;
|
||||
}
|
||||
}
|
||||
if (tensorflow::OpDeterminismRequired() || cluster_deterministic) {
|
||||
build_options.mutable_debug_options()
|
||||
->set_xla_gpu_exclude_nondeterministic_ops(true);
|
||||
}
|
||||
if (cluster_deterministic) {
|
||||
build_options.mutable_debug_options()
|
||||
->set_xla_gpu_experimental_cost_model_gemm_tiling_default(true);
|
||||
}
|
||||
return build_options;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,12 @@ limitations under the License.
|
||||
|
||||
#include "tensorflow/compiler/jit/device_compiler_client.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "tensorflow/compiler/jit/defs.h"
|
||||
#include "xla/hlo/builder/xla_computation.h"
|
||||
#include "xla/service/hlo.pb.h"
|
||||
|
||||
namespace tensorflow {
|
||||
namespace {
|
||||
@@ -72,5 +77,28 @@ TEST(GetExecutableOptionTest, DumpingWithoutDetailedLogging) {
|
||||
EXPECT_TRUE(build_option.debug_options().xla_enable_dumping());
|
||||
}
|
||||
|
||||
TEST(GetExecutableOptionTest, XlaDeterministicFrontendAttribute) {
|
||||
XlaCompiler::Options options;
|
||||
XlaCompiler::CompilationResult result;
|
||||
result.computation = std::make_shared<xla::XlaComputation>();
|
||||
|
||||
auto default_build_option =
|
||||
GetExecutableBuildOptions(options, result, /*default_device_ordinal=*/-1);
|
||||
EXPECT_FALSE(default_build_option.debug_options()
|
||||
.xla_gpu_exclude_nondeterministic_ops());
|
||||
EXPECT_FALSE(default_build_option.debug_options()
|
||||
.xla_gpu_experimental_cost_model_gemm_tiling_default());
|
||||
|
||||
(*result.computation->mutable_proto()
|
||||
->mutable_frontend_attributes()
|
||||
->mutable_map())[kXlaDeterministicAttr] = "true";
|
||||
auto deterministic_build_option =
|
||||
GetExecutableBuildOptions(options, result, /*default_device_ordinal=*/-1);
|
||||
EXPECT_TRUE(deterministic_build_option.debug_options()
|
||||
.xla_gpu_exclude_nondeterministic_ops());
|
||||
EXPECT_TRUE(deterministic_build_option.debug_options()
|
||||
.xla_gpu_experimental_cost_model_gemm_tiling_default());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tensorflow
|
||||
@@ -94,7 +94,7 @@ struct XlaClusterInfo {
|
||||
// TODO(klimek): In c++17 we'll be able to initialize host_compute_core
|
||||
// without losing aggregate initialization, which allows us to get rid of
|
||||
// the constructor definitions again.
|
||||
XlaClusterInfo() {}
|
||||
XlaClusterInfo() = default;
|
||||
XlaClusterInfo(const std::string& cluster_name,
|
||||
const NameAttrList& func_name_attrs, Node* node,
|
||||
const std::map<std::string, int>& host_compute_core)
|
||||
|
||||
@@ -100,6 +100,6 @@ void RegisterXlaActivityListener(
|
||||
|
||||
void XlaActivityListener::Flush() {}
|
||||
|
||||
XlaActivityListener::~XlaActivityListener() {}
|
||||
XlaActivityListener::~XlaActivityListener() = default;
|
||||
|
||||
} // namespace tensorflow
|
||||
@@ -48,7 +48,7 @@ class TestListener : public XlaActivityListener {
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
~TestListener() override {}
|
||||
~TestListener() override = default;
|
||||
|
||||
const XlaAutoClusteringActivity& auto_clustering_activity() const {
|
||||
return auto_clustering_activity_;
|
||||
|
||||
@@ -149,6 +149,7 @@ cc_library(
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/log",
|
||||
"@com_google_absl//absl/log:check",
|
||||
"@com_google_absl//absl/log:vlog_is_on",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/strings:string_view",
|
||||
"@llvm-project//llvm:Support",
|
||||
|
||||
@@ -1100,6 +1100,7 @@ cc_library(
|
||||
":pass",
|
||||
":pass_options",
|
||||
":tensorflow_lite_ops",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@llvm-project//mlir:FuncDialect",
|
||||
"@llvm-project//mlir:IR",
|
||||
@@ -1553,6 +1554,7 @@ cc_library(
|
||||
srcs = [
|
||||
"transforms/decompose_hybrid_quantization.cc",
|
||||
"transforms/default_quant_params.cc",
|
||||
"transforms/fold_fp16_casts_into_fully_connected_pass.cc",
|
||||
"transforms/fold_stablehlo_constant_transforms_pass.cc",
|
||||
"transforms/fuse_a4w2_drq_fully_connected_pass.cc",
|
||||
"transforms/generated_post_quantize.inc",
|
||||
|
||||
@@ -118,6 +118,10 @@ struct PassConfig {
|
||||
|
||||
// When set to true, enable unsafe single batch rank reduction.
|
||||
bool unsafe_single_batch_rank_reduction = false;
|
||||
|
||||
// When set to true, fold f16 <-> f32 casts around float fully_connected ops
|
||||
// into them, producing mixed precision (f16 activation) fully_connected ops.
|
||||
bool fold_fp16_casts_into_fully_connected = false;
|
||||
};
|
||||
|
||||
inline llvm::raw_ostream& operator<<(llvm::raw_ostream& os,
|
||||
@@ -153,7 +157,9 @@ inline llvm::raw_ostream& operator<<(llvm::raw_ostream& os,
|
||||
<< tflite::ConverterFlags::ModelOriginFramework_Name(
|
||||
pass_config.model_origin_framework)
|
||||
<< "\nunsafe_single_batch_rank_reduction: "
|
||||
<< pass_config.unsafe_single_batch_rank_reduction << "\n";
|
||||
<< pass_config.unsafe_single_batch_rank_reduction
|
||||
<< "\nfold_fp16_casts_into_fully_connected: "
|
||||
<< pass_config.fold_fp16_casts_into_fully_connected << "\n";
|
||||
}
|
||||
|
||||
} // namespace TFL
|
||||
|
||||
@@ -39,7 +39,7 @@ enum FileFormat {
|
||||
// of as properties of models, instead describing how models are to be
|
||||
// processed in the context of the present tooling job.
|
||||
//
|
||||
// Next ID to use: 74.
|
||||
// Next ID to use: 75.
|
||||
message ConverterFlags {
|
||||
reserved 54, 61;
|
||||
|
||||
@@ -398,4 +398,11 @@ message ConverterFlags {
|
||||
// Directory for debug artifacts and printed IR dumps when debug mode is
|
||||
// enabled.
|
||||
optional string debug_dir = 73;
|
||||
|
||||
// If true, fold the f16 -> f32 cast feeding a float fully_connected and the
|
||||
// f32 -> f16 cast consuming it into the fully_connected, which then takes
|
||||
// and produces f16 activations while keeping its weights unchanged. The
|
||||
// resulting mixed precision fully_connected requires runtime support.
|
||||
// WARNING: Experimental interface, subject to change.
|
||||
optional bool fold_fp16_casts_into_fully_connected = 74 [default = false];
|
||||
}
|
||||
@@ -1584,10 +1584,9 @@ std::optional<BufferOffset<tflite::Buffer>> Translator::BuildBuffer(
|
||||
/*hash=*/0);
|
||||
|
||||
std::string buffer_data = buffer.GetData();
|
||||
if (custom_option_alignment_.has_value()) {
|
||||
builder_.ForceVectorAlignment(buffer_data.size(), sizeof(uint8_t),
|
||||
custom_option_alignment_.value());
|
||||
}
|
||||
builder_.ForceVectorAlignment(
|
||||
buffer_data.size(), sizeof(uint8_t),
|
||||
custom_option_alignment_.value_or(kFbAlignment));
|
||||
ReleaseBufferData(buffer);
|
||||
return tflite::CreateBuffer(
|
||||
builder_, builder_.CreateVector(
|
||||
@@ -3969,6 +3968,7 @@ std::optional<BufferOffset<tflite::SubGraph>> Translator::BuildSubGraph(
|
||||
BufferOffset<tflite::Metadata> Translator::BuildMetadata(StringRef name,
|
||||
StringRef content) {
|
||||
auto buffer_index = buffers_.size();
|
||||
builder_.ForceVectorAlignment(content.size(), sizeof(uint8_t), kFbAlignment);
|
||||
auto buffer_data = builder_.CreateVector(
|
||||
reinterpret_cast<const uint8_t*>(content.data()), content.size());
|
||||
buffers_.push_back(tflite::CreateBuffer(builder_, buffer_data));
|
||||
|
||||
@@ -128,7 +128,7 @@ void BuiltinOptionsToAttributes(
|
||||
// NOLINTNEXTLINE
|
||||
llvm::SmallVectorImpl<mlir::NamedAttribute>& attributes);
|
||||
|
||||
// While the last several tensors could be optional tensors for an tfl op, the
|
||||
// While the last several tensors could be optional tensors for a tfl op, the
|
||||
// number of input operands could vary. This function gets the min/max number of
|
||||
// operands from tflite op name.
|
||||
llvm::MinMax OperandNumbersMinMax(llvm::StringRef op_name);
|
||||
|
||||
@@ -6018,11 +6018,55 @@ int64_t L2NormalizationOp::GetArithmeticCount(Operation* op) {
|
||||
// PadOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
OpFoldResult PadOp::fold(FoldAdaptor) {
|
||||
OpFoldResult PadOp::fold(FoldAdaptor adaptor) {
|
||||
if (InputOutputHasSameShape(getInput().getType(), getOutput().getType()))
|
||||
return getInput();
|
||||
|
||||
return {};
|
||||
// Fold a constant input padded by constant amounts. Left unfolded, a
|
||||
// constant-input PAD survives into the flatbuffer, and delegates that
|
||||
// require a runtime input (e.g. ML Drift) reject it.
|
||||
if (!ShouldFoldOperation(this->getOperation())) return {};
|
||||
|
||||
auto input = dyn_cast_or_null<DenseElementsAttr>(adaptor.getInput());
|
||||
if (!input) return {};
|
||||
auto output_type = mlir::dyn_cast<RankedTensorType>(getOutput().getType());
|
||||
if (!output_type || !output_type.hasStaticShape()) return {};
|
||||
// DenseElementsAttr does not support quantized element types.
|
||||
Type element_type = output_type.getElementType();
|
||||
if (!element_type.isSignlessIntOrFloat()) return {};
|
||||
|
||||
auto input_type = mlir::cast<ShapedType>(input.getType());
|
||||
const int64_t rank = input_type.getRank();
|
||||
const llvm::SmallVector<int64_t> paddings =
|
||||
UnpackIndexOperand(adaptor.getPadding());
|
||||
if (paddings.size() != 2 * rank) return {};
|
||||
for (int64_t d = 0; d < rank; ++d) {
|
||||
const int64_t before = paddings[2 * d], after = paddings[2 * d + 1];
|
||||
if (before < 0 || after < 0 ||
|
||||
input_type.getDimSize(d) + before + after !=
|
||||
output_type.getDimSize(d)) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// TFL PAD fills with zero (PADV2 takes an explicit value).
|
||||
Attribute zero = mlir::isa<FloatType>(element_type)
|
||||
? Attribute(FloatAttr::get(element_type, 0.0))
|
||||
: Attribute(IntegerAttr::get(element_type, 0));
|
||||
std::vector<Attribute> result(output_type.getNumElements(), zero);
|
||||
|
||||
const FlatIndHelper read_inds(input_type);
|
||||
const FlatIndHelper write_inds(output_type);
|
||||
llvm::SmallVector<int64_t> offset(rank);
|
||||
for (int64_t d = 0; d < rank; ++d) offset[d] = paddings[2 * d];
|
||||
|
||||
int64_t read_flat_ind = 0;
|
||||
for (Attribute value : input.getValues<Attribute>()) {
|
||||
llvm::SmallVector<int64_t> ind = read_inds.GetShapedInd(read_flat_ind++);
|
||||
FlatIndHelper::AddOffset(ind, offset);
|
||||
result[write_inds.GetFlatInd(ind)] = value;
|
||||
}
|
||||
return DenseElementsAttr::get(output_type, result);
|
||||
}
|
||||
|
||||
// When padding amounts are constants, cast them to i32. XNN can only
|
||||
|
||||
@@ -117,6 +117,7 @@ cc_library(
|
||||
":conversion_failure_reporter",
|
||||
"//tensorflow/compiler/mlir/lite:common",
|
||||
"//tensorflow/compiler/mlir/lite:converter_flags_proto_cc",
|
||||
"//tensorflow/compiler/mlir/lite:downcast_x64",
|
||||
"//tensorflow/compiler/mlir/lite:flatbuffer_export",
|
||||
"//tensorflow/compiler/mlir/lite:optimize_broadcast_like_pass_options",
|
||||
"//tensorflow/compiler/mlir/lite:tensorflow_lite",
|
||||
|
||||
@@ -59,6 +59,7 @@ limitations under the License.
|
||||
#include "tensorflow/compiler/mlir/lite/quantization/ir/QuantOps.h"
|
||||
#include "tensorflow/compiler/mlir/lite/stablehlo/transforms/stablehlo_passes.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/cast_bf16_ops_to_f32_pass.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/downcast_x64_pass.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/large_constant_fold_pass.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/optimize_broadcast_like_pass.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/optimize_broadcast_like_pass_options.h"
|
||||
@@ -140,7 +141,8 @@ std::unique_ptr<mlir::Pass> CreatePruneDeadResourcesPass() {
|
||||
} // namespace
|
||||
|
||||
void AddPipelinePasses(mlir::OpPassManager& pass_manager,
|
||||
const mlir::TFL::PassConfig& pass_config) {
|
||||
const mlir::TFL::PassConfig& pass_config,
|
||||
const tflite::ConverterFlags& converter_flags) {
|
||||
// =========================================================================
|
||||
// 1. Skip-to-TFLite & Pre-Lowering Passes
|
||||
// =========================================================================
|
||||
@@ -303,10 +305,20 @@ void AddPipelinePasses(mlir::OpPassManager& pass_manager,
|
||||
pass_manager.addPass(CreatePruneDeadResourcesPass());
|
||||
pass_manager.addNestedPass<mlir::func::FuncOp>(
|
||||
mlir::TFL::CreateFuseA4W2DRQFullyConnectedPass());
|
||||
if (pass_config.fold_fp16_casts_into_fully_connected) {
|
||||
pass_manager.addNestedPass<mlir::func::FuncOp>(
|
||||
mlir::TFL::CreateFoldFp16CastsIntoFullyConnectedPass());
|
||||
}
|
||||
pass_manager.addNestedPass<mlir::func::FuncOp>(
|
||||
mlir::createCanonicalizerPass());
|
||||
pass_manager.addPass(mlir::createSymbolDCEPass());
|
||||
pass_manager.addPass(mlir::TFL::CreateCleanupOptimizationBarrierPass());
|
||||
if (!converter_flags.enable_x64()) {
|
||||
pass_manager.addNestedPass<mlir::func::FuncOp>(
|
||||
mlir::TFL::CreateDowncastX64Pass());
|
||||
pass_manager.addNestedPass<mlir::func::FuncOp>(
|
||||
mlir::createCanonicalizerPass());
|
||||
}
|
||||
pass_manager.addPass(mlir::odml::createLegalizeStablehloToVhloPass());
|
||||
pass_manager.addPass(mlir::createReconcileUnrealizedCastsPass());
|
||||
pass_manager.addPass(CreatePruneDeadResourcesPass());
|
||||
@@ -426,7 +438,7 @@ absl::Status ConvertStableHloToTFLite(
|
||||
tensorflow::InitPassManager(pm, converter_flags.debug_options());
|
||||
}
|
||||
|
||||
AddPipelinePasses(pm, pass_config);
|
||||
AddPipelinePasses(pm, pass_config, converter_flags);
|
||||
|
||||
mlir::StatusScopedDiagnosticHandler status_handler(context,
|
||||
/*propagate=*/false);
|
||||
|
||||
@@ -394,6 +394,8 @@ absl::Status ConvertMlirBytecodeToTFLite(
|
||||
pass_config.fold_fp16_resource_casts =
|
||||
converter_flags.fold_fp16_resource_casts();
|
||||
pass_config.unfold_batch_matmul = converter_flags.unfold_batchmatmul();
|
||||
pass_config.fold_fp16_casts_into_fully_connected =
|
||||
converter_flags.fold_fp16_casts_into_fully_connected();
|
||||
|
||||
return mlir::TFL::ConvertStableHloToTFLite(*module, converter_flags,
|
||||
pass_config, export_stream);
|
||||
|
||||
@@ -103,6 +103,14 @@ absl::Status QuantizeModel(
|
||||
|
||||
auto flatbuffer_model = FlatBufferModelAbslError::BuildFromBuffer(
|
||||
output_buffer.data(), output_buffer.size());
|
||||
for (const auto* buffer : *flatbuffer_model->GetModel()->buffers()) {
|
||||
if (buffer != nullptr && buffer->data() != nullptr) {
|
||||
EXPECT_EQ((buffer->data()->Data() -
|
||||
reinterpret_cast<const uint8_t*>(output_buffer.data())) %
|
||||
16,
|
||||
0);
|
||||
}
|
||||
}
|
||||
*model = UnPackFlatBufferModel(*flatbuffer_model->GetModel());
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -111,8 +111,12 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
"//tensorflow/compiler/mlir/lite:tensorflow_lite",
|
||||
"//tensorflow/compiler/mlir/lite:tensorflow_lite_quantize",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@llvm-project//mlir:ArithDialect",
|
||||
"@llvm-project//mlir:AsmParser",
|
||||
"@llvm-project//mlir:IR",
|
||||
"@llvm-project//mlir:QuantOps",
|
||||
"@llvm-project//mlir:Support",
|
||||
"@llvm-project//mlir:TransformUtils",
|
||||
"@xla//xla/mlir_hlo",
|
||||
|
||||
+331
@@ -15,16 +15,29 @@ limitations under the License.
|
||||
|
||||
#include "tensorflow/compiler/mlir/lite/stablehlo/transforms/legalize_hlo_conversions/custom_call.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "mlir/AsmParser/AsmParser.h" // from @llvm-project
|
||||
#include "mlir/Dialect/Arith/IR/Arith.h" // from @llvm-project
|
||||
#include "mlir/Dialect/Quant/IR/Quant.h" // from @llvm-project
|
||||
#include "mlir/Dialect/Quant/IR/QuantTypes.h" // from @llvm-project
|
||||
#include "mlir/IR/Builders.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinAttributes.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinTypeInterfaces.h" // from @llvm-project
|
||||
#include "mlir/IR/OperationSupport.h" // from @llvm-project
|
||||
#include "mlir/IR/PatternMatch.h" // from @llvm-project
|
||||
#include "mlir/Support/LLVM.h" // from @llvm-project
|
||||
#include "mlir/Support/LogicalResult.h" // from @llvm-project
|
||||
#include "mlir/Transforms/DialectConversion.h" // from @llvm-project
|
||||
#include "tensorflow/compiler/mlir/lite/ir/tfl_ops.h" // IWYU pragma: keep
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/lower_quant_annotations_helper.h"
|
||||
#include "xla/mlir_hlo/mhlo/IR/hlo_ops.h"
|
||||
|
||||
namespace mlir {
|
||||
@@ -61,10 +74,242 @@ mlir::DictionaryAttr ParseSerializedTFLOpAttributes(
|
||||
return dict_attribute;
|
||||
}
|
||||
|
||||
// Quantization parameters carried in the backend_config of a `tfl.quantize` /
|
||||
// `tfl.dequantize` custom call.
|
||||
struct QuantParams {
|
||||
SmallVector<double, 4> scales;
|
||||
SmallVector<int64_t, 4> zero_points;
|
||||
int num_bits = 8;
|
||||
bool is_signed = true;
|
||||
bool narrow_range = false;
|
||||
int32_t quantized_dimension = 0;
|
||||
};
|
||||
|
||||
QuantParams ParseQuantParams(DictionaryAttr attributes) {
|
||||
QuantParams params;
|
||||
|
||||
if (auto scale_attr = attributes.getAs<mlir::FloatAttr>("scale")) {
|
||||
params.scales.push_back(scale_attr.getValueAsDouble());
|
||||
} else if (auto scale_array = attributes.getAs<mlir::ArrayAttr>("scale")) {
|
||||
for (auto elem : scale_array) {
|
||||
if (auto f_attr = mlir::dyn_cast<mlir::FloatAttr>(elem)) {
|
||||
params.scales.push_back(f_attr.getValueAsDouble());
|
||||
} else if (auto i_attr = mlir::dyn_cast<mlir::IntegerAttr>(elem)) {
|
||||
params.scales.push_back(static_cast<double>(i_attr.getInt()));
|
||||
}
|
||||
}
|
||||
} else if (auto scale_dense =
|
||||
attributes.getAs<mlir::DenseFPElementsAttr>("scale")) {
|
||||
for (auto val : scale_dense.getValues<APFloat>()) {
|
||||
params.scales.push_back(val.convertToDouble());
|
||||
}
|
||||
} else if (auto scale_int = attributes.getAs<mlir::IntegerAttr>("scale")) {
|
||||
params.scales.push_back(static_cast<double>(scale_int.getInt()));
|
||||
}
|
||||
|
||||
if (auto zp_attr = attributes.getAs<mlir::IntegerAttr>("zero_point")) {
|
||||
params.zero_points.push_back(zp_attr.getInt());
|
||||
} else if (auto zp_array = attributes.getAs<mlir::ArrayAttr>("zero_point")) {
|
||||
for (auto elem : zp_array) {
|
||||
if (auto i_attr = mlir::dyn_cast<mlir::IntegerAttr>(elem)) {
|
||||
params.zero_points.push_back(i_attr.getInt());
|
||||
}
|
||||
}
|
||||
} else if (auto zp_dense =
|
||||
attributes.getAs<mlir::DenseIntElementsAttr>("zero_point")) {
|
||||
for (auto val : zp_dense.getValues<APInt>()) {
|
||||
params.zero_points.push_back(val.getSExtValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (auto num_bits_attr = attributes.getAs<mlir::IntegerAttr>("num_bits")) {
|
||||
params.num_bits = num_bits_attr.getInt();
|
||||
}
|
||||
if (auto is_signed_attr = attributes.getAs<mlir::BoolAttr>("is_signed")) {
|
||||
params.is_signed = is_signed_attr.getValue();
|
||||
}
|
||||
if (auto narrow_range_attr =
|
||||
attributes.getAs<mlir::BoolAttr>("narrow_range")) {
|
||||
params.narrow_range = narrow_range_attr.getValue();
|
||||
}
|
||||
if (auto dim_attr =
|
||||
attributes.getAs<mlir::IntegerAttr>("quantized_dimension")) {
|
||||
params.quantized_dimension = dim_attr.getInt();
|
||||
}
|
||||
|
||||
if (params.scales.empty()) {
|
||||
params.scales.push_back(1.0);
|
||||
}
|
||||
if (params.zero_points.empty()) {
|
||||
params.zero_points.push_back(0);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// Builds the quantized tensor type of `shape` described by `params`. Returns
|
||||
// null if `params.quantized_dimension` is out of range for `shape`.
|
||||
RankedTensorType BuildQuantizedTensorType(Builder& builder,
|
||||
const QuantParams& params,
|
||||
ArrayRef<int64_t> shape,
|
||||
Type expressed_type, Location loc) {
|
||||
Type quantized_element_type;
|
||||
if (params.scales.size() == 1) {
|
||||
quantized_element_type = TFL::GetPerTensorQuantizedTensorType(
|
||||
builder, params.scales[0], params.zero_points[0], expressed_type,
|
||||
params.num_bits, loc, params.narrow_range, params.is_signed);
|
||||
} else {
|
||||
const int64_t rank = static_cast<int64_t>(shape.size());
|
||||
int64_t quant_dim = params.quantized_dimension;
|
||||
if (quant_dim < 0) {
|
||||
quant_dim += rank;
|
||||
}
|
||||
if (quant_dim < 0 || quant_dim >= rank) {
|
||||
return nullptr;
|
||||
}
|
||||
quantized_element_type = TFL::GetPerAxisQuantizedTensorType(
|
||||
builder, params.scales, params.zero_points,
|
||||
static_cast<int32_t>(quant_dim), expressed_type, params.num_bits, loc,
|
||||
params.narrow_range, params.is_signed);
|
||||
}
|
||||
if (!quantized_element_type) {
|
||||
return nullptr;
|
||||
}
|
||||
return RankedTensorType::get(shape, quantized_element_type);
|
||||
}
|
||||
|
||||
bool ApproxEqual(double a, double b) {
|
||||
return std::abs(a - b) <= 1e-6 * std::max(std::abs(a), std::abs(b));
|
||||
}
|
||||
|
||||
// Returns true if `qtype` has the scales and zero points in `params`.
|
||||
bool MatchesScalesAndZeroPoints(quant::QuantizedType qtype,
|
||||
const QuantParams& params) {
|
||||
if (auto uniform = mlir::dyn_cast<quant::UniformQuantizedType>(qtype)) {
|
||||
return params.scales.size() == 1 &&
|
||||
ApproxEqual(uniform.getScale(), params.scales[0]) &&
|
||||
uniform.getZeroPoint() == params.zero_points[0];
|
||||
}
|
||||
if (auto per_axis =
|
||||
mlir::dyn_cast<quant::UniformQuantizedPerAxisType>(qtype)) {
|
||||
ArrayRef<double> scales = per_axis.getScales();
|
||||
ArrayRef<int64_t> zero_points = per_axis.getZeroPoints();
|
||||
if (scales.size() != params.scales.size() ||
|
||||
zero_points.size() != params.zero_points.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < scales.size(); ++i) {
|
||||
if (!ApproxEqual(scales[i], params.scales[i]) ||
|
||||
zero_points[i] != params.zero_points[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rewrites a `tfl.quantize` custom call on `input` into `tfl.quantize`.
|
||||
LogicalResult RewriteQuantizeCustomCall(mhlo::CustomCallOp op, Value input,
|
||||
PatternRewriter& rewriter) {
|
||||
DictionaryAttr attributes =
|
||||
ParseSerializedTFLOpAttributes(op.getBackendConfig(), op.getContext());
|
||||
if (!attributes) {
|
||||
return failure();
|
||||
}
|
||||
QuantParams params = ParseQuantParams(attributes);
|
||||
|
||||
ShapedType input_shaped_type = mlir::cast<ShapedType>(input.getType());
|
||||
RankedTensorType output_type =
|
||||
BuildQuantizedTensorType(rewriter, params, input_shaped_type.getShape(),
|
||||
input_shaped_type.getElementType(), op.getLoc());
|
||||
if (!output_type) {
|
||||
return op.emitError("invalid quantization parameters for tfl.quantize");
|
||||
}
|
||||
auto quant_op = rewriter.create<TFL::QuantizeOp>(
|
||||
op.getLoc(), output_type, input, TypeAttr::get(output_type));
|
||||
rewriter.replaceOp(op, quant_op.getOutput());
|
||||
return success();
|
||||
}
|
||||
|
||||
// Rewrites a `tfl.dequantize` custom call on `input` into `tfl.dequantize`.
|
||||
// - An already-quantized input is dequantized with its own type, which must
|
||||
// match the scales and zero points in the backend_config.
|
||||
// - An integer constant input becomes a `tfl.pseudo_qconst` with the quantized
|
||||
// type described by the backend_config.
|
||||
// Any other input (e.g. a runtime integer tensor) can't be given a quantized
|
||||
// type and is rejected.
|
||||
LogicalResult RewriteDequantizeCustomCall(mhlo::CustomCallOp op, Value input,
|
||||
PatternRewriter& rewriter) {
|
||||
DictionaryAttr attributes =
|
||||
ParseSerializedTFLOpAttributes(op.getBackendConfig(), op.getContext());
|
||||
if (!attributes) {
|
||||
return failure();
|
||||
}
|
||||
|
||||
if (auto call_op = input.getDefiningOp<mhlo::CustomCallOp>()) {
|
||||
if (call_op.getCallTargetName() == "tfl.quantize") {
|
||||
return failure();
|
||||
}
|
||||
}
|
||||
ShapedType input_shaped_type = mlir::cast<ShapedType>(input.getType());
|
||||
Type output_type = op.getResultTypes().front();
|
||||
Type expressed_type = mlir::cast<ShapedType>(output_type).getElementType();
|
||||
QuantParams params = ParseQuantParams(attributes);
|
||||
|
||||
Value tfl_dequant_input = input;
|
||||
if (auto qtype = mlir::dyn_cast<quant::QuantizedType>(
|
||||
input_shaped_type.getElementType())) {
|
||||
if (!MatchesScalesAndZeroPoints(qtype, params)) {
|
||||
return op.emitError(
|
||||
"tfl.dequantize scale/zero_point don't match the quantized type of "
|
||||
"its input");
|
||||
}
|
||||
} else {
|
||||
ElementsAttr const_value;
|
||||
Location const_loc = op.getLoc();
|
||||
if (auto const_op = input.getDefiningOp<arith::ConstantOp>()) {
|
||||
const_value = mlir::dyn_cast<ElementsAttr>(const_op.getValue());
|
||||
const_loc = const_op.getLoc();
|
||||
} else if (auto const_op = input.getDefiningOp<mhlo::ConstantOp>()) {
|
||||
const_value = mlir::dyn_cast<ElementsAttr>(const_op.getValue());
|
||||
const_loc = const_op.getLoc();
|
||||
}
|
||||
if (!const_value ||
|
||||
!mlir::isa<mlir::IntegerType>(const_value.getElementType())) {
|
||||
return rewriter.notifyMatchFailure(
|
||||
op, "tfl.dequantize input must be quantized or an integer constant");
|
||||
}
|
||||
RankedTensorType qtensor_type =
|
||||
BuildQuantizedTensorType(rewriter, params, input_shaped_type.getShape(),
|
||||
expressed_type, op.getLoc());
|
||||
if (!qtensor_type) {
|
||||
return op.emitError("invalid quantization parameters for tfl.dequantize");
|
||||
}
|
||||
auto qconst_op = rewriter.create<TFL::QConstOp>(
|
||||
const_loc, qtensor_type, TypeAttr::get(qtensor_type), const_value);
|
||||
tfl_dequant_input = qconst_op.getResult();
|
||||
}
|
||||
|
||||
auto dequant_op = rewriter.create<TFL::DequantizeOp>(op.getLoc(), output_type,
|
||||
tfl_dequant_input);
|
||||
rewriter.replaceOp(op, dequant_op.getOutput());
|
||||
return success();
|
||||
}
|
||||
|
||||
LogicalResult ConvertCustomCallOp::matchAndRewrite(
|
||||
mhlo::CustomCallOp mhlo_custom_call, OpAdaptor adaptor,
|
||||
ConversionPatternRewriter& rewriter) const {
|
||||
auto call_target_name = mhlo_custom_call.getCallTargetName();
|
||||
|
||||
if (call_target_name == "tfl.quantize") {
|
||||
return RewriteQuantizeCustomCall(mhlo_custom_call,
|
||||
adaptor.getOperands().front(), rewriter);
|
||||
}
|
||||
if (call_target_name == "tfl.dequantize") {
|
||||
return RewriteDequantizeCustomCall(mhlo_custom_call,
|
||||
adaptor.getOperands().front(), rewriter);
|
||||
}
|
||||
|
||||
if (call_target_name.starts_with("tfl.")) {
|
||||
auto bc = mhlo_custom_call.getBackendConfig();
|
||||
if (mlir::DictionaryAttr attributes =
|
||||
@@ -101,6 +346,34 @@ LogicalResult ConvertCustomCallOp::matchAndRewrite(
|
||||
return success();
|
||||
}
|
||||
|
||||
class RewriteQuantizeCustomCallOp
|
||||
: public OpRewritePattern<mhlo::CustomCallOp> {
|
||||
public:
|
||||
using OpRewritePattern<mhlo::CustomCallOp>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(mhlo::CustomCallOp op,
|
||||
PatternRewriter& rewriter) const override {
|
||||
if (op.getCallTargetName() != "tfl.quantize") {
|
||||
return failure();
|
||||
}
|
||||
return RewriteQuantizeCustomCall(op, op.getOperand(0), rewriter);
|
||||
}
|
||||
};
|
||||
|
||||
class RewriteDequantizeCustomCallOp
|
||||
: public OpRewritePattern<mhlo::CustomCallOp> {
|
||||
public:
|
||||
using OpRewritePattern<mhlo::CustomCallOp>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(mhlo::CustomCallOp op,
|
||||
PatternRewriter& rewriter) const override {
|
||||
if (op.getCallTargetName() != "tfl.dequantize") {
|
||||
return failure();
|
||||
}
|
||||
return RewriteDequantizeCustomCall(op, op.getOperand(0), rewriter);
|
||||
}
|
||||
};
|
||||
|
||||
// Removes the `mhlo.custom_call @shape_assertion` custom call which represents
|
||||
// an assertion that the first operand (`assert_what`) evaluates to `true`.
|
||||
// This is a temporary workaround for unblocking dynamic model conversion
|
||||
@@ -132,6 +405,61 @@ LogicalResult RemoveCustomCallWithShapeAssertion::matchAndRewrite(
|
||||
return success();
|
||||
}
|
||||
|
||||
// TFL ops whose quantized output uses the same scale and zero point as their
|
||||
// first operand: they only move, select or replicate elements.
|
||||
constexpr llvm::StringLiteral kScalePreservingTflOps[] = {
|
||||
"tfl.broadcast_to", "tfl.depth_to_space", "tfl.expand_dims",
|
||||
"tfl.gather", "tfl.gather_nd", "tfl.reshape",
|
||||
"tfl.reverse_v2", "tfl.slice", "tfl.space_to_depth",
|
||||
"tfl.squeeze", "tfl.strided_slice", "tfl.tile",
|
||||
"tfl.transpose",
|
||||
};
|
||||
|
||||
// Gives a scale-preserving `tfl.*` custom call the quantized type of its first
|
||||
// operand, so that e.g. `reshape(quantize(x))` stays quantized. Ops that
|
||||
// compute new values (FC, BMM, add, ...) need their own output scale and are
|
||||
// deliberately not handled here.
|
||||
class PropagateQuantizedTypeToCustomCallOp
|
||||
: public OpRewritePattern<mhlo::CustomCallOp> {
|
||||
public:
|
||||
using OpRewritePattern<mhlo::CustomCallOp>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(mhlo::CustomCallOp op,
|
||||
PatternRewriter& rewriter) const override {
|
||||
if (!llvm::is_contained(kScalePreservingTflOps, op.getCallTargetName())) {
|
||||
return failure();
|
||||
}
|
||||
if (op.getNumOperands() == 0 || op.getNumResults() != 1) {
|
||||
return failure();
|
||||
}
|
||||
auto operand_type = mlir::dyn_cast<ShapedType>(op.getOperand(0).getType());
|
||||
if (!operand_type) {
|
||||
return failure();
|
||||
}
|
||||
auto qtype =
|
||||
mlir::dyn_cast<quant::QuantizedType>(operand_type.getElementType());
|
||||
if (!qtype) {
|
||||
return failure();
|
||||
}
|
||||
auto res_shaped_type =
|
||||
mlir::dyn_cast<ShapedType>(op.getResult(0).getType());
|
||||
if (!res_shaped_type) {
|
||||
return failure();
|
||||
}
|
||||
// Only retype results that already hold the storage type, e.g. i8 -> !quant
|
||||
// with i8 storage.
|
||||
auto res_int_type =
|
||||
mlir::dyn_cast<mlir::IntegerType>(res_shaped_type.getElementType());
|
||||
if (!res_int_type ||
|
||||
res_int_type.getWidth() != qtype.getStorageTypeIntegralWidth()) {
|
||||
return failure();
|
||||
}
|
||||
rewriter.modifyOpInPlace(
|
||||
op, [&] { op.getResult(0).setType(res_shaped_type.clone(qtype)); });
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<bool> IsCustomCallLegal(mhlo::CustomCallOp op) {
|
||||
auto call_target_name = op.getCallTargetName();
|
||||
if (call_target_name.starts_with("custom_call.")) {
|
||||
@@ -160,6 +488,9 @@ void PopulateCustomCallPatterns(MLIRContext* ctx, RewritePatternSet& patterns,
|
||||
void PopulateCustomCallPreparePatterns(MLIRContext* ctx,
|
||||
RewritePatternSet& patterns) {
|
||||
patterns.add<RemoveCustomCallWithShapeAssertion>(ctx);
|
||||
patterns.add<RewriteQuantizeCustomCallOp>(ctx);
|
||||
patterns.add<RewriteDequantizeCustomCallOp>(ctx);
|
||||
patterns.add<PropagateQuantizedTypeToCustomCallOp>(ctx);
|
||||
}
|
||||
|
||||
} // namespace odml
|
||||
|
||||
@@ -101,3 +101,15 @@ func.func private @test.impl(%arg0: tensor<2x2xbf16>) -> tensor<2x2xbf16> {
|
||||
%0 = "tfl.add"(%arg0, %arg0) <{fused_activation_function = "NONE"}> : (tensor<2x2xbf16>, tensor<2x2xbf16>) -> tensor<2x2xbf16>
|
||||
return %0 : tensor<2x2xbf16>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// CHECK-LABEL: func.func @keep_non_bf16_cast_round_trip
|
||||
func.func @keep_non_bf16_cast_round_trip(%arg0: tensor<2x2xf32>) -> tensor<2x2xf32> {
|
||||
// CHECK: %[[NARROW:.*]] = "tfl.cast"(%arg0) : (tensor<2x2xf32>) -> tensor<2x2xf16>
|
||||
// CHECK: %[[WIDEN:.*]] = "tfl.cast"(%[[NARROW]]) : (tensor<2x2xf16>) -> tensor<2x2xf32>
|
||||
// CHECK: return %[[WIDEN]] : tensor<2x2xf32>
|
||||
%0 = "tfl.cast"(%arg0) : (tensor<2x2xf32>) -> tensor<2x2xf16>
|
||||
%1 = "tfl.cast"(%0) : (tensor<2x2xf16>) -> tensor<2x2xf32>
|
||||
return %1 : tensor<2x2xf32>
|
||||
}
|
||||
@@ -537,6 +537,37 @@ func.func @ConstFoldPad(%arg0: tensor<15600xf32>) -> tensor<15600xf32> {
|
||||
// CHECK: return %arg0
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @ConstFoldPadConstInput
|
||||
func.func @ConstFoldPadConstInput() -> tensor<3x4xf32> {
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[[1.0, 2.0], [3.0, 4.0]]> : tensor<2x2xf32>} : () -> tensor<2x2xf32>
|
||||
%1 = "tfl.pseudo_const"() {value = dense<[[1, 0], [0, 2]]> : tensor<2x2xi32>} : () -> tensor<2x2xi32>
|
||||
%2 = "tfl.pad"(%0, %1) : (tensor<2x2xf32>, tensor<2x2xi32>) -> tensor<3x4xf32>
|
||||
func.return %2 : tensor<3x4xf32>
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<{{\[\[}}0.000000e+00, 0.000000e+00, 0.000000e+00, 0.000000e+00], [1.000000e+00, 2.000000e+00, 0.000000e+00, 0.000000e+00], [3.000000e+00, 4.000000e+00, 0.000000e+00, 0.000000e+00]]> : tensor<3x4xf32>
|
||||
// CHECK-NOT: tfl.pad
|
||||
// CHECK: return %[[CST]]
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @ConstFoldPadConstInputI64Paddings
|
||||
func.func @ConstFoldPadConstInputI64Paddings() -> tensor<4xi32> {
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[7, 8]> : tensor<2xi32>} : () -> tensor<2xi32>
|
||||
%1 = "tfl.pseudo_const"() {value = dense<[[1, 1]]> : tensor<1x2xi64>} : () -> tensor<1x2xi64>
|
||||
%2 = "tfl.pad"(%0, %1) : (tensor<2xi32>, tensor<1x2xi64>) -> tensor<4xi32>
|
||||
func.return %2 : tensor<4xi32>
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[0, 7, 8, 0]> : tensor<4xi32>
|
||||
// CHECK-NOT: tfl.pad
|
||||
// CHECK: return %[[CST]]
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @NotFoldPadRuntimeInput
|
||||
func.func @NotFoldPadRuntimeInput(%arg0: tensor<2x2xf32>) -> tensor<3x4xf32> {
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[[1, 0], [0, 2]]> : tensor<2x2xi32>} : () -> tensor<2x2xi32>
|
||||
%1 = "tfl.pad"(%arg0, %0) : (tensor<2x2xf32>, tensor<2x2xi32>) -> tensor<3x4xf32>
|
||||
func.return %1 : tensor<3x4xf32>
|
||||
// CHECK: %[[PAD:.*]] = "tfl.pad"(%arg0
|
||||
// CHECK: return %[[PAD]]
|
||||
}
|
||||
|
||||
func.func @ConstFoldPadV2(%arg0: tensor<15600xf32>) -> tensor<15600xf32> {
|
||||
%0 = "tfl.pseudo_const"() {value = dense<0> : tensor<1x2xi32>} : () -> tensor<1x2xi32>
|
||||
%1 = "tfl.pseudo_const"() {value = dense<0.0> : tensor<f32>} : () -> tensor<f32>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// ==============================================================================
|
||||
// RUN: litert-opt %s --tfl-downcast-x64 --canonicalize | FileCheck %s
|
||||
// RUN: litert-opt %s --tfl-downcast-x64 --canonicalize -verify-diagnostics | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: testFuncSignature
|
||||
// CHECK: (%arg0: tensor<4xi32>, %arg1: tensor<2x2xf32>) -> tensor<2x2xf32>
|
||||
@@ -37,10 +37,140 @@ func.func @testI64ConstantDowncast() -> tensor<i64> {
|
||||
func.return %0 : tensor<i64>
|
||||
}
|
||||
|
||||
// INT64_MAX / INT64_MIN sentinels saturate to INT32_MAX / INT32_MIN instead of
|
||||
// wrapping to -1 / 0.
|
||||
// CHECK-LABEL: testI64ConstantSaturates
|
||||
func.func @testI64ConstantSaturates() -> tensor<4xi64> {
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[2147483647, -2147483648, 2147483647, -2147483648]> : tensor<4xi32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [INT32_MIN, INT32_MAX]}}
|
||||
%0 = arith.constant dense<[9223372036854775807, -9223372036854775808, 2147483648, -2147483649]> : tensor<4xi64>
|
||||
func.return %0 : tensor<4xi64>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: testI64SplatConstantSaturates
|
||||
func.func @testI64SplatConstantSaturates() -> tensor<2x3xi64> {
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<2147483647> : tensor<2x3xi32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [INT32_MIN, INT32_MAX]}}
|
||||
%0 = arith.constant dense<9223372036854775807> : tensor<2x3xi64>
|
||||
func.return %0 : tensor<2x3xi64>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: testTflConstSaturates
|
||||
func.func @testTflConstSaturates() -> tensor<2xi64> {
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[-2147483648, 7]> : tensor<2xi32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [INT32_MIN, INT32_MAX]}}
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[-9223372036854775808, 7]> : tensor<2xi64>} : () -> tensor<2xi64>
|
||||
func.return %0 : tensor<2xi64>
|
||||
}
|
||||
|
||||
// Unsigned constants clamp to [0, UINT32_MAX] with unsigned comparisons: a
|
||||
// value with the top bit set saturates to UINT32_MAX rather than being read as
|
||||
// negative and clamped to the bottom of the range. (arith.constant requires
|
||||
// signless types, so these use tfl.pseudo_const.)
|
||||
// CHECK-LABEL: testUI64ConstantSaturates
|
||||
func.func @testUI64ConstantSaturates() -> tensor<3xui64> {
|
||||
// CHECK: %[[CST:.*]] = "tfl.pseudo_const"() <{value = dense<[4294967295, 4294967295, 7]> : tensor<3xui32>}> : () -> tensor<3xui32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [0, UINT32_MAX]}}
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[18446744073709551615, 4294967296, 7]> : tensor<3xui64>} : () -> tensor<3xui64>
|
||||
func.return %0 : tensor<3xui64>
|
||||
}
|
||||
|
||||
// Unsigned values that fit in 32 bits but exceed INT32_MAX are kept.
|
||||
// CHECK-LABEL: testUI64ConstantInRangeNoWarning
|
||||
func.func @testUI64ConstantInRangeNoWarning() -> tensor<2xui64> {
|
||||
// CHECK: %[[CST:.*]] = "tfl.pseudo_const"() <{value = dense<[4294967295, 2147483648]> : tensor<2xui32>}> : () -> tensor<2xui32>
|
||||
// CHECK: return %[[CST]]
|
||||
%0 = "tfl.pseudo_const"() {value = dense<[4294967295, 2147483648]> : tensor<2xui64>} : () -> tensor<2xui64>
|
||||
func.return %0 : tensor<2xui64>
|
||||
}
|
||||
|
||||
// ui64 function arguments narrow to ui32, preserving signedness.
|
||||
// CHECK-LABEL: testUI64FuncSignature
|
||||
// CHECK: (%arg0: tensor<4xui32>) -> tensor<4xui32>
|
||||
func.func @testUI64FuncSignature(%arg0: tensor<4xui64>) -> tensor<4xui64> {
|
||||
// CHECK: return %arg0 : tensor<4xui32>
|
||||
func.return %arg0 : tensor<4xui64>
|
||||
}
|
||||
|
||||
// In-range values pass through unchanged and emit no warning.
|
||||
// CHECK-LABEL: testI64ConstantInRangeNoWarning
|
||||
func.func @testI64ConstantInRangeNoWarning() -> tensor<2xi64> {
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense<[2147483647, -2147483648]> : tensor<2xi32>
|
||||
// CHECK: return %[[CST]]
|
||||
%0 = arith.constant dense<[2147483647, -2147483648]> : tensor<2xi64>
|
||||
func.return %0 : tensor<2xi64>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: testGeneralOpForceConvert
|
||||
func.func @testGeneralOpForceConvert(%arg0: tensor<2xf64>) -> tensor<2xf64> {
|
||||
// CHECK: %[[ADD:.*]] = tfl.add %arg0, %arg0 {fused_activation_function = "NONE"} : tensor<2xf32>
|
||||
// CHECK: return %[[ADD]]
|
||||
%0 = "tfl.add"(%arg0, %arg0) {fused_activation_function = "NONE"} : (tensor<2xf64>, tensor<2xf64>) -> tensor<2xf64>
|
||||
func.return %0 : tensor<2xf64>
|
||||
}
|
||||
}
|
||||
|
||||
// Block arguments of nested regions (here a region-based tfl.while, which is
|
||||
// only outlined into functions after this pass runs) are narrowed along with
|
||||
// the operands feeding them.
|
||||
// CHECK-LABEL: testWhileRegionBlockArgs
|
||||
// CHECK-SAME: (%arg0: tensor<i32>) -> tensor<i32>
|
||||
func.func @testWhileRegionBlockArgs(%arg0: tensor<i64>) -> tensor<i64> {
|
||||
// CHECK: "tfl.while"(%arg0) ({
|
||||
// CHECK: ^bb0(%[[CARG:.*]]: tensor<i32>):
|
||||
// CHECK: tfl.less(%[[CARG]], %{{.*}}) : (tensor<i32>, tensor<i32>) -> tensor<i1>
|
||||
// CHECK: ^bb0(%[[BARG:.*]]: tensor<i32>):
|
||||
// CHECK: tfl.add %[[BARG]], %{{.*}} {fused_activation_function = "NONE"} : tensor<i32>
|
||||
// CHECK: }) : (tensor<i32>) -> tensor<i32>
|
||||
%0 = "tfl.while"(%arg0) ({
|
||||
^bb0(%carg: tensor<i64>):
|
||||
%limit = arith.constant dense<10> : tensor<i64>
|
||||
%cond = "tfl.less"(%carg, %limit) : (tensor<i64>, tensor<i64>) -> tensor<i1>
|
||||
"tfl.yield"(%cond) : (tensor<i1>) -> ()
|
||||
}, {
|
||||
^bb0(%barg: tensor<i64>):
|
||||
%one = arith.constant dense<1> : tensor<i64>
|
||||
%next = "tfl.add"(%barg, %one) {fused_activation_function = "NONE"} : (tensor<i64>, tensor<i64>) -> tensor<i64>
|
||||
"tfl.yield"(%next) : (tensor<i64>) -> ()
|
||||
}) : (tensor<i64>) -> tensor<i64>
|
||||
func.return %0 : tensor<i64>
|
||||
}
|
||||
|
||||
// Resource-backed constants (what lightweight_conversion produces for large
|
||||
// tensors) are narrowed into a new blob with the same saturation rules.
|
||||
// res_i64 = [INT64_MAX, INT64_MIN, 5].
|
||||
// CHECK-LABEL: testI64ResourceConstantSaturates
|
||||
func.func @testI64ResourceConstantSaturates() -> tensor<3xi64> {
|
||||
// CHECK: %[[CST:.*]] = arith.constant dense_resource<res_i64_x32> : tensor<3xi32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [INT32_MIN, INT32_MAX]}}
|
||||
%0 = arith.constant dense_resource<res_i64> : tensor<3xi64>
|
||||
func.return %0 : tensor<3xi64>
|
||||
}
|
||||
|
||||
// res_ui64 = [UINT64_MAX, 7].
|
||||
// CHECK-LABEL: testUI64ResourceConstantSaturates
|
||||
func.func @testUI64ResourceConstantSaturates() -> tensor<2xui64> {
|
||||
// CHECK: %[[CST:.*]] = "tfl.pseudo_const"() <{value = dense_resource<res_ui64_x32> : tensor<2xui32>}> : () -> tensor<2xui32>
|
||||
// CHECK: return %[[CST]]
|
||||
// expected-warning @+1 {{saturating them to [0, UINT32_MAX]}}
|
||||
%0 = "tfl.pseudo_const"() {value = dense_resource<res_ui64> : tensor<2xui64>} : () -> tensor<2xui64>
|
||||
func.return %0 : tensor<2xui64>
|
||||
}
|
||||
|
||||
// The narrowed blobs: 64-byte alignment prefix, then
|
||||
// [INT32_MAX, INT32_MIN, 5] and [UINT32_MAX, 7] as little-endian 32-bit.
|
||||
// CHECK-DAG: res_i64_x32: "0x40000000FFFFFF7F0000008005000000"
|
||||
// CHECK-DAG: res_ui64_x32: "0x40000000FFFFFFFF07000000"
|
||||
|
||||
{-#
|
||||
dialect_resources: {
|
||||
builtin: {
|
||||
res_i64: "0x08000000FFFFFFFFFFFFFF7F00000000000000800500000000000000",
|
||||
res_ui64: "0x08000000FFFFFFFFFFFFFFFF0700000000000000"
|
||||
}
|
||||
}
|
||||
#-}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2026 The TensorFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed 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.
|
||||
// ==============================================================================
|
||||
|
||||
// RUN: litert-opt %s -tfl-fold-fp16-casts-into-fully-connected | FileCheck %s
|
||||
|
||||
// CHECK-LABEL: @FoldCasts
|
||||
// CHECK-SAME: (%[[X:.*]]: tensor<4x64xf16>, %[[W:.*]]: tensor<32x64xf32>, %[[B:.*]]: tensor<32xf32>)
|
||||
func.func @FoldCasts(%arg0: tensor<4x64xf16>, %w: tensor<32x64xf32>, %b: tensor<32xf32>) -> tensor<4x32xf16> {
|
||||
%0 = "tfl.cast"(%arg0) : (tensor<4x64xf16>) -> tensor<4x64xf32>
|
||||
%1 = "tfl.fully_connected"(%0, %w, %b) {fused_activation_function = "RELU", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<32x64xf32>, tensor<32xf32>) -> tensor<4x32xf32>
|
||||
%2 = "tfl.cast"(%1) : (tensor<4x32xf32>) -> tensor<4x32xf16>
|
||||
func.return %2 : tensor<4x32xf16>
|
||||
// CHECK-NOT: tfl.cast
|
||||
// CHECK: %[[FC:.*]] = "tfl.fully_connected"(%[[X]], %[[W]], %[[B]]) <{fused_activation_function = "RELU", keep_num_dims = false, weights_format = "DEFAULT"}> : (tensor<4x64xf16>, tensor<32x64xf32>, tensor<32xf32>) -> tensor<4x32xf16>
|
||||
// CHECK-NOT: tfl.cast
|
||||
// CHECK: return %[[FC]]
|
||||
}
|
||||
|
||||
// The input cast feeding several fully_connected ops (e.g. q/k/v projections)
|
||||
// is folded into each of them.
|
||||
|
||||
// CHECK-LABEL: @FoldSharedInputCast
|
||||
func.func @FoldSharedInputCast(%arg0: tensor<4x64xf16>, %w0: tensor<32x64xf32>, %w1: tensor<16x64xf32>) -> (tensor<4x32xf16>, tensor<4x16xf16>) {
|
||||
%none = "tfl.no_value"() {value} : () -> none
|
||||
%0 = "tfl.cast"(%arg0) : (tensor<4x64xf16>) -> tensor<4x64xf32>
|
||||
%1 = "tfl.fully_connected"(%0, %w0, %none) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<32x64xf32>, none) -> tensor<4x32xf32>
|
||||
%2 = "tfl.cast"(%1) : (tensor<4x32xf32>) -> tensor<4x32xf16>
|
||||
%3 = "tfl.fully_connected"(%0, %w1, %none) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<16x64xf32>, none) -> tensor<4x16xf32>
|
||||
%4 = "tfl.cast"(%3) : (tensor<4x16xf32>) -> tensor<4x16xf16>
|
||||
func.return %2, %4 : tensor<4x32xf16>, tensor<4x16xf16>
|
||||
// CHECK-NOT: tfl.cast
|
||||
// CHECK: "tfl.fully_connected"(%arg0, %arg1, %{{.*}}) {{.*}} -> tensor<4x32xf16>
|
||||
// CHECK: "tfl.fully_connected"(%arg0, %arg2, %{{.*}}) {{.*}} -> tensor<4x16xf16>
|
||||
// CHECK-NOT: tfl.cast
|
||||
}
|
||||
|
||||
// The f32 result has another user, so the fully_connected must stay f32.
|
||||
|
||||
// CHECK-LABEL: @NoFoldOutputHasOtherUsers
|
||||
func.func @NoFoldOutputHasOtherUsers(%arg0: tensor<4x64xf16>, %w: tensor<32x64xf32>) -> (tensor<4x32xf16>, tensor<4x32xf32>) {
|
||||
%none = "tfl.no_value"() {value} : () -> none
|
||||
%0 = "tfl.cast"(%arg0) : (tensor<4x64xf16>) -> tensor<4x64xf32>
|
||||
%1 = "tfl.fully_connected"(%0, %w, %none) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<32x64xf32>, none) -> tensor<4x32xf32>
|
||||
%2 = "tfl.cast"(%1) : (tensor<4x32xf32>) -> tensor<4x32xf16>
|
||||
func.return %2, %1 : tensor<4x32xf16>, tensor<4x32xf32>
|
||||
// CHECK: "tfl.fully_connected"({{.*}}) {{.*}} -> tensor<4x32xf32>
|
||||
// CHECK: "tfl.cast"
|
||||
}
|
||||
|
||||
// The activation is not cast from f16, so there is nothing to fold.
|
||||
|
||||
// CHECK-LABEL: @NoFoldInputNotFromF16
|
||||
func.func @NoFoldInputNotFromF16(%arg0: tensor<4x64xf32>, %w: tensor<32x64xf32>) -> tensor<4x32xf16> {
|
||||
%none = "tfl.no_value"() {value} : () -> none
|
||||
%0 = "tfl.fully_connected"(%arg0, %w, %none) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<32x64xf32>, none) -> tensor<4x32xf32>
|
||||
%1 = "tfl.cast"(%0) : (tensor<4x32xf32>) -> tensor<4x32xf16>
|
||||
func.return %1 : tensor<4x32xf16>
|
||||
// CHECK: "tfl.fully_connected"({{.*}}) {{.*}} -> tensor<4x32xf32>
|
||||
// CHECK: "tfl.cast"
|
||||
}
|
||||
|
||||
// A bf16 input cast is a different precision contract and must not be folded.
|
||||
|
||||
// CHECK-LABEL: @NoFoldBf16
|
||||
func.func @NoFoldBf16(%arg0: tensor<4x64xbf16>, %w: tensor<32x64xf32>) -> tensor<4x32xbf16> {
|
||||
%none = "tfl.no_value"() {value} : () -> none
|
||||
%0 = "tfl.cast"(%arg0) : (tensor<4x64xbf16>) -> tensor<4x64xf32>
|
||||
%1 = "tfl.fully_connected"(%0, %w, %none) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<4x64xf32>, tensor<32x64xf32>, none) -> tensor<4x32xf32>
|
||||
%2 = "tfl.cast"(%1) : (tensor<4x32xf32>) -> tensor<4x32xbf16>
|
||||
func.return %2 : tensor<4x32xbf16>
|
||||
// CHECK: "tfl.cast"
|
||||
// CHECK: "tfl.fully_connected"({{.*}}) {{.*}} -> tensor<4x32xf32>
|
||||
// CHECK: "tfl.cast"
|
||||
}
|
||||
@@ -148,6 +148,12 @@ class RemoveUnneededCastOps : public OpRewritePattern<mlir::TFL::CastOp> {
|
||||
if (orig_input.getType() != op.getType()) {
|
||||
return failure();
|
||||
}
|
||||
// Only fold round trips through bf16, which is what this pass introduces.
|
||||
// Other narrowing round trips (e.g. f32 -> f16 -> f32) are explicit
|
||||
// precision changes in the source program and must be preserved.
|
||||
if (!getElementTypeOrSelf(prev_cast.getType()).isBF16()) {
|
||||
return failure();
|
||||
}
|
||||
rewriter.replaceOp(op, orig_input);
|
||||
return success();
|
||||
}
|
||||
|
||||
@@ -15,13 +15,27 @@ limitations under the License.
|
||||
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/downcast_x64_pass.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "llvm/ADT/APInt.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project
|
||||
#include "mlir/IR/AsmState.h" // from @llvm-project
|
||||
#include "mlir/IR/Block.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinAttributeInterfaces.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinAttributes.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinTypeInterfaces.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinTypes.h" // from @llvm-project
|
||||
#include "mlir/IR/MLIRContext.h" // from @llvm-project
|
||||
#include "mlir/IR/OpDefinition.h" // from @llvm-project
|
||||
#include "mlir/IR/Operation.h" // from @llvm-project
|
||||
#include "mlir/IR/Types.h" // from @llvm-project
|
||||
#include "mlir/IR/Value.h" // from @llvm-project
|
||||
#include "mlir/Pass/Pass.h" // from @llvm-project
|
||||
@@ -52,12 +66,152 @@ mlir::Type ConvertX64TypeToX32(mlir::Type type) {
|
||||
if (type.isF64()) {
|
||||
return mlir::Float32Type::get(type.getContext());
|
||||
}
|
||||
if (type.isInteger(64)) {
|
||||
return mlir::IntegerType::get(type.getContext(), 32);
|
||||
// Preserve signedness so ui64 narrows to ui32 rather than signless i32.
|
||||
if (auto int_type = mlir::dyn_cast<mlir::IntegerType>(type);
|
||||
int_type && int_type.getWidth() == 64) {
|
||||
return mlir::IntegerType::get(type.getContext(), 32,
|
||||
int_type.getSignedness());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// Emits the saturation warning on `op`.
|
||||
void EmitSaturationWarning(mlir::Operation* op, bool is_unsigned) {
|
||||
op->emitWarning()
|
||||
<< "enable_x64=false: 64-bit integer constant has values outside the "
|
||||
"32-bit range; saturating them to "
|
||||
<< (is_unsigned ? "[0, UINT32_MAX]" : "[INT32_MIN, INT32_MAX]")
|
||||
<< ". Pass enable_x64=true to keep 64-bit types.";
|
||||
}
|
||||
|
||||
// Narrows a `DenseIntElementsAttr` of 64-bit integers to 32 bits, clamping
|
||||
// out-of-range values. Sets `*clamped` if any value was clamped.
|
||||
mlir::DenseIntElementsAttr NarrowDenseI64(mlir::DenseIntElementsAttr value,
|
||||
mlir::Type new_element_type,
|
||||
bool* clamped) {
|
||||
const bool is_unsigned = value.getElementType().isUnsignedInteger();
|
||||
const APInt min_value =
|
||||
is_unsigned ? APInt::getZero(64) : APInt::getSignedMinValue(32).sext(64);
|
||||
const APInt max_value = is_unsigned ? APInt::getMaxValue(32).zext(64)
|
||||
: APInt::getSignedMaxValue(32).sext(64);
|
||||
auto less_than = [is_unsigned](const APInt& a, const APInt& b) {
|
||||
return is_unsigned ? a.ult(b) : a.slt(b);
|
||||
};
|
||||
return mlir::cast<mlir::DenseIntElementsAttr>(
|
||||
value.mapValues(new_element_type, [&](const APInt& v) -> APInt {
|
||||
if (less_than(v, min_value)) {
|
||||
*clamped = true;
|
||||
return min_value.trunc(32);
|
||||
}
|
||||
if (less_than(max_value, v)) {
|
||||
*clamped = true;
|
||||
return max_value.trunc(32);
|
||||
}
|
||||
return v.trunc(32);
|
||||
}));
|
||||
}
|
||||
|
||||
// Narrows a `DenseResourceElementsAttr` of 64-bit integers to 32 bits by
|
||||
// allocating a new blob, clamping out-of-range values. Returns a null
|
||||
// attribute if the resource's blob is unavailable. Sets `*clamped` if any
|
||||
// value was clamped.
|
||||
mlir::DenseResourceElementsAttr NarrowResourceI64(
|
||||
mlir::DenseResourceElementsAttr value, mlir::ShapedType new_type,
|
||||
bool* clamped) {
|
||||
mlir::AsmResourceBlob* blob = value.getRawHandle().getBlob();
|
||||
if (!blob) {
|
||||
if (auto* resource = value.getRawHandle().getResource()) {
|
||||
blob = resource->getBlob();
|
||||
}
|
||||
}
|
||||
if (!blob) return nullptr;
|
||||
|
||||
const bool is_unsigned = value.getElementType().isUnsignedInteger();
|
||||
llvm::ArrayRef<char> src_bytes = blob->getData();
|
||||
const int64_t num_elements = new_type.getNumElements();
|
||||
if (src_bytes.size() != num_elements * sizeof(int64_t)) return nullptr;
|
||||
mlir::AsmResourceBlob new_blob = mlir::HeapAsmResourceBlob::allocate(
|
||||
num_elements * sizeof(int32_t), /*align=*/64, /*dataIsMutable=*/true);
|
||||
llvm::MutableArrayRef<char> dst_bytes = new_blob.getMutableData();
|
||||
|
||||
// Copy element-wise through memcpy: the source blob's alignment is not
|
||||
// guaranteed to be suitable for a direct int64_t view.
|
||||
for (int64_t i = 0; i < num_elements; ++i) {
|
||||
const char* src = src_bytes.data() + i * sizeof(int64_t);
|
||||
char* dst = dst_bytes.data() + i * sizeof(int32_t);
|
||||
if (is_unsigned) {
|
||||
uint64_t v;
|
||||
std::memcpy(&v, src, sizeof(v));
|
||||
if (v > std::numeric_limits<uint32_t>::max()) {
|
||||
*clamped = true;
|
||||
v = std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
const uint32_t narrowed = static_cast<uint32_t>(v);
|
||||
std::memcpy(dst, &narrowed, sizeof(narrowed));
|
||||
} else {
|
||||
int64_t v;
|
||||
std::memcpy(&v, src, sizeof(v));
|
||||
if (v < std::numeric_limits<int32_t>::min()) {
|
||||
*clamped = true;
|
||||
v = std::numeric_limits<int32_t>::min();
|
||||
} else if (v > std::numeric_limits<int32_t>::max()) {
|
||||
*clamped = true;
|
||||
v = std::numeric_limits<int32_t>::max();
|
||||
}
|
||||
const int32_t narrowed = static_cast<int32_t>(v);
|
||||
std::memcpy(dst, &narrowed, sizeof(narrowed));
|
||||
}
|
||||
}
|
||||
|
||||
std::string new_key =
|
||||
absl::StrCat(value.getRawHandle().getKey().str(), "_x32");
|
||||
return mlir::DenseResourceElementsAttr::get(new_type, new_key,
|
||||
std::move(new_blob));
|
||||
}
|
||||
|
||||
// If `op` is a 64-bit integer constant, narrows its `value` attribute and
|
||||
// result type to 32 bits in place and returns true. Values outside the 32-bit
|
||||
// range are clamped to that range first, with a warning, so the constant
|
||||
// saturates instead of wrapping. Handles both `DenseIntElementsAttr` and
|
||||
// `DenseResourceElementsAttr` (the latter is what `lightweight_conversion`
|
||||
// produces for large constants). Returns false for any other constant (e.g.
|
||||
// f64), leaving it for the cast path.
|
||||
//
|
||||
// Signed and signless constants clamp to [INT32_MIN, INT32_MAX]; unsigned
|
||||
// constants clamp to [0, UINT32_MAX] using unsigned comparisons, so a value
|
||||
// with the top bit set is treated as large rather than negative. Narrowing
|
||||
// directly (rather than through `tfl.cast`) also keeps ui64 working, since
|
||||
// `tfl.cast` does not accept a ui64 input.
|
||||
//
|
||||
// PyTorch exports routinely use INT64_MAX / INT64_MIN as sentinels (e.g. the
|
||||
// fill value of `torch.where(mask, x, iinfo(long).max).min()` or
|
||||
// `masked_fill(~m, iinfo(long).min).argmax()`). Truncating those to -1 / 0
|
||||
// would silently change the result; saturating keeps their meaning.
|
||||
bool NarrowI64Constant(mlir::Operation* op) {
|
||||
if (op->getNumResults() != 1) return false;
|
||||
auto value = op->getAttrOfType<mlir::ElementsAttr>("value");
|
||||
if (!value || !value.getElementType().isInteger(64)) return false;
|
||||
if (value.getType() != op->getResult(0).getType()) return false;
|
||||
|
||||
const bool is_unsigned = value.getElementType().isUnsignedInteger();
|
||||
auto new_type =
|
||||
mlir::cast<mlir::ShapedType>(ConvertX64TypeToX32(value.getType()));
|
||||
bool clamped = false;
|
||||
mlir::ElementsAttr new_value;
|
||||
if (auto dense = mlir::dyn_cast<mlir::DenseIntElementsAttr>(value)) {
|
||||
new_value = NarrowDenseI64(dense, new_type.getElementType(), &clamped);
|
||||
} else if (auto resource =
|
||||
mlir::dyn_cast<mlir::DenseResourceElementsAttr>(value)) {
|
||||
new_value = NarrowResourceI64(resource, new_type, &clamped);
|
||||
}
|
||||
if (!new_value) return false;
|
||||
|
||||
if (clamped) EmitSaturationWarning(op, is_unsigned);
|
||||
op->setAttr("value", new_value);
|
||||
op->getResult(0).setType(new_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DowncastX64Pass::runOnOperation() {
|
||||
@@ -97,8 +251,11 @@ void DowncastX64Pass::runOnOperation() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle constant-like operations by inserting CastOps.
|
||||
// Handle constant-like operations. 64-bit integer constants (dense or
|
||||
// resource-backed) are narrowed in place; anything else gets a CastOp for
|
||||
// the constant folder to resolve.
|
||||
if (op->hasTrait<mlir::OpTrait::ConstantLike>()) {
|
||||
if (NarrowI64Constant(op)) return;
|
||||
for (mlir::OpResult result : op->getResults()) {
|
||||
mlir::Type old_type = result.getType();
|
||||
mlir::Type new_type = ConvertX64TypeToX32(old_type);
|
||||
@@ -120,6 +277,17 @@ void DowncastX64Pass::runOnOperation() {
|
||||
for (mlir::Value result : op->getResults()) {
|
||||
result.setType(ConvertX64TypeToX32(result.getType()));
|
||||
}
|
||||
|
||||
// Update block arguments of any nested regions (e.g. the cond/body of a
|
||||
// region-based `tfl.while`, or a residual `stablehlo.while`/`reduce`), so
|
||||
// they stay consistent with the narrowed operands feeding them.
|
||||
for (mlir::Region& region : op->getRegions()) {
|
||||
for (mlir::Block& block : region) {
|
||||
for (mlir::BlockArgument arg : block.getArguments()) {
|
||||
arg.setType(ConvertX64TypeToX32(arg.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/* Copyright 2026 The TensorFlow Authors. All Rights Reserved.
|
||||
|
||||
Licensed 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.
|
||||
==============================================================================*/
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project
|
||||
#include "mlir/IR/BuiltinTypes.h" // from @llvm-project
|
||||
#include "mlir/IR/MLIRContext.h" // from @llvm-project
|
||||
#include "mlir/IR/PatternMatch.h" // from @llvm-project
|
||||
#include "mlir/IR/Value.h" // from @llvm-project
|
||||
#include "mlir/Pass/Pass.h" // from @llvm-project
|
||||
#include "mlir/Support/LLVM.h" // from @llvm-project
|
||||
#include "mlir/Support/LogicalResult.h" // from @llvm-project
|
||||
#include "mlir/Support/TypeID.h" // from @llvm-project
|
||||
#include "mlir/Transforms/GreedyPatternRewriteDriver.h" // from @llvm-project
|
||||
#include "tensorflow/compiler/mlir/lite/ir/tfl_ops.h"
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/passes.h"
|
||||
|
||||
namespace mlir {
|
||||
namespace TFL {
|
||||
namespace {
|
||||
|
||||
#define GEN_PASS_DEF_FOLDFP16CASTSINTOFULLYCONNECTEDPASS
|
||||
#include "tensorflow/compiler/mlir/lite/transforms/passes.h.inc"
|
||||
|
||||
// Returns `value`'s type if it is a ranked tensor of exactly `element_type`.
|
||||
RankedTensorType GetRankedTensorOf(Value value, Type element_type) {
|
||||
auto type = mlir::dyn_cast<RankedTensorType>(value.getType());
|
||||
if (!type || type.getElementType() != element_type) return nullptr;
|
||||
return type;
|
||||
}
|
||||
|
||||
// Folds the f16 <-> f32 casts around a float fully_connected:
|
||||
//
|
||||
// %x32 = tfl.cast(%x16) : f16 -> f32
|
||||
// %y32 = tfl.fully_connected(%x32, %filter, %bias) : f32
|
||||
// %y16 = tfl.cast(%y32) : f32 -> f16
|
||||
//
|
||||
// into
|
||||
//
|
||||
// %y16 = tfl.fully_connected(%x16, %filter, %bias) : f16
|
||||
//
|
||||
// The filter and bias keep their types, so the result is a mixed precision
|
||||
// fully_connected (f16 activations, f32 or quantized weights). Only runtimes
|
||||
// that support that combination can execute the result, which is why the pass
|
||||
// is opt-in.
|
||||
struct FoldFp16CastsIntoFullyConnectedPattern
|
||||
: public OpRewritePattern<TFL::CastOp> {
|
||||
using OpRewritePattern<TFL::CastOp>::OpRewritePattern;
|
||||
|
||||
LogicalResult matchAndRewrite(TFL::CastOp output_cast,
|
||||
PatternRewriter& rewriter) const override {
|
||||
const Type f16 = rewriter.getF16Type();
|
||||
const Type f32 = rewriter.getF32Type();
|
||||
|
||||
if (!GetRankedTensorOf(output_cast.getOutput(), f16)) return failure();
|
||||
Value fc_output = output_cast.getInput();
|
||||
if (!GetRankedTensorOf(fc_output, f32)) return failure();
|
||||
|
||||
auto fc = fc_output.getDefiningOp<TFL::FullyConnectedOp>();
|
||||
// A shuffled-weights fully_connected has a second result and cannot be
|
||||
// replaced by the single-result op built below.
|
||||
if (!fc || fc.getNumResults() != 1) return failure();
|
||||
if (fc.getWeightsFormat() != "DEFAULT") return failure();
|
||||
// Other users still need the f32 result.
|
||||
if (!fc_output.hasOneUse()) return failure();
|
||||
|
||||
auto input_cast = fc.getInput().getDefiningOp<TFL::CastOp>();
|
||||
if (!input_cast) return failure();
|
||||
Value input = input_cast.getInput();
|
||||
if (!GetRankedTensorOf(input, f16)) return failure();
|
||||
if (!GetRankedTensorOf(input_cast.getOutput(), f32)) return failure();
|
||||
|
||||
auto fc_output_type = mlir::cast<RankedTensorType>(fc_output.getType());
|
||||
auto new_fc = rewriter.create<TFL::FullyConnectedOp>(
|
||||
fc.getLoc(), fc_output_type.clone(f16), input, fc.getFilter(),
|
||||
fc.getBias(), fc.getFusedActivationFunctionAttr(),
|
||||
fc.getWeightsFormatAttr(), fc.getKeepNumDimsAttr(),
|
||||
fc.getAsymmetricQuantizeInputsAttr());
|
||||
|
||||
rewriter.replaceOp(output_cast, new_fc.getOutput());
|
||||
rewriter.eraseOp(fc);
|
||||
// `input_cast` may still feed other ops; it is dead code otherwise and is
|
||||
// erased by the greedy driver.
|
||||
return success();
|
||||
}
|
||||
};
|
||||
|
||||
struct FoldFp16CastsIntoFullyConnectedPass
|
||||
: public impl::FoldFp16CastsIntoFullyConnectedPassBase<
|
||||
FoldFp16CastsIntoFullyConnectedPass> {
|
||||
public:
|
||||
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(
|
||||
FoldFp16CastsIntoFullyConnectedPass)
|
||||
|
||||
void runOnOperation() override {
|
||||
func::FuncOp func = getOperation();
|
||||
MLIRContext* context = func.getContext();
|
||||
|
||||
RewritePatternSet patterns(context);
|
||||
patterns.add<FoldFp16CastsIntoFullyConnectedPattern>(context);
|
||||
|
||||
if (failed(applyPatternsGreedily(func, std::move(patterns)))) {
|
||||
signalPassFailure();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<OperationPass<func::FuncOp>>
|
||||
CreateFoldFp16CastsIntoFullyConnectedPass() {
|
||||
return std::make_unique<FoldFp16CastsIntoFullyConnectedPass>();
|
||||
}
|
||||
|
||||
} // namespace TFL
|
||||
} // namespace mlir
|
||||
@@ -129,6 +129,11 @@ CreateFoldStablehloConstantTransformsPass();
|
||||
std::unique_ptr<OperationPass<func::FuncOp>>
|
||||
CreateFuseA4W2DRQFullyConnectedPass();
|
||||
|
||||
// Folds f16 <-> f32 casts around a float fully_connected into it, producing a
|
||||
// fully_connected with f16 activations and unchanged weights.
|
||||
std::unique_ptr<OperationPass<func::FuncOp>>
|
||||
CreateFoldFp16CastsIntoFullyConnectedPass();
|
||||
|
||||
std::unique_ptr<OperationPass<ModuleOp>> CreateLowerQuantAnnotationsPass();
|
||||
|
||||
// Creates an instance of the TFLite PropagateQParams pass which propagates
|
||||
|
||||
@@ -385,6 +385,21 @@ def FuseA4W2DRQFullyConnectedPass : Pass<"tfl-fuse-a4w2-drq-fully-connected", "m
|
||||
];
|
||||
}
|
||||
|
||||
def FoldFp16CastsIntoFullyConnectedPass : Pass<"tfl-fold-fp16-casts-into-fully-connected", "mlir::func::FuncOp"> {
|
||||
let summary = "Folds f16 <-> f32 casts around a float fully_connected into it.";
|
||||
let description = [{
|
||||
Rewrites `cast(f32 -> f16)(fully_connected(cast(f16 -> f32)(x), w, b))`
|
||||
into `fully_connected(x, w, b)` with an f16 result. The filter and bias
|
||||
keep their types, so the result is a mixed precision fully_connected
|
||||
(f16 activations, f32 or quantized weights) that only runtimes supporting
|
||||
that combination can execute.
|
||||
}];
|
||||
let constructor = "CreateFoldFp16CastsIntoFullyConnectedPass()";
|
||||
let dependentDialects = [
|
||||
"TFL::TensorFlowLiteDialect"
|
||||
];
|
||||
}
|
||||
|
||||
def PropagateQParamsPass : Pass<"tfl-propagate-qparams", "mlir::ModuleOp"> {
|
||||
let summary = "Propagates Quantization Parameters (scale and zero point) information through the graph.";
|
||||
let description = [{
|
||||
|
||||
@@ -22,6 +22,7 @@ limitations under the License.
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/log/vlog_is_on.h"
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
|
||||
@@ -122,7 +122,7 @@ class ConvertInitializeTableFromTextFileV2
|
||||
line_nums.resize(lines.size());
|
||||
std::iota(line_nums.begin(), line_nums.end(), 0);
|
||||
|
||||
// Create constant ops for keys an values.
|
||||
// Create constant ops for keys and values.
|
||||
Value key_constant_tensor = arith::ConstantOp::create(
|
||||
rewriter, op.getLoc(),
|
||||
DenseStringElementsAttr::get(
|
||||
|
||||
@@ -73,13 +73,6 @@ TEST_F(StatusScopedDiagnosticHandlerTest,
|
||||
EXPECT_THAT(s.message(), HasSubstr("Second diagnostic message reported"));
|
||||
}
|
||||
|
||||
TEST_F(StatusScopedDiagnosticHandlerTest, VerifyThatWarningsAreIgnored) {
|
||||
// Note: this logic is actually implemented in BaseScopedDiagnosticHandler's
|
||||
// handler() function, but only StatusScopedDiagnosticHandler uses it.
|
||||
StatusScopedDiagnosticHandler handler(&context_);
|
||||
emitWarning(loc_) << "Warning message";
|
||||
TF_EXPECT_OK(handler.ConsumeStatus());
|
||||
}
|
||||
|
||||
TEST(ErrorUtilTest, StatusScopedDiagnosticHandlerWithFilter) {
|
||||
// Filtering logic is based on tensorflow::IsInternalFrameForFilename()
|
||||
|
||||
@@ -342,7 +342,7 @@ def TFR_GetElementOp : TFR_Op<"get_element", [Pure]> {
|
||||
def TFR_BuildListOp : TFR_Op<"build_list", [Pure]> {
|
||||
let description = [{
|
||||
The `build_list` operation builds a tensor list from a list of tensors, or
|
||||
an tfr.attr from a list of scalars.
|
||||
a tfr.attr from a list of scalars.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ LogicalResult RewriteTFRCallOp::AddDerivedAttrs(
|
||||
ArrayRef<Attribute> input_types,
|
||||
llvm::StringMap<Attribute>* derived_attrs) const {
|
||||
// If there is an attribute associated to the input in the signature, we
|
||||
// store it as an derived attribute.
|
||||
// store it as a derived attribute.
|
||||
if (auto tensor_type = mlir::dyn_cast<TFRTensorType>(input_tfr_type)) {
|
||||
auto attr_names = tensor_type.getAttrKeys();
|
||||
if (attr_names.empty()) return success();
|
||||
@@ -195,7 +195,7 @@ LogicalResult RewriteTFRCallOp::AddDerivedAttrs(
|
||||
}
|
||||
|
||||
// If there is an attribute associated to the input in the signature,
|
||||
// we store it as an derived attribute.
|
||||
// we store it as a derived attribute.
|
||||
if (auto list_type = mlir::dyn_cast<TFRTensorListType>(input_tfr_type)) {
|
||||
auto attr_names = list_type.getAttrKeys();
|
||||
if (attr_names.empty()) return success();
|
||||
|
||||
@@ -125,6 +125,7 @@ cc_library(
|
||||
"//tensorflow/compiler/mlir/tools/kernel_gen/ir:tf_framework_ops",
|
||||
"//tensorflow/core:lib",
|
||||
"//tensorflow/core/platform:errors",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@llvm-project//llvm:Support",
|
||||
"@llvm-project//llvm:TargetParser",
|
||||
"@llvm-project//llvm:TransformUtils",
|
||||
@@ -182,6 +183,9 @@ cc_library(
|
||||
"@xla//xla/stream_executor:device_description",
|
||||
] + if_cuda_is_configured([
|
||||
"@tsl//tsl/platform:cuda_root_path",
|
||||
"@xla//xla/stream_executor/cuda:assemble_compilation_provider",
|
||||
"@xla//xla/stream_executor/cuda:compilation_provider",
|
||||
"@xla//xla/stream_executor/cuda:compilation_provider_options",
|
||||
"@xla//xla/stream_executor/cuda:cuda_asm_compiler",
|
||||
"@xla//xla/service/gpu/llvm_gpu_backend:nvptx_backend",
|
||||
]) + if_rocm_is_configured([
|
||||
|
||||
@@ -128,8 +128,8 @@ DenseIntElementsAttr composeBroadcastDimensionsAttr(OpBuilder &builder,
|
||||
}
|
||||
|
||||
// Find all the broadcast intents and their dependencies. Start analyzing from
|
||||
// the root an collect all broadcast intents that can help broadcast propagation
|
||||
// from there.
|
||||
// the root and collect all broadcast intents that can help broadcast
|
||||
// propagation from there.
|
||||
void findBroadcastIntents(
|
||||
DynamicBroadcastInDimOp root, Block *parentBlock,
|
||||
BroadcastIntent &rootBcastIntent,
|
||||
|
||||
@@ -16,10 +16,12 @@ limitations under the License.
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "llvm/TargetParser/Triple.h"
|
||||
#include "llvm/Transforms/Utils/Cloning.h"
|
||||
#include "mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project
|
||||
@@ -40,6 +42,9 @@ limitations under the License.
|
||||
|
||||
#if GOOGLE_CUDA
|
||||
#include "xla/service/gpu/llvm_gpu_backend/nvptx_backend.h"
|
||||
#include "xla/stream_executor/cuda/assemble_compilation_provider.h"
|
||||
#include "xla/stream_executor/cuda/compilation_provider.h"
|
||||
#include "xla/stream_executor/cuda/compilation_provider_options.h"
|
||||
#include "xla/stream_executor/cuda/cuda_asm_compiler.h"
|
||||
#elif TENSORFLOW_USE_ROCM
|
||||
#include "xla/service/gpu/llvm_gpu_backend/amdgpu_backend.h"
|
||||
@@ -143,6 +148,20 @@ class GpuKernelToBlobPass
|
||||
llvmModule->setDataLayout(xla::gpu::nvptx::DataLayout());
|
||||
llvmModule->setTargetTriple(llvm::Triple(xla::gpu::nvptx::TargetTriple()));
|
||||
|
||||
std::optional<int> max_ptx_isa_version;
|
||||
absl::StatusOr<std::unique_ptr<tensorflow::se::cuda::CompilationProvider>>
|
||||
compilation_provider =
|
||||
tensorflow::se::cuda::AssembleCompilationProvider(
|
||||
tensorflow::se::cuda::CompilationProviderOptions::
|
||||
FromDebugOptions(options));
|
||||
if (compilation_provider.ok()) {
|
||||
absl::StatusOr<int> ptx_isa_version =
|
||||
(*compilation_provider)->GetLatestPtxIsaVersion();
|
||||
if (ptx_isa_version.ok()) {
|
||||
max_ptx_isa_version = *ptx_isa_version;
|
||||
}
|
||||
}
|
||||
|
||||
// Compile and collect requested cubin and PTX images.
|
||||
std::vector<tensorflow::se::CubinOrPTXImage> images;
|
||||
auto gpu_asm_opts = xla::gpu::PtxOptsFromDebugOptions(options);
|
||||
@@ -161,7 +180,7 @@ class GpuKernelToBlobPass
|
||||
std::string ptx,
|
||||
xla::gpu::nvptx::CompileToPtx(
|
||||
llvm_module_copy.get(), stream_executor::GpuComputeCapability(cc),
|
||||
options));
|
||||
options, /*configure_target=*/nullptr, max_ptx_isa_version));
|
||||
if (print_ptx_) {
|
||||
llvm::dbgs() << "Generated PTX code for module '"
|
||||
<< gpu_module.getName() << "' on architecture sm_" << arch
|
||||
|
||||
@@ -1213,6 +1213,8 @@ tf_xla_py_strict_test(
|
||||
],
|
||||
deps = [
|
||||
":xla_test",
|
||||
"//tensorflow/python/eager:def_function",
|
||||
"//tensorflow/python/framework:dtypes",
|
||||
"//tensorflow/python/ops:array_ops",
|
||||
"//tensorflow/python/ops:array_ops_gen",
|
||||
"//tensorflow/python/platform:test",
|
||||
@@ -1390,6 +1392,33 @@ tf_xla_py_strict_test(
|
||||
],
|
||||
)
|
||||
|
||||
tf_xla_py_strict_test(
|
||||
name = "qr_op_large_test",
|
||||
size = "large",
|
||||
srcs = ["qr_op_large_test.py"],
|
||||
disabled_backends = [
|
||||
# Test is very slow on CPU.
|
||||
"cpu",
|
||||
"cpu_ondemand",
|
||||
],
|
||||
# Keep shard_count = 2 so testLarge2000x2000 and testLarge17500x128 (if enabled) run in separate shards.
|
||||
shard_count = 2,
|
||||
tags = [
|
||||
"no_pip", # TODO(b/149738646): fix pip install so these tests run on kokoro pip
|
||||
"nozapfhahn",
|
||||
"optonly",
|
||||
"requires-mem:24g",
|
||||
],
|
||||
deps = [
|
||||
":qr_op_test_lib",
|
||||
":xla_test",
|
||||
"//tensorflow/python/framework:test_lib",
|
||||
"//tensorflow/python/platform:client_testlib",
|
||||
"//third_party/py/numpy",
|
||||
"@absl_py//absl/testing:parameterized",
|
||||
],
|
||||
)
|
||||
|
||||
tf_xla_py_strict_test(
|
||||
name = "unstack_test",
|
||||
size = "medium",
|
||||
@@ -1506,6 +1535,8 @@ tf_xla_py_strict_test(
|
||||
],
|
||||
deps = [
|
||||
":xla_test",
|
||||
"//tensorflow/python/eager:context",
|
||||
"//tensorflow/python/eager:def_function",
|
||||
"//tensorflow/python/framework:dtypes",
|
||||
"//tensorflow/python/ops:array_ops",
|
||||
"//tensorflow/python/platform:client_testlib",
|
||||
@@ -1845,6 +1876,7 @@ tf_xla_py_strict_test(
|
||||
deps = [
|
||||
":xla_test",
|
||||
"//tensorflow/python:extra_py_tests_deps",
|
||||
"//tensorflow/python/eager:def_function",
|
||||
"//tensorflow/python/framework:dtypes",
|
||||
"//tensorflow/python/framework:test_lib",
|
||||
"//tensorflow/python/ops:array_ops",
|
||||
@@ -1869,7 +1901,9 @@ tf_xla_py_strict_test(
|
||||
],
|
||||
deps = [
|
||||
":xla_test",
|
||||
"//tensorflow/python/eager:def_function",
|
||||
"//tensorflow/python/framework:dtypes",
|
||||
"//tensorflow/python/framework:errors",
|
||||
"//tensorflow/python/ops:array_ops",
|
||||
"//tensorflow/python/ops:array_ops_stack",
|
||||
"//tensorflow/python/ops:bitwise_ops",
|
||||
|
||||
@@ -138,6 +138,31 @@ class CategoricalTest(xla_test.XLATestCase):
|
||||
chi2 = self._chi2(probs, freqs)
|
||||
self.assertLess(chi2, 1e-3)
|
||||
|
||||
def testHalfPrecisionLargeLogits(self):
|
||||
# Regression test for GitHub issue 128958: half-precision logits were
|
||||
# sampled in their own type, where the spacing between values (32 at 4096
|
||||
# for bfloat16) rounds the Gumbel noise away, so equal logits always gave
|
||||
# the first class.
|
||||
num_samples = 40000
|
||||
probs = [[0.5, 0.5]]
|
||||
for dtype in self.float_types:
|
||||
if dtypes.as_dtype(dtype) not in (dtypes.float16, dtypes.bfloat16):
|
||||
continue
|
||||
logits = np.array([[4096.0, 4096.0]], dtype=dtype)
|
||||
with self.subTest(dtype=dtypes.as_dtype(dtype).name):
|
||||
freqs = self._do_sampling(logits, num_samples)
|
||||
self.assertLess(self._chi2(probs, freqs), 1e-3)
|
||||
if dtypes.as_dtype(dtype) == dtypes.bfloat16:
|
||||
with self.session() as sess:
|
||||
with self.test_scope():
|
||||
seed_t = array_ops.placeholder(dtypes.int32, shape=[2])
|
||||
x = stateless_random_ops.stateless_multinomial(
|
||||
logits, num_samples, seed_t, output_dtype=dtypes.int32
|
||||
)
|
||||
y = sess.run(x, {seed_t: [0x12345678, 0xABCDEF12]})
|
||||
freqs = [[np.mean(y == k) for k in range(2)]]
|
||||
self.assertLess(self._chi2(probs, freqs), 1e-3)
|
||||
|
||||
def testStatelessMultinomialIsInRange(self):
|
||||
for dtype in self.float_types.intersection(
|
||||
[dtypes.float32, dtypes.bfloat16]):
|
||||
@@ -190,6 +215,5 @@ class CategoricalTest(xla_test.XLATestCase):
|
||||
self.assertEqual(y.shape, (42, 0))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
googletest.main()
|
||||
@@ -1022,6 +1022,103 @@ class NonMaxSuppressionTest(xla_test.XLATestCase):
|
||||
self.assertEqual(indices_tf.size, 3)
|
||||
self.assertAllClose(indices_tf[:3], [3, 0, 5])
|
||||
|
||||
def _testMixedThresholdDtype(self, nms):
|
||||
# Regression test for GitHub issue 128614: boxes and scores of one
|
||||
# floating-point type with thresholds of another, which
|
||||
# tf.image.non_max_suppression produces for float16 boxes, failed to
|
||||
# compile because XLA doesn't mix floating-point types in one operation.
|
||||
if np.float16 not in self.float_types:
|
||||
self.skipTest("float16 is not supported on this device")
|
||||
boxes_data = [
|
||||
[0, 0, 1, 1],
|
||||
[0, 0.1, 1, 1.1],
|
||||
[0, -0.1, 1, 0.9],
|
||||
[0, 10, 1, 11],
|
||||
[0, 10.1, 1, 11.1],
|
||||
[0, 100, 1, 101],
|
||||
]
|
||||
scores_data = [0.9, 0.75, 0.6, 0.95, 0.5, 0.3]
|
||||
for dtype, threshold_dtype in (
|
||||
(np.float16, np.float32),
|
||||
(np.float32, np.float16),
|
||||
):
|
||||
with self.subTest(
|
||||
dtype=dtype.__name__, threshold_dtype=threshold_dtype.__name__
|
||||
), self.session() as sess:
|
||||
boxes = array_ops.placeholder(dtype, shape=[6, 4])
|
||||
scores = array_ops.placeholder(dtype, shape=[6])
|
||||
iou_threshold = array_ops.placeholder(threshold_dtype, shape=[])
|
||||
score_threshold = array_ops.placeholder(threshold_dtype, shape=[])
|
||||
with self.device_scope():
|
||||
selected_indices, num_valid = nms(
|
||||
boxes, scores, iou_threshold, score_threshold
|
||||
)
|
||||
indices_tf, num_valid_tf = sess.run(
|
||||
[selected_indices, num_valid],
|
||||
feed_dict={
|
||||
boxes: np.array(boxes_data, dtype=dtype),
|
||||
scores: np.array(scores_data, dtype=dtype),
|
||||
iou_threshold: 0.5,
|
||||
score_threshold: 0.4,
|
||||
},
|
||||
)
|
||||
# Box 5 is below the score threshold.
|
||||
self.assertEqual(num_valid_tf, 2)
|
||||
self.assertAllEqual([3, 0], indices_tf[:num_valid_tf])
|
||||
|
||||
def testNMSV3MixedThresholdDtype(self):
|
||||
|
||||
def nms(boxes, scores, iou_threshold, score_threshold):
|
||||
selected_indices = image_ops.non_max_suppression_v3(
|
||||
boxes=boxes,
|
||||
scores=scores,
|
||||
max_output_size=6,
|
||||
iou_threshold=iou_threshold,
|
||||
score_threshold=score_threshold,
|
||||
)
|
||||
return selected_indices, array_ops.size(selected_indices)
|
||||
|
||||
self._testMixedThresholdDtype(nms)
|
||||
|
||||
def testNMSV4MixedThresholdDtype(self):
|
||||
|
||||
def nms(boxes, scores, iou_threshold, score_threshold):
|
||||
return gen_image_ops.non_max_suppression_v4(
|
||||
boxes=boxes,
|
||||
scores=scores,
|
||||
max_output_size=6,
|
||||
iou_threshold=iou_threshold,
|
||||
score_threshold=score_threshold,
|
||||
pad_to_max_output_size=True,
|
||||
)
|
||||
|
||||
self._testMixedThresholdDtype(nms)
|
||||
|
||||
def testNMSV3NonScalarScoreThreshold(self):
|
||||
# Shape inference can't reject a score_threshold of unknown shape, so the
|
||||
# XLA kernel checks that it is a scalar when it compiles.
|
||||
with self.session() as sess:
|
||||
boxes = array_ops.placeholder(np.float32, shape=[6, 4])
|
||||
scores = array_ops.placeholder(np.float32, shape=[6])
|
||||
score_threshold = array_ops.placeholder(np.float32)
|
||||
with self.device_scope():
|
||||
selected_indices = image_ops.non_max_suppression_v3(
|
||||
boxes=boxes,
|
||||
scores=scores,
|
||||
max_output_size=6,
|
||||
iou_threshold=0.5,
|
||||
score_threshold=score_threshold,
|
||||
)
|
||||
with self.assertRaisesOpError("Score Threshold isn't a scalar"):
|
||||
sess.run(
|
||||
selected_indices,
|
||||
feed_dict={
|
||||
boxes: np.zeros([6, 4], np.float32),
|
||||
scores: np.zeros([6], np.float32),
|
||||
score_threshold: [0.4],
|
||||
},
|
||||
)
|
||||
|
||||
def testNMSV3EmptyInput(self):
|
||||
# Regression test for #117245: with no boxes the suppression loop was
|
||||
# built from zero-sized dimensions and segfaulted the compiler. The
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import numpy as np
|
||||
|
||||
from tensorflow.compiler.tests import xla_test
|
||||
from tensorflow.python.eager import def_function
|
||||
from tensorflow.python.framework import dtypes
|
||||
from tensorflow.python.ops import array_ops
|
||||
from tensorflow.python.ops import gen_array_ops
|
||||
from tensorflow.python.platform import googletest
|
||||
@@ -767,6 +769,118 @@ class MatrixDiagPartTest(xla_test.XLATestCase):
|
||||
"align": align
|
||||
}, solution)
|
||||
|
||||
def _diagPartOfSelected(self, op, x, masks, dtype=np.int32, **params):
|
||||
# masks maps axes of x to masks of the entries to keep along them, which
|
||||
# gives those dimensions a size only known at run time.
|
||||
axes = sorted(masks)
|
||||
|
||||
@def_function.function(jit_compile=True)
|
||||
def f(x, *x_masks):
|
||||
for axis, mask in zip(axes, x_masks):
|
||||
x = array_ops.gather(
|
||||
x, array_ops.reshape(array_ops.where(mask), [-1]), axis=axis
|
||||
)
|
||||
return op(x, **params)
|
||||
|
||||
x = np.array(x, dtype=dtype)
|
||||
with self.session() as session:
|
||||
x_ph = array_ops.placeholder(dtypes.as_dtype(dtype), x.shape)
|
||||
mask_phs = [
|
||||
array_ops.placeholder(dtypes.bool, [x.shape[axis]]) for axis in axes
|
||||
]
|
||||
output = f(x_ph, *mask_phs)
|
||||
feed_dict = {x_ph: x}
|
||||
feed_dict.update({ph: masks[axis] for ph, axis in zip(mask_phs, axes)})
|
||||
return session.run(output, feed_dict)
|
||||
|
||||
def testDynamicDimensions(self):
|
||||
# Regression test for GitHub issue 129053: with a dynamic dimension, the
|
||||
# diagonals had the length they have at its bound. The mask keeps 2 of 5
|
||||
# entries, so the dimensions it selects have size 2 and bound 5.
|
||||
keep = [True, False, True, False, False]
|
||||
x = np.arange(1, 16).reshape(5, 3) # Keeps [[1, 2, 3], [7, 8, 9]].
|
||||
for dtype in (np.int32, np.float32, np.float64):
|
||||
for op in (array_ops.matrix_diag_part, gen_array_ops.matrix_diag_part):
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(op, x, {-2: keep}, dtype), [1, 8]
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part,
|
||||
x,
|
||||
{-2: keep},
|
||||
dtype,
|
||||
k=(-1, 1),
|
||||
padding_value=9,
|
||||
),
|
||||
[[2, 9], [1, 8], [7, 9]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part,
|
||||
x,
|
||||
{-2: keep},
|
||||
dtype,
|
||||
k=(-1, 1),
|
||||
padding_value=9,
|
||||
align="LEFT_RIGHT",
|
||||
),
|
||||
[[2, 9], [1, 8], [9, 7]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
gen_array_ops.matrix_diag_part_v2,
|
||||
x,
|
||||
{-2: keep},
|
||||
dtype,
|
||||
k=[-1, 1],
|
||||
padding_value=9,
|
||||
),
|
||||
[[2, 9], [1, 8], [7, 9]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part,
|
||||
np.arange(1, 16).reshape(3, 5),
|
||||
{-1: keep},
|
||||
dtype,
|
||||
),
|
||||
[1, 8],
|
||||
)
|
||||
# With no rows kept, the diagonal is empty.
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part, x, {-2: [False] * 5}, dtype
|
||||
),
|
||||
np.zeros([0]),
|
||||
)
|
||||
|
||||
def testDynamicDimensionsBatch(self):
|
||||
keep = [True, False, True, False, False]
|
||||
x = np.arange(1, 31).reshape(2, 5, 3)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(array_ops.matrix_diag_part, x, {-2: keep}),
|
||||
[[1, 8], [16, 23]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part,
|
||||
x,
|
||||
{-2: keep},
|
||||
k=(-1, 1),
|
||||
padding_value=9,
|
||||
),
|
||||
[[[2, 9], [1, 8], [7, 9]], [[17, 24], [16, 23], [22, 9]]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._diagPartOfSelected(
|
||||
array_ops.matrix_diag_part,
|
||||
np.arange(1, 51).reshape(2, 5, 5),
|
||||
{-2: keep, -1: keep},
|
||||
),
|
||||
[[1, 13], [26, 38]],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
googletest.main()
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright 2026 The TensorFlow Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed 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.
|
||||
# ==============================================================================
|
||||
"""Large tests for tensorflow.ops.linalg_ops.qr."""
|
||||
|
||||
import unittest
|
||||
|
||||
from absl.testing import parameterized
|
||||
import numpy as np
|
||||
|
||||
from tensorflow.compiler.tests import qr_op_test
|
||||
from tensorflow.compiler.tests import xla_test
|
||||
from tensorflow.python.framework import test_util
|
||||
from tensorflow.python.platform import test
|
||||
|
||||
|
||||
@test_util.run_all_without_tensor_float_32(
|
||||
"XLA QR op calls matmul. Also, matmul used for verification. Also with "
|
||||
'TensorFloat-32, mysterious "Unable to launch cuBLAS gemm" error '
|
||||
"occasionally occurs"
|
||||
)
|
||||
# TODO(b/165435566): Fix "Unable to launch cuBLAS gemm" error
|
||||
class QrOpLargeTest(
|
||||
qr_op_test.QrOpTestBase, xla_test.XLATestCase, parameterized.TestCase
|
||||
):
|
||||
|
||||
def testLarge2000x2000(self):
|
||||
x_np = self._random_matrix(np.float32, (2000, 2000))
|
||||
self._test(x_np, full_matrices=True)
|
||||
|
||||
@unittest.skip("Test times out on CI")
|
||||
def testLarge17500x128(self):
|
||||
x_np = self._random_matrix(np.float32, (17500, 128))
|
||||
self._test(x_np, full_matrices=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test.main()
|
||||
@@ -15,7 +15,6 @@
|
||||
"""Tests for tensorflow.ops.math_ops.matrix_inverse."""
|
||||
|
||||
import itertools
|
||||
import unittest
|
||||
|
||||
from absl.testing import parameterized
|
||||
import numpy as np
|
||||
@@ -29,12 +28,7 @@ from tensorflow.python.ops import math_ops
|
||||
from tensorflow.python.platform import test
|
||||
|
||||
|
||||
@test_util.run_all_without_tensor_float_32(
|
||||
"XLA QR op calls matmul. Also, matmul used for verification. Also with "
|
||||
'TensorFloat-32, mysterious "Unable to launch cuBLAS gemm" error '
|
||||
"occasionally occurs")
|
||||
# TODO(b/165435566): Fix "Unable to launch cuBLAS gemm" error
|
||||
class QrOpTest(xla_test.XLATestCase, parameterized.TestCase):
|
||||
class QrOpTestBase:
|
||||
|
||||
def AdjustedNorm(self, x):
|
||||
"""Computes the norm of matrices in 'x', adjusted for dimension and type."""
|
||||
@@ -111,6 +105,15 @@ class QrOpTest(xla_test.XLATestCase, parameterized.TestCase):
|
||||
self.CheckApproximation(x_np, q_tf_val, r_tf_val)
|
||||
self.CheckUnitary(q_tf_val)
|
||||
|
||||
|
||||
@test_util.run_all_without_tensor_float_32(
|
||||
"XLA QR op calls matmul. Also, matmul used for verification. Also with "
|
||||
'TensorFloat-32, mysterious "Unable to launch cuBLAS gemm" error '
|
||||
"occasionally occurs"
|
||||
)
|
||||
# TODO(b/165435566): Fix "Unable to launch cuBLAS gemm" error
|
||||
class QrOpTest(QrOpTestBase, xla_test.XLATestCase, parameterized.TestCase):
|
||||
|
||||
SIZES = [1, 2, 5, 10, 32, 100, 300, 603]
|
||||
DTYPES = [np.float32, np.complex64]
|
||||
PARAMS = itertools.product(SIZES, SIZES, DTYPES)
|
||||
@@ -123,15 +126,6 @@ class QrOpTest(xla_test.XLATestCase, parameterized.TestCase):
|
||||
x_np = self._random_matrix(dtype, batch_dims + (rows, cols))
|
||||
self._test(x_np, full_matrices)
|
||||
|
||||
def testLarge2000x2000(self):
|
||||
x_np = self._random_matrix(np.float32, (2000, 2000))
|
||||
self._test(x_np, full_matrices=True)
|
||||
|
||||
@unittest.skip("Test times out on CI")
|
||||
def testLarge17500x128(self):
|
||||
x_np = self._random_matrix(np.float32, (17500, 128))
|
||||
self._test(x_np, full_matrices=True)
|
||||
|
||||
@parameterized.parameters((23, 25), (513, 23))
|
||||
def testZeroColumn(self, rows, cols):
|
||||
x_np = self._random_matrix(np.complex64, (rows, cols))
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
import numpy as np
|
||||
|
||||
from tensorflow.compiler.tests import xla_test
|
||||
from tensorflow.python.eager import context
|
||||
from tensorflow.python.eager import def_function
|
||||
from tensorflow.python.framework import dtypes
|
||||
from tensorflow.python.ops import array_ops
|
||||
from tensorflow.python.platform import test
|
||||
@@ -84,6 +86,79 @@ class ReverseSequenceTest(xla_test.XLATestCase):
|
||||
for seq_dtype in self.all_types & {np.int32, np.int64}:
|
||||
self._testBasic(dtype, seq_dtype)
|
||||
|
||||
def _reverseSelected(self, x, seq_lengths, axis, seq_axis, batch_axis):
|
||||
# Gathers the entries of x along axis where mask is true, which gives that
|
||||
# dimension a size only known at run time, and reverses the result.
|
||||
@def_function.function(jit_compile=True)
|
||||
def f(x, seq_lengths, mask):
|
||||
indices = array_ops.reshape(array_ops.where(mask), [-1])
|
||||
x = array_ops.gather(x, indices, axis=axis)
|
||||
if axis == batch_axis:
|
||||
seq_lengths = array_ops.gather(seq_lengths, indices)
|
||||
return array_ops.reverse_sequence(
|
||||
x, seq_lengths, seq_axis=seq_axis, batch_axis=batch_axis
|
||||
)
|
||||
|
||||
x = np.array(x, dtype=np.int32)
|
||||
seq_lengths = np.array(seq_lengths, dtype=np.int32)
|
||||
mask = np.array([True, False, True, True, False])
|
||||
with self.session() as session:
|
||||
with self.device_scope():
|
||||
x_ph = array_ops.placeholder(dtypes.int32, x.shape)
|
||||
seq_lengths_ph = array_ops.placeholder(dtypes.int32, seq_lengths.shape)
|
||||
mask_ph = array_ops.placeholder(dtypes.bool, mask.shape)
|
||||
output = f(x_ph, seq_lengths_ph, mask_ph)
|
||||
result = session.run(
|
||||
output, {x_ph: x, seq_lengths_ph: seq_lengths, mask_ph: mask}
|
||||
)
|
||||
# Eager execution of the compiled function gives the same result, also
|
||||
# when the test itself runs in graph mode.
|
||||
with context.eager_mode(), self.device_scope():
|
||||
self.assertAllEqual(f(x, seq_lengths, mask), result)
|
||||
return result
|
||||
|
||||
def testDynamicDimensions(self):
|
||||
if "TPU" in self.device:
|
||||
self.skipTest(
|
||||
"XLA:TPU fails to compile these dynamic dimensions with a "
|
||||
"bitcast shape size mismatch."
|
||||
)
|
||||
# Regression test for GitHub issue 128953: the output had the bound of a
|
||||
# dynamic input dimension instead of its size. The mask selects 3 of 5
|
||||
# entries, so the selected dimension has size 3 and bound 5. (XLA turns a
|
||||
# gather from 4 or fewer entries into selects, which XLA:GPU doesn't
|
||||
# support with dynamic shapes.)
|
||||
self.assertAllEqual(
|
||||
self._reverseSelected(
|
||||
[[1, 2, 3, 4, 0], [5, 6, 7, 8, 0]],
|
||||
[3, 2],
|
||||
axis=1,
|
||||
seq_axis=1,
|
||||
batch_axis=0,
|
||||
),
|
||||
[[4, 3, 1], [7, 5, 8]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._reverseSelected(
|
||||
[[1, 5], [2, 6], [3, 7], [4, 8], [0, 0]],
|
||||
[3, 2],
|
||||
axis=0,
|
||||
seq_axis=0,
|
||||
batch_axis=1,
|
||||
),
|
||||
[[4, 7], [3, 5], [1, 8]],
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._reverseSelected(
|
||||
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [0, 0, 0]],
|
||||
[3, 1, 2, 3, 1],
|
||||
axis=0,
|
||||
seq_axis=1,
|
||||
batch_axis=0,
|
||||
),
|
||||
[[3, 2, 1], [8, 7, 9], [12, 11, 10]],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test.main()
|
||||
@@ -19,6 +19,7 @@ import numpy as np
|
||||
import scipy.special as sps
|
||||
|
||||
from tensorflow.compiler.tests import xla_test
|
||||
from tensorflow.python.eager import def_function
|
||||
from tensorflow.python.framework import dtypes
|
||||
from tensorflow.python.framework import test_util
|
||||
from tensorflow.python.ops import array_ops
|
||||
@@ -75,6 +76,62 @@ class TernaryOpsTest(xla_test.XLATestCase, parameterized.TestCase):
|
||||
dtype(2),
|
||||
expected=np.array([1, 3, 5], dtype=dtype),
|
||||
)
|
||||
# The distance from start to limit doesn't fit in int64.
|
||||
self._testTernary(
|
||||
math_ops.range,
|
||||
np.int64(-(2**63)),
|
||||
np.int64(2**63 - 1),
|
||||
np.int64(2**62),
|
||||
expected=np.array([-(2**63), -(2**62), 0, 2**62], dtype=np.int64),
|
||||
)
|
||||
|
||||
def _rangeOfMaskedSize(self, args, dtype=np.int32):
|
||||
# n is the number of true entries in mask, a size only known at run time,
|
||||
# and args maps it to the range's start, limit and delta.
|
||||
@def_function.function(jit_compile=True)
|
||||
def f(mask):
|
||||
indices = array_ops.reshape(array_ops.where(mask), [-1])
|
||||
n = array_ops.size(array_ops.gather(math_ops.range(6), indices))
|
||||
return gen_math_ops._range(
|
||||
*[math_ops.cast(a, dtype) for a in args(math_ops.cast(n, dtype))]
|
||||
)
|
||||
|
||||
with self.session() as session:
|
||||
with self.device_scope():
|
||||
mask = array_ops.placeholder(dtypes.bool, [6])
|
||||
output = f(mask)
|
||||
return session.run(
|
||||
output, {mask: [True, False, True, False, True, False]}
|
||||
)
|
||||
|
||||
def testRangeDynamicBounds(self):
|
||||
# Regression test for GitHub issue 128950: with a negative delta, the
|
||||
# output was sized from the wrong bounds of start and limit, and with a
|
||||
# dynamic start, its values began at start's bound. Here n is 3, bound 6.
|
||||
for dtype in (np.int32, np.int64, np.float32):
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n - 1, -1, -1), dtype), [2, 1, 0]
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (6, n, -1), dtype), [6, 5, 4]
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n, 5, 1), dtype), [3, 4]
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n, -n, -2), dtype), [3, 1, -1]
|
||||
)
|
||||
# The CPU kernel rejects runtime values in the wrong order for delta's
|
||||
# sign. XLA can't report that at run time, and returns an empty range.
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n, 1, 1), dtype), []
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n, 4, -1), dtype), []
|
||||
)
|
||||
self.assertAllEqual(
|
||||
self._rangeOfMaskedSize(lambda n: (n, 10, 2), np.uint32), [3, 5, 7, 9]
|
||||
)
|
||||
|
||||
def testSelect(self):
|
||||
for dtype in self.numeric_types:
|
||||
|
||||
@@ -20,7 +20,9 @@ import numpy as np
|
||||
import six
|
||||
|
||||
from tensorflow.compiler.tests import xla_test
|
||||
from tensorflow.python.eager import def_function
|
||||
from tensorflow.python.framework import dtypes
|
||||
from tensorflow.python.framework import errors
|
||||
from tensorflow.python.ops import array_ops
|
||||
from tensorflow.python.ops import array_ops_stack # pylint: disable=g-direct-tensorflow-import
|
||||
from tensorflow.python.ops import bitwise_ops
|
||||
@@ -335,6 +337,52 @@ class UnaryOpsTest(xla_test.XLATestCase):
|
||||
),
|
||||
)
|
||||
|
||||
def testQuantizeAndDequantizeV3NumBitsBoundaries(self):
|
||||
for signed_input, invalid_num_bits in (
|
||||
(True, (0, -1, 62, 63)),
|
||||
(False, (0, -1, 63, 64)),
|
||||
):
|
||||
for num_bits in invalid_num_bits:
|
||||
|
||||
@def_function.function(jit_compile=True)
|
||||
def quantize_and_dequantize_v3(
|
||||
x, num_bits=num_bits, signed_input=signed_input
|
||||
):
|
||||
return array_ops.quantize_and_dequantize_v3(
|
||||
x, -1.0, 1.0, num_bits=num_bits, signed_input=signed_input
|
||||
)
|
||||
|
||||
with self.session() as sess:
|
||||
with self.device_scope():
|
||||
x = array_ops.placeholder(dtypes.float32)
|
||||
output = quantize_and_dequantize_v3(x)
|
||||
with self.assertRaisesRegex(
|
||||
errors.InvalidArgumentError, "num_bits is out of range"
|
||||
):
|
||||
sess.run(
|
||||
output, {x: np.array([-1, -0.5, 0, 0.3], dtype=np.float32)}
|
||||
)
|
||||
|
||||
for signed_input, valid_num_bits in (
|
||||
(True, (1, 61)),
|
||||
(False, (1, 62)),
|
||||
):
|
||||
for num_bits in valid_num_bits:
|
||||
|
||||
@def_function.function(jit_compile=True)
|
||||
def quantize_and_dequantize_v3_valid(
|
||||
x, num_bits=num_bits, signed_input=signed_input
|
||||
):
|
||||
return array_ops.quantize_and_dequantize_v3(
|
||||
x, -1.0, 1.0, num_bits=num_bits, signed_input=signed_input
|
||||
)
|
||||
|
||||
with self.session() as sess:
|
||||
with self.device_scope():
|
||||
x = array_ops.placeholder(dtypes.float32)
|
||||
output = quantize_and_dequantize_v3_valid(x)
|
||||
sess.run(output, {x: np.array([-1, -0.5, 0, 0.3], dtype=np.float32)})
|
||||
|
||||
def testComplexOps(self):
|
||||
for dtype in self.complex_types:
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace tensorrt {
|
||||
class TRTBaseAllocator : public nvinfer1::IGpuAllocator {
|
||||
// Base allocator class so we can have a virtual destructor;
|
||||
public:
|
||||
// python wrapper seems to be not happy with an pure virtual destructor;
|
||||
// python wrapper seems to be not happy with a pure virtual destructor;
|
||||
virtual ~TRTBaseAllocator() = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -1012,10 +1012,10 @@ cc_library(
|
||||
],
|
||||
visibility = [":friends"],
|
||||
deps = [
|
||||
"//tensorflow/core:core_cpu_internal",
|
||||
"//tensorflow/core:framework",
|
||||
"//tensorflow/core:lib",
|
||||
"//tensorflow/core:protos_all_cc",
|
||||
"//tensorflow/core/common_runtime:dma_helper",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
@@ -1234,6 +1234,8 @@ tf_cc_test(
|
||||
"//tensorflow/cc:ops",
|
||||
"//tensorflow/cc:resource_variable_ops",
|
||||
"//tensorflow/cc:scope",
|
||||
"//tensorflow/compiler/jit:common",
|
||||
"//tensorflow/compiler/tf2xla/kernels:xla_dummy_ops",
|
||||
"//tensorflow/compiler/tf2xla/kernels:xla_ops",
|
||||
"//tensorflow/core:core_cpu_internal",
|
||||
"//tensorflow/core:framework",
|
||||
|
||||
@@ -549,10 +549,10 @@ tf_kernel_library(
|
||||
"check_numerics_op.cc",
|
||||
],
|
||||
deps = [
|
||||
"//tensorflow/compiler/tf2xla:mlir_xla_op_kernel",
|
||||
"//tensorflow/compiler/tf2xla:xla_compiler",
|
||||
"//tensorflow/compiler/tf2xla:xla_op_registry",
|
||||
"//tensorflow/core:array_ops_op_lib",
|
||||
"//tensorflow/core:framework",
|
||||
"//tensorflow/core:lib",
|
||||
"//tensorflow/core:logging_ops_op_lib",
|
||||
"@com_google_absl//absl/log",
|
||||
|
||||
@@ -101,6 +101,16 @@ class CategoricalOp : public XlaOpKernel {
|
||||
}
|
||||
xla::PrimitiveType type;
|
||||
OP_REQUIRES_OK(ctx, DataTypeToPrimitiveType(input_type(0), &type));
|
||||
// Sample in float32 for half-precision logits (the non-XLA kernels sample
|
||||
// in at least float32): the spacing between half-precision values grows
|
||||
// with the logits and can round the Gumbel noise away, which makes equal
|
||||
// logits always pick the first class.
|
||||
xla::XlaOp sampling_logits = logits;
|
||||
if (type == xla::F16 || type == xla::BF16) {
|
||||
type = xla::F32;
|
||||
uniform_shape.set_element_type(type);
|
||||
sampling_logits = xla::ConvertElementType(logits, type);
|
||||
}
|
||||
xla::XlaOp log_uniforms = GetLogUniforms(uniform_shape, type, ctx);
|
||||
|
||||
if (num_samples_is_dynamic) {
|
||||
@@ -113,7 +123,7 @@ class CategoricalOp : public XlaOpKernel {
|
||||
// https://hips.seas.harvard.edu/blog/2013/04/06/the-gumbel-max-trick-for-discrete-distributions/
|
||||
// TODO(b/68769470): Switch to using a cumulative sum approach.
|
||||
auto softmax_entries =
|
||||
xla::Sub(logits, log_uniforms,
|
||||
xla::Sub(sampling_logits, log_uniforms,
|
||||
/*broadcast_dimensions=*/{0, class_dimension});
|
||||
|
||||
xla::PrimitiveType xla_output_type;
|
||||
@@ -166,9 +176,6 @@ class StatelessCategoricalOp : public CategoricalOp {
|
||||
xla::XlaOp seed = ctx->Input(2);
|
||||
|
||||
xla::XlaBuilder* builder = ctx->builder();
|
||||
if (uniform_shape.element_type() == xla::BF16) {
|
||||
uniform_shape.set_element_type(xla::F32);
|
||||
}
|
||||
// We want a number in (0, 1) rather than [0, 1) or (0, 1]:
|
||||
// * log(-log(0)) is ∞.
|
||||
// * log(-log(1)) is -∞.
|
||||
|
||||
@@ -13,12 +13,38 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==============================================================================*/
|
||||
|
||||
#include "tensorflow/compiler/tf2xla/mlir_xla_op_kernel.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
|
||||
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
|
||||
#include "tensorflow/core/framework/op_kernel.h"
|
||||
#include "tensorflow/core/platform/mutex.h"
|
||||
|
||||
namespace tensorflow {
|
||||
namespace {
|
||||
|
||||
REGISTER_XLA_OP(Name("CheckNumerics"), MlirXlaOpKernel);
|
||||
class CheckNumericsOp : public XlaOpKernel {
|
||||
public:
|
||||
explicit CheckNumericsOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {}
|
||||
~CheckNumericsOp() override = default;
|
||||
|
||||
void Compile(XlaOpKernelContext* ctx) override {
|
||||
static mutex mu(tensorflow::LINKER_INITIALIZED);
|
||||
static int log_counter = 0;
|
||||
|
||||
mutex_lock l(mu);
|
||||
if (log_counter < 20) {
|
||||
++log_counter;
|
||||
LOG(WARNING) << "Ignoring CheckNumerics operator " << name();
|
||||
}
|
||||
ctx->SetOutput(0, ctx->Input(0));
|
||||
}
|
||||
|
||||
private:
|
||||
CheckNumericsOp(const CheckNumericsOp&) = delete;
|
||||
void operator=(const CheckNumericsOp&) = delete;
|
||||
};
|
||||
|
||||
REGISTER_XLA_OP(Name("CheckNumerics"), CheckNumericsOp);
|
||||
|
||||
} // anonymous namespace
|
||||
} // namespace tensorflow
|
||||
@@ -36,9 +36,8 @@ DstT CastTo(SrcT src) {
|
||||
}
|
||||
|
||||
template <typename DstT,
|
||||
typename std::enable_if<std::is_same<DstT, Eigen::half>::value ||
|
||||
std::is_same<DstT, bfloat16>::value>::type* =
|
||||
nullptr>
|
||||
std::enable_if_t<std::is_same_v<DstT, Eigen::half> ||
|
||||
std::is_same_v<DstT, bfloat16>>* = nullptr>
|
||||
DstT CastTo(int32_t src) {
|
||||
return absl::bit_cast<DstT>(static_cast<uint16_t>(src));
|
||||
}
|
||||
|
||||
@@ -484,6 +484,10 @@ class NonMaxSuppressionOp : public XlaOpKernel {
|
||||
context,
|
||||
TensorShapeUtils::IsScalar(context->InputShape("iou_threshold")),
|
||||
absl::InvalidArgumentError("IOU Threshold isn't a scalar"));
|
||||
OP_REQUIRES(
|
||||
context,
|
||||
TensorShapeUtils::IsScalar(context->InputShape("score_threshold")),
|
||||
absl::InvalidArgumentError("Score Threshold isn't a scalar"));
|
||||
OP_REQUIRES_OK(context, context->ConstantInputAsIntScalar(2, &output_size));
|
||||
OP_REQUIRES(context, output_size >= 0,
|
||||
absl::InvalidArgumentError(
|
||||
@@ -491,8 +495,6 @@ class NonMaxSuppressionOp : public XlaOpKernel {
|
||||
OP_REQUIRES(context, output_size <= std::numeric_limits<int32_t>::max(),
|
||||
absl::InvalidArgumentError(absl::StrCat(
|
||||
"Need output_size <= kint32Max, got ", output_size)));
|
||||
const xla::XlaOp score_thresh = context->Input("score_threshold");
|
||||
const xla::XlaOp iou_thresh = context->Input("iou_threshold");
|
||||
xla::XlaBuilder* const builder = context->builder();
|
||||
|
||||
if (num_boxes == 0) {
|
||||
@@ -521,6 +523,14 @@ class NonMaxSuppressionOp : public XlaOpKernel {
|
||||
return;
|
||||
}
|
||||
|
||||
// The thresholds' type, T_threshold, can differ from T, the type of boxes
|
||||
// and scores. Compare in T, as the TensorFlow kernels do, since XLA
|
||||
// doesn't mix floating-point types in one operation.
|
||||
const xla::XlaOp score_thresh = xla::ConvertElementType(
|
||||
context->Input("score_threshold"), scores_xla_type);
|
||||
const xla::XlaOp iou_thresh = xla::ConvertElementType(
|
||||
context->Input("iou_threshold"), boxes_xla_type);
|
||||
|
||||
// Choose a more convenient layout.
|
||||
const xla::XlaOp boxes = xla::Transpose(boxes_input, {1, 0});
|
||||
const xla::XlaOp boxes_sorted = xla::GetTupleElement(
|
||||
|
||||
@@ -31,6 +31,7 @@ limitations under the License.
|
||||
#include "xla/hlo/builder/lib/constants.h"
|
||||
#include "xla/hlo/builder/lib/matrix.h"
|
||||
#include "xla/hlo/builder/xla_builder.h"
|
||||
#include "xla/shape.h"
|
||||
#include "xla/util.h"
|
||||
#include "xla/xla_data.pb.h"
|
||||
#include "tensorflow/core/framework/op_kernel.h"
|
||||
@@ -416,42 +417,133 @@ class MatrixDiagPartOp : public XlaOpKernel {
|
||||
|
||||
// Computes output.
|
||||
xla::XlaOp input = context->Input(0);
|
||||
|
||||
// With dynamic dimensions, take the diagonals of the matrices at their
|
||||
// bound sizes, which hold the actual matrices in the top left, and give
|
||||
// the output its actual sizes at the end.
|
||||
xla::Shape input_xla_shape;
|
||||
OP_REQUIRES_VALUE(input_xla_shape, context, context->InputXlaShape(0));
|
||||
std::vector<xla::XlaOp> input_dim_sizes(input_rank);
|
||||
for (int i = 0; i < input_rank; ++i) {
|
||||
if (input_xla_shape.is_dynamic_dimension(i)) {
|
||||
input_dim_sizes[i] = xla::GetDimensionSize(input, i);
|
||||
input = xla::RemoveDynamicDimension(input, i);
|
||||
}
|
||||
}
|
||||
const bool dynamic_matrix =
|
||||
input_xla_shape.is_dynamic_dimension(input_rank - 2) ||
|
||||
input_xla_shape.is_dynamic_dimension(input_rank - 1);
|
||||
const auto actual_size = [&](int dim) {
|
||||
return input_xla_shape.is_dynamic_dimension(dim)
|
||||
? input_dim_sizes[dim]
|
||||
: xla::ConstantR0<int32_t>(
|
||||
context->builder(),
|
||||
static_cast<int32_t>(input_shape.dim_size(dim)));
|
||||
};
|
||||
// Only built with dynamic matrix dimensions, so that static inputs build
|
||||
// the same HLO as before.
|
||||
xla::XlaOp actual_num_rows;
|
||||
xla::XlaOp actual_num_cols;
|
||||
// The length of the longest diagonal from lower to upper at the actual
|
||||
// sizes. Diagonals outside the actual matrix, which the CPU kernel
|
||||
// rejects, have length 0.
|
||||
const auto actual_len = [&](int64_t lower, int64_t upper) {
|
||||
return xla::Max(
|
||||
xla::Min(xla::Add(actual_num_rows,
|
||||
xla::ScalarLike(actual_num_rows,
|
||||
std::min(upper, int64_t{0}))),
|
||||
xla::Sub(actual_num_cols,
|
||||
xla::ScalarLike(actual_num_cols,
|
||||
std::max(lower, int64_t{0})))),
|
||||
xla::ScalarLike(actual_num_rows, 0));
|
||||
};
|
||||
xla::XlaOp actual_max_diag_len;
|
||||
if (dynamic_matrix) {
|
||||
actual_num_rows = actual_size(input_rank - 2);
|
||||
actual_num_cols = actual_size(input_rank - 1);
|
||||
actual_max_diag_len = actual_len(lower_diag_index, upper_diag_index);
|
||||
}
|
||||
|
||||
xla::XlaOp output;
|
||||
std::vector<xla::XlaOp> diag_list;
|
||||
xla::PaddingConfig padding_config =
|
||||
xla::MakeNoPaddingConfig(input_rank - 1);
|
||||
// XLA:GPU can lay out the gather's output with the batch dimensions minor,
|
||||
// and a batched dynamic output laid out that way comes back transposed.
|
||||
const bool use_gather = is_gpu_ && !input_xla_shape.is_dynamic();
|
||||
if (num_diags == 1) {
|
||||
context->SetOutput(
|
||||
0, is_gpu_ ? xla::GetMatrixDiagonalViaGather(input, upper_diag_index)
|
||||
: xla::GetMatrixDiagonal(input, upper_diag_index));
|
||||
return;
|
||||
}
|
||||
for (int diag_index = upper_diag_index; diag_index >= lower_diag_index;
|
||||
--diag_index) {
|
||||
xla::XlaOp single_diag =
|
||||
is_gpu_ ? xla::GetMatrixDiagonalViaGather(input, diag_index)
|
||||
: xla::GetMatrixDiagonal(input, diag_index);
|
||||
const int64_t diag_len = ComputeDiagLen(diag_index, num_rows, num_cols);
|
||||
const int64_t padding_len = max_diag_len - diag_len;
|
||||
if (padding_len > 0) {
|
||||
if (IsLeftAligned(diag_index, left_align_superdiagonal_,
|
||||
left_align_subdiagonal_)) {
|
||||
output = use_gather
|
||||
? xla::GetMatrixDiagonalViaGather(input, upper_diag_index)
|
||||
: xla::GetMatrixDiagonal(input, upper_diag_index);
|
||||
} else {
|
||||
auto slice_sizes = input_shape.dim_sizes();
|
||||
slice_sizes.pop_back();
|
||||
slice_sizes.back() = max_diag_len;
|
||||
for (int diag_index = upper_diag_index; diag_index >= lower_diag_index;
|
||||
--diag_index) {
|
||||
xla::XlaOp single_diag =
|
||||
use_gather ? xla::GetMatrixDiagonalViaGather(input, diag_index)
|
||||
: xla::GetMatrixDiagonal(input, diag_index);
|
||||
const int64_t diag_len = ComputeDiagLen(diag_index, num_rows, num_cols);
|
||||
const int64_t padding_len = max_diag_len - diag_len;
|
||||
const bool left_aligned = IsLeftAligned(
|
||||
diag_index, left_align_superdiagonal_, left_align_subdiagonal_);
|
||||
if (dynamic_matrix) {
|
||||
// Place the diagonal after the padding it has at the actual sizes,
|
||||
// and pad its entries past its actual length, which come from
|
||||
// outside the actual matrix, with padding_value too.
|
||||
const xla::XlaOp len = actual_len(diag_index, diag_index);
|
||||
const xla::XlaOp offset = left_aligned
|
||||
? xla::ScalarLike(len, 0)
|
||||
: xla::Sub(actual_max_diag_len, len);
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_low(0);
|
||||
->set_edge_padding_low(max_diag_len);
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_high(padding_len);
|
||||
} else {
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_low(padding_len);
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_high(0);
|
||||
->set_edge_padding_high(max_diag_len);
|
||||
single_diag = xla::Pad(single_diag, padding_value, padding_config);
|
||||
std::vector<xla::XlaOp> start_indices(input_rank - 1,
|
||||
xla::ScalarLike(len, 0));
|
||||
start_indices.back() =
|
||||
xla::Sub(xla::ScalarLike(len, max_diag_len), offset);
|
||||
single_diag =
|
||||
xla::DynamicSlice(single_diag, start_indices, slice_sizes);
|
||||
const xla::XlaOp in_diagonal =
|
||||
xla::Lt(xla::Iota(context->builder(), xla::S32, max_diag_len),
|
||||
xla::Add(offset, len));
|
||||
single_diag = xla::Select(
|
||||
xla::BroadcastInDim(in_diagonal, slice_sizes, {input_rank - 2}),
|
||||
single_diag, xla::Broadcast(padding_value, slice_sizes));
|
||||
} else if (padding_len > 0) {
|
||||
if (left_aligned) {
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_low(0);
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_high(padding_len);
|
||||
} else {
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_low(padding_len);
|
||||
padding_config.mutable_dimensions(input_rank - 2)
|
||||
->set_edge_padding_high(0);
|
||||
}
|
||||
single_diag = xla::Pad(single_diag, padding_value, padding_config);
|
||||
}
|
||||
single_diag = xla::Pad(single_diag, padding_value, padding_config);
|
||||
diag_list.emplace_back(single_diag);
|
||||
}
|
||||
diag_list.emplace_back(single_diag);
|
||||
auto concat =
|
||||
xla::ConcatInDim(context->builder(), diag_list, input_rank - 2);
|
||||
output = xla::Reshape(concat, output_shape.dim_sizes());
|
||||
}
|
||||
auto concat =
|
||||
xla::ConcatInDim(context->builder(), diag_list, input_rank - 2);
|
||||
context->SetOutput(0, xla::Reshape(concat, output_shape.dim_sizes()));
|
||||
|
||||
if (dynamic_matrix) {
|
||||
output = xla::SetDimensionSize(output, actual_max_diag_len,
|
||||
output_shape.dims() - 1);
|
||||
}
|
||||
for (int i = 0; i < input_rank - 2; ++i) {
|
||||
if (input_xla_shape.is_dynamic_dimension(i)) {
|
||||
output = xla::SetDimensionSize(output, input_dim_sizes[i], i);
|
||||
}
|
||||
}
|
||||
context->SetOutput(0, output);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -466,10 +558,34 @@ REGISTER_XLA_OP(Name("MatrixDiagPartV2")
|
||||
.CompileTimeConstantInput("k")
|
||||
.CompileTimeConstantInput("padding_value"),
|
||||
MatrixDiagPartOp);
|
||||
|
||||
// MatrixDiagPartV3 is lowered through MLIR, whose lowering only takes static
|
||||
// shapes, so an input with a dynamic dimension goes through MatrixDiagPartOp.
|
||||
class MatrixDiagPartV3Op : public XlaOpKernel {
|
||||
public:
|
||||
explicit MatrixDiagPartV3Op(OpKernelConstruction* context)
|
||||
: XlaOpKernel(context),
|
||||
mlir_kernel_(context),
|
||||
dynamic_shape_kernel_(context) {}
|
||||
|
||||
void Compile(XlaOpKernelContext* context) override {
|
||||
xla::Shape input_xla_shape;
|
||||
OP_REQUIRES_VALUE(input_xla_shape, context, context->InputXlaShape(0));
|
||||
XlaOpKernel& kernel = input_xla_shape.is_dynamic()
|
||||
? static_cast<XlaOpKernel&>(dynamic_shape_kernel_)
|
||||
: static_cast<XlaOpKernel&>(mlir_kernel_);
|
||||
kernel.Compile(context);
|
||||
}
|
||||
|
||||
private:
|
||||
MlirXlaOpKernel mlir_kernel_;
|
||||
MatrixDiagPartOp dynamic_shape_kernel_;
|
||||
};
|
||||
|
||||
REGISTER_XLA_OP(Name("MatrixDiagPartV3")
|
||||
.CompileTimeConstantInput("k")
|
||||
.CompileTimeConstantInput("padding_value"),
|
||||
MlirXlaOpKernel);
|
||||
MatrixDiagPartV3Op);
|
||||
|
||||
class MatrixSetDiagOp : public XlaOpKernel {
|
||||
public:
|
||||
|
||||
@@ -64,7 +64,7 @@ class PackOp : public XlaOpKernel {
|
||||
std::vector<xla::XlaOp> reshaped_inputs(num);
|
||||
|
||||
TensorShape child_shape(shapes[0]);
|
||||
child_shape.InsertDim(axis, 1);
|
||||
OP_REQUIRES_OK(ctx, child_shape.InsertDimWithStatus(axis, 1));
|
||||
|
||||
for (int i = 0; i < num; ++i) {
|
||||
// Reshape the inputs to have an extra dimension of size 1.
|
||||
|
||||
@@ -112,6 +112,13 @@ class QuantizeAndDequantizeOp : public XlaOpKernel {
|
||||
OP_REQUIRES(
|
||||
ctx, ctx->num_inputs() == 4,
|
||||
errors::Internal("Expected 4 inputs to QuantizeAndDequantize"));
|
||||
int64_t num_bits_val;
|
||||
if (ctx->ConstantInputAsIntScalar(3, &num_bits_val).ok()) {
|
||||
OP_REQUIRES(
|
||||
ctx, num_bits_val > 0 && num_bits_val < (signed_input_ ? 62 : 63),
|
||||
errors::InvalidArgument("num_bits is out of range: ", num_bits_val,
|
||||
" with signed_input_ ", signed_input_));
|
||||
}
|
||||
num_bits = ctx->Input(3);
|
||||
} else {
|
||||
num_bits = xla::ConstantR0<int32_t>(b, num_bits_);
|
||||
|
||||
@@ -19,6 +19,7 @@ limitations under the License.
|
||||
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
|
||||
#include "xla/hlo/builder/lib/constants.h"
|
||||
#include "xla/hlo/builder/xla_builder.h"
|
||||
#include "xla/shape.h"
|
||||
#include "xla/shape_util.h"
|
||||
#include "xla/xla_data.pb.h"
|
||||
#include "tensorflow/core/framework/op_kernel.h"
|
||||
@@ -129,8 +130,21 @@ class ReverseSequenceOp : public XlaOpKernel {
|
||||
slice_sizes[batch_dim_] = 1;
|
||||
slice_sizes[seq_dim_] = 1;
|
||||
|
||||
context->SetOutput(0,
|
||||
xla::Gather(input, start_indices, dnums, slice_sizes));
|
||||
xla::XlaOp output = xla::Gather(input, start_indices, dnums, slice_sizes);
|
||||
// The gather indices and slice sizes come from each dimension's static
|
||||
// bound, so the output drops the input's dynamic sizes. Its dimensions are
|
||||
// the input's, in the same order, so give them the input's sizes.
|
||||
xla::Shape input_xla_shape;
|
||||
OP_REQUIRES_VALUE(input_xla_shape, context, context->InputXlaShape(0));
|
||||
if (input_xla_shape.is_dynamic()) {
|
||||
for (int i = 0; i < input_shape.dims(); ++i) {
|
||||
if (input_xla_shape.is_dynamic_dimension(i)) {
|
||||
output =
|
||||
xla::SetDimensionSize(output, xla::GetDimensionSize(input, i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
context->SetOutput(0, output);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
@@ -40,7 +41,8 @@ limitations under the License.
|
||||
namespace tensorflow {
|
||||
namespace {
|
||||
|
||||
// The type-specific part of the implementation of Range.
|
||||
// The type-specific part of the implementation of Range: checks the arguments
|
||||
// and returns delta * [0, 1, ..., size - 1] for the range's size.
|
||||
template <typename T>
|
||||
absl::StatusOr<xla::XlaOp> CreateRangeTensor(
|
||||
const xla::LiteralSlice& start_literal,
|
||||
@@ -65,13 +67,22 @@ absl::StatusOr<xla::XlaOp> CreateRangeTensor(
|
||||
}
|
||||
}
|
||||
int64_t size;
|
||||
if constexpr (std::is_integral<T>::value) {
|
||||
int64_t start_i = static_cast<int64_t>(start);
|
||||
int64_t limit_i = static_cast<int64_t>(limit);
|
||||
int64_t delta_i = static_cast<int64_t>(delta);
|
||||
size = (limit_i == start_i
|
||||
? 0
|
||||
: (std::abs(limit_i - start_i) - 1) / std::abs(delta_i) + 1);
|
||||
if constexpr (std::is_integral_v<T>) {
|
||||
// Take |limit - start| and |delta| in uint64_t, which holds them for every
|
||||
// T, as the CPU kernel does, so that a range wider than T can hold doesn't
|
||||
// overflow.
|
||||
const bool count_up = delta > static_cast<T>(0);
|
||||
const uint64_t range =
|
||||
count_up ? static_cast<uint64_t>(limit) - static_cast<uint64_t>(start)
|
||||
: static_cast<uint64_t>(start) - static_cast<uint64_t>(limit);
|
||||
const uint64_t abs_delta =
|
||||
count_up ? static_cast<uint64_t>(delta) : -static_cast<uint64_t>(delta);
|
||||
const uint64_t size_unsigned = range == 0 ? 0 : (range - 1) / abs_delta + 1;
|
||||
if (size_unsigned > std::numeric_limits<int64_t>::max()) {
|
||||
return errors::InvalidArgument("Requires ((limit - start) / delta) <= ",
|
||||
std::numeric_limits<int64_t>::max());
|
||||
}
|
||||
size = static_cast<int64_t>(size_unsigned);
|
||||
} else {
|
||||
double start_f = static_cast<double>(start);
|
||||
double limit_f = static_cast<double>(limit);
|
||||
@@ -80,10 +91,9 @@ absl::StatusOr<xla::XlaOp> CreateRangeTensor(
|
||||
std::ceil(std::abs((limit_f - start_f) / delta_f)));
|
||||
}
|
||||
|
||||
return xla::ConstantR0(builder, start) +
|
||||
xla::ConstantR0(builder, delta) *
|
||||
xla::Iota(builder, xla::primitive_util::NativeToPrimitiveType<T>(),
|
||||
size);
|
||||
return xla::ConstantR0(builder, delta) *
|
||||
xla::Iota(builder, xla::primitive_util::NativeToPrimitiveType<T>(),
|
||||
size);
|
||||
}
|
||||
|
||||
class RangeOp : public XlaOpKernel {
|
||||
@@ -104,11 +114,24 @@ class RangeOp : public XlaOpKernel {
|
||||
errors::InvalidArgument("delta must be a scalar, not shape ",
|
||||
delta_in_shape.DebugString()));
|
||||
xla::Literal start, limit, delta;
|
||||
OP_REQUIRES_OK(ctx, ctx->ConstantInput(
|
||||
0, &start, xla::ValueInferenceMode::kLowerBound));
|
||||
OP_REQUIRES_OK(ctx, ctx->ConstantInput(
|
||||
1, &limit, xla::ValueInferenceMode::kUpperBound));
|
||||
OP_REQUIRES_OK(ctx, ctx->ConstantInput(2, &delta));
|
||||
// Size the output from the bounds of start and limit that give the longest
|
||||
// range: the lowest start and highest limit when counting up, and the
|
||||
// highest start and lowest limit when counting down.
|
||||
const xla::PrimitiveType delta_type = delta.shape().element_type();
|
||||
const bool count_down =
|
||||
xla::primitive_util::IsFloatingPointType(delta_type)
|
||||
? delta.GetAsDouble({}).value_or(0) < 0
|
||||
: xla::primitive_util::IsSignedIntegralType(delta_type) &&
|
||||
delta.GetIntegralAsS64({}).value_or(0) < 0;
|
||||
OP_REQUIRES_OK(ctx, ctx->ConstantInput(
|
||||
0, &start,
|
||||
count_down ? xla::ValueInferenceMode::kUpperBound
|
||||
: xla::ValueInferenceMode::kLowerBound));
|
||||
OP_REQUIRES_OK(ctx, ctx->ConstantInput(
|
||||
1, &limit,
|
||||
count_down ? xla::ValueInferenceMode::kLowerBound
|
||||
: xla::ValueInferenceMode::kUpperBound));
|
||||
|
||||
DataType type = input_type(0);
|
||||
absl::StatusOr<xla::XlaOp> output;
|
||||
@@ -165,22 +188,50 @@ class RangeOp : public XlaOpKernel {
|
||||
bool limit_is_dynamic = false;
|
||||
OP_REQUIRES_OK(ctx,
|
||||
ctx->ResolveInputDynamismIntoPred(1, &limit_is_dynamic));
|
||||
// If start is dynamic, its literal is a bound, so add the input instead.
|
||||
output = (start_is_dynamic ? ctx->Input(0)
|
||||
: xla::ConstantLiteral(ctx->builder(), start)) +
|
||||
output.value();
|
||||
|
||||
if (start_is_dynamic || limit_is_dynamic) {
|
||||
xla::XlaOp delta = ctx->Input(2);
|
||||
xla::XlaOp limit = ctx->Input(1);
|
||||
xla::XlaOp start = ctx->Input(0);
|
||||
// The runtime values can be in the wrong order for delta's sign, which
|
||||
// the CPU kernel rejects. Give such a range size 0 rather than a size
|
||||
// past the output's static size.
|
||||
const xla::XlaOp is_empty =
|
||||
count_down ? xla::Le(start, limit) : xla::Ge(start, limit);
|
||||
xla::XlaOp dynamic_size;
|
||||
if (DataTypeIsInteger(type)) {
|
||||
auto dynamic_size = (xla::Abs(limit - start) + xla::Abs(delta) -
|
||||
xla::One(ctx->builder(), ctx->input_xla_type(0))) /
|
||||
xla::Abs(delta);
|
||||
dynamic_size = xla::ConvertElementType(dynamic_size, xla::S32);
|
||||
output = xla::SetDimensionSize(output.value(), dynamic_size, 0);
|
||||
// Take |limit - start| and |delta| in the unsigned type of the same
|
||||
// width, which holds them. XLA's Abs doesn't take unsigned types, whose
|
||||
// delta is positive anyway.
|
||||
const xla::PrimitiveType xla_type = ctx->input_xla_type(0);
|
||||
const xla::PrimitiveType unsigned_type =
|
||||
xla::primitive_util::UnsignedIntegralTypeForBitWidth(
|
||||
xla::primitive_util::BitWidth(xla_type));
|
||||
const auto as_unsigned = [&](xla::XlaOp x) {
|
||||
return xla::BitcastConvertType(x, unsigned_type);
|
||||
};
|
||||
const xla::XlaOp range = count_down
|
||||
? as_unsigned(start) - as_unsigned(limit)
|
||||
: as_unsigned(limit) - as_unsigned(start);
|
||||
const xla::XlaOp abs_delta =
|
||||
as_unsigned(xla::primitive_util::IsSignedIntegralType(xla_type)
|
||||
? xla::Abs(delta)
|
||||
: delta);
|
||||
const xla::XlaOp one = xla::One(ctx->builder(), unsigned_type);
|
||||
dynamic_size =
|
||||
xla::Select(is_empty, xla::Zero(ctx->builder(), unsigned_type),
|
||||
(range - one) / abs_delta + one);
|
||||
} else {
|
||||
auto dynamic_size = (xla::Ceil(xla::Abs((limit - start) / delta)));
|
||||
dynamic_size = xla::ConvertElementType(dynamic_size, xla::S32);
|
||||
output = xla::SetDimensionSize(output.value(), dynamic_size, 0);
|
||||
dynamic_size = xla::Select(
|
||||
is_empty, xla::Zero(ctx->builder(), ctx->input_xla_type(0)),
|
||||
xla::Ceil(xla::Abs((limit - start) / delta)));
|
||||
}
|
||||
dynamic_size = xla::ConvertElementType(dynamic_size, xla::S32);
|
||||
output = xla::SetDimensionSize(output.value(), dynamic_size, 0);
|
||||
}
|
||||
|
||||
ctx->SetOutput(0, output.value());
|
||||
|
||||
@@ -289,6 +289,12 @@ class ExpandDimsOp : public XlaOpKernel {
|
||||
" for tensor with ", input_shape.dims(),
|
||||
" dimensions."));
|
||||
|
||||
OP_REQUIRES(ctx, input_shape.dims() < TensorShape::MaxDimensions(),
|
||||
errors::InvalidArgument(
|
||||
"Cannot expand a tensor of rank ", input_shape.dims(),
|
||||
": expanding would exceed the maximum supported rank of ",
|
||||
TensorShape::MaxDimensions(), "."));
|
||||
|
||||
auto existing_dims = input_shape.dim_sizes();
|
||||
// Safe - # elements in tensor dims bounded.
|
||||
const int existing_dims_size = static_cast<int>(existing_dims.size());
|
||||
|
||||
@@ -28,6 +28,7 @@ limitations under the License.
|
||||
#include "xla/tsl/platform/statusor.h"
|
||||
#include "xla/xla_data.pb.h"
|
||||
#include "tensorflow/core/framework/op_kernel.h"
|
||||
#include "tensorflow/core/framework/types.h"
|
||||
#include "tensorflow/core/framework/types.pb.h"
|
||||
|
||||
namespace tensorflow {
|
||||
@@ -100,7 +101,21 @@ XLAJIT_MAKE_UNARY(PopulationCount,
|
||||
XLAJIT_MAKE_UNARY(Neg, -x);
|
||||
|
||||
XLAJIT_MAKE_UNARY(Rint, xla::RoundToEven(x));
|
||||
XLAJIT_MAKE_UNARY(Round, xla::RoundToEven(x));
|
||||
|
||||
class RoundOp : public XlaOpKernel {
|
||||
public:
|
||||
explicit RoundOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {}
|
||||
void Compile(XlaOpKernelContext* ctx) override {
|
||||
xla::XlaOp x = ctx->Input(0);
|
||||
// Round on integers is a no-op; xla::RoundToEven rejects non-float types.
|
||||
if (DataTypeIsInteger(ctx->input_type(0))) {
|
||||
ctx->SetOutput(0, x);
|
||||
} else {
|
||||
ctx->SetOutput(0, xla::RoundToEven(x));
|
||||
}
|
||||
}
|
||||
};
|
||||
REGISTER_XLA_OP(Name("Round"), RoundOp);
|
||||
|
||||
REGISTER_XLA_OP(Name("Rsqrt"), MlirXlaOpKernel);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class ResourceUsageAnalysis {
|
||||
std::string node_name_;
|
||||
std::string op_;
|
||||
|
||||
NodeInfo() {}
|
||||
NodeInfo() = default;
|
||||
|
||||
NodeInfo(const std::optional<std::string>& function_name,
|
||||
std::string node_name, std::string op)
|
||||
|
||||
Loaded 100 of 1974 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user