我想打PAC队的第一次评注 #16

Open
zbtrs2 wants to merge 43 commits from ssk015/mindspore2022:master into master
15 changed files with 1002 additions and 71 deletions
Showing only changes of commit 8883033746 - Show all commits

View File

@ -25,57 +25,104 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Clones the primitive of the given node.
*
* In scenarios where multiple CNodes may share a primitive pointer,
* this function ensures that each CNode gets its own cloned primitive.
*
* @param node The AnfNode which has the primitive to be cloned.
*/
void ClonePrimitive(const AnfNodePtr &node) {
// Several CNode may share a primitive pointer, so we clone the primitive before setting attr.
auto cnode = node->cast<CNodePtr>();
if (cnode == nullptr) return;
// Clone the primitive and set it back to the CNode.
auto prim_node = NewValueNode(common::AnfAlgo::GetCNodePrimitive(cnode)->Clone());
cnode->set_input(kAnfPrimitiveIndex, prim_node);
}
} // namespace
namespace {
/**
* @brief Processes the Cast operation node to set appropriate AKG kernel attributes.
*
* @param node The AnfNode representing the Cast operation.
*/
void ProcessCast(const AnfNodePtr &node) {
// The x and output are akg op input and output param.
ClonePrimitive(node);
std::vector<std::string> input_names = {"x", kAttrDstType};
std::vector<std::string> output_names = {"output"};
ClonePrimitive(node);
// Set the input and output names as attributes.
common::AnfAlgo::SetNodeAttr(kAttrInputNames, MakeValue(input_names), node);
common::AnfAlgo::SetNodeAttr(kAttrOutputNames, MakeValue(output_names), node);
// Set the destination type attribute.
TypeId output_type = AnfAlgo::GetOutputDeviceDataType(node, 0);
common::AnfAlgo::SetNodeAttr(kAttrDstType, TypeIdToType(output_type), node);
}
/**
* @brief Processes the MatMul operation node to set appropriate AKG kernel attributes.
*
* @param node The AnfNode representing the MatMul operation.
*/
void ProcessMatMul(const AnfNodePtr &node) {
ClonePrimitive(node);
// Set the destination type attribute.
TypeId output_type = AnfAlgo::GetOutputDeviceDataType(node, 0);
common::AnfAlgo::SetNodeAttr(kAttrDstType, TypeIdToType(output_type), node);
// Set the left and right format attributes.
auto left_format = AnfAlgo::GetInputFormat(node, 0);
auto right_format = AnfAlgo::GetInputFormat(node, 1);
common::AnfAlgo::SetNodeAttr("left_format", MakeValue(left_format), node);
common::AnfAlgo::SetNodeAttr("right_format", MakeValue(right_format), node);
}
/**
* @brief Process nodes in the FuncGraph to set appropriate AKG kernel attributes.
*
* @param func_graph The function graph being processed.
* @param node The node in the graph to be processed.
* @param equiv Not used in the current implementation.
* @return AnfNodePtr Modified node if changes were made, otherwise nullptr.
*/
const AnfNodePtr AddAkgKernelAttrs::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
MS_EXCEPTION_IF_NULL(func_graph);
MS_EXCEPTION_IF_NULL(node);
auto shape = node->Shape();
// dynamic shape nodes will re-infer the shape and dtype
// Nodes with dynamic shape will re-infer the shape and dtype.
if (shape == nullptr || shape->IsDynamic()) {
return nullptr;
}
// Process Cast and MatMul nodes.
if (IsPrimitiveCNode(node, prim::kPrimCast)) {
ProcessCast(node);
} else if (IsPrimitiveCNode(node, prim::kPrimMatMul) || IsPrimitiveCNode(node, prim::kPrimBatchMatMul)) {
ProcessMatMul(node);
}
return nullptr;
}
/**
* @brief Define the pattern to be matched in the graph.
*
* @return BaseRef Pattern to be matched.
*/
const BaseRef AddAkgKernelAttrs::DefinePattern() const {
VarPtr X = std::make_shared<Var>();
VarPtr Xs = std::make_shared<SeqVar>();
return VectorRef({X, Xs});
}
} // namespace
} // namespace opt
} // namespace mindspore

View File

@ -21,17 +21,33 @@
namespace mindspore {
namespace opt {
/**
* @brief Process nodes in the FuncGraph to set appropriate dynamic shape attributes.
*
* If the given node has dynamic shape attributes, the function will log
* the node and set the graph's dynamic attribute to true.
*
* @param func_graph The function graph being processed.
* @param node The node in the graph to be processed.
* @param equiv Not used in the current implementation.
* @return AnfNodePtr The input node as it's not modified within this method.
*/
const AnfNodePtr AddDynamicShapeAttr::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
MS_EXCEPTION_IF_NULL(func_graph);
MS_EXCEPTION_IF_NULL(node);
// If the node has dynamic shape, set the dynamic attribute of the graph to true.
if (common::AnfAlgo::IsDynamicShape(node)) {
MS_LOG(DEBUG) << "Set Dynamic Shape Attr to Node:" << node->fullname_with_scope();
auto kernel_graph = func_graph->cast<KernelGraphPtr>();
MS_EXCEPTION_IF_NULL(kernel_graph);
kernel_graph->SetGraphDynamicAttr(true);
}
return node;
}
} // namespace opt
} // namespace mindspore

View File

@ -29,19 +29,35 @@
namespace mindspore {
namespace opt {
namespace {
// Define the operations and their respective sets for marking.
mindspore::HashMap<std::string, mindspore::HashSet<std::string>> MarkOp{
{"LSTM", {"LSTMGradWeight", "LSTMGrad", "LSTMGradData"}}};
{"LSTM", {"LSTMGradWeight", "LSTMGrad", "LSTMGradData"}}
};
/**
* @brief Checks if any user of the given node is within the provided set of operations.
*
* This method recursively traverses the users of the node and checks if any of them belong to
* the provided set. This helps in identifying certain patterns or connections in the graph.
*
* @param manager The FuncGraphManager for managing nodes in the graph.
* @param cnode Node to check for its users.
* @param set The set of operation names to check against.
* @return bool Indicates whether any user of the node is in the set.
*/
bool CheckOP(const FuncGraphManagerPtr &manager, const AnfNodePtr &cnode, const mindspore::HashSet<std::string> &set) {
for (const auto &node_index : manager->node_users()[cnode]) {
auto output = node_index.first;
MS_EXCEPTION_IF_NULL(output);
// If the user is a TupleGetItem, recursively check its users.
if (common::AnfAlgo::CheckPrimitiveType(output, prim::kPrimTupleGetItem)) {
if (CheckOP(manager, output, set)) {
return true;
}
} else if (output->isa<CNode>()) {
auto name = common::AnfAlgo::GetCNodeName(output);
// If the user's name is in the set, return true.
if (set.find(name) != set.end()) {
return true;
}
@ -49,23 +65,47 @@ bool CheckOP(const FuncGraphManagerPtr &manager, const AnfNodePtr &cnode, const
}
return false;
}
/**
* @brief Adds a training attribute to the given CNode based on its connections.
*
* The method checks if the CNode is connected to certain operations and based on the check
* result, it sets the 'kAttrIsTraining' attribute on the node.
*
* @param func_graph The function graph containing the node.
* @param cnode The CNode to which the attribute should be added.
*/
void AddAttrTraining(const FuncGraphPtr &func_graph, const CNodePtr &cnode) {
MS_EXCEPTION_IF_NULL(func_graph);
MS_EXCEPTION_IF_NULL(cnode);
auto manager = func_graph->manager();
MS_EXCEPTION_IF_NULL(manager);
// If the node has no users, exit.
if (manager->node_users().find(cnode) == manager->node_users().end()) {
return;
}
auto set = MarkOp[common::AnfAlgo::GetCNodeName(cnode)];
if (CheckOP(manager, cnode, set)) {
// Add 'IsTraining' attribute with a value of 'true'.
cnode->AddAttr(kAttrIsTraining, MakeValue(true));
} else {
// Add 'IsTraining' attribute with a value of 'false'.
cnode->AddAttr(kAttrIsTraining, MakeValue(false));
}
}
} // namespace
namespace {
/**
* @brief Processes the given node in the function graph and adds training attributes if necessary.
*
* @param func_graph The function graph being processed.
* @param node The AnfNode being examined.
* @param equiv Not used in the current implementation.
* @return AnfNodePtr Returns the modified node or nullptr if no changes are made.
*/
const AnfNodePtr AddTrainingAttr::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
if (node == nullptr || func_graph == nullptr || common::AnfAlgo::CheckPrimitiveType(node, prim::kPrimTupleGetItem) ||
@ -75,14 +115,23 @@ const AnfNodePtr AddTrainingAttr::Process(const FuncGraphPtr &func_graph, const
if (!node->isa<CNode>()) {
return nullptr;
}
// Get the name of the CNode.
auto name = common::AnfAlgo::GetCNodeName(node);
// If the name isn't in the MarkOp, exit.
auto iter = MarkOp.find(name);
if (iter == MarkOp.end()) {
return nullptr;
}
// Add training attributes and return the modified node.
auto cnode = node->cast<CNodePtr>();
AddAttrTraining(func_graph, cnode);
return cnode;
}
} // namespace
} // namespace opt
} // namespace mindspore

View File

@ -21,34 +21,58 @@
namespace mindspore {
namespace opt {
constexpr const int64_t kFusionGap = 2;
/**
* @brief Adjusts the dependencies for parallel optimizer recompute AllGather operations within the function graph.
*
* This method processes the function graph, identifies the AllGather operations that are subject
* to recompute optimization and adjusts their dependencies to ensure proper computation sequence.
* The dependencies of AllGather operations might need adjustment due to parallel optimization strategies.
*
* @param graph The function graph to process.
* @return bool Indicates whether any adjustments were made to the function graph.
*/
bool AdjustDependForParallelOptimizerRecomputeAllGather::Run(const FuncGraphPtr &graph) {
// Validate the input function graph.
MS_EXCEPTION_IF_NULL(graph);
// A mapping to determine if an AllGather operation within a fusion group is set for recompute.
mindspore::HashMap<int64_t, bool> forward_allgather_recompute_value_in_fusion_group;
// Get the nodes of the function graph in topological order.
std::vector<AnfNodePtr> node_list = TopoSort(graph->get_return());
// Variables to keep track of the AllGather operations and their attributes.
std::vector<int64_t> parallel_optimizer_recompute_allgather_fusion_ids;
std::vector<AnfNodePtr> parallel_optimizer_recompute_allgathers;
std::vector<AnfNodePtr> parallel_optimizer_recompute_first_fusion_allgathers;
int64_t unrecompute_max_fusion_id = -1;
int64_t recompute_min_fusion_id = 0;
// Process each node to identify the AllGather operations subject to recompute.
for (auto &node : node_list) {
MS_EXCEPTION_IF_NULL(node);
// Filter out non-kernel nodes.
if (!node->cast<CNodePtr>() || !AnfUtils::IsRealKernel(node)) {
continue;
}
auto cnode = node->cast<CNodePtr>();
// Filter out nodes that aren't AllGather or associated with fusion and parallel optimizer.
if (!common::AnfAlgo::IsAllgather(cnode) || !common::AnfAlgo::IsFusion(cnode) ||
!common::AnfAlgo::IsFromParallelOptimizer(cnode)) {
continue;
}
// If the node is set for recompute, update the fusion IDs and track it.
if (common::AnfAlgo::IsRecompute(cnode)) {
int64_t fusion_id = common::AnfAlgo::GetNodeAttr<int64_t>(cnode, kAttrFusion);
// Check and add unique fusion_ids.
if (std::find(parallel_optimizer_recompute_allgather_fusion_ids.begin(),
parallel_optimizer_recompute_allgather_fusion_ids.end(),
fusion_id) == parallel_optimizer_recompute_allgather_fusion_ids.end()) {
parallel_optimizer_recompute_allgather_fusion_ids.push_back(fusion_id);
if (recompute_min_fusion_id == 0 || fusion_id < recompute_min_fusion_id) {
recompute_min_fusion_id = fusion_id;
}
recompute_min_fusion_id = recompute_min_fusion_id == 0 ? fusion_id : std::min(fusion_id, recompute_min_fusion_id);
parallel_optimizer_recompute_first_fusion_allgathers.push_back(node);
} else {
parallel_optimizer_recompute_allgathers.push_back(node);
@ -60,57 +84,104 @@ bool AdjustDependForParallelOptimizerRecomputeAllGather::Run(const FuncGraphPtr
common::AnfAlgo::GetNodeAttr<bool>(cnode, kAttrRecompute);
auto [iter, inserted] =
forward_allgather_recompute_value_in_fusion_group.emplace(unrecompute_fusion_id, would_be_recomputed);
// Ensure consistency within the fusion group.
if (!inserted && iter->second != would_be_recomputed) {
MS_LOG(EXCEPTION) << "In same fusion group, the allgather recompute attribute should be equal. "
MS_LOG(EXCEPTION) << "In the same fusion group, the AllGather recompute attribute should be equal. "
"The normal node is:"
<< cnode->fullname_with_scope();
}
}
}
// Adjust the fusion IDs for the identified AllGather operations.
IncreaseAllgatherFusionId(parallel_optimizer_recompute_allgathers,
parallel_optimizer_recompute_first_fusion_allgathers, unrecompute_max_fusion_id,
recompute_min_fusion_id);
// Adjust the dependencies for the AllGather operations.
return AdjustAllgatherDepend(graph, parallel_optimizer_recompute_allgathers);
}
/**
* @brief Increases the fusion ID of AllGather operations to adjust for recompute optimization.
*
* The purpose of this method is to handle cases where duplicated AllGather operations would be fused with forward
* AllGather operations. In such cases, fusion IDs need adjustment to ensure proper execution.
*
* @param parallel_optimizer_recompute_allgathers All the AllGather operations marked for recompute.
* @param parallel_optimizer_recompute_first_fusion_allgathers First AllGather operations in the recomputed segments.
* @param unrecompute_max_fusion_id Maximum fusion ID of unrecomputed AllGather operations.
* @param recompute_min_fusion_id Minimum fusion ID of recomputed AllGather operations.
*/
void AdjustDependForParallelOptimizerRecomputeAllGather::IncreaseAllgatherFusionId(
const std::vector<AnfNodePtr> &parallel_optimizer_recompute_allgathers,
const std::vector<AnfNodePtr> &parallel_optimizer_recompute_first_fusion_allgathers,
int64_t unrecompute_max_fusion_id, int64_t recompute_min_fusion_id) {
// means that there may some forward allgather and duplicated allgather would be fused.
// If the condition holds, some forward AllGather and duplicated AllGather may be fused.
if (recompute_min_fusion_id <= unrecompute_max_fusion_id) {
MS_LOG(WARNING) << "Increase the duplicated allgather fusion id";
// Adjust fusion ID for the first AllGather operations in recomputed segments.
for (auto &adjust_node : parallel_optimizer_recompute_first_fusion_allgathers) {
// Calculate the new fusion ID.
int64_t current_fusion_id = common::AnfAlgo::GetNodeAttr<int64_t>(adjust_node, kAttrFusion);
int64_t destination_fusion_id =
(kFusionGap + current_fusion_id + unrecompute_max_fusion_id) - recompute_min_fusion_id;
// Set the new fusion ID.
common::AnfAlgo::SetNodeAttr(kAttrFusion, MakeValue(destination_fusion_id), adjust_node);
}
// Adjust fusion ID for the remaining AllGather operations marked for recompute.
for (auto &adjust_node : parallel_optimizer_recompute_allgathers) {
// Calculate the new fusion ID.
int64_t current_fusion_id = common::AnfAlgo::GetNodeAttr<int64_t>(adjust_node, kAttrFusion);
int64_t destination_fusion_id =
(kFusionGap + current_fusion_id + unrecompute_max_fusion_id) - recompute_min_fusion_id;
// Set the new fusion ID.
common::AnfAlgo::SetNodeAttr(kAttrFusion, MakeValue(destination_fusion_id), adjust_node);
}
}
}
/**
* @brief Adjusts the dependencies of AllGather operations subject to recompute within the function graph.
*
* This method ensures that the execution dependencies of AllGather operations are maintained as expected,
* especially after introducing the recompute optimization.
*
* @param graph The function graph to process.
* @param parallel_optimizer_recompute_allgathers All the AllGather operations marked for recompute.
* @return bool Indicates whether any adjustments were made to the function graph.
*/
bool AdjustDependForParallelOptimizerRecomputeAllGather::AdjustAllgatherDepend(
const FuncGraphPtr &graph, const std::vector<AnfNodePtr> &parallel_optimizer_recompute_allgathers) {
// Get the manager for the function graph.
FuncGraphManagerPtr manager = graph->manager();
bool changed = false;
// Process each AllGather operation marked for recompute.
for (auto &node : parallel_optimizer_recompute_allgathers) {
auto cnode = node->cast<CNodePtr>();
auto depend_node = common::AnfAlgo::GetInputNode(cnode, 0);
// If the dependency is a Depend operation, adjust the dependency.
if (IsPrimitiveCNode(depend_node, prim::kPrimDepend)) {
auto depend_cnode = depend_node->cast<CNodePtr>();
AnfNodeIndexSet allgather_node_set = manager->node_users()[cnode];
for (auto &node_pair : allgather_node_set) {
auto allgather_next_node = node_pair.first;
CNodePtr allgather_next_cnode = node_pair.first->cast<CNodePtr>();
// Continue if the node isn't a valid CNode or a primitive.
if (allgather_next_cnode == nullptr || !IsValueNode<Primitive>(allgather_next_cnode->input(0))) {
continue;
}
// Construct a new Depend operation and adjust the connections.
std::vector<AnfNodePtr> inputs = {NewValueNode(std::make_shared<Primitive>(prim::kPrimDepend->name())),
allgather_next_node, common::AnfAlgo::GetInputNode(depend_cnode, 1)};
auto new_depend = graph->NewCNode(inputs);
@ -119,8 +190,11 @@ bool AdjustDependForParallelOptimizerRecomputeAllGather::AdjustAllgatherDepend(
(void)manager->Replace(allgather_next_node, new_depend);
changed = true;
}
} else if (IsPrimitiveCNode(depend_node, prim::kPrimCast) &&
}
// Handle the case where the dependency is a Cast operation with an underlying Depend operation.
else if (IsPrimitiveCNode(depend_node, prim::kPrimCast) &&
IsPrimitiveCNode(common::AnfAlgo::GetInputNode(depend_node->cast<CNodePtr>(), 0), prim::kPrimDepend)) {
// The logic here mirrors the above block for Depend operations, with an additional layer of handling for Cast.
auto cast_cnode = depend_node->cast<CNodePtr>();
auto cast_depend_node = common::AnfAlgo::GetInputNode(cast_cnode, 0);
auto cast_depend_cnode = cast_depend_node->cast<CNodePtr>();
@ -145,5 +219,6 @@ bool AdjustDependForParallelOptimizerRecomputeAllGather::AdjustAllgatherDepend(
}
return changed;
}
} // namespace opt
} // namespace mindspore

View File

@ -37,19 +37,39 @@ constexpr auto kAttrDefaultOp = "default_op";
constexpr size_t kAlignSize = 2 << 9;
constexpr int64_t kDefaultThresholdMb2Byte = 262144;
/**
* @brief Generate kernel build information for a range of communication operations.
*
* This function processes a range of communication operation nodes (from `start_index` to `end_index`)
* within `communication_op_info` and extracts their device format, device type, and other properties to
* create a kernel build information object.
*
* @param communication_op_info The structure containing all communication operation nodes.
* @param start_index The starting index within the `communication_op_info` structure.
* @param end_index The ending index within the `communication_op_info` structure.
* @return The constructed kernel build information object.
*/
kernel::KernelBuildInfoPtr GenerateKernelBuildInfo(const CommunicationOpInfo &communication_op_info, size_t start_index,
size_t end_index) {
// Check the validity of the given range.
if (end_index >= communication_op_info.communication_op_nodes.size()) {
MS_LOG(EXCEPTION) << "end index out of communication_op_nodes size";
}
// Initialize containers for device information.
std::vector<std::string> inputs_device_format;
std::vector<std::string> outputs_device_format;
std::vector<TypeId> inputs_device_type;
std::vector<TypeId> outputs_device_type;
std::vector<std::vector<size_t>> outputs_shape;
kernel::KernelBuildInfo::KernelBuildInfoBuilder builder;
// Process each communication operation node in the given range.
for (size_t idx = start_index; idx <= end_index; ++idx) {
auto cnode = communication_op_info.communication_op_nodes[idx];
// Extract rank size for certain operations.
int64_t rank_size = 1;
if (common::AnfAlgo::HasNodeAttr(kAttrRankSize, cnode) &&
common::AnfAlgo::GetCNodeName(cnode) == kAllGatherOpName) {
@ -59,12 +79,15 @@ kernel::KernelBuildInfoPtr GenerateKernelBuildInfo(const CommunicationOpInfo &co
if (rank_size_t == 0) {
MS_LOG(EXCEPTION) << "Rank size should not be zero.";
}
MS_EXCEPTION_IF_NULL(cnode);
// Extract device format and type for inputs of the communication operation.
size_t input_num = common::AnfAlgo::GetInputTensorNum(cnode);
for (size_t input_index = 0; input_index < input_num; ++input_index) {
inputs_device_format.push_back(AnfAlgo::GetInputFormat(cnode, input_index));
inputs_device_type.push_back(AnfAlgo::GetInputDeviceDataType(cnode, input_index));
}
// Extract device format, type, and shape for outputs of the communication operation.
for (size_t rank_index = 0; rank_index < rank_size_t; ++rank_index) {
size_t output_num = common::AnfAlgo::GetOutputTensorNum(cnode);
for (size_t output_index = 0; output_index < output_num; ++output_index) {
@ -77,20 +100,39 @@ kernel::KernelBuildInfoPtr GenerateKernelBuildInfo(const CommunicationOpInfo &co
outputs_shape.push_back(common::AnfAlgo::GetOutputInferShape(cnode, output_index));
}
}
// Add more general attributes to the builder.
builder.SetFusionType(AnfAlgo::GetFusionType(cnode));
builder.SetProcessor(AnfAlgo::GetProcessor(cnode));
builder.SetKernelType(AnfAlgo::GetKernelType(cnode));
}
// Finalize the builder with extracted information.
builder.SetInputsFormat(inputs_device_format);
builder.SetOutputsFormat(outputs_device_format);
builder.SetInputsDeviceType(inputs_device_type);
builder.SetOutputsDeviceType(outputs_device_type);
return builder.Build();
}
/**
* @brief Generate a unique key for a fusion group based on a given node.
*
* This function uses various attributes such as `fusion`, `group`, `op`, and data type from
* the given node to generate a unique key which can be used to identify or group
* related fusion operations in MindSpore.
*
* @param node The AnfNode for which the fusion group key is to be generated.
* @return A string representing the fusion group key.
*/
std::string GetFusionGroupKey(const AnfNodePtr &node) {
// Extract the primitive from the given node.
auto primitive = common::AnfAlgo::GetCNodePrimitive(node);
MS_EXCEPTION_IF_NULL(primitive);
// Extract and check fusion attribute.
ValuePtr attr_fusion = primitive->GetAttr(kAttrFusion);
if (attr_fusion == nullptr) {
return "";
@ -99,6 +141,8 @@ std::string GetFusionGroupKey(const AnfNodePtr &node) {
if (fusion == 0) {
return "";
}
// Extract or use default values for group and op attributes.
std::string group = kAttrDefaultGroup;
ValuePtr attr_group = primitive->GetAttr(kAttrGroup);
if (attr_group != nullptr) {
@ -109,23 +153,51 @@ std::string GetFusionGroupKey(const AnfNodePtr &node) {
if (attr_op != nullptr) {
op = GetValue<std::string>(attr_op);
}
// Extract the data type for the node.
auto dtype = common::AnfAlgo::GetPrevNodeOutputInferDataType(node, 0);
// Construct and return the fusion group key.
return group + op + std::to_string(fusion) + TypeIdLabel(dtype);
}
/**
* @brief Checks if multiple fusion inputs have duplicates.
*
* In the context of fusion operations within MindSpore, different communication operations
* in one segment should not share the same input. This function checks if this condition is met.
*
* @param fusion_inputs A vector of AnfNodePtr representing the fusion inputs to check.
*/
void CheckInputs(const std::vector<AnfNodePtr> &fusion_inputs) {
std::set<AnfNodePtr> inputs_set(fusion_inputs.begin(), fusion_inputs.end());
// If the set size is smaller than the vector, there are duplicates.
if (inputs_set.size() < fusion_inputs.size()) {
MS_LOG(EXCEPTION) << "Different communication op in one segment cannot share the same input";
}
}
/**
* @brief Validate the segments for a given communication operation node.
*
* This function checks the validity of segments for a communication operation node.
* It ensures the correct ordering and range of segment indices.
*
* @param communication_op_node_size The size of the communication operation node.
* @param segment_index The segment index to be validated.
* @return true if the segments are valid, otherwise an exception is thrown.
*/
bool CheckSegments(size_t communication_op_node_size, const std::vector<size_t> *segment_index) {
MS_EXCEPTION_IF_NULL(segment_index);
// Check the last segment index for validity.
auto segments = segment_index->size();
if (segment_index->at(segments - 1) != communication_op_node_size - 1) {
MS_LOG(EXCEPTION) << "the last segment index is invalid.";
}
// Validate the ordering of segment indices.
for (size_t i = 0; i < segments - 1; ++i) {
if (segment_index->at(i) > segment_index->at(i + 1)) {
MS_LOG(EXCEPTION) << "illegal split: segment_index[" << i << "]=" << segment_index->at(i) << ", segment_index[ "
@ -134,14 +206,30 @@ bool CheckSegments(size_t communication_op_node_size, const std::vector<size_t>
}
return true;
}
} // namespace
/**
* @brief Get the segmentation indices for fusion in communication operations.
*
* This function determines the segments' boundaries for communication operations fusion
* based on the provided CommunicationOpInfo and a specific group. The segmentation might
* vary based on the operation name (e.g., HcomSendOp, ReceiveOp) and other factors.
*
* @param communication_op_info The CommunicationOpInfo containing communication operation nodes.
* @param segment_index A vector to be filled with the segmentation indices.
* @param group The specific group for which the segmentation is required.
* @return true if the segmentation was successful, otherwise false.
*/
bool CommunicationOpFusion::GetSplitSegments(const CommunicationOpInfo &communication_op_info,
std::vector<size_t> *segment_index, const std::string &group) const {
MS_EXCEPTION_IF_NULL(segment_index);
// Get the size of communication operation nodes.
size_t communication_op_node_size = communication_op_info.communication_op_nodes.size();
MS_LOG(INFO) << "graph " << op_name_ << " node size " << communication_op_node_size;
// Special handling for send and receive operations.
if (op_name_ == kHcomSendOpName || op_name_ == kReceiveOpName) {
if (communication_op_node_size == 0) {
return false;
@ -152,15 +240,20 @@ bool CommunicationOpFusion::GetSplitSegments(const CommunicationOpInfo &communic
auto parallel_context = parallel::ParallelContext::GetInstance();
MS_EXCEPTION_IF_NULL(parallel_context);
// Determine split indices.
std::vector<uint32_t> split_indices;
if (!parallel_context->enable_parallel_optimizer()) {
split_indices = parallel_context->GetAllReduceFusionSplitIndices(group);
}
// Handling based on split indices.
if (!split_indices.empty()) {
uint32_t last_index = 0;
for (size_t i = 0; i < split_indices.size(); ++i) {
uint32_t index = split_indices[i];
// Check validity of split index.
if (index <= last_index && i != 0) {
MS_LOG(EXCEPTION) << "invalid " << op_name_ << " split index " << i << " " << index;
}
@ -172,15 +265,19 @@ bool CommunicationOpFusion::GetSplitSegments(const CommunicationOpInfo &communic
segment_index->push_back(index);
last_index = index;
}
// Ensure the inclusion of the last index.
if (last_index != communication_op_node_size - 1) {
segment_index->push_back(communication_op_node_size - 1);
}
} else {
// Default segmentation strategy.
for (size_t i = 0; i < groups_ - 1; ++i) {
segment_index->push_back((i + 1) * (communication_op_node_size / groups_) - 1);
}
segment_index->push_back(communication_op_node_size - 1);
}
// Further segmentation strategy for data parallelism mode with AllReduce operations.
auto parallel_mode = parallel_context->parallel_mode();
if (parallel_mode == parallel::kDataParallel && op_name_ == kAllReduceOpName) {
auto threshold = parallel_context->dp_fusion_threshold_mb();
@ -188,19 +285,36 @@ bool CommunicationOpFusion::GetSplitSegments(const CommunicationOpInfo &communic
MS_LOG(INFO) << "The split threshold for AllReduce is " << threshold << ", the segment num is "
<< segment_index->size();
}
return CheckSegments(communication_op_node_size, segment_index);
}
/**
* @brief Determine the segmentation indices for AllReduce operations based on a threshold.
*
* This function refines the segmentation indices for AllReduce communication operations
* based on a given memory threshold. If the cumulative memory size exceeds the threshold,
* a new segment is started.
*
* @param nodes A vector of CNodePtr representing the nodes being considered for segmentation.
* @param threshold The memory threshold in MB for the segmentation.
* @param segment_index A vector to be filled with the refined segmentation indices.
*/
void CommunicationOpFusion::GetAllReduceSplitSegment(const std::vector<CNodePtr> &nodes, int64_t threshold,
std::vector<size_t> *segment_index) const {
MS_EXCEPTION_IF_NULL(segment_index);
// Check the provided threshold.
if (threshold <= 0) {
MS_LOG(WARNING) << "Split threshold is " << threshold << ". AllReduce nodes will take default fusion strategy.";
return;
}
threshold *= kDefaultThresholdMb2Byte;
threshold *= kDefaultThresholdMb2Byte; // Convert MB to Byte.
std::vector<size_t> real_segment_index;
size_t start_index = 0;
// Iterate through the segments to refine them based on the threshold.
for (auto index : *segment_index) {
if (index >= nodes.size()) {
MS_LOG(WARNING) << "split index is greater than or equal to total gradient's number " << nodes.size();
@ -216,14 +330,17 @@ void CommunicationOpFusion::GetAllReduceSplitSegment(const std::vector<CNodePtr>
accumulate += tensor_size;
}
}
// Add segments with remaining accumulated size.
if (accumulate != 0) {
real_segment_index.push_back(index);
}
start_index = index + 1;
}
*segment_index = std::move(real_segment_index);
}
// Hard coded Load(%paraxxx, cnode()) to Load(%paraxxx, U) to prevent
// cycle after AllReduce fused. It's a workaround.
// case 1:
@ -265,29 +382,50 @@ void CommunicationOpFusion::GetAllReduceSplitSegment(const std::vector<CNodePtr>
// ...
// %109 = AssignAdd(%para485, Tensor(34), cnode_u)
// %110 = UpdateState(cnode_u, xxx)
/**
* @brief Adjust the input of an AllReduce node if it has a Load dependency.
*
* This function identifies whether an AllReduce operation has a Load operation dependency.
* If found, it performs several adjustments, mainly replacing certain nodes' input to UMonads.
*
* @param cnode The CNodePtr representing the AllReduce node to be checked and adjusted.
*/
static void AdjustAllReduceInputWithLoad(const CNodePtr &cnode) {
// Constant definitions for indexing and size checks.
const size_t monad_index = 2;
const size_t tuple_inputs_size = 2;
const size_t load_inputs_size = 3;
// Search for a Load operation dependency.
auto cnode_load = BroadFirstSearchFirstOf({cnode}, [](const CNodePtr &search_cnode) {
if (!IsPrimitiveCNode(search_cnode, prim::kPrimLoad)) {
return false;
}
// Ensuring the Load CNode has the expected number of inputs.
if (search_cnode->inputs().size() != load_inputs_size) {
MS_LOG(EXCEPTION) << "Load CNode should have 3 inputs, but: " << search_cnode->DebugString();
}
return search_cnode->input(monad_index)->isa<CNode>();
});
// If a Load operation is found, perform adjustments.
if (cnode_load != nullptr) {
// Create a UMonad ValueNode.
auto const_u_monad = NewValueNode(kUMonad);
const_u_monad->set_abstract(kUMonad->ToAbstract());
const auto &cnode_u = cnode_load->input(monad_index);
MS_LOG(DEBUG) << "Replace Load with CNode U to constant U for cnode: " << cnode_load->DebugString();
// Ensure the cnode belongs to a valid FuncGraph.
MS_EXCEPTION_IF_NULL(cnode->func_graph());
MS_EXCEPTION_IF_NULL(cnode->func_graph()->manager());
// Replace the UMonad input of Load CNode.
auto manager = cnode->func_graph()->manager();
manager->SetEdge(cnode_load, monad_index, const_u_monad);
// Update the u_monad input of UpdateState from CNode U same as Load to constant U.
// Identify UpdateState dependencies and adjust them.
CNodePtr cnode_update_state = nullptr;
CNodePtr cnode_make_tuple = nullptr;
const auto &cnode_load_users = manager->node_users()[cnode_load];
@ -316,14 +454,14 @@ static void AdjustAllReduceInputWithLoad(const CNodePtr &cnode) {
}
}
}
// Perform adjustments based on identified UpdateState dependencies.
if (cnode_update_state != nullptr) {
if (cnode_make_tuple == nullptr || cnode_make_tuple->inputs().size() == tuple_inputs_size) {
// case 1 and case 3: Replace cnode_update_state to cnode_u;
MS_LOG(DEBUG) << "Replace UpdateState with CNode U: " << cnode_update_state->DebugString()
<< " ::TO:: " << cnode_u->DebugString();
manager->Replace(cnode_update_state, cnode_u);
} else if (cnode_make_tuple->inputs().size() > tuple_inputs_size) {
// case 2: remove cnode_load from cnode_make_tuple;
MS_LOG(DEBUG) << "Drop " << cnode_load->DebugString() << " from " << cnode_make_tuple->DebugString();
const auto &make_tuple_inputs = cnode_make_tuple->inputs();
AnfNodePtrList new_tuple_inputs(make_tuple_inputs.size() - 1);
@ -339,9 +477,22 @@ static void AdjustAllReduceInputWithLoad(const CNodePtr &cnode) {
}
}
/**
* @brief Creates a fused communication operation.
*
* This function creates a new CNode representing a fused communication operation based
* on the specified communication operation information.
*
* @param func_graph The function graph in which the new node will be created.
* @param communication_op_info The structure containing information about communication operations.
* @param start_index The starting index of the communication operation to be fused.
* @param end_index The ending index of the communication operation to be fused.
* @return AnfNodePtr The newly created CNode representing the fused operation.
*/
AnfNodePtr CommunicationOpFusion::CreateFusedCommunicationOp(const FuncGraphPtr &func_graph,
const CommunicationOpInfo &communication_op_info,
size_t start_index, size_t end_index) const {
// Validate the input function graph.
MS_EXCEPTION_IF_NULL(func_graph);
auto prim = std::make_shared<Primitive>(op_name_);
MS_EXCEPTION_IF_NULL(prim);
@ -430,64 +581,110 @@ AnfNodePtr CommunicationOpFusion::CreateFusedCommunicationOp(const FuncGraphPtr
return fused_node;
}
bool CommunicationOpFusion::DoFusion(const FuncGraphPtr &func_graph, const CommunicationOpInfo &communication_op_info,
/**
* @brief Performs fusion on communication operations within a function graph.
*
* This method attempts to fuse a sequence of communication operations within a given function graph
* into a single composite operation. This is done in order to optimize performance and reduce
* overhead of executing multiple small operations. The method uses segment indices to determine
* which operations should be fused together.
*
* @param func_graph The function graph in which communication operations are to be fused.
* @param communication_op_info The structure containing information about communication operations.
* @param segment_index A list of indices that denote segments of operations to be fused.
* @return bool Indicates whether any fusion has taken place.
*/
bool CommunicationOpFusion::DoFusion(const FuncGraphPtr &func_graph,
const CommunicationOpInfo &communication_op_info,
const std::vector<size_t> &segment_index) const {
// Validate the input function graph.
MS_EXCEPTION_IF_NULL(func_graph);
auto manager = func_graph->manager();
MS_EXCEPTION_IF_NULL(manager);
bool changed = false;
size_t start_index = 0;
// Iterate through segment indices to determine operations to fuse.
for (size_t segment_idx = 0; segment_idx < segment_index.size(); ++segment_idx) {
size_t end_index = segment_index.at(segment_idx);
// Skip if the segment is too small to fuse.
if (end_index - start_index < 1) {
start_index = end_index + 1;
continue;
}
// Cast the function graph to a KernelGraph.
auto kernel_graph = func_graph->cast<KernelGraphPtr>();
MS_EXCEPTION_IF_NULL(kernel_graph);
auto graph_id = kernel_graph->graph_id();
AnfNodePtr new_communication_op =
// Create a new fused communication operation for the segment.
AnfNodePtr new_communication_op =
CreateFusedCommunicationOp(func_graph, communication_op_info, start_index, end_index);
AnfAlgo::SetGraphId(graph_id, new_communication_op.get());
// replace old communication op with new communication op
// Replace the old communication operations in the segment with the new fused operation.
for (auto idx = start_index; idx <= end_index; ++idx) {
std::vector<AnfNodePtr> tuple_getitem_input;
tuple_getitem_input.push_back(NewValueNode(prim::kPrimTupleGetItem));
tuple_getitem_input.push_back(new_communication_op);
auto offset = SizeToLong(idx - start_index);
auto index = NewValueNode(offset);
MS_EXCEPTION_IF_NULL(index);
auto imm = std::make_shared<Int64Imm>(idx - start_index);
MS_EXCEPTION_IF_NULL(imm);
auto abstract_scalar = std::make_shared<abstract::AbstractScalar>();
MS_EXCEPTION_IF_NULL(abstract_scalar);
index->set_abstract(abstract_scalar);
tuple_getitem_input.push_back(index);
// Create a new TupleGetItem node to extract outputs from the fused operation.
std::vector<AnfNodePtr> tuple_getitem_input = {
NewValueNode(prim::kPrimTupleGetItem),
new_communication_op,
NewValueNode(SizeToLong(idx - start_index))
};
AnfNodePtr tuple_getitem = func_graph->NewCNode(tuple_getitem_input);
MS_EXCEPTION_IF_NULL(tuple_getitem);
// Update abstract and replace the old operation with TupleGetItem node.
auto communication_op_node_item = communication_op_info.communication_op_nodes.at(idx);
MS_EXCEPTION_IF_NULL(communication_op_node_item);
tuple_getitem->set_abstract(communication_op_node_item->abstract());
// Handle internal outputs if the operation is an internal output of the kernel graph.
if (kernel_graph->IsInternalOutput(communication_op_node_item, 0)) {
kernel_graph->ReplaceInternalOutput(communication_op_node_item, new_communication_op, 0, LongToSize(offset));
kernel_graph->ReplaceInternalOutput(communication_op_node_item, new_communication_op, 0, LongToSize(idx - start_index));
}
// Perform the replacement in the graph manager.
if (!manager->Replace(communication_op_node_item, tuple_getitem)) {
MS_LOG(EXCEPTION) << "Manager replace node failed";
}
}
// Move to the next segment.
start_index = end_index + 1;
changed = true;
}
return changed;
}
/**
* @brief Fuses communication operations within the provided function graph.
*
* This method processes the given function graph, identifies and groups the communication
* operations that are candidates for fusion, and then fuses them. Fusion can lead to
* improved performance by reducing the overhead of executing multiple smaller operations.
*
* @param func_graph The function graph to process.
* @return bool Indicates whether any fusion operations were applied to the function graph.
*/
bool CommunicationOpFusion::Run(const FuncGraphPtr &func_graph) {
// Validate the input function graph.
MS_EXCEPTION_IF_NULL(func_graph);
const float input_grad_size_num = 0.0;
const float input_grad_time_num = 0.0;
// divide candidate fusion groups with same (group,op,fusion,dtype) attrs, fusion==0 means not fusion
// Initialize a mapping to keep track of candidate operations that can be fused.
// Operations are grouped by certain attributes such as group, operation, fusion, and dtype.
mindspore::HashMap<std::string, CommunicationOpInfo> candidate_groups;
// Retrieve the nodes of the function graph in topological order.
std::vector<AnfNodePtr> node_list = TopoSort(func_graph->get_return());
// Iterate through the nodes and identify communication operations for potential fusion.
for (auto &node : node_list) {
if (node != nullptr && node->isa<CNode>() && common::AnfAlgo::GetCNodeName(node) == op_name_) {
std::string key = GetFusionGroupKey(node);
@ -503,14 +700,20 @@ bool CommunicationOpFusion::Run(const FuncGraphPtr &func_graph) {
candidate_groups[key].input_grad_time.push_back(input_grad_time_num);
}
}
// split candidate group to segments according to _group class member
bool changed = false;
// Process each group of candidate operations.
for (auto &it : candidate_groups) {
// Skip groups with only one operation since they cannot be fused.
if (it.second.communication_op_nodes.size() <= 1) {
continue;
}
auto first_node = it.second.communication_op_nodes[0];
TraceGuard guard(std::make_shared<TraceOpt>(first_node->debug_info()));
// If the nodes have the "index" attribute, sort them based on this attribute.
if (common::AnfAlgo::HasNodeAttr(kAttrIndex, first_node) &&
common::AnfAlgo::GetNodeAttr<int64_t>(first_node, kAttrIndex) > 0) {
std::stable_sort(it.second.communication_op_nodes.begin(), it.second.communication_op_nodes.end(),
@ -519,14 +722,19 @@ bool CommunicationOpFusion::Run(const FuncGraphPtr &func_graph) {
common::AnfAlgo::GetNodeAttr<int64_t>(b, kAttrIndex);
});
}
// Determine segments of the group to be fused.
std::vector<size_t> segment_index;
if (GetSplitSegments(it.second, &segment_index, it.first)) {
// Apply the fusion on the identified segments.
if (DoFusion(func_graph, it.second, segment_index)) {
changed = true;
}
}
}
return changed;
}
} // namespace opt
} // namespace mindspore

View File

@ -29,45 +29,84 @@ namespace opt {
namespace {
const size_t strides_index = 5;
/**
* @brief Retrieves the stride values from the StridedSliceGrad node.
*
* This function is designed to extract the stride values from the given StridedSliceGrad node in MindSpore.
* It primarily checks the validity of the node, extracts the desired stride values, and returns whether the operation was successful.
*
* @param strided_slice_grad The given StridedSliceGrad node.
* @param strides_values Pointer to the list where the stride values will be stored.
* @return True if the stride values were successfully retrieved, otherwise False.
*/
bool GetStridesValues(const CNodePtr &strided_slice_grad, ValuePtrList *strides_values) {
// Check for null inputs.
MS_EXCEPTION_IF_NULL(strided_slice_grad);
MS_EXCEPTION_IF_NULL(strides_values);
constexpr size_t kSizeChange = 6;
// Ensure the strided_slice_grad node has the expected size.
if (strided_slice_grad->size() < kSizeChange) {
MS_LOG(DEBUG) << "Op strided_slice_grad's inputs size less than 6, graph not changed";
return false;
}
// Extract the strides input from the strided_slice_grad node.
auto strides_input = strided_slice_grad->input(strides_index);
MS_EXCEPTION_IF_NULL(strides_input);
// Cast the strides input to a value node.
auto strides_value_node = strides_input->cast<ValueNodePtr>();
if (strides_value_node == nullptr) {
MS_LOG(DEBUG) << "strides is not a value node.";
return false;
}
// Retrieve the value of the strides_value_node.
auto value = strides_value_node->value();
if (value == nullptr) {
MS_LOG(DEBUG) << "strides has no value.";
return false;
}
// Cast the retrieved value to a tuple.
auto value_tuple = value->cast<ValueTuplePtr>();
if (value_tuple == nullptr) {
MS_LOG(DEBUG) << "strides is not a value tuple.";
return false;
}
// Assign the retrieved tuple value to the strides_values pointer.
*strides_values = value_tuple->value();
return true;
}
/**
* @brief Checks the values of the given strides.
*
* This function checks whether the provided stride values meet specific conditions.
* It validates if each stride value is a scalar of integer type and equal to 1, which seems to be a requirement for StridedSliceGrad in MindSpore.
*
* @param strides_values List containing the stride values.
* @return True if all stride values meet the required conditions, otherwise False.
*/
bool CheckValues(const ValuePtrList &strides_values) {
// Check for an empty strides_values list.
if (strides_values.empty()) {
MS_LOG(DEBUG) << "strides_values is empty";
return false;
}
// Iterate over the stride values and check each value.
for (auto &value : strides_values) {
MS_EXCEPTION_IF_NULL(value);
if (value->isa<Scalar>()) {
auto scalar = value->cast<ScalarPtr>();
MS_EXCEPTION_IF_NULL(scalar);
// Check if the scalar value is an integer and equals 1.
if (scalar->isa<Int32Imm>()) {
if (GetValue<int>(scalar) != 1) {
MS_LOG(DEBUG) << "StridedSliceGrad has no 1 value";
@ -87,61 +126,112 @@ bool CheckValues(const ValuePtrList &strides_values) {
return false;
}
}
return true;
}
/**
* @brief Checks the attributes of the StridedSliceGrad node.
*
* This function verifies the existence and values of the `new_axis_mask` and `shrink_axis_mask` attributes
* in the given StridedSliceGrad node.
*
* @param strided_slice_grad The StridedSliceGrad node to check.
* @return True if both attributes exist and their values are 0; otherwise, False.
*/
bool CheckAttrs(const CNodePtr &strided_slice_grad) {
// Ensure strided_slice_grad is not null.
MS_EXCEPTION_IF_NULL(strided_slice_grad);
// Check for the existence of the required attributes.
if (!common::AnfAlgo::HasNodeAttr(kAttrNewAxisMask, strided_slice_grad) ||
!common::AnfAlgo::HasNodeAttr(kAttrShrinkAxisMask, strided_slice_grad)) {
MS_LOG(INFO) << "new_axis_mask or shrink_axis_mask not exist in cnode[" + strided_slice_grad->DebugString() + "]";
return false;
}
// Extract the values of the required attributes.
auto new_axis_mask = common::AnfAlgo::GetNodeAttr<int64_t>(strided_slice_grad, kAttrNewAxisMask);
auto shrink_axis_mask = common::AnfAlgo::GetNodeAttr<int64_t>(strided_slice_grad, kAttrShrinkAxisMask);
// Check the attribute values.
if (new_axis_mask != 0 || shrink_axis_mask != 0) {
MS_LOG(INFO) << "new_axis_mask or shrink_axis_mask not equal 0";
return false;
}
return true;
}
} // namespace
// Namespace wrapping might be in the original code. Assuming it's still the case.
namespace {
/**
* @brief Defines the pattern for the ConstToAttrStridedSliceGradPass.
*
* This function sets up the pattern to identify the StridedSliceGrad operations that should be
* processed by the ConstToAttrStridedSliceGradPass.
*
* @return The pattern for identifying the appropriate nodes.
*/
const BaseRef ConstToAttrStridedSliceGradPass::DefinePattern() const {
VarPtr Xs = std::make_shared<SeqVar>();
auto strided_slice_grad_prim = std::make_shared<Primitive>(kStridedSliceGradOpName);
return VectorRef({strided_slice_grad_prim, Xs});
}
/**
* @brief Processes the identified StridedSliceGrad node.
*
* For the StridedSliceGrad nodes that match the previously defined pattern, this function checks
* attributes and strides' values. If they satisfy certain conditions (based on the device and other factors),
* the node's inputs are then converted to attributes.
*
* @param graph The functional graph containing the node.
* @param node The StridedSliceGrad node to process.
* @param [unused] The equivalence class of the node. (currently unused in this function)
* @return nullptr, indicating the node was processed (or not modified).
*/
const AnfNodePtr ConstToAttrStridedSliceGradPass::Process(const FuncGraphPtr &graph, const AnfNodePtr &node,
const EquivPtr &) const {
// Ensure inputs are not null.
MS_EXCEPTION_IF_NULL(graph);
MS_EXCEPTION_IF_NULL(node);
auto strided_slice_grad = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(strided_slice_grad);
auto ms_context = MsContext::GetInstance();
MS_EXCEPTION_IF_NULL(ms_context);
// Check if the processing is specific to Ascend device.
if (ms_context->get_param<std::string>(MS_CTX_DEVICE_TARGET) == kAscendDevice) {
// Validate the node's attributes.
if (!CheckAttrs(strided_slice_grad)) {
MS_LOG(INFO) << "Check strided_slice_grad's attrs failed, graph not changed";
return nullptr;
}
ValuePtrList strides_values;
// Extract the stride values.
if (!GetStridesValues(strided_slice_grad, &strides_values)) {
return nullptr;
}
// Validate the extracted stride values.
if (!CheckValues(strides_values)) {
MS_LOG(INFO) << "Check strides' values failed, graph not changed";
return nullptr;
}
}
// Convert node's constant inputs to attributes.
ConstInputToAttr(strided_slice_grad, {1, 2, 3, 4});
return nullptr;
}
} // end of the namespace
} // namespace opt
} // namespace mindspore

View File

@ -27,29 +27,67 @@ namespace {
constexpr size_t kCNodePrimitiveIdx = 0;
}
/**
* @brief This class aims to transform the convolution transpose (Deconvolution) operator
* into a convolution backpropagation to the input operator.
*/
/**
* @brief Defines the pattern to be matched for this transformation.
*
* The pattern looks for a sequence of nodes where the main operation is a Convolution Transpose operation.
*
* @return A VectorRef containing the pattern to be matched.
*/
const BaseRef ConvTransposeToConvBackpropInputPass::DefinePattern() const {
// A variable representing a sequence of nodes.
VarPtr Xs = std::make_shared<SeqVar>();
// Represents the Conv2DTranspose operation.
auto conv_transpose = std::make_shared<Primitive>(kConv2DTransposeOpName);
// Define the pattern: The Conv2DTranspose operation followed by any sequence of nodes.
return VectorRef({conv_transpose, Xs});
}
/**
* @brief Processes the matched node and transforms it as required.
*
* If the provided node matches the pattern (i.e., it is a Convolution Transpose operation),
* this function renames it to Convolution Backpropagation to the input operation.
*
* @param graph The function graph that the node belongs to.
* @param node The matched node to be processed.
* @param An equivalence mapping (not used in this function but retained for consistency).
*
* @return The processed node (either transformed or untouched).
*/
const AnfNodePtr ConvTransposeToConvBackpropInputPass::Process(const FuncGraphPtr &graph, const AnfNodePtr &node,
const EquivPtr &) const {
// Basic null checks.
MS_EXCEPTION_IF_NULL(graph);
MS_EXCEPTION_IF_NULL(node);
// Cast the node to a computational node.
auto conv_transpose = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(conv_transpose);
// Ensure the computational node has inputs.
if (conv_transpose->inputs().empty()) {
MS_LOG(EXCEPTION) << "Cnode inputs should not be empty, cnode: " << node->DebugString()
<< trace::DumpSourceLines(conv_transpose);
}
// Extract the main operation of the computational node.
auto prim = GetValueNode<PrimitivePtr>(conv_transpose->input(kCNodePrimitiveIdx));
MS_EXCEPTION_IF_NULL(prim);
// Rename the operation to Conv2DBackpropInput (Convolution Backpropagation to the input).
prim->Named::operator=(Named(kConv2DBackpropInputOpName));
// Return the modified node.
return node;
}
} // namespace opt
} // namespace mindspore

View File

@ -23,27 +23,49 @@
namespace mindspore {
namespace opt {
/**
* @brief Converts attributes of a node to a unified format for MindIR.
*
* This function checks a node's attributes and, if necessary, converts them into a format
* that is suitable for MindIR representation. This standardization aids in serialization
* and further processing.
*
* @param node The node whose attributes are to be processed and converted.
*
* @return The processed CNode with converted attributes or nullptr if the node isn't suitable.
*/
const AnfNodePtr ConvertAttrToUnifyMindIR::Process(const FuncGraphPtr &, const AnfNodePtr &node,
const EquivPtr &) const {
// Check if the node is valid and is a real computational node.
if (node == nullptr || !AnfUtils::IsRealCNodeKernel(node)) {
return nullptr;
}
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
// Extract the operator from the computational node.
auto inputs = cnode->inputs();
AnfNodePtr op = inputs[0];
MS_EXCEPTION_IF_NULL(op);
// If the operator is a primitive value, process its attributes.
if (IsValueNode<Primitive>(op)) {
auto prim = GetValueNode<PrimitivePtr>(op);
MS_EXCEPTION_IF_NULL(prim);
auto attrs = prim->attrs();
std::string type_name = prim->name();
// Iterate over the attributes of the primitive.
for (auto attr : attrs) {
// Attempt to convert the attribute value to a string representation.
bool converted = CheckAndConvertUtils::ConvertAttrValueToString(type_name, attr.first, &attr.second);
if (converted) {
prim->set_attr(attr.first, attr.second);
}
// Attempt to convert intermediate representation attributes to operator attributes.
bool converted_ir_attr = CheckAndConvertUtils::CheckIrAttrtoOpAttr(type_name, attr.first, &attr.second);
if (converted_ir_attr) {
prim->set_attr(attr.first, attr.second);
@ -53,5 +75,6 @@ const AnfNodePtr ConvertAttrToUnifyMindIR::Process(const FuncGraphPtr &, const A
return node;
}
} // namespace opt
} // namespace mindspore

View File

@ -24,53 +24,61 @@
namespace mindspore {
namespace opt {
/**
* @brief Converts certain constant inputs of a computational node to node attributes.
*
* For specific operators, this function checks if they have constant inputs that need to be
* converted to attributes. Such conversions are essential for some backend compilations.
*
* @param node The node to be processed.
*
* @return Processed CNode or nullptr if no conversion occurred.
*/
const AnfNodePtr ConvertConstInputToAttr::Process(const FuncGraphPtr &, const AnfNodePtr &node,
const EquivPtr &) const {
// Check if the node is valid and is a real computational node.
if (node == nullptr || !AnfUtils::IsRealCNodeKernel(node)) {
return nullptr;
}
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
// Obtain the relevant input-to-attribute conversion registry for the node's operator.
ConstInputToAttrInfoRegister reg;
if (!ConstInputToAttrInfoRegistry::Instance().GetRegisterByOpName(common::AnfAlgo::GetCNodeName(cnode), &reg)) {
return nullptr;
}
// Specific checks for certain node types.
if (common::AnfAlgo::GetCNodeName(cnode) == prim::kPrimEmbeddingLookup->name() ||
common::AnfAlgo::GetCNodeName(cnode) == prim::kPrimEmbeddingLookupCommGrad->name()) {
if (!common::AnfAlgo::HasNodeAttr(kAttrPrimitiveTarget, cnode)) {
return nullptr;
}
}
auto ms_context = MsContext::GetInstance();
MS_EXCEPTION_IF_NULL(ms_context);
// Get the device target from context.
auto device = ms_context->get_param<std::string>(MS_CTX_DEVICE_TARGET);
if (common::AnfAlgo::GetCNodeName(cnode) == prim::kPrimGatherD->name()) {
if (device != kGPUDevice) {
return nullptr;
}
}
// Check if the node handles dynamic shapes.
if (common::AnfAlgo::IsDynamicShape(cnode)) {
if (device == kGPUDevice) {
if (DynamicShapeConstInputToAttrGPU.find(common::AnfAlgo::GetCNodeName(cnode)) ==
DynamicShapeConstInputToAttrGPU.end()) {
MS_LOG(INFO) << "current node is dynamic shape " << cnode->fullname_with_scope();
return nullptr;
}
} else if (device == kCPUDevice) {
if (DynamicShapeConstInputToAttrCPU.find(common::AnfAlgo::GetCNodeName(cnode)) ==
DynamicShapeConstInputToAttrCPU.end()) {
MS_LOG(INFO) << "current node is dynamic shape " << cnode->fullname_with_scope();
return nullptr;
}
} else {
if (DynamicShapeConstInputToAttr.find(common::AnfAlgo::GetCNodeName(cnode)) ==
DynamicShapeConstInputToAttr.end()) {
MS_LOG(INFO) << "current node is dynamic shape " << cnode->fullname_with_scope();
return nullptr;
}
// Ensure the operator can handle dynamic shape based on the device.
if (!IsSupportedDynamicShapeOp(device, common::AnfAlgo::GetCNodeName(cnode))) {
MS_LOG(INFO) << "current node is dynamic shape " << cnode->fullname_with_scope();
return nullptr;
}
}
// Specific checks for Ascend device.
if (device == kAscendDevice &&
NeedConvertToValueNodeSet.find(common::AnfAlgo::GetCNodeName(cnode)) != NeedConvertToValueNodeSet.end() &&
!common::AnfAlgo::HasNodeAttr(kAttrNeedConvertToValueNode, cnode)) {
@ -82,9 +90,22 @@ const AnfNodePtr ConvertConstInputToAttr::Process(const FuncGraphPtr &, const An
return nullptr;
}
// Convert the designated constant inputs to attributes.
ConstInputToAttr(cnode, reg.GetConstInputAttrInfo());
return node;
}
// Helper function: Check if an operator can handle dynamic shape for a specific device.
bool IsSupportedDynamicShapeOp(const std::string &device, const std::string &op_name) {
if (device == kGPUDevice) {
return DynamicShapeConstInputToAttrGPU.find(op_name) != DynamicShapeConstInputToAttrGPU.end();
} else if (device == kCPUDevice) {
return DynamicShapeConstInputToAttrCPU.find(op_name) != DynamicShapeConstInputToAttrCPU.end();
} else {
return DynamicShapeConstInputToAttr.find(op_name) != DynamicShapeConstInputToAttr.end();
}
}
} // namespace opt
} // namespace mindspore

View File

@ -29,55 +29,109 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Converts a ValueNode containing a Scalar or ValueTuple to a tensor node.
*
* If the provided node contains a Scalar or ValueTuple value, this function
* will convert that value into a Tensor and encapsulate it in a new ValueNode.
*
* @param kernel_graph The computational graph the node belongs to.
* @param input_node The node containing Scalar or ValueTuple to be converted.
*
* @return A ValueNode containing the Tensor representation or nullptr if conversion fails.
*/
AnfNodePtr CreateTensorInput(const KernelGraphPtr &kernel_graph, const AnfNodePtr &input_node) {
// Validate if the provided node is not null.
MS_EXCEPTION_IF_NULL(input_node);
auto value_node = input_node->cast<ValueNodePtr>();
MS_EXCEPTION_IF_NULL(value_node);
// Extract the value held by the ValueNode.
auto value = value_node->value();
MS_EXCEPTION_IF_NULL(value);
tensor::TensorPtr tensor_ptr = nullptr;
// Convert the value to a tensor.
// If it's a Scalar, use ScalarToTensor function.
if (value->isa<Scalar>()) {
tensor_ptr = ScalarToTensor(value->cast<ScalarPtr>());
} else if (value->isa<ValueTuple>()) {
}
// If it's a ValueTuple, use CreateTupleTensor function.
else if (value->isa<ValueTuple>()) {
tensor_ptr = CreateTupleTensor(value->cast<ValueTuplePtr>());
} else {
}
// Throw an exception if the value is neither Scalar nor ValueTuple.
else {
MS_LOG(EXCEPTION) << "The value should be a scalar or value tuple";
}
// If tensor conversion fails, return nullptr.
if (tensor_ptr == nullptr) {
MS_LOG(DEBUG) << "Create tensor failed";
return nullptr;
}
// Wrap the tensor in a new ValueNode.
auto tensor_input = std::make_shared<ValueNode>(tensor_ptr);
MS_EXCEPTION_IF_NULL(tensor_input);
tensor_input->set_abstract(tensor_ptr->ToAbstract());
// If a KernelGraph is provided, add the new ValueNode to the graph.
if (kernel_graph != nullptr) {
tensor_input = kernel_graph->NewValueNode(tensor_input);
kernel_graph->AddValueNodeToGraph(tensor_input);
} else {
tensor_input = MakeValueNode(tensor_input);
}
// Assign the original node's scope to the new ValueNode.
tensor_input->set_scope(input_node->scope());
return tensor_input;
}
} // namespace
/**
* @brief Convert const inputs of a CNode to tensor inputs.
*
* If a given computational node has Scalar or ValueTuple as inputs,
* this function ensures those inputs are converted into Tensors.
*
* @param func_graph The computational graph the node belongs to.
* @param cnode The node whose const inputs need to be converted.
*
* @return A new CNode with converted tensor inputs or nullptr if no conversion occurred.
*/
AnfNodePtr ConvertConstInputToTensorInput::ConstInputToTensorInput(const FuncGraphPtr &func_graph,
const CNodePtr &cnode) const {
MS_EXCEPTION_IF_NULL(func_graph);
MS_EXCEPTION_IF_NULL(cnode);
// A set of node types that shouldn't be converted.
const std::set<std::string> no_need_to_convert_nodes = {kStackOpName};
auto node_type = common::AnfAlgo::GetCNodeName(cnode);
if (no_need_to_convert_nodes.find(node_type) != no_need_to_convert_nodes.end()) {
return nullptr;
}
std::vector<AnfNodePtr> new_inputs;
auto kernel_graph = func_graph->cast<std::shared_ptr<session::KernelGraph>>();
auto inputs = cnode->inputs();
// Keep the first input (typically the operator node) unchanged.
new_inputs.push_back(inputs[0]);
bool need_update = false;
// the first input is primitive node which is not the real input
// Check each input for Scalar or ValueTuple.
for (size_t i = 0; i < inputs.size() - 1; ++i) {
auto input_node = inputs[i + 1];
// Convert Scalar or ValueTuple inputs to Tensor inputs.
if (IsValueNode<Scalar>(input_node) || IsValueNode<ValueTuple>(input_node)) {
auto tensor_input = CreateTensorInput(kernel_graph, input_node);
if (tensor_input == nullptr) {
@ -90,8 +144,9 @@ AnfNodePtr ConvertConstInputToTensorInput::ConstInputToTensorInput(const FuncGra
new_inputs.push_back(input_node);
}
}
// If any inputs were converted, create a new CNode with updated inputs.
if (need_update) {
MS_EXCEPTION_IF_NULL(func_graph);
auto new_cnode = NewCNode(new_inputs, func_graph);
MS_EXCEPTION_IF_NULL(new_cnode);
if (common::AnfAlgo::CheckPrimitiveType(cnode, prim::kPrimDepend)) {
@ -109,17 +164,29 @@ AnfNodePtr ConvertConstInputToTensorInput::ConstInputToTensorInput(const FuncGra
return nullptr;
}
/**
* @brief Entry function to initiate the conversion of const inputs to tensor inputs.
*
* @param func_graph The computational graph the node belongs to.
* @param node The node to be processed.
* @param equiv Placeholder for equivalence classes in pattern matching (unused in this function).
*
* @return Processed CNode or nullptr if no conversion occurred.
*/
const AnfNodePtr ConvertConstInputToTensorInput::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
if (node == nullptr || func_graph == nullptr || common::AnfAlgo::CheckPrimitiveType(node, prim::kPrimTupleGetItem) ||
if (node == nullptr || func_graph == nullptr ||
common::AnfAlgo::CheckPrimitiveType(node, prim::kPrimTupleGetItem) ||
common::AnfAlgo::CheckPrimitiveType(node, prim::kPrimMakeTuple)) {
return nullptr;
}
if (!node->isa<CNode>()) {
return nullptr;
}
return ConstInputToTensorInput(func_graph, node->cast<CNodePtr>());
}
} // namespace opt
} // namespace mindspore

View File

@ -25,64 +25,119 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Create a tensor input from a given scalar node.
*
* If the provided node is a ValueNode containing a Scalar, this function
* converts that Scalar into a Tensor and wraps it in a new ValueNode.
*
* @param kernel_graph The computational graph the node belongs to.
* @param input_node The scalar node to be converted.
*
* @return A ValueNode containing the Tensor representation or nullptr for non-scalars.
*/
AnfNodePtr CreateTensorInput(const KernelGraphPtr &kernel_graph, const AnfNodePtr &input_node) {
// Ensure the provided node is not null.
MS_EXCEPTION_IF_NULL(input_node);
// If the input_node is not a ValueNode, return nullptr.
if (!input_node->isa<ValueNode>()) {
return nullptr;
}
auto value_node = input_node->cast<ValueNodePtr>();
MS_EXCEPTION_IF_NULL(value_node);
// Extract the value held by the ValueNode.
auto value = value_node->value();
MS_EXCEPTION_IF_NULL(value);
// If the value is not a Scalar, return nullptr.
if (!value->isa<Scalar>()) {
return nullptr;
}
// Convert the scalar value to a tensor.
tensor::TensorPtr tensor_ptr = ScalarToTensor(value->cast<ScalarPtr>());
if (tensor_ptr == nullptr) {
MS_LOG(WARNING) << "Create tensor of" << input_node->DebugString() << "failed";
return nullptr;
}
// Wrap the tensor in a new ValueNode.
auto tensor_input = std::make_shared<ValueNode>(tensor_ptr);
MS_EXCEPTION_IF_NULL(tensor_input);
tensor_input->set_abstract(tensor_ptr->ToAbstract());
// If a KernelGraph is provided, add the new ValueNode to the graph.
if (kernel_graph != nullptr) {
tensor_input = kernel_graph->NewValueNode(tensor_input);
kernel_graph->AddValueNodeToGraph(tensor_input);
} else {
tensor_input = MakeValueNode(tensor_input);
}
// Assign the original node's scope to the new ValueNode.
tensor_input->set_scope(input_node->scope());
return tensor_input;
}
} // namespace
/**
* @brief Convert nodes with scalar values to tensor nodes.
*
* For a given computational node, if the node has scalar values as inputs,
* this function will convert these scalars into tensors.
*
* @param func_graph The computational graph the node belongs to.
* @param node The node whose scalar inputs need to be converted.
* @param equiv Placeholder for equivalence classes in pattern matching (unused in this function).
*
* @return A new CNode with converted tensor inputs or nullptr if no conversion occurred.
*/
const AnfNodePtr ConvertConstScalarToTensor::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
// Check if the node is null, the function graph is null or if the node is of type TupleGetItem.
if (node == nullptr || func_graph == nullptr || common::AnfAlgo::CheckPrimitiveType(node, prim::kPrimTupleGetItem)) {
return nullptr;
}
// input is scalar, and link to graph return
// If the node is the graph's output and is a scalar, convert it to a tensor.
if (node->isa<ValueNode>() && node == func_graph->output()) {
return CreateTensorInput(func_graph->cast<KernelGraphPtr>(), node);
}
// If the node is not a CNode, return nullptr.
if (!node->isa<CNode>()) {
return nullptr;
}
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
bool input_changed = false;
// Iterate over all inputs of the CNode.
for (size_t i = 0; i < cnode->inputs().size(); ++i) {
// If the input is a scalar, convert it to a tensor.
auto new_input = CreateTensorInput(func_graph->cast<KernelGraphPtr>(), cnode->inputs()[i]);
if (new_input != nullptr) {
cnode->set_input(i, new_input);
input_changed = true;
}
}
// If no inputs were converted or the graph isn't of type KernelGraph, return nullptr.
auto kernel_graph = func_graph->cast<KernelGraphPtr>();
if (kernel_graph == nullptr || !input_changed) {
return nullptr;
}
// Return a new CNode with converted tensor inputs.
return NewCNode(cnode, kernel_graph);
}
} // namespace opt
} // namespace mindspore

View File

@ -24,16 +24,32 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Split the inputs of a tuple into separate inputs.
*
* If the provided node outputs a tuple, this function extracts each element of the tuple
* and appends them to the provided plant_inputs vector.
*
* @param graph The computational graph the node belongs to.
* @param tuple_input The tuple node whose elements need to be extracted.
* @param plant_inputs A vector where the extracted tuple elements will be appended.
*
* @return Number of extracted tuple elements or -1 if not a tuple output.
*/
int64_t SplitTupleInputs(const FuncGraphPtr &graph, const AnfNodePtr &tuple_input,
std::vector<AnfNodePtr> *plant_inputs) {
// Ensure provided node is a tuple output.
if (!common::AnfAlgo::IsTupleOutput(tuple_input)) {
auto abs = tuple_input->abstract();
MS_EXCEPTION_IF_NULL(abs);
MS_LOG(WARNING) << "The Function only split the output type is tuple type but got" << abs->ToString();
return -1;
}
MS_EXCEPTION_IF_NULL(plant_inputs);
auto input_size = common::AnfAlgo::GetOutputTensorNum(tuple_input);
// If tuple_input is a MakeTuple node, extract its inputs directly.
if (tuple_input->isa<CNode>() && common::AnfAlgo::CheckPrimitiveType(tuple_input, prim::kPrimMakeTuple)) {
auto make_tuple = tuple_input->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(make_tuple);
@ -46,27 +62,47 @@ int64_t SplitTupleInputs(const FuncGraphPtr &graph, const AnfNodePtr &tuple_inpu
}
return input_size;
}
// For general tuple inputs, create TupleGetItem nodes for each element.
for (size_t index = 0; index < input_size; ++index) {
auto dynamic_input_node = CreatTupleGetItemNode(graph, tuple_input, index);
(void)plant_inputs->emplace_back(dynamic_input_node);
}
return input_size;
}
/**
* @brief Convert MakeTuple inputs of a CNode to individual inputs.
*
* For a given CNode, if any of its inputs are of type MakeTuple, this function will
* split the tuple and replace the MakeTuple input with its individual elements.
*
* @param graph The computational graph the CNode belongs to.
* @param cnode_ptr The CNode whose inputs need processing.
*/
void ConvertMakeTupleInputToPlantInputs(const FuncGraphPtr &graph, const CNodePtr &cnode_ptr) {
MS_EXCEPTION_IF_NULL(cnode_ptr);
MS_EXCEPTION_IF_NULL(graph);
// If the CNode is either a Call or Partial node, no processing is required.
if (common::AnfAlgo::CheckPrimitiveType(cnode_ptr, prim::kPrimCall) ||
common::AnfAlgo::CheckPrimitiveType(cnode_ptr, prim::kPrimPartial)) {
return;
}
std::vector<AnfNodePtr> plant_inputs;
std::vector<int64_t> dyn_input_sizes;
// Always add the CNode's primitive to the new input list.
plant_inputs.push_back(common::AnfAlgo::GetCNodePrimitiveNode(cnode_ptr));
size_t input_num = cnode_ptr->inputs().size() - 1;
for (size_t i = 0; i < input_num; ++i) {
auto input_node = common::AnfAlgo::GetInputNode(cnode_ptr, i);
MS_EXCEPTION_IF_NULL(input_node);
// If the input is a tuple output, split and append its elements to plant_inputs.
if (common::AnfAlgo::IsTupleOutput(input_node)) {
(void)dyn_input_sizes.emplace_back(SplitTupleInputs(graph, input_node, &plant_inputs));
} else {
@ -74,27 +110,54 @@ void ConvertMakeTupleInputToPlantInputs(const FuncGraphPtr &graph, const CNodePt
plant_inputs.push_back(input_node);
}
}
// If there is dynamic input, set the dyn_input_sizes as an attribute and update the inputs.
// If there are dynamic inputs, update the CNode's attributes and inputs.
if (std::any_of(dyn_input_sizes.begin(), dyn_input_sizes.end(), [](int64_t s) { return s >= 0; })) {
common::AnfAlgo::SetNodeAttr(kAttrDynInputSizes, MakeValue(dyn_input_sizes), cnode_ptr);
cnode_ptr->set_inputs(plant_inputs);
}
}
} // namespace
/**
* @brief Define the pattern to be matched.
*
* This function returns a vector pattern where the first item is a variable
* and the subsequent items are a sequence of variables.
*
* @return A BaseRef representing the defined pattern.
*/
const BaseRef ConvertTupleInputToDynamicInput::DefinePattern() const {
VarPtr V = std::make_shared<Var>();
VarPtr Xs = std::make_shared<SeqVar>();
return VectorRef({V, Xs});
}
/**
* @brief Converts MakeTuple inputs of a CNode to individual dynamic inputs.
*
* If a node in the computational graph has MakeTuple type inputs, this function ensures
* that those inputs are converted to individual dynamic inputs.
*
* @param func_graph The computational graph the node belongs to.
* @param node The node to be processed.
* @param equiv Placeholder for equivalence classes in pattern matching (unused in this function).
*
* @return The original node after potential modification.
*/
const AnfNodePtr ConvertTupleInputToDynamicInput::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
// Validate if the given node is not null, is of type CNode, and is a real kernel.
if (node == nullptr || !node->isa<CNode>() || !AnfUtils::IsRealKernel(node)) {
return nullptr;
}
// Convert MakeTuple inputs of the CNode to individual dynamic inputs.
ConvertMakeTupleInputToPlantInputs(func_graph, node->cast<CNodePtr>());
return node;
}
} // namespace opt
} // namespace mindspore

View File

@ -25,69 +25,139 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Converts a tuple input to a MakeTuple node.
*
* If a given node has a tuple output, this function ensures its representation
* is a MakeTuple node. This is especially useful for operations that expect
* their inputs to be explicitly represented as MakeTuple nodes, instead of
* implicit tuple structures.
*
* @param graph The computational graph the node belongs to.
* @param tuple_anf The node that potentially has tuple output.
*
* @return An AnfNode pointer which is either the original node or a MakeTuple representation.
*/
AnfNodePtr ConvertTupleInputToMakeTuple(const FuncGraphPtr &graph, const AnfNodePtr &tuple_anf) {
// Ensure provided nodes and graph are not null.
MS_EXCEPTION_IF_NULL(tuple_anf);
MS_EXCEPTION_IF_NULL(graph);
// If the node does not produce a tuple output, return it as-is.
if (!common::AnfAlgo::IsTupleOutput(tuple_anf)) {
return tuple_anf;
}
// Initially consider the graph as a KernelGraph.
auto kernel_graph = graph->cast<KernelGraphPtr>();
FuncGraphPtr anf_graph = tuple_anf->func_graph();
// If the tuple node is associated with a functional graph,
// attempt to cast it to a KernelGraph.
if (anf_graph != nullptr) {
kernel_graph = anf_graph->cast<KernelGraphPtr>();
}
MS_EXCEPTION_IF_NULL(kernel_graph);
// Check if the tuple node is already mapped to a MakeTuple node.
if (kernel_graph->FindTupleParameterToMakeTupleMap(tuple_anf)) {
return kernel_graph->FindTupleParameterToMakeTupleMap(tuple_anf);
}
// Convert the tuple node to a MakeTuple node.
auto make_tuple = kernel_graph->TransTupleToMakeTuple(tuple_anf);
MS_EXCEPTION_IF_NULL(make_tuple);
// Store the mapping from the original tuple node to the new MakeTuple node.
kernel_graph->InsertTupleParameterToMakeTupleMap(tuple_anf, make_tuple);
// replace graph inputs if input is a parameter
// If the tuple node was an input to the graph, replace it with the MakeTuple node.
kernel_graph->ReplaceGraphInput(tuple_anf, make_tuple);
return make_tuple;
}
} // namespace
/**
* @brief Define the pattern to be matched.
*
* This function returns a vector pattern where the first item is a variable
* and the subsequent items are a sequence of variables.
*
* @return A BaseRef representing the defined pattern.
*/
const BaseRef ConvertTupleOutputToMaketuple::DefinePattern() const {
VarPtr V = std::make_shared<Var>();
VarPtr Xs = std::make_shared<SeqVar>();
return VectorRef({V, Xs});
}
/**
* @brief Converts tuple outputs of nodes to MakeTuple nodes.
*
* If a node in the computational graph outputs a tuple, this function ensures
* its representation is a MakeTuple node. If the node's input is a tuple output,
* this function replaces that input with a MakeTuple node.
*
* @param func_graph The computational graph the node belongs to.
* @param node The node to be processed.
* @param equiv Placeholder for equivalence classes in pattern matching (unused in this function).
*
* @return A new CNode with replaced inputs if changes were made; otherwise, nullptr.
*/
const AnfNodePtr ConvertTupleOutputToMaketuple::Process(const FuncGraphPtr &func_graph, const AnfNodePtr &node,
const EquivPtr &) const {
// Validate if the given node is not null and is of type CNode.
if (node == nullptr || !node->isa<CNode>()) {
return nullptr;
}
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
// If the node fetches an item from a tuple, check its real input.
if (IsPrimitiveCNode(cnode, prim::kPrimTupleGetItem)) {
auto real_input = common::AnfAlgo::GetTupleGetItemRealInput(cnode);
MS_EXCEPTION_IF_NULL(real_input);
// If the real input isn't a parameter or a value node, return nullptr.
if (!real_input->isa<Parameter>() && !real_input->isa<ValueNode>()) {
return nullptr;
}
}
// If the node updates the state, no need to proceed further.
if (IsPrimitiveCNode(cnode, prim::kPrimUpdateState)) {
return nullptr;
}
bool cnode_input_changed = false;
// Iterate over all inputs of the node.
for (size_t i = 0; i < cnode->inputs().size(); ++i) {
const auto &input = cnode->inputs()[i];
// If the input produces a tuple output and is not of type kPrimCall,
// Convert that tuple input to a MakeTuple node.
if (input->Type() != nullptr && AnfUtils::IsRealKernel(input) && common::AnfAlgo::IsTupleOutput(input) &&
!common::AnfAlgo::CheckPrimitiveType(input, prim::kPrimCall)) {
cnode->set_input(i, ConvertTupleInputToMakeTuple(func_graph, input));
cnode_input_changed = true;
}
}
// If no inputs were changed or the graph isn't of type KernelGraph, return nullptr.
FuncGraphPtr graph = node->func_graph();
auto kernel_graph = graph->cast<KernelGraphPtr>();
if (kernel_graph == nullptr || !cnode_input_changed) {
return nullptr;
}
// Return a new CNode with replaced inputs.
return NewCNode(cnode, kernel_graph);
}
} // namespace opt
} // namespace mindspore

View File

@ -23,26 +23,48 @@
namespace mindspore {
namespace opt {
const AnfNodePtr CustomOpConstInputToAttr::Process(const FuncGraphPtr &, const AnfNodePtr &node,
const EquivPtr &) const {
/**
* @brief Convert constant inputs to attributes for a custom operation node.
*
* This function is responsible for identifying constant inputs in a custom operation node and
* converting them into operation attributes. This is useful in optimizing the representation
* of custom operations where some inputs could be better represented as attributes.
*
* @param func_graph The computational graph the node belongs to (unused in this snippet).
* @param node The node to be processed.
* @param equiv Placeholder for equivalence classes in pattern matching (unused in this snippet).
*
* @return Processed AnfNode pointer.
*/
const AnfNodePtr CustomOpConstInputToAttr::Process(const FuncGraphPtr &, const AnfNodePtr &node, const EquivPtr &) const {
// Validate if the given node is not null and if it's a real CNode kernel.
if (node == nullptr || !AnfUtils::IsRealCNodeKernel(node)) {
return nullptr;
}
// Cast the node to a CNode pointer.
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
// Ensure that the given CNode is a custom operation node.
if (!IsPrimitiveCNode(cnode, prim::kPrimCustom)) {
return nullptr;
}
// Get the indices in the CNode which correspond to custom operation attributes.
mindspore::HashSet<size_t> attr_indices;
GetCustomOpAttrIndex(common::AnfAlgo::GetCNodePrimitive(cnode), &attr_indices);
// If no custom attribute indices are identified, return early.
if (attr_indices.empty()) {
return nullptr;
}
// Convert constant inputs at the identified indices to attributes.
ConstInputToAttr(cnode, attr_indices);
return node;
}
} // namespace opt
} // namespace mindspore

View File

@ -27,10 +27,28 @@
namespace mindspore {
namespace opt {
namespace {
/**
* @brief Parses and sets default attribute values for a given primitive based on attribute type.
*
* This function is responsible for parsing different types of attributes from string representation
* and then setting them to the provided primitive. Supported attribute types include:
* "int", "str", "bool", "float", "listInt", "listStr", "listBool", and "listFloat".
*
* @param op_name Name of the operation.
* @param attr_name Name of the attribute.
* @param attr_value String representation of the attribute value.
* @param attr_type Type of the attribute.
* @param prim Pointer to the primitive where the parsed attribute should be set.
*
* @throws Exception when prim is null or when encountering unsupported attribute type.
*/
void ParseAttrDefaultValue(const std::string &op_name, const std::string &attr_name, const std::string &attr_value,
const std::string &attr_type, const PrimitivePtr &prim) {
// Ensure that the provided primitive is not null.
MS_EXCEPTION_IF_NULL(prim);
try {
// Parse and set the attribute based on its type.
if (attr_type == "int") {
prim->set_attr(attr_name, std::make_shared<Int64Imm>(std::stoi(attr_value)));
} else if (attr_type == "str") {
@ -45,103 +63,163 @@ void ParseAttrDefaultValue(const std::string &op_name, const std::string &attr_n
std::stringstream ss(attr_value);
std::string elem;
std::vector<ValuePtr> value;
// Parse comma-separated integers and add to the list.
while (std::getline(ss, elem, ',')) {
value.push_back(std::make_shared<Int64Imm>(std::stoi(elem)));
}
prim->set_attr(attr_name, std::make_shared<ValueList>(value));
} else if (attr_type == "listStr") {
std::stringstream ss(attr_value);
std::string elem;
std::vector<ValuePtr> value;
// Parse comma-separated strings and add to the list.
while (std::getline(ss, elem, ',')) {
value.push_back(std::make_shared<StringImm>(elem));
}
prim->set_attr(attr_name, std::make_shared<ValueList>(value));
} else if (attr_type == "listBool") {
std::stringstream ss(attr_value);
std::string elem;
std::vector<ValuePtr> value;
// Parse comma-separated booleans and add to the list.
while (std::getline(ss, elem, ',')) {
bool cur_value = false;
std::istringstream(elem) >> std::boolalpha >> cur_value;
value.push_back(std::make_shared<BoolImm>(cur_value));
}
prim->set_attr(attr_name, std::make_shared<ValueList>(value));
} else if (attr_type == "listFloat") {
std::stringstream ss(attr_value);
std::string elem;
std::vector<ValuePtr> value;
// Parse comma-separated floats and add to the list.
while (std::getline(ss, elem, ',')) {
value.push_back(std::make_shared<FP32Imm>(std::stof(elem)));
}
prim->set_attr(attr_name, std::make_shared<ValueList>(value));
} else {
// Unsupported attribute type.
MS_LOG(EXCEPTION) << "Unsupported attr type: " << attr_type;
}
} catch (const std::exception &e) {
// Handle exceptions during parsing and attribute setting.
MS_LOG(EXCEPTION) << "Parse attr [" << attr_name << "] of op [" << op_name << "] failed! attr type: " << attr_type
<< ", default value: " << attr_value << ", error message: " << e.what();
}
}
namespace {
/**
* @brief Add missing attributes with their default values to a given CNode based on Op registration info.
*
* @param cnode Pointer to the CNode.
* @param imply_type Type of kernel operation.
* @param missing_attrs Set of missing attribute names.
*
* @throws Exception if cnode or its primitive is null.
*/
void AddMissingAttrs(const CNodePtr &cnode, kernel::OpImplyType imply_type,
const std::unordered_set<std::string> &missing_attrs) {
// Ensure that the CNode is not null.
MS_EXCEPTION_IF_NULL(cnode);
// Retrieve the primitive associated with the CNode.
auto primitive = common::AnfAlgo::GetCNodePrimitive(cnode);
MS_EXCEPTION_IF_NULL(primitive);
// Clone the primitive for modifications.
primitive = primitive->Clone();
// Retrieve the operation name from the CNode.
auto op_name = common::AnfAlgo::GetCNodeName(cnode);
// Get the operation registration information based on the operation name and its type.
auto op_info_ptr = mindspore::kernel::OpLib::FindOp(op_name, imply_type);
MS_EXCEPTION_IF_NULL(op_info_ptr);
// Retrieve all the attributes associated with the operation.
auto all_attrs = op_info_ptr->attrs_ptr();
bool need_update = false;
for (const auto &attr : all_attrs) {
auto attr_name = attr->name();
// Skip attributes that are not missing.
if (missing_attrs.find(attr_name) == missing_attrs.end()) {
continue;
}
// If attr's param_type is required, it should have default value.
// If attr have default value, we should parse it no matter whether its param_type is required or not.
// Retrieve the default value for the attribute.
auto default_value = attr->default_value();
// If the attribute type isn't required and doesn't have a default value, continue to the next attribute.
if (default_value.empty() && attr->param_type() != "required") {
continue;
}
// If the attribute doesn't have a default value, raise an exception.
if (default_value.empty()) {
MS_LOG(EXCEPTION) << "attr [" << attr_name << "] in the registration information of op [" << op_name
<< "] does not have a value." << trace::DumpSourceLines(cnode);
}
// Parse and set the default attribute value to the primitive.
ParseAttrDefaultValue(op_name, attr_name, default_value, attr->type(), primitive);
// Indicate that an update to the CNode is needed.
need_update = true;
}
// If there were changes, update the primitive in the CNode.
if (need_update) {
cnode->set_input(kAnfPrimitiveIndex, NewValueNode(primitive));
}
}
} // namespace
const AnfNodePtr CustomOpRegInfoToAttr::Process(const FuncGraphPtr &, const AnfNodePtr &node, const EquivPtr &) const {
// Check for null node or non-real CNode kernels.
if (node == nullptr || !AnfUtils::IsRealCNodeKernel(node)) {
return nullptr;
}
// Convert the node to a CNode pointer.
auto cnode = node->cast<CNodePtr>();
MS_EXCEPTION_IF_NULL(cnode);
// Check if the CNode is of type kPrimCustom.
if (!IsPrimitiveCNode(cnode, prim::kPrimCustom)) {
return nullptr;
}
// Retrieve the primitive associated with the CNode.
auto primitive = common::AnfAlgo::GetCNodePrimitive(cnode);
MS_EXCEPTION_IF_NULL(primitive);
// Determine the function type of the CNode.
auto func_type = common::AnfAlgo::GetNodeAttr<std::string>(cnode, kAttrFuncType);
// AKG/AICPU need to process attr, TBE will process later in the json creating phase.
// If the node's function type is neither AKG nor AICPU, return early.
if (kCustomTypeAkg.find(func_type) == kCustomTypeAkg.end() || func_type == kCustomTypeAICPU) {
return nullptr;
}
// Early return if current node does not have attr
// Check if the CNode has any attributes. If not, return early.
auto attr_names = primitive->GetAttr(kAttrAttrNames);
if (attr_names == nullptr) {
return nullptr;
}
// Early return if all attr in reg info exist in the node's attr
// Check if all attributes in the registration info are present in the CNode's attributes.
std::unordered_set<std::string> missing_attrs;
auto attr_names_vec = GetValue<std::vector<std::string>>(attr_names);
for (const auto &name : attr_names_vec) {
@ -149,14 +227,23 @@ const AnfNodePtr CustomOpRegInfoToAttr::Process(const FuncGraphPtr &, const AnfN
(void)missing_attrs.insert(name);
}
}
// If no attributes are missing, return early.
if (missing_attrs.empty()) {
return nullptr;
}
// Determine the implication type for the operation.
kernel::OpImplyType imply_type =
func_type == kCustomTypeAICPU ? kernel::OpImplyType::kAICPU : kernel::OpImplyType::kAKG;
// Add the missing attributes to the CNode.
AddMissingAttrs(cnode, imply_type, missing_attrs);
return node;
}
} // namespace
} // namespace opt
} // namespace mindspore