[MLIR:TF/XLA] Side effect analysis.

An analysis that infers control dependencies based on side-effects on known and
unknown resources. Side-effecting ops on uknown resources are conservatively
treated as interferencing all known resource op accesses. It distinguishes
accesses based on whether they are read-only, and read-only ops do not interfer
with each other.

PiperOrigin-RevId: 282815679
Change-Id: I4f430c5b6cbfb02284c150c85b41f0b81458b6e9
This commit is contained in:
Yuanzhong Xu 2019-11-27 12:29:01 -08:00 committed by TensorFlower Gardener
parent bbd8a3e0e4
commit fe77f79256
5 changed files with 831 additions and 0 deletions

View File

@ -217,6 +217,7 @@ cc_library(
"transforms/shape_inference.cc",
"transforms/shape_inference_pass.cc",
"transforms/sink_constant.cc",
"transforms/test_side_effect_analysis.cc",
"transforms/tpu_cluster_formation.cc",
"transforms/tpu_merge_variables_with_execute.cc",
"transforms/tpu_rewrite_pass.cc",
@ -239,6 +240,7 @@ cc_library(
":error_util",
":export_tf_dialect_op",
":mangling_util",
":side_effect_analysis",
":tensorflow",
":tensorflow_optimize_inc_gen",
":tpu_rewrite_device_util",
@ -981,3 +983,19 @@ cc_library(
"@local_config_mlir//:Pass",
],
)
cc_library(
name = "side_effect_analysis",
srcs = ["analysis/side_effect_analysis.cc"],
hdrs = ["analysis/side_effect_analysis.h"],
deps = [
":tensorflow",
"//tensorflow/compiler/tf2xla:resource_operation_table",
"//tensorflow/core:framework",
"@com_google_absl//absl/strings",
"@llvm//:support",
"@local_config_mlir//:IR",
"@local_config_mlir//:Pass",
"@local_config_mlir//:Support",
],
)

View File

@ -0,0 +1,374 @@
/* Copyright 2019 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 "tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h"
#include <cstdint>
#include <initializer_list>
#include "absl/strings/str_cat.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "mlir/IR/Attributes.h" // TF:local_config_mlir
#include "mlir/IR/Block.h" // TF:local_config_mlir
#include "mlir/IR/Builders.h" // TF:local_config_mlir
#include "mlir/IR/Location.h" // TF:local_config_mlir
#include "mlir/IR/Operation.h" // TF:local_config_mlir
#include "mlir/IR/StandardTypes.h" // TF:local_config_mlir
#include "mlir/Support/LLVM.h" // TF:local_config_mlir
#include "mlir/Support/LogicalResult.h" // TF:local_config_mlir
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h"
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_types.h"
#include "tensorflow/compiler/tf2xla/resource_operation_table.h"
#include "tensorflow/core/framework/resource_mgr.h"
namespace mlir {
namespace TF {
namespace {
constexpr int64_t kUnknownResourceId = -1;
// Returns if a VarHandleOp is anonymous, which means it always creates a new
// variable.
bool IsResourceHandleAnonymous(TF::VarHandleOp handle) {
return handle.shared_name() == tensorflow::ResourceHandle::ANONYMOUS_NAME;
}
// Returns a string unique identifier for a non-anonymous VarHandleOp.
std::string GetVarHandleStringId(TF::VarHandleOp handle) {
auto device = handle.getAttrOfType<StringAttr>("device");
return absl::StrCat(handle.container().str(), "/", handle.shared_name().str(),
"/", device ? device.getValue().str() : std::string(""));
}
// Finds a unique ID for a VarHandleOp's output. If it is anonymous, always
// creates a new ID; otherwise, tries to reuse the existing ID for the
// referenced variable if it exists, or creates a new one if not.
int64_t GetOrCreateIdForVarHandle(TF::VarHandleOp handle, int64_t* next_id,
llvm::StringMap<int64_t>* name_id_map) {
// Always create a new ID for anonymous handle.
if (IsResourceHandleAnonymous(handle)) return (*next_id)++;
auto name = GetVarHandleStringId(handle);
auto emplace_res = name_id_map->try_emplace(name, *next_id);
// New ID created, increment next_id.
if (emplace_res.second) ++(*next_id);
return emplace_res.first->second;
}
} // namespace
ResourceAliasAnalysis::ResourceAliasAnalysis(Operation* op) {
auto func_op = llvm::dyn_cast<FuncOp>(op);
if (!func_op) return;
AnalyzeFunction(func_op);
}
void ResourceAliasAnalysis::AnalyzeFunction(FuncOp func_op) {
// This function populates resource_value_to_ids_.
//
// TODO(yuanzx): Pass variable aliasing information to functions so we can
// properly resolve aliasing arguments.
//
// Before having that, we assume function arguments do not alias each other.
int64_t next_unique_id = 0;
for (auto arg : func_op.getArguments()) {
if (!mlir::getElementTypeOrSelf(arg->getType()).isa<TF::ResourceType>())
continue;
resource_value_to_ids_[arg].insert(next_unique_id++);
}
llvm::StringMap<int64_t> var_handle_name_id_map;
auto forward_input_to_output = [&](Value* operand, Value* result) {
if (!mlir::getElementTypeOrSelf(result->getType()).isa<TF::ResourceType>())
return;
auto operand_it = resource_value_to_ids_.find(operand);
assert(operand_it != resource_value_to_ids_.end() &&
"A resource-type output does not have the corresponding "
"resource-type input.");
resource_value_to_ids_[result].insert(operand_it->getSecond().begin(),
operand_it->getSecond().end());
};
// TODO(yuanzx): Consider control-flow ops.
func_op.walk([&](Operation* op) {
if (auto var_handle = llvm::dyn_cast<TF::VarHandleOp>(op)) {
resource_value_to_ids_[var_handle.resource()].insert(
GetOrCreateIdForVarHandle(var_handle, &next_unique_id,
&var_handle_name_id_map));
} else if (llvm::isa<TF::IdentityNOp>(op) ||
llvm::isa<TF::IdentityOp>(op)) {
for (auto operand_and_result :
llvm::zip(op->getOperands(), op->getResults())) {
forward_input_to_output(std::get<0>(operand_and_result),
std::get<1>(operand_and_result));
}
} else {
for (auto result : op->getResults()) {
if (!mlir::getElementTypeOrSelf(result->getType())
.isa<TF::ResourceType>())
continue;
resource_value_to_ids_[result].insert(kUnknownResourceId);
}
}
});
}
bool ResourceAliasAnalysis::IsUnknownResource(const Value* resource) const {
auto it = resource_value_to_ids_.find(resource);
assert(it != resource_value_to_ids_.end() && !it->getSecond().empty());
// The set is sorted so we only need to check the first element since
// kUnknownResourceId < 0.
static_assert(kUnknownResourceId < 0,
"kUnknownResourceId should be negative");
return *it->getSecond().begin() == kUnknownResourceId;
}
const llvm::SmallSet<int64_t, 8>& ResourceAliasAnalysis::GetResourceUniqueIds(
const Value* resource) const {
auto it = resource_value_to_ids_.find(resource);
assert(it != resource_value_to_ids_.end() && "Unseen resource was queried");
return it->getSecond();
}
namespace {
// Returns a set that contains only kUnknownResourceId.
llvm::SmallDenseSet<int64_t, 8> UnknownResourceSet() {
llvm::SmallDenseSet<int64_t, 8> unknown_set;
unknown_set.insert(kUnknownResourceId);
return unknown_set;
}
// Returns all resources that could be accessed by op, or UnknownResourceSet()
// if we cannot find all of them.
llvm::SmallDenseSet<int64_t, 8> FindAccessedResources(
Operation* op, const ResourceAliasAnalysis& alias_analysis) {
llvm::SmallDenseSet<int64_t, 8> resources;
for (auto operand : op->getOperands()) {
if (!mlir::getElementTypeOrSelf(operand->getType()).isa<TF::ResourceType>())
continue;
if (alias_analysis.IsUnknownResource(operand)) return UnknownResourceSet();
const auto& ids = alias_analysis.GetResourceUniqueIds(operand);
resources.insert(ids.begin(), ids.end());
}
for (auto result : op->getResults()) {
if (!mlir::getElementTypeOrSelf(result->getType()).isa<TF::ResourceType>())
continue;
if (alias_analysis.IsUnknownResource(result)) return UnknownResourceSet();
const auto& ids = alias_analysis.GetResourceUniqueIds(result);
resources.insert(ids.begin(), ids.end());
}
return resources;
}
// Returns an XlaResourceOpInfo (or nullptr if it does not exist) that specifies
// the resource access type of the op. It tells whether the op is read only,
// etc.
//
// TODO(yuanzx): Define this information in a different place. Currently we use
// tensorflow/compiler/tf2xla/resource_operation_table.h.
const tensorflow::XlaResourceOpInfo* GetResourceInfoForOp(Operation* op) {
auto op_name = op->getName().getStringRef().str();
if (op->getName().getDialect() !=
TF::TensorFlowDialect::getDialectNamespace()) {
return nullptr;
}
return tensorflow::GetResourceOpInfoForOp(
op->getName().getStringRef().split('.').second.str());
}
// Returns whether `op` accesses resources and it is known to be read-only.
bool OpIsReadOnly(Operation* op) {
auto resource_op_info = GetResourceInfoForOp(op);
return resource_op_info &&
resource_op_info->kind() == tensorflow::XlaResourceOpKind::kRead;
}
// Returns if `op` is a resource declaration.
bool OpIsDeclaration(Operation* op,
const ResourceAliasAnalysis& alias_analysis) {
// TODO(yuanzx): Add other types of resources.
return llvm::isa<TF::VarHandleOp>(op) ||
((llvm::isa<TF::IdentityNOp>(op) || llvm::isa<TF::IdentityOp>(op)) &&
!FindAccessedResources(op, alias_analysis).empty());
}
} // namespace
void SideEffectAnalysis::TrackAccess(int64_t resource_id, Operation* op,
bool read_only) {
if (resource_id == kUnknownResourceId) {
if (read_only) {
// New unknown read is not tracked by any known resource access.
for (auto& entry : per_resource_access_info_) {
entry.getSecond().tracked_last_unknown_read = false;
}
} else {
// Unknown write can clear all other tracked information, since it acts
// like a barrier.
per_resource_access_info_.clear();
}
}
auto& info = per_resource_access_info_[resource_id];
if (read_only) {
info.reads_since_last_write.push_back(op);
// Resource read must have carried control dependencies of unknown write.
info.tracked_last_unknown_write = true;
} else {
// Resource write must have carried control dependencies of unknown access.
info.tracked_last_unknown_write = true;
info.tracked_last_unknown_read = true;
info.last_write = op;
info.reads_since_last_write.clear();
}
}
void SideEffectAnalysis::AddPredecessorsForAccess(int64_t resource_id,
Operation* op,
bool read_only) {
auto it = per_resource_access_info_.find(resource_id);
if (it == per_resource_access_info_.end()) return;
const auto& access_info = it->getSecond();
auto& control_predecessors = control_predecessors_[op];
bool read_tracked = false;
if (!read_only) {
control_predecessors.insert(access_info.reads_since_last_write.begin(),
access_info.reads_since_last_write.end());
read_tracked = !access_info.reads_since_last_write.empty();
}
if (access_info.last_write && !read_tracked) {
control_predecessors.insert(access_info.last_write);
}
}
void SideEffectAnalysis::AnalyzeFunction(
FuncOp func_op, const ResourceAliasAnalysis& alias_analysis) {
// This function populates control_predecessors_ and control_successors_ by
// walking through func_op's body, and tracking resource accesses in
// per_resource_access_info_.
// Returns whether an access to `resource` can skip control edges from
// prevoius accesses to unknown resources, due to that earlier accesses to
// `resource` already indirectly tracked previous accesses to uknown
// resources. `read_only` specifies the type of access of the current op being
// considered.
auto unknown_access_indirectly_tracked_by_resource = [&](int64_t resource,
bool read_only) {
auto it = per_resource_access_info_.find(resource);
if (it == per_resource_access_info_.end()) return false;
auto unknown_it = per_resource_access_info_.find(kUnknownResourceId);
const bool no_unknown_read =
unknown_it == per_resource_access_info_.end() ||
unknown_it->getSecond().reads_since_last_write.empty();
return read_only
? it->second.tracked_last_unknown_write
: it->second.tracked_last_unknown_write &&
(it->second.tracked_last_unknown_read || no_unknown_read);
};
func_op.walk([&](Operation* op) {
// We do not need explicit control edges for declaration ops.
if (OpIsDeclaration(op, alias_analysis)) return;
auto resource_op_info = GetResourceInfoForOp(op);
if (!resource_op_info && op->hasNoSideEffect()) return;
llvm::SmallDenseSet<int64_t, 8> resources =
resource_op_info ? FindAccessedResources(op, alias_analysis)
: UnknownResourceSet();
assert(!resources.empty());
const bool is_unknown = resources.count(kUnknownResourceId) > 0;
const bool read_only = OpIsReadOnly(op);
bool indirectly_tracked_unknown_access = false;
// First add edges from known resources.
if (is_unknown) {
for (auto& entry : per_resource_access_info_) {
if (entry.getFirst() == kUnknownResourceId) continue;
AddPredecessorsForAccess(entry.getFirst(), op, read_only);
indirectly_tracked_unknown_access |=
unknown_access_indirectly_tracked_by_resource(entry.getFirst(),
read_only);
}
} else {
for (int64_t resource : resources) {
AddPredecessorsForAccess(resource, op, read_only);
indirectly_tracked_unknown_access |=
unknown_access_indirectly_tracked_by_resource(resource, read_only);
// Update access info for known resources.
TrackAccess(resource, op, read_only);
}
}
// If not indirectly tracked, add edges from the unknown resource.
if (!indirectly_tracked_unknown_access) {
AddPredecessorsForAccess(kUnknownResourceId, op, read_only);
}
if (is_unknown) {
// Update access info for unknown resource.
TrackAccess(kUnknownResourceId, op, read_only);
}
});
// Populate control_successors_ based on control_predecessors_.
for (auto& entry : control_predecessors_) {
auto op = entry.getFirst();
for (auto predecessor : entry.getSecond()) {
control_successors_[predecessor].insert(op);
}
}
}
llvm::SmallVector<Operation*, 8> SideEffectAnalysis::DirectControlPredecessors(
Operation* op, llvm::function_ref<bool(Operation*)> filter) const {
llvm::SmallVector<Operation*, 8> result;
auto it = control_predecessors_.find(op);
if (it == control_predecessors_.end()) return result;
result.reserve(it->getSecond().size());
for (auto predecessor : it->getSecond()) {
if (!filter || filter(predecessor)) result.push_back(predecessor);
}
llvm::sort(result,
[](Operation* a, Operation* b) { return a->isBeforeInBlock(b); });
return result;
}
llvm::SmallVector<Operation*, 8> SideEffectAnalysis::DirectControlSuccessors(
Operation* op, llvm::function_ref<bool(Operation*)> filter) const {
llvm::SmallVector<Operation*, 8> result;
auto it = control_successors_.find(op);
if (it == control_successors_.end()) return result;
result.reserve(it->getSecond().size());
for (auto successor : it->getSecond()) {
if (!filter || filter(successor)) result.push_back(successor);
}
llvm::sort(result,
[](Operation* a, Operation* b) { return a->isBeforeInBlock(b); });
return result;
}
SideEffectAnalysis::SideEffectAnalysis(Operation* op) {
auto func_op = llvm::dyn_cast<FuncOp>(op);
if (!func_op) return;
ResourceAliasAnalysis alias_analysis(op);
AnalyzeFunction(func_op, alias_analysis);
}
} // namespace TF
} // namespace mlir

View File

@ -0,0 +1,125 @@
/* Copyright 2019 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.
==============================================================================*/
#ifndef TENSORFLOW_COMPILER_MLIR_TENSORFLOW_ANALYSIS_SIDE_EFFECT_ANALYSIS_H_
#define TENSORFLOW_COMPILER_MLIR_TENSORFLOW_ANALYSIS_SIDE_EFFECT_ANALYSIS_H_
#include <cstdint>
#include <memory>
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "mlir/IR/Function.h" // TF:local_config_mlir
#include "mlir/IR/Operation.h" // TF:local_config_mlir
#include "mlir/IR/Region.h" // TF:local_config_mlir
#include "mlir/Support/LogicalResult.h" // TF:local_config_mlir
namespace mlir {
namespace TF {
// An analysis that runs on a function and maps each resource-type value to a
// set of unique int64_t IDs representing the possible resources it could alias.
class ResourceAliasAnalysis {
public:
explicit ResourceAliasAnalysis(Operation* op);
~ResourceAliasAnalysis() = default;
ResourceAliasAnalysis(ResourceAliasAnalysis&&) = default;
// Returns if the analysis fails to resolve a resource-type value.
bool IsUnknownResource(const Value* resource) const;
// Returns the set unique IDs which `resource` could alias. Requires that
// IsUnknownResource(resource) == true.
const llvm::SmallSet<int64_t, 8>& GetResourceUniqueIds(
const Value* resource) const;
private:
ResourceAliasAnalysis() = default;
// Runs the analysis on `func_op` and populates resource_value_to_ids_.
void AnalyzeFunction(FuncOp func_op);
// Maps each resource-type value to a set of unique IDs that it could alias.
llvm::SmallDenseMap<const Value*, llvm::SmallSet<int64_t, 8>, 8>
resource_value_to_ids_;
};
// An analysis that runs on a function and infers the control predecessors and
// successors for each op, based on side-effects on known and unknown resources.
// Side-effecting ops on uknown resources are conservatively treated as
// interfering with all known resource op accesses. It distinguishes accesses
// based on whether they are read-only, and read-only ops do not interfer with
// each other.
class SideEffectAnalysis {
public:
explicit SideEffectAnalysis(Operation* op);
SideEffectAnalysis(SideEffectAnalysis&& other) = default;
~SideEffectAnalysis() = default;
// Returns a vector of ops that are direct control predecessors of `op`,
// sorted in program order. If `filter` is provided, only predecessors that
// pass the filter (returning true) will be included.
llvm::SmallVector<Operation*, 8> DirectControlPredecessors(
Operation* op,
llvm::function_ref<bool(Operation*)> filter = nullptr) const;
// Returns a vector of ops that are direct control successors of `op`, sorted
// in program order. If `filter` is provided, only successors that pass the
// filter (returning true) will be included.
llvm::SmallVector<Operation*, 8> DirectControlSuccessors(
Operation* op,
llvm::function_ref<bool(Operation*)> filter = nullptr) const;
private:
// Runs the analysis on `func_op` and populates control_predecessors_ and
// control_successors_.
void AnalyzeFunction(FuncOp func_op,
const ResourceAliasAnalysis& alias_analysis);
// Updates control_predecessors_ for `op` that is being visted, on the given
// `resource_id`.
void AddPredecessorsForAccess(int64_t resource_id, Operation* op,
bool read_only);
// Adds op's access to per_resource_access_info_.
void TrackAccess(int64_t resource_id, Operation* op, bool read_only);
// Maps from an op to its control predecessors.
llvm::SmallDenseMap<Operation*, llvm::SmallPtrSet<Operation*, 8>, 8>
control_predecessors_;
// Maps from an op to its control successors.
llvm::SmallDenseMap<Operation*, llvm::SmallPtrSet<Operation*, 8>, 8>
control_successors_;
// Internal per-resource data structure when we build the dependencies.
struct PerResourceAcessInfo {
// Last op that writes the resource before the current op being analyzed.
Operation* last_write = nullptr;
// Read ops since last_write before the current op being analyzed.
llvm::SmallVector<Operation*, 8> reads_since_last_write;
// Whether previous accesses of this resource already tracked last unknown
// read/write.
bool tracked_last_unknown_read = false;
bool tracked_last_unknown_write = false;
};
llvm::SmallDenseMap<int64_t, PerResourceAcessInfo, 8>
per_resource_access_info_;
};
} // namespace TF
} // namespace mlir
#endif // TENSORFLOW_COMPILER_MLIR_TENSORFLOW_ANALYSIS_SIDE_EFFECT_ANALYSIS_H_

View File

@ -0,0 +1,237 @@
// RUN: tf-opt -split-input-file -tf-test-side-effect-analysis -verify-diagnostics %s | FileCheck %s --dump-input=fail
// Tests that the pass tracks control dependencies for reads/writes on the same
// resource.
// CHECK-LABEL: func @non_aliasing_reads_writes
func @non_aliasing_reads_writes(
// expected-remark@above {{ID: 13}}
// expected-remark@above {{Predecessors: {12}}}
%arg0: tensor<*x!tf.resource<tensor<32xf32>>>,
%arg1: tensor<*x!tf.resource<tensor<32xf32>>>,
%arg2: tensor<32xf32>) -> (tensor<32xf32>) {
%graph = tf_executor.graph {
// expected-remark@above {{ID: 11}}
// expected-remark@above {{Predecessors: {10}}}
// expected-remark@above {{Successors: {12}}}
// CHECK: tf_executor.island
%island:2 = tf_executor.island {
// expected-remark@above {{ID: 9}}
// expected-remark@above {{Predecessors: {8}}}
// expected-remark@above {{Successors: {10}}}
%read0 = "tf.ReadVariableOp"(%arg0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 0}}
// expected-remark@above {{Successors: {1}}}
"tf.AssignVariableOp"(%arg0, %arg2) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 1}}
// expected-remark@above {{Predecessors: {0}}}
// expected-remark@above {{Successors: {6}}}
%read1 = "tf.ReadVariableOp"(%arg1) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 2}}
// expected-remark@above {{Successors: {5}}}
%var_handle = "tf.VarHandleOp"() {container = "c", shared_name = "v0"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 3}}
%read2 = "tf.ReadVariableOp"(%var_handle) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 4}}
// expected-remark@above {{Successors: {8}}}
"tf.AssignVariableOp"(%arg1, %read0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 5}}
// expected-remark@above {{Predecessors: {2}}}
// expected-remark@above {{Successors: {8}}}
"tf.AssignVariableOp"(%arg0, %read2) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 6}}
// expected-remark@above {{Predecessors: {1}}}
// expected-remark@above {{Successors: {7}}}
%read3 = "tf.ReadVariableOp"(%arg0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 7}}
// expected-remark@above {{Predecessors: {6}}}
// expected-remark@above {{Successors: {8}}}
tf_executor.yield %read3 : tensor<32xf32>
// expected-remark@above {{ID: 8}}
// expected-remark@above {{Predecessors: {4,5,7}}}
// expected-remark@above {{Successors: {9}}}
}
tf_executor.fetch %island#0 : tensor<32xf32>
// expected-remark@above {{ID: 10}}
// expected-remark@above {{Predecessors: {9}}}
// expected-remark@above {{Successors: {11}}}
}
return %graph : tensor<32xf32>
// expected-remark@above {{ID: 12}}
// expected-remark@above {{Predecessors: {11}}}
// expected-remark@above {{Successors: {13}}}
}
// -----
// Tests that the pass tracks control dependencies for reads/writes on the two
// resource handles that refer to the same variable.
// CHECK-LABEL: func @aliasing_reads_writes
func @aliasing_reads_writes(%arg0: tensor<32xf32>) -> () {
// expected-remark@above {{ID: 14}}
// expected-remark@above {{Predecessors: {13}}}
tf_executor.graph {
// expected-remark@above {{ID: 12}}
// expected-remark@above {{Predecessors: {11}}}
// expected-remark@above {{Successors: {13}}}
// CHECK: tf_executor.island
%island = tf_executor.island {
// expected-remark@above {{ID: 10}}
// expected-remark@above {{Predecessors: {9}}}
// expected-remark@above {{Successors: {11}}}
%vh0 = "tf.VarHandleOp"() {container = "c", shared_name = "v0"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 0}}
%vh1 = "tf.VarHandleOp"() {container = "c", shared_name = "v0"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 1}}
%vh1_id:2 = "tf.IdentityN"(%vh1, %arg0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>)
// expected-remark@above {{ID: 2}}
%read0 = "tf.ReadVariableOp"(%vh0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 3}}
// expected-remark@above {{Successors: {4}}}
"tf.AssignVariableOp"(%vh1_id#0, %arg0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 4}}
// expected-remark@above {{Predecessors: {3}}}
// expected-remark@above {{Successors: {5,6}}}
%read1 = "tf.ReadVariableOp"(%vh0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 5}}
// expected-remark@above {{Predecessors: {4}}}
// expected-remark@above {{Successors: {7}}}
%read2 = "tf.ReadVariableOp"(%vh1) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 6}}
// expected-remark@above {{Predecessors: {4}}}
// expected-remark@above {{Successors: {7}}}
"tf.AssignVariableOp"(%vh0, %read2) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 7}}
// expected-remark@above {{Predecessors: {5,6}}}
// expected-remark@above {{Successors: {8}}}
"tf.AssignVariableOp"(%vh1_id#0, %read1) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 8}}
// expected-remark@above {{Predecessors: {7}}}
// expected-remark@above {{Successors: {9}}}
tf_executor.yield
// expected-remark@above {{ID: 9}}
// expected-remark@above {{Predecessors: {8}}}
// expected-remark@above {{Successors: {10}}}
}
tf_executor.fetch %island : !tf_executor.control
// expected-remark@above {{ID: 11}}
// expected-remark@above {{Predecessors: {10}}}
// expected-remark@above {{Successors: {12}}}
}
return
// expected-remark@above {{ID: 13}}
// expected-remark@above {{Predecessors: {12}}}
// expected-remark@above {{Successors: {14}}}
}
// -----
// Tests that the pass tracks control dependencies for side-effecting on unknown
// resources.
// CHECK-LABEL: func @unknown_side_effecting_op
func @unknown_side_effecting_op(%arg0: tensor<32xf32>) -> () {
// expected-remark@above {{ID: 13}}
// expected-remark@above {{Predecessors: {12}}}
tf_executor.graph {
// expected-remark@above {{ID: 11}}
// expected-remark@above {{Predecessors: {10}}}
// expected-remark@above {{Successors: {12}}}
// CHECK: tf_executor.island
%island = tf_executor.island {
// expected-remark@above {{ID: 9}}
// expected-remark@above {{Predecessors: {8}}}
// expected-remark@above {{Successors: {10}}}
%vh0 = "tf.VarHandleOp"() {container = "c", shared_name = "v0"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 0}}
%vh1 = "tf.VarHandleOp"() {container = "c", shared_name = "v1"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 1}}
%read0 = "tf.ReadVariableOp"(%vh0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 2}}
// expected-remark@above {{Successors: {4}}}
"tf.AssignVariableOp"(%vh1, %arg0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 3}}
// expected-remark@above {{Successors: {4}}}
"tf._UnknownSideEffectingOp_"() : () -> ()
// expected-remark@above {{ID: 4}}
// expected-remark@above {{Predecessors: {2,3}}}
// expected-remark@above {{Successors: {5,6}}}
%read1 = "tf.ReadVariableOp"(%vh1) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 5}}
// expected-remark@above {{Predecessors: {4}}}
// expected-remark@above {{Successors: {7}}}
"tf.AssignVariableOp"(%vh0, %read1) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 6}}
// expected-remark@above {{Predecessors: {4}}}
// expected-remark@above {{Successors: {8}}}
"tf.AssignVariableOp"(%vh1, %read0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 7}}
// expected-remark@above {{Predecessors: {5}}}
// expected-remark@above {{Successors: {8}}}
tf_executor.yield
// expected-remark@above {{ID: 8}}
// expected-remark@above {{Predecessors: {6,7}}}
// expected-remark@above {{Successors: {9}}}
}
tf_executor.fetch %island : !tf_executor.control
// expected-remark@above {{ID: 10}}
// expected-remark@above {{Predecessors: {9}}}
// expected-remark@above {{Successors: {11}}}
}
return
// expected-remark@above {{ID: 12}}
// expected-remark@above {{Predecessors: {11}}}
// expected-remark@above {{Successors: {13}}}
}
// -----
// Tests that the pass tracks control dependencies for read-only ops on unknown
// resources.
// CHECK-LABEL: func @read_only_unknown_resource
func @read_only_unknown_resource(%arg0: tensor<32xf32>) -> () {
// expected-remark@above {{ID: 10}}
// expected-remark@above {{Predecessors: {9}}}
tf_executor.graph {
// expected-remark@above {{ID: 8}}
// expected-remark@above {{Predecessors: {7}}}
// expected-remark@above {{Successors: {9}}}
// CHECK: tf_executor.island
%island = tf_executor.island {
// expected-remark@above {{ID: 6}}
// expected-remark@above {{Predecessors: {5}}}
// expected-remark@above {{Successors: {7}}}
%vh0 = "tf._UnknownSideEffectingOp_"() : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 0}}
// expected-remark@above {{Successors: {2,3}}}
%vh1 = "tf.VarHandleOp"() {container = "c", shared_name = "v1"} : () -> tensor<*x!tf.resource<tensor<32xf32>>>
// expected-remark@above {{ID: 1}}
%read0 = "tf.ReadVariableOp"(%vh0) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 2}}
// expected-remark@above {{Predecessors: {0}}}
// expected-remark@above {{Successors: {4}}}
%read1 = "tf.ReadVariableOp"(%vh1) : (tensor<*x!tf.resource<tensor<32xf32>>>) -> tensor<32xf32>
// expected-remark@above {{ID: 3}}
// expected-remark@above {{Predecessors: {0}}}
// expected-remark@above {{Successors: {4}}}
"tf.AssignVariableOp"(%vh1, %read0) : (tensor<*x!tf.resource<tensor<32xf32>>>, tensor<32xf32>) -> ()
// expected-remark@above {{ID: 4}}
// expected-remark@above {{Predecessors: {2,3}}}
// expected-remark@above {{Successors: {5}}}
tf_executor.yield
// expected-remark@above {{ID: 5}}
// expected-remark@above {{Predecessors: {4}}}
// expected-remark@above {{Successors: {6}}}
}
tf_executor.fetch %island : !tf_executor.control
// expected-remark@above {{ID: 7}}
// expected-remark@above {{Predecessors: {6}}}
// expected-remark@above {{Successors: {8}}}
}
return
// expected-remark@above {{ID: 9}}
// expected-remark@above {{Predecessors: {8}}}
// expected-remark@above {{Successors: {10}}}
}

View File

@ -0,0 +1,77 @@
/* Copyright 2019 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 <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Debug.h"
#include "mlir/Pass/Pass.h" // TF:local_config_mlir
#include "mlir/Pass/PassManager.h" // TF:local_config_mlir
#include "mlir/Support/LLVM.h" // TF:local_config_mlir
#include "mlir/Transforms/Passes.h" // TF:local_config_mlir
#include "mlir/Transforms/RegionUtils.h" // TF:local_config_mlir
#include "tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h"
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h"
#include "tensorflow/compiler/mlir/tensorflow/utils/error_util.h"
namespace mlir {
namespace tf_executor {
namespace {
// A pass that adds "Predecessors" and "Successors" remarks for each op based on
// SideEffectAnalysis result. For testing purpose only.
struct TestSideEffectAnalysis
: public mlir::FunctionPass<TestSideEffectAnalysis> {
void runOnFunction() override {
int64_t next_id = 0;
llvm::SmallDenseMap<Operation*, int64_t, 8> ids;
getFunction().walk([&](Operation* op) {
ids[op] = next_id++;
op->emitRemark("ID: ") << ids[op];
});
auto join_ids = [&](const llvm::ArrayRef<Operation*> ops) {
llvm::SmallVector<std::string, 8> id_vec;
id_vec.reserve(ops.size());
for (auto op : ops) id_vec.push_back(std::to_string(ids[op]));
return llvm::join(id_vec, ",");
};
auto& analysis = getAnalysis<TF::SideEffectAnalysis>();
getFunction().walk([&](Operation* op) {
if (!analysis.DirectControlPredecessors(op).empty()) {
op->emitRemark("Predecessors: ")
<< "{" << join_ids(analysis.DirectControlPredecessors(op)) << "}";
}
if (!analysis.DirectControlSuccessors(op).empty()) {
op->emitRemark("Successors: ")
<< "{" << join_ids(analysis.DirectControlSuccessors(op)) << "}";
}
});
}
};
static mlir::PassRegistration<TestSideEffectAnalysis> pass(
"tf-test-side-effect-analysis",
"Add remarks based on side-effect analysis result, for testing purpose.");
} // anonymous namespace
} // namespace tf_executor
} // namespace mlir