tvm
Loading...
Searching...
No Matches
axis_group_graph.h
Go to the documentation of this file.
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#ifndef TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
21#define TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
22
24#include <tvm/relax/expr.h>
25#include <tvm/s_tir/stmt.h>
28#include <tvm/tirx/function.h>
29
30#include <algorithm>
31#include <limits>
32#include <optional>
33#include <string>
34#include <tuple>
35#include <unordered_map>
36#include <unordered_set>
37#include <utility>
38#include <vector>
39
40namespace tvm {
41namespace tirx {
42// (var, axis)
43using TIRVarAxis = std::pair<Var, int>;
44// (tensor, axis)
45using TensorAxis = std::pair<TensorVar, int>;
47 public:
48 size_t operator()(const TensorAxis& tensor_axis) const {
49 size_t const h1(ffi::ObjectPtrHash()(tensor_axis.first));
50 size_t const h2(std::hash<int>()(tensor_axis.second));
51 return h1 ^ (h2 << 1);
52 }
53};
63ffi::Optional<Var> GetShardingVarFromIndex(PrimExpr index, ffi::Map<Var, Range> var_range,
64 const sym::Analyzer& analyzer);
65
71 public:
72 static std::vector<std::vector<TIRVarAxis>> GetTIRVarAxisGraph(const tirx::Function& function) {
73 auto extractor = ffi::make_object<TensorAxisGraphExtractor>();
74 extractor->Visit(function->body);
75 ffi::Map<TensorVar, Var> inverse_tensor_map;
76 for (const Var& param : function->params) {
77 if (param->ty.as<TensorTypeNode>()) {
78 inverse_tensor_map.Set(param.as_or_throw<TensorVar>(), param);
79 }
80 }
81 std::vector<std::vector<TIRVarAxis>> tir_var_axis_group_list;
82 std::unordered_set<TensorAxis, TensorAxisHash> visited;
83 for (const Var& param : function->params) {
84 if (!param->ty.as<TensorTypeNode>()) {
85 continue;
86 }
87 TensorVar tensor = param.as_or_throw<TensorVar>();
88 for (int i = 0; i < static_cast<int>(tensor->shape.size()); i++) {
89 if (extractor->tensor_axis_graph_.count({tensor, i})) {
90 std::vector<TensorAxis> tensor_axis_group;
91 extractor->DFSGraph({tensor, i}, &visited, &tensor_axis_group);
92 if (tensor_axis_group.size() <= 1) {
93 continue;
94 }
95 std::vector<TIRVarAxis> tir_var_axis_group;
96 for (const auto& tensor_axis : tensor_axis_group) {
97 if (!inverse_tensor_map.count(tensor_axis.first)) {
98 continue;
99 }
100 tir_var_axis_group.push_back(
101 {inverse_tensor_map[tensor_axis.first], tensor_axis.second});
102 }
103 tir_var_axis_group_list.push_back(tir_var_axis_group);
104 }
105 }
106 }
107 return tir_var_axis_group_list;
108 }
109
110 void DFSGraph(TensorAxis cur, std::unordered_set<TensorAxis, TensorAxisHash>* visited,
111 std::vector<TensorAxis>* tensor_axis_group) {
112 if (visited->count(cur)) {
113 return;
114 }
115 visited->insert(cur);
116 tensor_axis_group->push_back(cur);
117 for (const auto& next : tensor_axis_graph_[cur]) {
118 DFSGraph(next, visited, tensor_axis_group);
119 }
120 }
121
122 private:
123 ffi::Optional<VisitInterrupt> Visit_(const TensorStoreNode* op) final {
124 TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(s_tir::StmtExprVisitor::Visit_(op));
125 tensor_access_indices_.push_back({op->dest.as_or_throw<tvm::tirx::TensorVar>(), op->indices});
126
127 return std::nullopt;
128 }
129
130 ffi::Optional<VisitInterrupt> Visit_(const TensorLoadNode* op) final {
131 TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(s_tir::StmtExprVisitor::Visit_(op));
132 tensor_access_indices_.push_back({op->source.as_or_throw<tvm::tirx::TensorVar>(), op->indices});
133
134 return std::nullopt;
135 }
136
137 bool Match(PrimExpr a, PrimExpr tensor_shape_a, PrimExpr b, PrimExpr tensor_shape_b,
138 const sym::Analyzer& analyzer) {
139 if (b.as<PrimVar>()) {
140 std::swap(a, b);
141 std::swap(tensor_shape_a, tensor_shape_b);
142 }
143 auto prim_var = a.as<PrimVar>();
144 if (!prim_var) {
145 return false;
146 }
147 Var var = *prim_var;
148 analyzer->Bind(iter_var_range_);
149 b = analyzer->Simplify(b);
150 // index var `a` must access whole range of a specific tensor dimension
151 sym::IntSet intset_b = sym::EvalSet(b, sym::AsIntSet(iter_var_range_));
152 if (!analyzer->CanProveEqual(tensor_shape_a, iter_var_range_[var]->extent) ||
153 !intset_b.MatchRange(Range::FromMinExtent(0, tensor_shape_b))) {
154 return false;
155 }
156 auto matched_var = GetShardingVarFromIndex(b, iter_var_range_, analyzer);
157 if (!matched_var.has_value() || !matched_var.value().same_as(var)) {
158 return false;
159 }
160 return true;
161 }
162
163 ffi::Optional<VisitInterrupt> Visit_(const s_tir::SBlockNode* op) final {
164 if (op->name_hint == "root") {
166 }
167 tensor_access_indices_.clear();
168 TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(s_tir::StmtExprVisitor::Visit_(op));
169 iter_var_range_.clear();
170 for (const auto& iter_var : op->iter_vars) {
171 iter_var_range_.Set(iter_var->var, iter_var->dom.value());
172 }
173 sym::Analyzer analyzer;
174 for (const auto& access_pr : tensor_access_indices_) {
175 TensorVar tensor = access_pr.first;
176 ffi::Array<PrimExpr> indices = access_pr.second;
177 for (int i = 0; i < static_cast<int>(indices.size()); i++) {
178 for (const auto& another_access_pr : tensor_access_indices_) {
179 if (another_access_pr.first.same_as(tensor)) {
180 continue;
181 }
182 TensorVar another_tensor = another_access_pr.first;
183 ffi::Array<PrimExpr> another_indices = another_access_pr.second;
184 for (int j = 0; j < static_cast<int>(another_indices.size()); j++) {
185 if (Match(indices[i], tensor->shape[i], another_indices[j], another_tensor->shape[j],
186 analyzer)) {
187 JoinTensorAxis({tensor, i}, {another_tensor, j});
188 }
189 }
190 }
191 }
192 }
193
194 return std::nullopt;
195 }
196
197 void JoinTensorAxis(TensorAxis axis1, TensorAxis axis2) {
198 if (!tensor_axis_graph_.count(axis1)) {
199 tensor_axis_graph_[axis1] = {};
200 }
201 if (!tensor_axis_graph_.count(axis2)) {
202 tensor_axis_graph_[axis2] = {};
203 }
204 tensor_axis_graph_[axis1].push_back(axis2);
205 tensor_axis_graph_[axis2].push_back(axis1);
206 }
207
208 std::vector<std::pair<TensorVar, ffi::Array<PrimExpr>>> tensor_access_indices_;
209 std::unordered_map<TensorAxis, std::vector<TensorAxis>, TensorAxisHash> tensor_axis_graph_;
210 ffi::Map<Var, Range> iter_var_range_;
211 std::string func_name;
212};
213} // namespace tirx
214} // namespace tvm
215
216namespace tvm {
217namespace relax {
218namespace distributed {
219
221struct Axis {
222 const ExprNode* tensor;
223 int dim = 0;
224 int tuple_index = 0;
225
226 Axis(const ExprNode* tensor, int dim, int tuple_index = 0)
227 : tensor(tensor), dim(dim), tuple_index(tuple_index) {
228 TVM_FFI_ICHECK(tensor->IsInstance<GenericConstNode>() || tensor->IsInstance<VarNode>());
229 }
230
231 bool operator==(const Axis& other) const {
232 return tensor == other.tensor && dim == other.dim && tuple_index == other.tuple_index;
233 }
234};
235
236class AxisHash {
237 public:
238 size_t operator()(const Axis& axis) const {
239 size_t const h1(std::hash<const ExprNode*>()(axis.tensor));
240 size_t const h2(std::hash<int>()(axis.dim));
241 size_t const h3(std::hash<int>()(axis.tuple_index));
242 return h1 ^ (h2 << 1) ^ (h3 << 2);
243 }
244};
245
246using AxisGroup = std::unordered_set<Axis, AxisHash>;
247
248class AxisGroupHash {
249 public:
250 size_t operator()(const AxisGroup& axis_group) const {
251 size_t seed = 0;
252 for (auto axis : axis_group) {
253 seed ^= AxisHash()(axis) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
254 }
255 return seed;
256 }
257};
258
259using ShardingSpec = std::pair<DeviceMesh, Placement>;
260
261// device mesh and the device mesh axis that the tensor axis maps to
262using AxisShardingSpec = std::pair<DeviceMesh, int>;
263class AxisShardingSpecEqual {
264 public:
265 bool operator()(const AxisShardingSpec& lhs, const AxisShardingSpec& rhs) const {
266 return ffi::StructuralEqual()(lhs.first, rhs.first) && lhs.second == rhs.second;
267 }
268};
269
270class AxisShardingSpecHash {
271 public:
272 size_t operator()(const AxisShardingSpec& sharding_spec) const {
273 size_t seed = 0;
274 seed ^= ffi::StructuralHash()(sharding_spec.first);
275 seed ^= std::hash<int>()(sharding_spec.second) << 1;
276 return seed;
277 }
278};
279
285class AxisGroupGraph {
286 public:
287 enum class EdgeType { kAscend, kDescend, kSimbling };
288
289 private:
290 static EdgeType ReverseEdgeType(EdgeType type) {
291 switch (type) {
292 case EdgeType::kAscend:
293 return EdgeType::kDescend;
294 case EdgeType::kDescend:
295 return EdgeType::kAscend;
296 case EdgeType::kSimbling:
297 return EdgeType::kSimbling;
298 }
299 TVM_FFI_THROW(InternalError) << "Unreachable code";
300 throw;
301 }
302
303 static int GetEdgePriority(EdgeType type) {
304 switch (type) {
305 case EdgeType::kAscend:
306 return 0;
307 case EdgeType::kDescend:
308 return 2;
309 case EdgeType::kSimbling:
310 return 1;
311 }
312 TVM_FFI_THROW(InternalError) << "Unreachable code";
313 throw;
314 }
315
316 struct AxisGraphEdge {
317 Axis src;
318 Axis dst;
319
320 // the producer-consumer relationship between src tensor and dst tensor
321 // kAscend means consumer->producer
322 // kDescend means producer->consumer
323 // kSimbling means other cases
324 EdgeType type;
325
326 bool operator==(const AxisGraphEdge& other) const {
327 return src == other.src && dst == other.dst && type == other.type;
328 }
329 };
330
331 struct Path {
332 int direction = 0;
333
334 Path AddEdge(EdgeType type) { return {direction |= (1 << GetEdgePriority(type))}; }
335
336 int GetPriority() const {
337 switch (direction) {
338 case 1: // ascend only
339 return 0;
340 case 4: // descend only
341 return 2;
342 case 0: // empty path (source node)
343 return 3; // source node must have max priority
344 default: // mixed path
345 return 1;
346 }
347 }
348 };
349
350 public:
351 AxisGroupGraph() = default;
352
362 void JoinAxis(Axis axis1, Axis axis2, EdgeType type) {
363 AddEdge(axis1, axis2, type);
364 AddEdge(axis2, axis1, ReverseEdgeType(type));
365 }
366
372 void AddSrcShardingPoint(Axis axis, AxisShardingSpec spec) {
373 src_axis_sharding_spec_.insert_or_assign(axis, spec);
374 }
375
379 void PropagateShardingSpec() {
380 axis_sharding_specs_priority_.clear();
381 for (const auto& pr : src_axis_sharding_spec_) {
382 std::unordered_set<Axis, AxisHash> visited;
383 PropagateShardingSpec(pr.first, pr.second, Path(), &visited);
384 }
385 ChooseAxisShardingSpec();
386 }
387
394 void AddPropagationCutPoint(Axis axis, AxisShardingSpec spec) {
395 cutpoint_axis_sharding_spec_.insert_or_assign(axis, spec);
396 }
397
405 std::tuple<std::optional<AxisShardingSpec>, bool> GetAxisShardingSpec(Axis axis) {
406 if (axis_sharding_specs_priority_.count(axis)) {
407 return {axis_sharding_specs_priority_[axis].begin()->first, true};
408 } else {
409 return {std::nullopt, false};
410 }
411 }
412
413 private:
414 void AddEdge(Axis src, Axis dst, EdgeType type) {
415 if (!graph_.count(src)) {
416 graph_[src] = {};
417 }
418 graph_[src].push_back({src, dst, type});
419 }
420
421 void PropagateShardingSpec(Axis axis, AxisShardingSpec spec, Path path,
422 std::unordered_set<Axis, AxisHash>* visited) {
423 if (cutpoint_axis_sharding_spec_.count(axis) ||
424 (src_axis_sharding_spec_.count(axis) &&
425 !AxisShardingSpecEqual()(src_axis_sharding_spec_.at(axis), spec)) ||
426 visited->count(axis)) {
427 return;
428 }
429 visited->insert(axis);
430 if (!axis_sharding_specs_priority_.count(axis)) {
431 axis_sharding_specs_priority_[axis] = {};
432 }
433 axis_sharding_specs_priority_[axis][spec] = path.GetPriority();
434 for (auto edge : graph_[axis]) {
435 PropagateShardingSpec(edge.dst, spec, path.AddEdge(edge.type), visited);
436 }
437 }
438
439 void ChooseAxisShardingSpec() {
440 for (auto& pr : axis_sharding_specs_priority_) {
441 auto& axis = pr.first;
442 auto& specs = pr.second;
443 int max_priority = std::numeric_limits<int>::min();
444 for (auto& pr2 : specs) {
445 max_priority = std::max(max_priority, pr2.second);
446 }
447 for (auto it = specs.begin(); it != specs.end();) {
448 if (it->second != max_priority) {
449 it = specs.erase(it);
450 } else {
451 it++;
452 }
453 }
454 TVM_FFI_ICHECK(specs.size() == 1)
455 << "multiple possible sharding for axis: (" << ffi::GetRef<Expr>(axis.tensor) << ", "
456 << axis.dim << ")";
457 }
458 }
459
460 // union set
461 std::unordered_map<Axis, std::vector<AxisGraphEdge>, AxisHash> graph_;
462 std::unordered_map<Axis, AxisShardingSpec, AxisHash> src_axis_sharding_spec_;
463 std::unordered_map<Axis, AxisShardingSpec, AxisHash> cutpoint_axis_sharding_spec_;
464 std::unordered_map<
465 Axis, std::unordered_map<AxisShardingSpec, int, AxisShardingSpecHash, AxisShardingSpecEqual>,
466 AxisHash>
467 axis_sharding_specs_priority_;
468};
469
470using FBuildAxisGraph = std::function<void(const Var& output_var, const Call& call,
471 distributed::AxisGroupGraph* axis_group_graph)>;
472
473void BuildAxisGraphUnary(const Var& output_var, const Call& call,
474 distributed::AxisGroupGraph* axis_group_graph);
475void BuildAxisGraphBinary(const Var& output_var, const Call& call,
476 distributed::AxisGroupGraph* axis_group_graph);
477void BuildAxisGraphReduce(const Var& output_var, const Call& call,
478 distributed::AxisGroupGraph* axis_group_graph);
479void BuildAxisGraphMatmul(const Var& output_var, const Call& call,
480 distributed::AxisGroupGraph* axis_group_graph);
481void BuildAxisGraphPermuteDims(const Var& output_var, const Call& call,
482 distributed::AxisGroupGraph* axis_group_graph);
483void BuildAxisGraphReshape(const Var& output_var, const Call& call,
484 distributed::AxisGroupGraph* axis_group_graph);
485void BuildAxisGraphCallTIR(const Var& output_var, const Call& call, const tirx::Function& func,
486 distributed::AxisGroupGraph* axis_group_graph);
487
488} // namespace distributed
489} // namespace relax
490} // namespace tvm
491
492#endif // TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
Managed reference to CallNode.
Definition expr.h:972
Base type of all the expressions.
Definition base_expr.h:324
A constant whose payload is separate from its expression type.
Definition expr.h:66
Typed reference/view over any Expr whose ExprNode::ty is PrimType.
Definition base_expr.h:447
static TVM_TIRX_DLL Range FromMinExtent(PrimExpr min, PrimExpr extent, Location loc=UnknownLoc())
construct a new range with min and extent The corresponding constructor is removed,...
Store a primitive value to an indexed destination expression.
Definition stmt.h:308
A local variable in the IR.
Definition expr.h:694
Managed reference to VarNode.
Definition expr.h:714
Extend native TIRX traversal with schedulable block semantics.
Definition stmt_functor.h:76
virtual ffi::Optional< VisitInterrupt > Visit_(const SBlockNode *op)
Managed reference to AnalyzerObj.
Definition analyzer.h:916
Managed reference to FunctionNode.
Definition function.h:105
Construct an axis group graph from a tirx::Function. Two tensor axis are connected if they are access...
Definition axis_group_graph.h:70
static std::vector< std::vector< TIRVarAxis > > GetTIRVarAxisGraph(const tirx::Function &function)
Definition axis_group_graph.h:72
void DFSGraph(TensorAxis cur, std::unordered_set< TensorAxis, TensorAxisHash > *visited, std::vector< TensorAxis > *tensor_axis_group)
Definition axis_group_graph.h:110
Definition axis_group_graph.h:46
size_t operator()(const TensorAxis &tensor_axis) const
Definition axis_group_graph.h:48
Structural type of a TIRx tensor variable.
Definition type.h:64
ffi::Array< PrimExpr > shape
The type of the tensor prior to flattening.
Definition type.h:80
Checked zero-state view over an ordinary VarNode with TensorType.
Definition expr.h:50
Iterator quasi-affine mapping patterns.
IntSet EvalSet(PrimExpr e, const ffi::Map< Var, IntSet > &dom_map)
Find an symbolic integer set that contains all possible values of e given the domain of each variable...
ffi::Map< Var, sym::IntSet > AsIntSet(const ffi::Map< Var, Range > &var_dom)
Converts the Ranges to IntSets.
PrimVar var(std::string name_hint, PrimType t=PrimType::Int(32))
Construct a new Var expression.
std::pair< TensorVar, int > TensorAxis
Definition axis_group_graph.h:45
std::pair< Var, int > TIRVarAxis
Definition axis_group_graph.h:43
ffi::Optional< Var > GetShardingVarFromIndex(PrimExpr index, ffi::Map< Var, Range > var_range, const sym::Analyzer &analyzer)
Suppose we want to shard a tensor along a specific dimension, we need to know how to rewrite the acce...
An object that builds and maintains block scope and StmtSref mapping for Dependence analysis.
Definition attr.h:23
bool operator==(const PrimType &lhs, const PrimType &rhs)
Definition base_expr.h:314
Type definitions for DTensor (Distributed Tensor)
S-TIR (Schedulable TIR) statements and attributes.
Native traversal and statement dispatch for schedulable TIR.
TIR Function.