1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #ifndef TENSORFLOW_CORE_COMMON_RUNTIME_INLINE_FUNCTION_UTILS_H_
17 #define TENSORFLOW_CORE_COMMON_RUNTIME_INLINE_FUNCTION_UTILS_H_
18
19 #include <functional>
20 #include <memory>
21
22 #include "absl/types/optional.h"
23 #include "tensorflow/core/common_runtime/device.h"
24 #include "tensorflow/core/common_runtime/function_body.h"
25 #include "tensorflow/core/framework/function.h"
26 #include "tensorflow/core/graph/graph.h"
27 #include "tensorflow/core/protobuf/config.pb.h"
28
29 namespace tensorflow {
30
31 static constexpr const char* const kNoInlineAttr = "_noinline";
32
33 // Optionally override device assignment for nodes added to the graph for
34 // inlined functions:
35 // (1) Identity nodes added in place of function input arguments.
36 // (2) Identity nodes added in place of function return values.
37 // (3) Special NoOp nodes that enforce side-effects execution order.
38 // (4) All nodes inside function body specified in FunctionDef.
39 class InlinedFunctionBodyPlacer {
40 public:
41 virtual ~InlinedFunctionBodyPlacer() = default;
42
43 virtual absl::optional<string> InputNodeDevice(int input_index) const = 0;
44 virtual absl::optional<string> OutputNodeDevice(int output_index) const = 0;
45 // Returns true if the added input/output identity nodes should be colocated
46 // with the corresponding input/output from the function body.
47 virtual bool ColocateInputOutputIdentities() const = 0;
48 virtual absl::optional<string> ControlNodeDevice() const = 0;
49 virtual absl::optional<string> BodyNodeDevice(const NodeDef& ndef) const = 0;
50
51 // Place input nodes on the same device as the corresponding caller input
52 // node. Do not specify any placement for all other nodes.
53 static std::unique_ptr<InlinedFunctionBodyPlacer> DefaultPlacer(
54 const Graph& graph, const Node& caller);
55
56 // Place all nodes on the same device as caller node.
57 static std::unique_ptr<InlinedFunctionBodyPlacer> SingleDevicePlacer(
58 const Graph& graph, const Node& caller);
59
60 // Place input nodes on the same device as the corresponding caller input
61 // node. Do not place output node. Place control nodes on the same device as
62 // caller node. For all function body nodes overrides job, replica and task
63 // parts of the device assignment to match function caller node.
64 static std::unique_ptr<InlinedFunctionBodyPlacer> MultiDevicePlacer(
65 const Graph& graph, const Node& caller);
66
67 using Factory = std::function<std::unique_ptr<InlinedFunctionBodyPlacer>(
68 const Graph&, const Node&)>;
69
70 struct Config {
71 string name;
72 Factory get;
73 };
74
Default()75 static Config Default() { return {"default", DefaultPlacer}; }
SingleDevice()76 static Config SingleDevice() { return {"single_device", SingleDevicePlacer}; }
MultiDevice()77 static Config MultiDevice() { return {"multi_device", MultiDevicePlacer}; }
78 };
79
80 struct InlineFunctionBodyOptions {
81 // All nodes that have incoming control edge *from* the function call node,
82 // will be forwarded to the "output control node". There are two options for
83 // choosing which nodes will have a control edge *to* the "output control
84 // node":
85 // a) control returns (`control_ret` field in FunctionDef)
86 // b) data returns (`ret` field in FunctionDef)
87 enum class OutputControlSource { kDataOutputs, kControlOutputs };
88
89 // Keep a node in a graph with the same name as the function call node:
90 //
91 // a) DoNotKeep: Function call node is fully inlined, and there is no node in
92 // a graph with the same name.
93 //
94 // b) Fetchable: Add an IdentityN node to the graph in place of the inlined
95 // function call node. It will have a control edge from inlined
96 // 'output_control_node' and data edges from function output nodes.
97 // The IdentityN node will be placed on the same device as the caller node.
98 //
99 // This is mostly for compatibility with Tensorflow v1 and sessions.
100 // When we prepare a graph for execution in
101 // GraphExecutionState::MakeForBaseGraph we don't know what nodes will be
102 // fetched, so we can't safely remove any of them. When graph executed as a
103 // function it has 'Retval' nodes for all fetched tensors, and we can
104 // safely inline function calls.
105 //
106 // c) Targetable: Add a NoOp node to the graph in place of the inlined
107 // function call node. It will have a control edge from inline
108 // 'output_control_node' and no data edges. NoOp node will be placed on the
109 // same device as the caller node. This will keep the inlined function call
110 // node a valid 'session.run' target, and also will keep it a valid control
111 // output node.
112 enum class KeepCallerNode { kDoNotKeep, kFetchable, kTargetable };
113
114 // If 'true' function inlining is completely disabled. This allows to control
115 // function inlining for different types of function calls (see
116 // 'ExpandInlineFunctionsOptions' below).
117 bool disable_inlining = false;
118 // Ignore '_noinline' function attribute.
119 bool ignore_noinline = false;
120 // If 'true' function inlining will inline functions in implementation
121 // selection group. Normally those functions should not be inlined; they will
122 // be handled by Grappler.
123 bool inline_impl_selection_group_functions = false;
124 // Controls if we want to keep a node with the name as the function call node
125 // in a graph after function inlining.
126 KeepCallerNode keep_caller_node = KeepCallerNode::kDoNotKeep;
127 // For compatibility with Tensorflow v1 by default we will use data outputs.
128 // Control returns were added to Tensorflow v2 with automatic control
129 // dependencies tracking in Eager mode.
130 OutputControlSource output_control_src = OutputControlSource::kDataOutputs;
131 // Inlined function body placer decides what requested device assignments
132 // should be added to the nodes added to the graph. See documentation above
133 // for available strategies.
134 InlinedFunctionBodyPlacer::Config inlined_function_body_placer =
135 InlinedFunctionBodyPlacer::Default();
136 // If true, frame names in the function body will be
137 // made unique in the resulting graph (e.g. by prepending a unique prefix).
138 // NOTE(mrry): Only set this option to false when there is a single function
139 // call in the graph (e.g. when making a remote function call via
140 // ClusterFunctionLibraryRuntime). This option is provided because the graph
141 // partitioner generates frame names that must remain unmodified across all
142 // partitions of a multi-device function.
143 bool uniquify_frame_names = true;
144
145 // A human-readable debug string for this options.
146 string DebugString() const;
147 };
148
149 // Returns 'Status::OK()' iff the function '*fbody' can be inlined at 'node'
150 // based on the type signature of 'node' and 'fbody':
151 //
152 // (1) Caller node has the same number of inputs and outputs as the function.
153 // (2) Caller node inputs and outputs have the same data types as function
154 // inputs and returns.
155 // (3) Validation rules defined in InlineFunctionBodyOptions.
156 //
157 // If function can't be safely inlined, returns error message with details why
158 // inlining is not possible or safe.
159 Status ValidateInlining(const Node* node, const FunctionBody* fbody,
160 const InlineFunctionBodyOptions& options);
161
162 // Given a "caller" in graph "g", which is a function call of a function
163 // to "fbody". Replaces the "caller" with fbody->graph and connects
164 // edges properly. "override_device" specifies whether inlining should replace
165 // explicitly specified devices inside fbody with the callee's device.
166 //
167 // Returns 'Status::OK()' if function was successfully inlined into the graph.
168 // If function inlining is not possible returns an error with a reason, and
169 // leaves the graph in unmodified state.
170 Status InlineFunctionBody(const FunctionLibraryDefinition& flib_def, Graph* g,
171 Node* caller, const FunctionBody* fbody,
172 const InlineFunctionBodyOptions& options);
173
174 // There are three types of function calls that could be invoked during
175 // *Tensorflow graph execution*:
176 //
177 // 1) Native function call (node.type_string() is the function name). These
178 // functions are always executed on a single-device, which is the device of
179 // the function call node.
180 //
181 // 2) Multi-device function calls (PartitionedCall or StatefulPartitionedCall
182 // ops) can execute on multiple devices and accept DT_RESOURCE inputs that
183 // belong to different devices. This type of functions was added in
184 // Tensorflow 2.0 Eager mode, and it has control outputs to represent
185 // side-effects that must always execute (see `control_ret` in FunctionDef).
186 //
187 // 3) SymbolicGradient has been deprecated for a while, but we still keep it and
188 // use `native` options for inlining for compatibility.
189 //
190 // We need to have distinct inlining rules for compatibility with Tensorflow v1.
191 //
192 // There are few other places in Tensorflow that could execute functions:
193 //
194 // 1) common_runtime/eager/kernel_and_device.{h,cc} - executes "top level"
195 // functions directly via function library runtime, without going through
196 // the graph.
197 // 2) tf.data pipelines - also execute functions directly via function library
198 // runtime with custom executors.
199 struct ExpandInlineFunctionsOptions {
ExpandInlineFunctionsOptionsExpandInlineFunctionsOptions200 ExpandInlineFunctionsOptions() : native_options(), multi_device_options() {
201 using OutputControlSrc = InlineFunctionBodyOptions::OutputControlSource;
202 multi_device_options.output_control_src = OutputControlSrc::kControlOutputs;
203 }
204
205 InlineFunctionBodyOptions native_options;
206 InlineFunctionBodyOptions multi_device_options;
207 };
208
209 // WARNING(ezhulenev): PLEASE DO NOT USE THIS FUNCTION. This is a temporary
210 // workaround that will be enabled only during the function inlining unification
211 // (b/126811947). Contact ezhulenev@ if you think you need it.
212 // TODO(ezhulenev): Delete this function.
213 bool ExpandInlineFunctions(FunctionLibraryRuntime* lib, Graph* graph,
214 const ExpandInlineFunctionsOptions& options);
215
216 // For each node in "graph", if "lib" indicates that the node is a
217 // function call, inline the function body. Returns true if at least
218 // one node is inlined.
219 //
220 // This routine goes through "graph" nodes once and applies the
221 // inlining. The caller may decide to apply the inlining on "graph"
222 // multiple times by calling ExpandInlineFunctions a few times.
223 //
224 // Function calls that can't be safely inlined into the graph (ValidateInlining
225 // returns error), are ignored.
226 //
227 // TODO(ezhulenev): We do not FunctionLibraryRuntime for this. We need just the
228 // FunctionLibraryDefinition and FunctionDefToBodyHelper to implement this (see
229 // lower_function_call.cc).
ExpandInlineFunctions(FunctionLibraryRuntime * lib,Graph * graph)230 inline bool ExpandInlineFunctions(FunctionLibraryRuntime* lib, Graph* graph) {
231 return ExpandInlineFunctions(lib, graph, ExpandInlineFunctionsOptions());
232 }
233
234 struct LowerFunctionalOpsConstants {
235 static constexpr const char* const kLowerUsingSwitchMergeAttr =
236 "_lower_using_switch_merge";
237 static constexpr const char* const kLowerAsMultiDeviceFunctionAttr =
238 "_lower_as_multi_device_function";
239 };
240
241 } // end namespace tensorflow
242
243 #endif // TENSORFLOW_CORE_COMMON_RUNTIME_INLINE_FUNCTION_UTILS_H_
244