forked from apache/tvm
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmutate_parallel.cc
More file actions
312 lines (292 loc) · 11 KB
/
Copy pathmutate_parallel.cc
File metadata and controls
312 lines (292 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <algorithm>
#include <unordered_map>
#include "../utils.h"
namespace tvm {
namespace tir {
/*!
* \brief Check if the instruction is annotation with `meta_schedule_parallel`
* \param inst The instruction to be checked
* \return Whether the instruction is annotation with `meta_schedule_parallel`
*/
bool IsAnnotateWithParallel(const Instruction& inst) {
static const InstructionKind& inst_annotate = InstructionKind::Get("Annotate");
if (!inst->kind.same_as(inst_annotate)) {
return false;
}
ICHECK_EQ(inst->attrs.size(), 1);
String ann_key = Downcast<String>(inst->attrs[0]);
return ann_key == attr::meta_schedule_parallel;
}
/*!
* \brief Replace the annotation value
* \param inst The instruction to be replaced
* \param ann_val The new annotation value
* \return The replaced instruction
*/
Instruction ReplaceAnnValue(Instruction inst, int64_t ann_val) {
ICHECK_EQ(inst->inputs.size(), 2);
return Instruction(/*kind=*/inst->kind, //
/*inputs=*/{inst->inputs[0], Integer(ann_val)}, //
/*attrs=*/inst->attrs,
/*outputs=*/inst->outputs);
}
/*!
* \brief Get the output of the instruction Get-Block
* \param inst The instruction to be checked
* \return The output of the instruction Get-Block
*/
const BlockRVNode* GetInstGetBlockOutput(const Instruction& inst) {
static const InstructionKind& inst_get_block = InstructionKind::Get("GetBlock");
if (!inst->kind.same_as(inst_get_block)) {
return nullptr;
}
ICHECK_EQ(inst->outputs.size(), 1);
const BlockRVNode* block = TVM_TYPE_AS(block, inst->outputs[0], BlockRVNode);
return block;
}
/*!
* \brief Analyze the parallel structure
* \param self The schedule state
* \param block_name The name of the root block
* \param func_name The name of the PrimFunc
* \param limit The uplimit of the parallelism
* \return The parallel structure
*/
std::vector<std::vector<int64_t>> AnalyzeParallel(const ScheduleState& self,
const String& block_name, const String& func_name,
int64_t limit) {
Array<StmtSRef> block_srefs = tir::GetBlocks(self, block_name, func_name);
ICHECK_EQ(block_srefs.size(), 1);
const BlockNode* block = TVM_SREF_TO_BLOCK(block, block_srefs[0]);
ScopeBlockLoopInfo info = GetScopeBlockLoopInfo(GetRef<Block>(block));
std::vector<std::vector<int64_t>> results;
results.reserve(info.realizes.size());
for (const BlockRealize& realize : info.realizes) {
// Step 1. Extract static loop extents for spatial loops
std::vector<int64_t> loop_extents;
const ForNode* loop = nullptr;
for (const StmtSRefNode* loop_sref = self->stmt2ref.at(realize->block.get())->parent;
(loop = loop_sref->StmtAs<ForNode>()) != nullptr; //
loop_sref = loop_sref->parent) {
int64_t loop_extent = -1;
if (const auto* ext = GetLoopIntExtent(loop)) {
if (!info.non_spatial_vars.count(loop->loop_var.get())) {
loop_extent = *ext;
}
}
if (loop_extent != -1) {
loop_extents.push_back(loop_extent);
} else {
loop_extents.clear();
}
}
// Step 2. Take the prefix product of loop extents
if (!loop_extents.empty()) {
results.emplace_back();
std::vector<int64_t>& result = results.back();
result.reserve(loop_extents.size());
int64_t prod_extent = 1;
for (auto it = loop_extents.rbegin(); it != loop_extents.rend(); ++it) {
result.push_back(prod_extent *= *it);
if (prod_extent >= limit) {
break;
}
}
}
}
return results;
}
/*!
* \brief Get the number of parallelizable loops for each subtree
* \param loop_extent_prods The parallel structure for each subtree
* \param limit The uplimit of the parallelism
* \return The number of parallelizable loops for each subtree
*/
std::vector<int> GetNumFusedLoops(const std::vector<std::vector<int64_t>>& loop_extent_prods,
int64_t limit) {
std::vector<int> results;
results.reserve(loop_extent_prods.size());
for (const std::vector<int64_t>& prods : loop_extent_prods) {
int n = prods.size();
int i = std::upper_bound(prods.begin(), prods.end(), limit) - prods.begin();
if (i > 0 && prods[i - 1] == limit) {
--i;
}
if (i != n) {
++i;
}
results.push_back(i);
}
return results;
}
} // namespace tir
} // namespace tvm
namespace tvm {
namespace meta_schedule {
using tir::Instruction;
using tir::Trace;
/*! \brief Create a Mutator that mutates the parallel extent */
class MutateParallelNode : public MutatorNode {
public:
/*!
* \brief The maximum number of jobs to be launched per CPU core.
* It sets the uplimit of CPU parallelism, i.e. `num_cores * max_jobs_per_core`.
* Use -1 to disable parallelism.
*/
int64_t max_jobs_per_core;
/*! \brief The number of cores in CPU. */
int max_parallel_extent_;
/*! \brief JSON representation of the workload */
std::string json_mod_;
void VisitAttrs(tvm::AttrVisitor* v) {
v->Visit("max_jobs_per_core", &max_jobs_per_core);
// `max_parallel_extent_` is not visited.
// `json_mod` is not visited.
}
static constexpr const char* _type_key = "meta_schedule.MutateParallel";
TVM_DECLARE_FINAL_OBJECT_INFO(MutateParallelNode, MutatorNode);
public:
struct Candidate;
// Inherit from `MutatorNode`
void InitializeWithTuneContext(const TuneContext& context) final {
Target target = context->target.value();
this->max_parallel_extent_ = GetTargetNumCores(target) * this->max_jobs_per_core;
this->json_mod_ = SaveJSON(context->mod.value());
}
// Inherit from `MutatorNode`
Optional<Trace> Apply(const Trace& trace, TRandState* rand_state) final;
};
/*! \brief The candidate to be mutated */
struct MutateParallelNode::Candidate {
/*! \brief The annotation instruction */
Instruction inst;
/*! \brief The current parallel extent */
int64_t parallel_extent;
/*! \brief The name of the root block */
String block_name;
/*! \brief The name of the PrimFunc */
String func_name;
};
/*!
* \brief Get an instruction that annotates the maximum parallel extent
* \param trace The trace to be mutated
* \param rand_state The random state
* \param candidate The candidate to be mutated
* \return Whether a decision is found
*/
bool FindParallelDecision(const Trace& trace, TRandState* rand_state,
MutateParallelNode::Candidate* candidate) {
using tir::BlockRVNode;
using tir::InstructionNode;
std::unordered_map<const BlockRVNode*, const InstructionNode*> get_block_insts;
std::vector<const InstructionNode*> ann_insts;
get_block_insts.reserve(trace->insts.size());
ann_insts.reserve(trace->insts.size());
for (const Instruction& inst : trace->insts) {
if (tir::IsAnnotateWithParallel(inst)) {
ann_insts.push_back(inst.get());
}
if (const BlockRVNode* block_rv = tir::GetInstGetBlockOutput(inst)) {
get_block_insts[block_rv] = inst.get();
}
}
int n_ann_insts = ann_insts.size();
if (n_ann_insts == 0) {
return false;
}
const InstructionNode* ann_inst = ann_insts[tir::SampleInt(rand_state, 0, n_ann_insts)];
ICHECK_EQ(ann_inst->inputs.size(), 2);
const InstructionNode* get_block_inst =
get_block_insts.at(Downcast<tir::BlockRV>(ann_inst->inputs[0]).get());
ICHECK_EQ(get_block_inst->attrs.size(), 2);
candidate->inst = GetRef<Instruction>(ann_inst);
candidate->parallel_extent = Downcast<IntImm>(ann_inst->inputs[1])->value;
candidate->block_name = Downcast<String>(get_block_inst->attrs[0]);
candidate->func_name = Downcast<String>(get_block_inst->attrs[1]);
return true;
}
Optional<Trace> MutateParallelNode::Apply(const Trace& trace, TRandState* rand_state) {
// Step 1. Find a parallel decision.
Candidate candidate;
if (!FindParallelDecision(trace, rand_state, &candidate)) {
return NullOpt;
}
// Step 2. Replay the instructions to recover loop extents
tir::Schedule sch = tir::Schedule::Traced( //
/*mod=*/Downcast<IRModule>(LoadJSON(this->json_mod_)), //
/*rand_state=*/ForkSeed(rand_state), //
/*debug_mode=*/0,
/*error_render_level=*/tir::ScheduleErrorRenderLevel::kNone);
trace->ApplyToSchedule(sch, /*remove_postproc=*/true);
// Step 3. Find all possible parallel plans
std::vector<std::vector<int64_t>> loop_extent_prods = tir::AnalyzeParallel(
sch->state(), candidate.block_name, candidate.func_name, this->max_parallel_extent_);
std::unordered_map<int64_t, std::vector<int>> limit2plan;
std::map<std::vector<int>, int64_t> plan2limit;
for (const std::vector<int64_t>& prods : loop_extent_prods) {
for (int64_t limit : prods) {
if (limit <= this->max_parallel_extent_ && !limit2plan.count(limit)) {
std::vector<int> plan = tir::GetNumFusedLoops(loop_extent_prods, limit);
limit2plan[limit] = plan;
plan2limit[plan] = limit;
}
}
}
// Step 4. Remove the original plan and remove it
std::vector<int> original_plan =
tir::GetNumFusedLoops(loop_extent_prods, candidate.parallel_extent);
auto it = plan2limit.find(original_plan);
if (it != plan2limit.end()) {
plan2limit.erase(it);
}
// Step 5. Pick a new plan
int n_plans = plan2limit.size();
if (n_plans == 0) {
return NullOpt;
}
it = plan2limit.begin();
for (int i = 0, n = tir::SampleInt(rand_state, 0, n_plans); i < n; ++i) {
++it;
}
int64_t limit = it->second;
// Step 6. Assemble a new trace
Array<Instruction> insts;
insts.reserve(trace->insts.size());
for (const Instruction& inst : trace->insts) {
if (inst.same_as(candidate.inst)) {
insts.push_back(tir::ReplaceAnnValue(candidate.inst, limit));
} else if (inst->kind->IsPostproc()) {
break;
} else {
insts.push_back(inst);
}
}
return Trace(insts, trace->decisions);
}
Mutator Mutator::MutateParallel(int64_t max_jobs_per_core) {
ObjectPtr<MutateParallelNode> n = make_object<MutateParallelNode>();
n->max_jobs_per_core = max_jobs_per_core;
return Mutator(n);
}
TVM_REGISTER_NODE_TYPE(MutateParallelNode);
TVM_REGISTER_GLOBAL("meta_schedule.MutatorMutateParallel").set_body_typed(Mutator::MutateParallel);
} // namespace meta_schedule
} // namespace tvm