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
25#include <tvm/relax/expr.h>
26#include <tvm/tirx/function.h>
28
29#include <algorithm>
30#include <limits>
31#include <string>
32#include <tuple>
33#include <unordered_map>
34#include <unordered_set>
35#include <utility>
36#include <vector>
37
38namespace tvm {
39namespace tirx {
40// (var, axis)
41using TIRVarAxis = std::pair<Var, int>;
42// (buffer, axis)
43using BufferAxis = std::pair<BufferVar, int>;
45 public:
46 size_t operator()(const BufferAxis& buffer_axis) const {
47 size_t const h1(ffi::ObjectPtrHash()(buffer_axis.first));
48 size_t const h2(std::hash<int>()(buffer_axis.second));
49 return h1 ^ (h2 << 1);
50 }
51};
61Var GetShardingVarFromIndex(PrimExpr index, ffi::Map<Var, Range> var_range,
63
69 public:
70 static std::vector<std::vector<TIRVarAxis>> GetTIRVarAxisGraph(const PrimFunc& prim_func) {
72 extractor(prim_func->body);
73 ffi::Map<BufferVar, Var> inverse_buffer_map;
74 for (const Var& param : prim_func->params) {
75 if (param->ty.as<BufferTypeNode>()) {
77 }
78 }
79 std::vector<std::vector<TIRVarAxis>> tir_var_axis_group_list;
80 std::unordered_set<BufferAxis, BufferAxisHash> visited;
81 for (const Var& param : prim_func->params) {
82 if (!param->ty.as<BufferTypeNode>()) {
83 continue;
84 }
85 BufferVar buffer(param);
86 for (int i = 0; i < static_cast<int>(buffer->shape.size()); i++) {
87 if (extractor.buffer_axis_graph_.count({buffer, i})) {
88 std::vector<BufferAxis> buffer_axis_group;
89 extractor.DFSGraph({buffer, i}, &visited, &buffer_axis_group);
90 if (buffer_axis_group.size() <= 1) {
91 continue;
92 }
93 std::vector<TIRVarAxis> tir_var_axis_group;
94 for (const auto& buffer_axis : buffer_axis_group) {
95 if (!inverse_buffer_map.count(buffer_axis.first)) {
96 continue;
97 }
98 tir_var_axis_group.push_back(
100 }
102 }
103 }
104 }
106 }
107
108 void DFSGraph(BufferAxis cur, std::unordered_set<BufferAxis, BufferAxisHash>* visited,
109 std::vector<BufferAxis>* buffer_axis_group) {
110 if (visited->count(cur)) {
111 return;
112 }
113 visited->insert(cur);
114 buffer_axis_group->push_back(cur);
115 for (const auto& next : buffer_axis_graph_[cur]) {
117 }
118 }
119
120 private:
121 void VisitStmt_(const BufferStoreNode* op) final {
123 buffer_access_indices_.push_back({op->buffer, op->indices});
124 }
125
126 void VisitExpr_(const TensorLoadNode* op) final {
128 buffer_access_indices_.push_back({op->source.as_or_throw<tvm::tirx::BufferVar>(), op->indices});
129 }
130
131 bool Match(PrimExpr a, PrimExpr buffer_shape_a, PrimExpr b, PrimExpr buffer_shape_b,
132 const arith::Analyzer& analyzer) {
133 if (b.as<PrimVar>()) {
134 std::swap(a, b);
135 std::swap(buffer_shape_a, buffer_shape_b);
136 }
137 auto prim_var = a.as<PrimVar>();
138 if (!prim_var) {
139 return false;
140 }
141 Var var = *prim_var;
142 analyzer->Bind(iter_var_range_);
143 b = analyzer->Simplify(b);
144 // index var `a` must access whole range of a specific buffer dimension
145 arith::IntSet intset_b = arith::EvalSet(b, arith::AsIntSet(iter_var_range_));
146 if (!analyzer->CanProveEqual(buffer_shape_a, iter_var_range_[var]->extent) ||
147 !intset_b.MatchRange(Range::FromMinExtent(0, buffer_shape_b))) {
148 return false;
149 }
150 Var matched_var = GetShardingVarFromIndex(b, iter_var_range_, analyzer);
151 if (!matched_var.same_as(var)) {
152 return false;
153 }
154 return true;
155 }
156
157 void VisitStmt_(const SBlockNode* op) final {
158 if (op->name_hint == "root") {
160 return;
161 }
162 buffer_access_indices_.clear();
164 iter_var_range_.clear();
165 for (const auto& iter_var : op->iter_vars) {
166 iter_var_range_.Set(iter_var->var, iter_var->dom);
167 }
168 arith::Analyzer analyzer;
169 for (const auto& access_pr : buffer_access_indices_) {
170 BufferVar buffer = access_pr.first;
171 ffi::Array<PrimExpr> indices = access_pr.second;
172 for (int i = 0; i < static_cast<int>(indices.size()); i++) {
173 for (const auto& another_access_pr : buffer_access_indices_) {
174 if (another_access_pr.first.same_as(buffer)) {
175 continue;
176 }
177 BufferVar another_buffer = another_access_pr.first;
178 ffi::Array<PrimExpr> another_indices = another_access_pr.second;
179 for (int j = 0; j < static_cast<int>(another_indices.size()); j++) {
180 if (Match(indices[i], buffer->shape[i], another_indices[j], another_buffer->shape[j],
181 analyzer)) {
182 JoinBufferAxis({buffer, i}, {another_buffer, j});
183 }
184 }
185 }
186 }
187 }
188 }
189
190 void JoinBufferAxis(BufferAxis axis1, BufferAxis axis2) {
191 if (!buffer_axis_graph_.count(axis1)) {
192 buffer_axis_graph_[axis1] = {};
193 }
194 if (!buffer_axis_graph_.count(axis2)) {
195 buffer_axis_graph_[axis2] = {};
196 }
197 buffer_axis_graph_[axis1].push_back(axis2);
198 buffer_axis_graph_[axis2].push_back(axis1);
199 }
200
201 std::vector<std::pair<BufferVar, ffi::Array<PrimExpr>>> buffer_access_indices_;
202 std::unordered_map<BufferAxis, std::vector<BufferAxis>, BufferAxisHash> buffer_axis_graph_;
203 ffi::Map<Var, Range> iter_var_range_;
204 std::string func_name;
205};
206} // namespace tirx
207} // namespace tvm
208
209namespace tvm {
210namespace relax {
211namespace distributed {
212
214struct Axis {
215 const ExprNode* tensor;
216 int dim = 0;
217 int tuple_index = 0;
218
219 Axis(const ExprNode* tensor, int dim, int tuple_index = 0)
220 : tensor(tensor), dim(dim), tuple_index(tuple_index) {
221 TVM_FFI_ICHECK(tensor->IsInstance<ConstantNode>() || tensor->IsInstance<VarNode>());
222 }
223
224 bool operator==(const Axis& other) const {
225 return tensor == other.tensor && dim == other.dim && tuple_index == other.tuple_index;
226 }
227};
228
229class AxisHash {
230 public:
231 size_t operator()(const Axis& axis) const {
232 size_t const h1(std::hash<const ExprNode*>()(axis.tensor));
233 size_t const h2(std::hash<int>()(axis.dim));
234 size_t const h3(std::hash<int>()(axis.tuple_index));
235 return h1 ^ (h2 << 1) ^ (h3 << 2);
236 }
237};
238
239using AxisGroup = std::unordered_set<Axis, AxisHash>;
240
241class AxisGroupHash {
242 public:
243 size_t operator()(const AxisGroup& axis_group) const {
244 size_t seed = 0;
245 for (auto axis : axis_group) {
246 seed ^= AxisHash()(axis) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
247 }
248 return seed;
249 }
250};
251
252using ShardingSpec = std::pair<DeviceMesh, Placement>;
253
254// device mesh and the device mesh axis that the tensor axis maps to
255using AxisShardingSpec = std::pair<DeviceMesh, int>;
256class AxisShardingSpecEqual {
257 public:
258 bool operator()(const AxisShardingSpec& lhs, const AxisShardingSpec& rhs) const {
259 return ffi::StructuralEqual()(lhs.first, rhs.first) && lhs.second == rhs.second;
260 }
261};
262
263class AxisShardingSpecHash {
264 public:
265 size_t operator()(const AxisShardingSpec& sharding_spec) const {
266 size_t seed = 0;
267 seed ^= ffi::StructuralHash()(sharding_spec.first);
268 seed ^= std::hash<int>()(sharding_spec.second) << 1;
269 return seed;
270 }
271};
272
278class AxisGroupGraph {
279 public:
280 enum class EdgeType { kAscend, kDescend, kSimbling };
281
282 private:
283 static EdgeType ReverseEdgeType(EdgeType type) {
284 switch (type) {
285 case EdgeType::kAscend:
286 return EdgeType::kDescend;
287 case EdgeType::kDescend:
288 return EdgeType::kAscend;
289 case EdgeType::kSimbling:
290 return EdgeType::kSimbling;
291 }
292 TVM_FFI_THROW(InternalError) << "Unreachable code";
293 throw;
294 }
295
296 static int GetEdgePriority(EdgeType type) {
297 switch (type) {
298 case EdgeType::kAscend:
299 return 0;
300 case EdgeType::kDescend:
301 return 2;
302 case EdgeType::kSimbling:
303 return 1;
304 }
305 TVM_FFI_THROW(InternalError) << "Unreachable code";
306 throw;
307 }
308
309 struct AxisGraphEdge {
310 Axis src;
311 Axis dst;
312
313 // the producer-consumer relationship between src tensor and dst tensor
314 // kAscend means consumer->producer
315 // kDescend means producer->consumer
316 // kSimbling means other cases
317 EdgeType type;
318
319 bool operator==(const AxisGraphEdge& other) const {
320 return src == other.src && dst == other.dst && type == other.type;
321 }
322 };
323
324 struct Path {
325 int direction = 0;
326
327 Path AddEdge(EdgeType type) { return {direction |= (1 << GetEdgePriority(type))}; }
328
329 int GetPriority() const {
330 switch (direction) {
331 case 1: // ascend only
332 return 0;
333 case 4: // descend only
334 return 2;
335 case 0: // empty path (source node)
336 return 3; // source node must have max priority
337 default: // mixed path
338 return 1;
339 }
340 }
341 };
342
343 public:
344 AxisGroupGraph() = default;
345
355 void JoinAxis(Axis axis1, Axis axis2, EdgeType type) {
356 AddEdge(axis1, axis2, type);
357 AddEdge(axis2, axis1, ReverseEdgeType(type));
358 }
359
365 void AddSrcShardingPoint(Axis axis, AxisShardingSpec spec) {
366 src_axis_sharding_spec_[axis] = spec;
367 }
368
372 void PropagateShardingSpec() {
373 axis_sharding_specs_priority_.clear();
374 for (const auto& pr : src_axis_sharding_spec_) {
375 std::unordered_set<Axis, AxisHash> visited;
376 PropagateShardingSpec(pr.first, pr.second, Path(), &visited);
377 }
378 ChooseAxisShardingSpec();
379 }
380
387 void AddPropagationCutPoint(Axis axis, AxisShardingSpec spec) {
388 cutpoint_axis_sharding_spec_[axis] = spec;
389 }
390
398 std::tuple<AxisShardingSpec, bool> GetAxisShardingSpec(Axis axis) {
399 if (axis_sharding_specs_priority_.count(axis)) {
400 return {axis_sharding_specs_priority_[axis].begin()->first, true};
401 } else {
402 return {{DeviceMesh(), -1}, false};
403 }
404 }
405
406 private:
407 void AddEdge(Axis src, Axis dst, EdgeType type) {
408 if (!graph_.count(src)) {
409 graph_[src] = {};
410 }
411 graph_[src].push_back({src, dst, type});
412 }
413
414 void PropagateShardingSpec(Axis axis, AxisShardingSpec spec, Path path,
415 std::unordered_set<Axis, AxisHash>* visited) {
416 if (cutpoint_axis_sharding_spec_.count(axis) ||
417 (src_axis_sharding_spec_.count(axis) &&
418 !AxisShardingSpecEqual()(src_axis_sharding_spec_[axis], spec)) ||
419 visited->count(axis)) {
420 return;
421 }
422 visited->insert(axis);
423 if (!axis_sharding_specs_priority_.count(axis)) {
424 axis_sharding_specs_priority_[axis] = {};
425 }
426 axis_sharding_specs_priority_[axis][spec] = path.GetPriority();
427 for (auto edge : graph_[axis]) {
428 PropagateShardingSpec(edge.dst, spec, path.AddEdge(edge.type), visited);
429 }
430 }
431
432 void ChooseAxisShardingSpec() {
433 for (auto& pr : axis_sharding_specs_priority_) {
434 auto& axis = pr.first;
435 auto& specs = pr.second;
436 int max_priority = std::numeric_limits<int>::min();
437 for (auto& pr2 : specs) {
438 max_priority = std::max(max_priority, pr2.second);
439 }
440 for (auto it = specs.begin(); it != specs.end();) {
441 if (it->second != max_priority) {
442 it = specs.erase(it);
443 } else {
444 it++;
445 }
446 }
447 TVM_FFI_ICHECK(specs.size() == 1)
448 << "multiple possible sharding for axis: (" << ffi::GetRef<Expr>(axis.tensor) << ", "
449 << axis.dim << ")";
450 }
451 }
452
453 // union set
454 std::unordered_map<Axis, std::vector<AxisGraphEdge>, AxisHash> graph_;
455 std::unordered_map<Axis, AxisShardingSpec, AxisHash> src_axis_sharding_spec_;
456 std::unordered_map<Axis, AxisShardingSpec, AxisHash> cutpoint_axis_sharding_spec_;
457 std::unordered_map<
458 Axis, std::unordered_map<AxisShardingSpec, int, AxisShardingSpecHash, AxisShardingSpecEqual>,
459 AxisHash>
460 axis_sharding_specs_priority_;
461};
462
463using FBuildAxisGraph = std::function<void(const Var& output_var, const Call& call,
464 distributed::AxisGroupGraph* axis_group_graph)>;
465
466void BuildAxisGraphUnary(const Var& output_var, const Call& call,
467 distributed::AxisGroupGraph* axis_group_graph);
468void BuildAxisGraphBinary(const Var& output_var, const Call& call,
469 distributed::AxisGroupGraph* axis_group_graph);
470void BuildAxisGraphReduce(const Var& output_var, const Call& call,
471 distributed::AxisGroupGraph* axis_group_graph);
472void BuildAxisGraphMatmul(const Var& output_var, const Call& call,
473 distributed::AxisGroupGraph* axis_group_graph);
474void BuildAxisGraphPermuteDims(const Var& output_var, const Call& call,
475 distributed::AxisGroupGraph* axis_group_graph);
476void BuildAxisGraphReshape(const Var& output_var, const Call& call,
477 distributed::AxisGroupGraph* axis_group_graph);
478void BuildAxisGraphCallTIR(const Var& output_var, const Call& call, const tirx::PrimFunc& func,
479 distributed::AxisGroupGraph* axis_group_graph);
480
481} // namespace distributed
482} // namespace relax
483} // namespace tvm
484
485#endif // TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
Managed reference to CallNode.
Definition expr.h:474
Base type of all the expressions.
Definition base_expr.h:300
Typed reference/view over any Expr whose ExprNode::ty is PrimType.
Definition base_expr.h:401
static Range FromMinExtent(PrimExpr min, PrimExpr extent, Span span=Span())
construct a new range with min and extent The corresponding constructor is removed,...
A local variable in the IR.
Definition expr.h:355
Managed reference to VarNode.
Definition expr.h:372
RAII wrapper function to enter and exit a context object similar to python's with syntax.
Definition with_context.h:59
Managed reference to AnalyzerObj.
Definition analyzer.h:931
Constant tensor.
Definition expr.h:93
Managed reference to a DeviceMesh.
Definition global_info.h:63
Construct an axis group graph from a PrimFunc. Two buffer axis are connected if they are accessed by ...
Definition axis_group_graph.h:68
void DFSGraph(BufferAxis cur, std::unordered_set< BufferAxis, BufferAxisHash > *visited, std::vector< BufferAxis > *buffer_axis_group)
Definition axis_group_graph.h:108
static std::vector< std::vector< TIRVarAxis > > GetTIRVarAxisGraph(const PrimFunc &prim_func)
Definition axis_group_graph.h:70
Definition axis_group_graph.h:44
size_t operator()(const BufferAxis &buffer_axis) const
Definition axis_group_graph.h:46
Store value to the high dimension buffer.
Definition stmt.h:204
Structural type of a TIRx buffer variable.
Definition buffer.h:71
Checked zero-state view over an ordinary VarNode with BufferType.
Definition buffer.h:179
Managed reference to PrimFuncNode.
Definition function.h:105
Visitor that recursively visit stmts and exprs on them.
Definition stmt_functor.h:334
void VisitExpr_(const TensorLoadNode *op) override
void VisitStmt_(const BindNode *op) override
Iterator quasi-affine mapping patterns.
ffi::Map< Var, arith::IntSet > AsIntSet(const ffi::Map< Var, Range > &var_dom)
Converts the Ranges to IntSets.
IntSet EvalSet(PrimExpr e, const ffi::Map< IterVar, IntSet > &dom_map)
Find an symbolic integer set that contains all possible values of e given the domain of each iteratio...
PrimVar var(std::string name_hint, PrimType t=PrimType::Int(32))
Construct a new Var expression.
tvm::Var Var
Definition var.h:38
Var GetShardingVarFromIndex(PrimExpr index, ffi::Map< Var, Range > var_range, const arith::Analyzer &analyzer)
Suppose we want to shard a buffer along a specific dimension, we need to know how to rewrite the acce...
std::pair< Var, int > TIRVarAxis
Definition axis_group_graph.h:41
std::pair< BufferVar, int > BufferAxis
Definition axis_group_graph.h:43
An object that builds and maintains block scope and StmtSref mapping for Dependence analysis.
Definition analyzer.h:40
bool operator==(const PrimType &lhs, const PrimType &rhs)
Definition base_expr.h:290
Type definitions for DTensor (Distributed Tensor)
Functors for tirx stmts utility functions to call common functors.
TIR Function.