Each participating device needs to decide a) if there is a next iteration, and b) if the loop terminates. We take the approach to encode this control flow logic in the dataflow graph. There are at least two possible encodings. In a completely decentralized encoding, the participants communicate peer to peer. The other encoding uses a frame leader (the participant who owns the pivot termination pre
| 1039 | // TODO(yuanbyu): The correctness of this construction is rather subtle. I got |
| 1040 | // it wrong many times so it would be nice to write a proof to be sure. |
| 1041 | Status AddControlFlow(const PartitionOptions& opts, Graph* g, |
| 1042 | GraphInfo* g_info) { |
| 1043 | Status status; |
| 1044 | GraphDefBuilder::Options bopts(g, &status); |
| 1045 | std::vector<ControlFlowInfo>& cf_info = g_info->cf_info; |
| 1046 | |
| 1047 | // Build the control flow info for every node. |
| 1048 | status = BuildControlFlowInfo(g, &cf_info); |
| 1049 | if (!status.ok()) return status; |
| 1050 | |
| 1051 | OptimizeControlFlowColocation(g); |
| 1052 | |
| 1053 | // The map from frames to their LoopCond nodes. |
| 1054 | std::unordered_map<string, Node*> frame_cond_map; |
| 1055 | int num_node_ids = g->num_node_ids(); |
| 1056 | for (int i = 0; i < num_node_ids; ++i) { |
| 1057 | Node* node = g->FindNodeId(i); |
| 1058 | if (node == nullptr) continue; |
| 1059 | |
| 1060 | if (IsLoopCond(node)) { |
| 1061 | const string& frame_name = cf_info[node->id()].frame_name; |
| 1062 | DCHECK(!frame_name.empty()); |
| 1063 | frame_cond_map[frame_name] = node; |
| 1064 | } |
| 1065 | } |
| 1066 | |
| 1067 | // Add all control loops for cross-device frames. |
| 1068 | // A control loop is added only when there is a cross-device edge in a |
| 1069 | // non-root frame. Nothing is added if there is no loops. We also don't |
| 1070 | // add anything for a frame that is completely local to a device. For |
| 1071 | // nested loops, we stack the control loops together by connecting |
| 1072 | // the merge of the outer loop to the enter of the inner loop. |
| 1073 | // |
| 1074 | // A map from <frame_name, device_name> to ControlLoop. |
| 1075 | std::unordered_map<string, ControlLoop> control_loops; |
| 1076 | int num_edge_ids = g->num_edge_ids(); |
| 1077 | for (int i = 0; i < num_edge_ids; ++i) { |
| 1078 | const Edge* edge = g->FindEdgeId(i); |
| 1079 | if (edge == nullptr) continue; |
| 1080 | |
| 1081 | const Node* src = edge->src(); |
| 1082 | const Node* dst = edge->dst(); |
| 1083 | // Skip Sink/Source nodes. |
| 1084 | if (!src->IsOp() || !dst->IsOp()) continue; |
| 1085 | |
| 1086 | const string& src_device = src->assigned_device_name(); |
| 1087 | const string& dst_device = dst->assigned_device_name(); |
| 1088 | // Skip local edges. |
| 1089 | if (src_device == dst_device) continue; |
| 1090 | |
| 1091 | const Node* src_frame = OutputFrame(src, cf_info); |
| 1092 | const Node* dst_frame = InputFrame(dst, cf_info); |
| 1093 | const string& src_frame_name = cf_info[src_frame->id()].frame_name; |
| 1094 | const string& dst_frame_name = cf_info[dst_frame->id()].frame_name; |
| 1095 | // Skip if src and dst are not in the same frame. |
| 1096 | if (src_frame_name.empty() || src_frame_name != dst_frame_name) { |
| 1097 | continue; |
| 1098 | } |
no test coverage detected