Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

664 lines
27 KiB
C++

/* Copyright 2022 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 <algorithm>
#include <cassert>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/types/optional.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
#include "mlir/Dialect/Func/IR/FuncOps.h" // from @llvm-project
#include "mlir/IR/Attributes.h" // from @llvm-project
#include "mlir/IR/Builders.h" // from @llvm-project
#include "mlir/IR/BuiltinOps.h" // from @llvm-project
#include "mlir/IR/BuiltinTypes.h" // from @llvm-project
#include "mlir/IR/Diagnostics.h" // from @llvm-project
#include "mlir/IR/DialectRegistry.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/IR/Visitors.h" // from @llvm-project
#include "mlir/Pass/Pass.h" // from @llvm-project
#include "mlir/Pass/PassManager.h" // from @llvm-project
#include "mlir/Support/LLVM.h" // from @llvm-project
#include "mlir/Support/LogicalResult.h" // from @llvm-project
#include "mlir/Transforms/RegionUtils.h" // from @llvm-project
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_attributes.h"
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_device.h"
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h"
#include "tensorflow/compiler/mlir/tensorflow/ir/tf_remaining_ops.h"
#include "tensorflow/dtensor/cc/constants.h"
#include "tensorflow/dtensor/cc/tensor_layout.h"
#include "tensorflow/dtensor/mlir/dtensor_dialect/ir/dialect.h"
#include "tensorflow/dtensor/mlir/dtensor_dialect/ir/dtensor_attributes.h"
#include "tensorflow/dtensor/mlir/ir/tf_dtensor.h"
#include "tensorflow/dtensor/mlir/layout_parsing.h"
#include "tensorflow/dtensor/mlir/spmd_expander_common.h"
namespace tensorflow {
namespace dtensor {
namespace {
#define GEN_PASS_DEF_DTENSORMERGECLUSTERS
#define GEN_PASS_DEF_DTENSORDECOMPOSECONTROLFLOW
#include "tensorflow/dtensor/mlir/dtensor_passes.h.inc"
constexpr char kMissingMeshErrorMsg[] =
"Failed to extract mesh for DTensorMergeCluster pass. "
"All clusters must have specified mesh.";
constexpr char kSendRecvKeyPrefix[] = "SendRecvKeyForControlflow_";
// Extracts mesh from `cluster`.
mlir::LogicalResult ExtractMeshFromCluster(mlir::tf_device::ClusterOp cluster,
Mesh* mesh_output) {
auto mesh_or_status = ExtractDeviceMeshFromOp(cluster);
if (!mesh_or_status.ok()) return cluster.emitOpError(kMissingMeshErrorMsg);
const absl::optional<Mesh>& mesh_or_null = *mesh_or_status;
if (!mesh_or_null.has_value())
return cluster.emitOpError(kMissingMeshErrorMsg);
*mesh_output = mesh_or_null.value();
return mlir::success();
}
// Returns all tf_device.ClusterOps nested inside `op`.
llvm::SmallVector<mlir::tf_device::ClusterOp, 4> FindAllDeviceClusters(
mlir::Operation* op) {
llvm::SmallVector<mlir::tf_device::ClusterOp, 4> nested_clusters;
op->walk([&](mlir::tf_device::ClusterOp nested_cluster) {
nested_clusters.emplace_back(nested_cluster);
});
return nested_clusters;
}
mlir::LogicalResult MergeAttributes(
mlir::Operation* op, mlir::DenseIntElementsAttr indices_attr,
mlir::ArrayAttr layout_attr, mlir::DenseIntElementsAttr indices_attr2,
mlir::ArrayAttr layout_attr2, llvm::SmallVector<int, 4>* merged_indices,
llvm::SmallVector<mlir::Attribute, 4>* merged_layout) {
llvm::SmallDenseMap<llvm::APInt, mlir::Attribute> attr_map;
attr_map.reserve(indices_attr.size() + indices_attr2.size());
for (const auto& data : llvm::zip(indices_attr, layout_attr))
attr_map.try_emplace(std::get<0>(data), std::get<1>(data));
for (const auto& data : llvm::zip(indices_attr2, layout_attr2)) {
const auto& index = std::get<0>(data);
const auto& layout = std::get<1>(data);
auto result = attr_map.try_emplace(index, layout);
if (!result.second && layout != result.first->getSecond()) {
return op->emitOpError(
"Found conflicting metadata attributes while merging clusters");
}
}
merged_indices->reserve(attr_map.size());
merged_layout->reserve(attr_map.size());
for (const auto& it : attr_map) {
merged_indices->emplace_back(it.first.getSExtValue());
merged_layout->emplace_back(it.second);
}
return mlir::success();
}
// Merges metadata attribute from `src_cluster` to `target_cluster`. If metadata
// attribute exists for both clusters, merge the attributes and verify that
// there are no conflicing attributes.
mlir::LogicalResult MergeClusterMetadata(
mlir::tf_device::ClusterOp src_cluster,
mlir::tf_device::ClusterOp target_cluster) {
if (mlir::failed(ValidateMetadataAttributes(src_cluster)) ||
mlir::failed(ValidateMetadataAttributes(target_cluster)))
return mlir::failure();
mlir::OpBuilder builder(target_cluster);
// Extract resource metadata from src/target clusters.
auto src_resource_handle_indices_metadata =
src_cluster->getAttrOfType<mlir::DenseIntElementsAttr>(
kNewResourceLayoutIndices);
auto src_inferred_resource_handle_layouts_metadata =
src_cluster->getAttrOfType<mlir::ArrayAttr>(kNewResourceArgLayouts);
auto target_resource_handle_indices_metadata =
target_cluster->getAttrOfType<mlir::DenseIntElementsAttr>(
kNewResourceLayoutIndices);
auto target_inferred_resource_handle_layouts_metadata =
target_cluster->getAttrOfType<mlir::ArrayAttr>(kNewResourceArgLayouts);
const bool should_merge_resource_metadata =
(src_inferred_resource_handle_layouts_metadata &&
src_resource_handle_indices_metadata &&
target_inferred_resource_handle_layouts_metadata &&
target_resource_handle_indices_metadata);
// If only source cluster has metadata, then simply copy the metadata to
// target cluster.
if (src_inferred_resource_handle_layouts_metadata &&
!target_inferred_resource_handle_layouts_metadata) {
target_cluster->setAttr(kNewResourceLayoutIndices,
src_resource_handle_indices_metadata);
target_cluster->setAttr(kNewResourceArgLayouts,
src_inferred_resource_handle_layouts_metadata);
} else if (should_merge_resource_metadata) {
// If both src cluster and target cluster has metadata, merge the metadata
// and check if there are no conflicts.
llvm::SmallVector<int, 4> merged_resource_indices;
llvm::SmallVector<mlir::Attribute, 4> merged_resource_layouts;
if (mlir::failed(MergeAttributes(
src_cluster, src_resource_handle_indices_metadata,
src_inferred_resource_handle_layouts_metadata,
target_resource_handle_indices_metadata,
target_inferred_resource_handle_layouts_metadata,
&merged_resource_indices, &merged_resource_layouts)))
return mlir::failure();
target_cluster->setAttr(
kNewResourceArgLayouts,
builder.getArrayAttr(
llvm::ArrayRef<mlir::Attribute>(merged_resource_layouts)));
target_cluster->setAttr(
kNewResourceLayoutIndices,
builder.getI32VectorAttr(llvm::ArrayRef<int>(merged_resource_indices)));
}
// Extract shape metadata from src/target clusters.
auto src_shape_layouts =
src_cluster->getAttrOfType<mlir::ArrayAttr>(kShapeOpInputLayout);
auto src_shape_op_indices =
src_cluster->getAttrOfType<mlir::DenseIntElementsAttr>(
kShapeOpInputLayoutIndices);
auto target_shape_layouts =
target_cluster->getAttrOfType<mlir::ArrayAttr>(kShapeOpInputLayout);
auto target_shape_op_indices =
target_cluster->getAttrOfType<mlir::DenseIntElementsAttr>(
kShapeOpInputLayoutIndices);
const bool should_merge_shape_metadata =
(src_shape_layouts && src_shape_op_indices && target_shape_layouts &&
target_shape_op_indices);
// If only src cluster has shape metadata, copy shape metadata to target
// cluster.
if (src_shape_layouts && !target_shape_layouts) {
target_cluster->setAttr(kShapeOpInputLayoutIndices, src_shape_op_indices);
target_cluster->setAttr(kShapeOpInputLayout, src_shape_layouts);
} else if (should_merge_shape_metadata) {
// If both src/target clusters have shape metadata, merge the shape metadata
// and set the merged metadata to target cluster.
llvm::SmallVector<int, 4> merged_shape_indices;
llvm::SmallVector<mlir::Attribute, 4> merged_shape_layouts;
if (mlir::failed(MergeAttributes(
src_cluster, src_shape_op_indices, src_shape_layouts,
target_shape_op_indices, target_shape_layouts,
&merged_shape_indices, &merged_shape_layouts)))
return mlir::failure();
target_cluster->setAttr(
kShapeOpInputLayout,
builder.getArrayAttr(
llvm::ArrayRef<mlir::Attribute>(merged_shape_layouts)));
target_cluster->setAttr(
kShapeOpInputLayoutIndices,
builder.getI32VectorAttr(llvm::ArrayRef<int>(merged_shape_indices)));
}
return mlir::success();
}
// Removes tf_device.Cluster ops if tf_device.Cluster is nested inside another
// cluster and it has same mesh specification as parent cluster.
mlir::LogicalResult InlineNestedDeviceClusters(mlir::Operation* op) {
auto clusters = FindAllDeviceClusters(op);
for (mlir::tf_device::ClusterOp cluster : clusters) {
auto parent_cluster =
cluster->getParentOfType<mlir::tf_device::ClusterOp>();
if (!parent_cluster) continue;
Mesh cluster_mesh;
if (mlir::failed(ExtractMeshFromCluster(cluster, &cluster_mesh)))
return mlir::failure();
Mesh parent_cluster_mesh;
if (mlir::failed(
ExtractMeshFromCluster(parent_cluster, &parent_cluster_mesh)))
return mlir::failure();
if (parent_cluster_mesh != cluster_mesh) continue;
// Found a tf_device.cluster that has same mesh specification as parent
// enclosing cluster. Remove the child cluster and move all ops to parent
// cluster instead.
for (auto it : llvm::zip(cluster.GetBody().getTerminator()->getOperands(),
cluster.getResults())) {
mlir::Value new_value = std::get<0>(it);
mlir::Value value_to_replace = std::get<1>(it);
value_to_replace.replaceAllUsesWith(new_value);
}
for (mlir::Operation& op :
llvm::make_early_inc_range(cluster.GetBody().without_terminator())) {
op.moveBefore(cluster);
}
if (mlir::failed(MergeClusterMetadata(cluster, parent_cluster)))
return mlir::failure();
cluster.erase();
}
return mlir::success();
}
// Clones an IfRegionOp 'if_region' and attributes and creates then/else regions
// with yield op and an empty block.
void CloneEmptyIfWithPredicate(mlir::TF::IfRegionOp if_region, const Mesh& mesh,
mlir::OpBuilder& builder, int* num_send_recvs,
mlir::MLIRContext* context,
mlir::TF::IfRegionOp* cloned_if_region_op) {
// Create DTensorSend just before tf.If op before creating new cluster. The
// DTensorSend op sends the predicate to `mesh` cluster with replicated
// layout.
mlir::TensorType predicate_tensor_type =
mlir::cast<mlir::TensorType>(if_region.getCond().getType());
const std::string send_recv_key =
absl::StrCat(kSendRecvKeyPrefix, *num_send_recvs);
*num_send_recvs += 1;
mlir::TF::DTensorSend::create(builder, if_region.getLoc(),
if_region.getCond(),
builder.getStringAttr(send_recv_key),
mlir::dtensor::MeshAttr::get(context, mesh));
// Create new cluster op that contains cloned if operation.
auto new_cluster = mlir::tf_device::ClusterOp::create(
builder, if_region.getLoc(), llvm::SmallVector<mlir::Type, 4>{});
new_cluster.getBody().push_back(new mlir::Block);
builder.setInsertionPointToEnd(&new_cluster.GetBody());
auto return_op = mlir::tf_device::ReturnOp::create(
builder, if_region.getLoc(), llvm::SmallVector<mlir::Value, 4>{});
// Add DTensorRecv op inside new cluster that receives the cluster.
builder.setInsertionPoint(return_op);
auto recv_op = mlir::TF::DTensorRecv::create(
builder, if_region.getLoc(), predicate_tensor_type,
builder.getStringAttr(send_recv_key),
mlir::TF::ShapeAttr::get(context, predicate_tensor_type),
mlir::dtensor::MeshAttr::get(context, mesh));
// Clone tf.IfRegion op inside newly created cluster and make sure
// that the predicate tensor is from DTensorRecv op created above.
auto host_side_if = mlir::TF::IfRegionOp::create(
builder, if_region.getLoc(), llvm::SmallVector<mlir::Type, 4>{},
recv_op.getOutput(), if_region.getIsStateless(),
GetUniqueControlflowFnName("cloned_if_then", builder),
GetUniqueControlflowFnName("cloned_if_else", builder));
*cloned_if_region_op = host_side_if;
// Create empty then branch region.
auto& then_branch = host_side_if.getThenBranch();
then_branch.push_back(new mlir::Block);
builder.setInsertionPointToEnd(&then_branch.front());
mlir::TF::YieldOp::create(builder, if_region.getLoc(),
/*operands=*/llvm::ArrayRef<mlir::Value>{});
// Create empty else branch region.
auto& else_branch = host_side_if.getElseBranch();
else_branch.push_back(new mlir::Block);
builder.setInsertionPointToEnd(&else_branch.front());
mlir::TF::YieldOp::create(builder, if_region.getLoc(),
/*operands=*/llvm::ArrayRef<mlir::Value>{});
new_cluster->setAttr(kMeshAttr, builder.getStringAttr(mesh.ToString()));
}
// Verifies that send/recv ops are used for input output of cluster. That is,
// cluster should not have any input/output edges.
mlir::LogicalResult VerifyClusterInputOutput(
mlir::tf_device::ClusterOp cluster) {
if (cluster.getNumResults() > 0)
return cluster->emitOpError(
"found nested tf_device.Cluster op with outputs. Nested cluster must "
"use send/recv instead.");
mlir::LogicalResult result = mlir::success();
mlir::visitUsedValuesDefinedAbove(
cluster.getBody(), cluster.getBody(), [&](mlir::OpOperand* input) {
if (!mlir::isa<mlir::BlockArgument>(input->get())) {
result = cluster.emitOpError(
"found nested tf_device.Cluster op with inputs. Nested cluster "
"must use send/recv instead.");
return;
}
});
return result;
}
// Returns whether `cluster` is inside then branch of `if_op`.
bool IsInsideIfThenBranch(mlir::TF::IfRegionOp if_op,
mlir::tf_device::ClusterOp cluster) {
assert(if_op->isProperAncestor(cluster));
return if_op.getThenBranch().isAncestor(cluster->getParentRegion());
}
// Decomposes multi-mesh computation nested inside tf_if operations. See
// comments for `DecomposeControlflow()` function for details.
mlir::LogicalResult DecomposeIf(mlir::TF::IfRegionOp if_op,
mlir::MLIRContext* context,
int* num_control_flow_send_recvs) {
if (mlir::failed(InlineNestedDeviceClusters(if_op))) {
return mlir::failure();
}
auto nested_clusters = FindAllDeviceClusters(if_op);
if (nested_clusters.empty()) return mlir::success();
for (mlir::tf_device::ClusterOp nested_cluster : nested_clusters) {
if (mlir::failed(VerifyClusterInputOutput(nested_cluster)))
return mlir::failure();
Mesh nested_mesh;
if (mlir::failed(ExtractMeshFromCluster(nested_cluster, &nested_mesh)))
return mlir::failure();
mlir::OpBuilder builder(if_op);
mlir::TF::IfRegionOp cloned_if;
CloneEmptyIfWithPredicate(if_op, nested_mesh, builder,
num_control_flow_send_recvs, context, &cloned_if);
// Find nested clusters in then/else branch of original `if_op` and
// move all inner ops inside nested cluster to `tf_cloned` in
// corresponding branch.
if (IsInsideIfThenBranch(if_op, nested_cluster)) {
mlir::Operation* then_branch_terminator =
cloned_if.getThenBranch().begin()->getTerminator();
auto& nested_cluster_operations =
nested_cluster.GetBody().getOperations();
cloned_if.getThenBranch().begin()->getOperations().splice(
then_branch_terminator->getIterator(), nested_cluster_operations,
nested_cluster_operations.begin(),
std::prev(nested_cluster_operations.end()));
} else {
mlir::Operation* else_branch_terminator =
cloned_if.getElseBranch().begin()->getTerminator();
auto& nested_cluster_operations =
nested_cluster.GetBody().getOperations();
cloned_if.getElseBranch().begin()->getOperations().splice(
else_branch_terminator->getIterator(), nested_cluster_operations,
nested_cluster_operations.begin(),
std::prev(nested_cluster_operations.end()));
}
nested_cluster.erase();
}
return mlir::success();
}
// Decomposes controlflows with nested mesh computations. When multi-mesh
// computation exists inside control flow operations like tf.If, then
// the control flow operations should be replicated to ensure correct execution
// semantics.
// For example:
//
// "tf_device.cluster"() ( {
// %1 = "tf.G"() : () -> (tensor<i1>)
// "tf.IfRegion"(%1) ({
// "tf_device.cluster"() ( {
// "tf.D"() {} : () -> ()
// tf_device.return
// }) {_mesh = "TPU|x=1|0|0|TPU:0"} : () -> ()
//
// "tf.Yield"() : () -> ()
// }, {
// }) {is_stateless = false} : (tensor<i1>) -> ()
// tf_device.return
// }) {_mesh = "CPU|x=1|0|0|CPU:0"} : () -> ()
//
// Above computation includes TPU device computation that exists inside
// tf.If op in CPU mesh. In this case, tf.If op should be replicated to TPU
// device computation so that `tf.D` op is executed in sync with CPU side
// computation. After transformation in this function, above IR is changed to:
//
// "tf_device.cluster"() ( {
// %1 = "tf.DTensorRecv"() : () -> tensor<i1>
// "tf.IfRegion"(%1) ( {
// "tf.D"() : () -> ()
// "tf.Yield"() : () -> ()
// }, {
// "tf.Yield"() : () -> ()
// }) {is_stateless = false} : (tensor<i1>) -> ()
// tf_device.return
// }) {_mesh = "TPU|x=1|0|0|TPU:0"} : () -> ()
//
// "tf_device.cluster"() ( {
// %1 = "tf.G"() : () -> tensor<i1>
// "tf.DTensorSend"(%1) : (tensor<i1>) -> ()
// "tf.IfRegion"(%1) ( {
// "tf.Yield"() : () -> ()
// }, {
// "tf.Yield"() : () -> ()
// }) {is_stateless = false} : (tensor<i1>) -> ()
// tf_device.return
// }) {_mesh = "CPU|x=1|0|0|CPU:0"} : () -> ()
//
// Note that:
// 1) Control flow is replicated.
// 2) DTensorSend/Recv ops are added to transfer predicate tensors for
// control flow operations
mlir::LogicalResult DecomposeControlflow(mlir::MLIRContext* context,
int* num_control_flow_send_recvs,
mlir::ModuleOp module) {
llvm::SmallVector<mlir::tf_device::ClusterOp, 4> clusters;
// Identify all clusters in topological order.
module.walk([&](mlir::tf_device::ClusterOp cluster) {
clusters.emplace_back(cluster);
});
for (mlir::tf_device::ClusterOp cluster : clusters) {
mlir::WalkResult walk_result = cluster->walk([&](mlir::Operation* op) {
if (auto if_op = mlir::dyn_cast<mlir::TF::IfRegionOp>(op)) {
// Remove the device attr to follow the 'default' placement set during
// replicated execution. If there is a device attr, TensorFlow will
// run the body on that device instead.
op->removeAttr("device");
if (mlir::failed(
DecomposeIf(if_op, context, num_control_flow_send_recvs)))
return mlir::WalkResult::interrupt();
}
return mlir::WalkResult::advance();
});
if (walk_result.wasInterrupted()) return mlir::failure();
}
return mlir::success();
}
// Merges multiple tf_device.clusters with same mesh specification to a single
// mesh cluster.
mlir::LogicalResult MergeClusters(mlir::ModuleOp module) {
mlir::func::FuncOp main_func =
module.lookupSymbol<mlir::func::FuncOp>("main");
if (!main_func) return mlir::success();
// Create global cluster for each mesh in entire computation.
auto clusters = FindAllDeviceClusters(main_func);
mlir::Block& func_block = *main_func.getBody().begin();
mlir::OpBuilder builder(&func_block.front());
std::map<Mesh, llvm::SmallVector<mlir::tf_device::ClusterOp, 4>> cluster_map;
std::vector<Mesh> meshes;
for (mlir::tf_device::ClusterOp cluster : clusters) {
Mesh mesh;
if (mlir::failed(ExtractMeshFromCluster(cluster, &mesh)))
return mlir::failure();
if (cluster_map.find(mesh) != cluster_map.end()) {
cluster_map[mesh].emplace_back(cluster);
} else {
cluster_map[mesh] =
llvm::SmallVector<mlir::tf_device::ClusterOp, 4>{cluster};
meshes.push_back(std::move(mesh));
}
}
// Reevaluate if this sort is necessary after b/186804270 is closed.
std::sort(meshes.begin(), meshes.end(), [](const Mesh& a, const Mesh& b) {
if (a.device_type() != b.device_type()) {
return a.device_type() < b.device_type();
}
return a < b;
});
for (const Mesh& mesh : meshes) {
const auto& mesh_cluster_list = cluster_map[mesh];
llvm::SmallVector<mlir::Value, 4> merged_cluster_outputs;
llvm::SmallVector<mlir::Value, 4> merged_return_values;
llvm::SmallVector<mlir::Type, 4> merged_return_types;
for (mlir::tf_device::ClusterOp cluster : mesh_cluster_list) {
merged_cluster_outputs.insert(merged_cluster_outputs.end(),
cluster.getResults().begin(),
cluster.getResults().end());
auto return_values = cluster.GetBody().getTerminator()->getOperands();
merged_return_values.insert(merged_return_values.end(),
return_values.begin(), return_values.end());
auto return_type = cluster->getResultTypes();
merged_return_types.insert(merged_return_types.end(), return_type.begin(),
return_type.end());
}
// Create a single cluster op contains merged computations for `mesh`.
builder.setInsertionPoint(&func_block.front());
auto new_cluster = mlir::tf_device::ClusterOp::create(
builder, module.getLoc(), merged_return_types);
new_cluster.getBody().push_back(new mlir::Block);
new_cluster->setAttr(kMeshAttr, builder.getStringAttr(mesh.ToString()));
// Move all ops inside clusters in cluster mesh to `new_cluster`.
for (mlir::tf_device::ClusterOp cluster : mesh_cluster_list) {
mlir::Block& cluster_body = cluster.GetBody();
for (mlir::Operation& op_to_move :
llvm::make_early_inc_range(cluster_body.without_terminator())) {
for (mlir::OpOperand& use : op_to_move.getUses()) {
auto return_op =
llvm::dyn_cast<mlir::tf_device::ReturnOp>(use.getOwner());
if (!return_op) continue;
mlir::Value output = cluster.getResult(use.getOperandNumber());
output.replaceUsesWithIf(use.get(), [](mlir::OpOperand& operand) {
return operand.getOwner()
->getParentOfType<mlir::tf_device::ClusterOp>() !=
nullptr;
});
}
op_to_move.moveBefore(new_cluster.SingleBlock::getBody(),
new_cluster.SingleBlock::getBody()->end());
}
}
builder.setInsertionPointToEnd(&new_cluster.GetBody());
mlir::tf_device::ReturnOp::create(builder, new_cluster.getLoc(),
merged_return_values);
// Replace return value usages.
for (auto it :
llvm::zip(merged_cluster_outputs, new_cluster.getResults())) {
mlir::Value value_to_replace = std::get<0>(it);
mlir::Value new_result_value = std::get<1>(it);
value_to_replace.replaceAllUsesWith(new_result_value);
}
// Erase clusters in cluster_map now that all ops are moved.
for (mlir::tf_device::ClusterOp cluster : mesh_cluster_list) {
if (mlir::failed(MergeClusterMetadata(cluster, new_cluster)))
return mlir::failure();
cluster.erase();
}
}
return mlir::success();
}
// Pass that merges multiple tf_device.Cluster ops for multi-mesh computation
// into a single cluster. After this pass, exactly one tf_device.Cluster op
// exists for each device mesh.
struct DTensorMergeClusters
: public impl::DTensorMergeClustersBase<DTensorMergeClusters> {
void getDependentDialects(mlir::DialectRegistry& registry) const override {
registry.insert<mlir::dtensor::DTensorDialect>();
}
void runOnOperation() override {
mlir::MLIRContext& context = getContext();
mlir::OpBuilder op_builder(&context);
auto module = getOperation();
if (mlir::failed(InlineNestedDeviceClusters(module)))
return signalPassFailure();
if (mlir::failed(MergeClusters(module))) return signalPassFailure();
llvm::SmallVector<mlir::tf_device::ClusterOp, 4> clusters;
module.walk([&](mlir::tf_device::ClusterOp cluster) {
clusters.emplace_back(cluster);
});
for (mlir::tf_device::ClusterOp cluster : clusters) {
RemoveUnusedClusterResults(cluster);
}
};
};
struct DTensorDecomposeControlflow
: public impl::DTensorDecomposeControlflowBase<
DTensorDecomposeControlflow> {
void getDependentDialects(mlir::DialectRegistry& registry) const override {
registry.insert<mlir::dtensor::DTensorDialect>();
}
void runOnOperation() override {
mlir::MLIRContext& context = getContext();
mlir::OpBuilder op_builder(&context);
auto module = getOperation();
int num = 0;
if (mlir::failed(DecomposeControlflow(&context, &num, module)))
return signalPassFailure();
}
};
} // namespace
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
CreateDTensorMergeClustersPass() {
return std::make_unique<DTensorMergeClusters>();
}
std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
CreateDTensorDecomposeControlflowPass() {
return std::make_unique<DTensorDecomposeControlflow>();
}
} // namespace dtensor
} // namespace tensorflow