载入中...
搜索中...
未找到
Optimizer.cpp
浏览该文件的文档.
1#include "tensor/Optimizer.h"
2#include "tensor/CpuKernels.h"
3
4#include "common/Exception.h"
5
6#include <algorithm>
7#include <queue>
8
9namespace eve::tensor {
10namespace {
11
12int positionOf(const std::vector<int> &order, int id) {
13 return int(std::find(order.begin(), order.end(), id) - order.begin());
14}
15
16std::vector<int> topoOrder(const Graph &g, const std::vector<char> &live, int outputNode) {
17 const int n = g.nodeCount();
18 std::vector<int> indeg(static_cast<size_t>(n), 0);
19 std::vector<std::vector<int>> outs(static_cast<size_t>(n));
20 for (int i = 0; i < n; ++i) {
21 if (!live[static_cast<size_t>(i)]) continue;
22 const auto &nd = g.node(i);
23 auto link = [&](int from) {
24 if (from < 0 || !live[static_cast<size_t>(from)]) return;
25 outs[static_cast<size_t>(from)].push_back(i);
26 ++indeg[static_cast<size_t>(i)];
27 };
28 link(nd.in0);
29 link(nd.in1);
30 link(nd.in2);
31 link(nd.in3);
32 link(nd.in4);
33 }
34 std::queue<int> q;
35 for (int i = 0; i < n; ++i)
36 if (live[static_cast<size_t>(i)] && indeg[static_cast<size_t>(i)] == 0) q.push(i);
37 std::vector<int> order;
38 while (!q.empty()) {
39 int u = q.front();
40 q.pop();
41 order.push_back(u);
42 for (int v : outs[static_cast<size_t>(u)])
43 if (--indeg[static_cast<size_t>(v)] == 0) q.push(v);
44 }
45 const int liveCount = int(std::count(live.begin(), live.end(), char(1)));
46 if (int(order.size()) != liveCount)
47 throw eve::Exception("optimizeGraph: cycle in graph");
48 (void)outputNode;
49 return order;
50}
51
53void constantFold(Graph &g, const std::vector<int> &order) {
54 for (int id : order) {
55 auto &nd = g.node(id);
56 if (nd.type == OpType::Const || !kernels::isElementwiseOp(nd.type)) continue;
57 const int inputs[5] = {nd.in0, nd.in1, nd.in2, nd.in3, nd.in4};
58 bool allConst = true;
59 for (int k = 0; k < 5; ++k)
60 if (inputs[k] >= 0 &&
61 (g.node(inputs[k]).type != OpType::Const ||
62 !g.node(inputs[k]).constBytes.empty())) // quantized consts are opaque
63 allConst = false;
64 if (!allConst) continue;
65
66 nd.constData.resize(static_cast<size_t>(nd.size));
67 if (nd.type == OpType::Where) {
68 const auto &c = g.node(nd.in0).constData;
69 const auto &a = g.node(nd.in1).constData;
70 const auto &b = g.node(nd.in2).constData;
71 for (int i = 0; i < nd.size; ++i)
72 nd.constData[static_cast<size_t>(i)] =
73 c[static_cast<size_t>(i)] > 0.5f ? a[static_cast<size_t>(i)]
74 : b[static_cast<size_t>(i)];
75 } else if (nd.type == OpType::Add || nd.type == OpType::Sub ||
76 nd.type == OpType::Multiply || nd.type == OpType::Divide) {
77 const auto &na = g.node(nd.in0);
78 const auto &nb = g.node(nd.in1);
79 kernels::binaryOp(nd.type, na.constData.data(), na.dims, na.rank,
80 nb.constData.data(), nb.dims, nb.rank, nd.constData.data(), nd.dims,
81 nd.rank);
82 } else {
83 kernels::unaryOp(nd.type, g.node(nd.in0).constData.data(), nd.size,
84 nd.constData.data(), nd.s0, nd.s1);
85 }
86 nd.type = OpType::Const;
87 nd.in0 = nd.in1 = nd.in2 = nd.in3 = nd.in4 = -1;
88 }
89}
90
91GroupKind kindForOp(OpType t) {
92 switch (t) {
98 case OpType::Softmax:
109 case OpType::Slice: return GroupKind::Slice;
110 case OpType::Permute:
114 case OpType::Reshape:
115 case OpType::Flatten:
116 case OpType::Cast: return GroupKind::Alias;
117 default: return GroupKind::Elementwise;
118 }
119}
120
121void fillGroupAttrs(FusedGroup &g, const Graph &graph, int nodeId) {
122 const auto &nd = graph.node(nodeId);
123 g.s0 = nd.s0;
124 g.s1 = nd.s1;
125 g.s2 = nd.s2;
126 g.s3 = nd.s3;
127 g.i0 = nd.i0;
128 g.i1 = nd.i1;
129 g.i2 = nd.i2;
130 g.i3 = nd.i3;
131 for (int k = 0; k < Tensor::kMaxRank; ++k) g.perm[k] = nd.perm[k];
132 g.permRank = nd.permRank;
133 g.dtype = nd.dtype;
134 g.op = nd.type;
135 switch (nd.type) {
136 case OpType::LogSoftmax: g.logMode = true; break;
138 g.hasScale = nd.in1 >= 0;
139 g.hasBias = nd.in2 >= 0;
140 break;
141 case OpType::RMSNorm: g.hasScale = nd.in1 >= 0; break;
142 case OpType::ScaledDotProductAttention: g.masked = nd.in3 >= 0; break;
143 default: break;
144 }
145}
146
147bool isEpilogueOp(OpType t) {
148 switch (t) {
149 case OpType::Add:
154 case OpType::Neg:
155 case OpType::Abs:
156 case OpType::Exp:
157 case OpType::Sqrt:
158 case OpType::Relu:
159 case OpType::Sigmoid:
160 case OpType::Tanh:
161 case OpType::Gelu:
162 case OpType::Silu:
163 case OpType::Clamp:
167 return true;
168 default:
169 return false;
170 }
171}
172
173} // namespace
174
175OptimizedGraph optimizeGraph(const Graph &graph, int outputNode) {
176 const int n = graph.nodeCount();
177 if (n <= 0 || outputNode < 0 || outputNode >= n)
178 throw eve::Exception("optimizeGraph: invalid output");
179
180 // 1. dead-code elimination
181 std::vector<char> live(static_cast<size_t>(n), 0);
182 {
183 std::vector<int> stack = {outputNode};
184 while (!stack.empty()) {
185 int u = stack.back();
186 stack.pop_back();
187 if (u < 0 || u >= n || live[static_cast<size_t>(u)]) continue;
188 live[static_cast<size_t>(u)] = 1;
189 const auto &nd = graph.node(u);
190 if (nd.in0 >= 0) stack.push_back(nd.in0);
191 if (nd.in1 >= 0) stack.push_back(nd.in1);
192 if (nd.in2 >= 0) stack.push_back(nd.in2);
193 if (nd.in3 >= 0) stack.push_back(nd.in3);
194 if (nd.in4 >= 0) stack.push_back(nd.in4);
195 }
196 }
197
198 // 2. topological order (on a mutable copy; const folding may rewrite nodes)
199 Graph g = graph;
200 std::vector<int> order = topoOrder(g, live, outputNode);
201 constantFold(g, order);
202 // Re-topo after folding (folded nodes become roots; order is still valid but
203 // recompute to keep positions consistent with the rewritten graph).
204 order = topoOrder(g, live, outputNode);
205
206 // 3. fusion
207 std::vector<int> consumerCount(static_cast<size_t>(n), 0);
208 for (int id : order) {
209 const auto &nd = g.node(id);
210 auto inc = [&](int p) {
211 if (p >= 0 && live[static_cast<size_t>(p)]) ++consumerCount[static_cast<size_t>(p)];
212 };
213 inc(nd.in0);
214 inc(nd.in1);
215 inc(nd.in2);
216 inc(nd.in3);
217 inc(nd.in4);
218 }
219
220 std::vector<char> visited(static_cast<size_t>(n), 0);
221 std::vector<int> groupOf(static_cast<size_t>(n), -1);
222 std::vector<char> absorbed;
223 std::vector<FusedGroup> groups;
224
225 // 3a. elementwise chains (reverse topo, walking backward through
226 // single-consumer elementwise producers)
227 for (int idx = int(order.size()) - 1; idx >= 0; --idx) {
228 const int id = order[static_cast<size_t>(idx)];
229 if (!live[static_cast<size_t>(id)] || visited[static_cast<size_t>(id)]) continue;
230 if (!kernels::isElementwiseOp(g.node(id).type)) continue;
231 FusedGroup grp;
233 grp.outputNode = id;
234 fillGroupAttrs(grp, g, id);
235 std::vector<int> stack = {id};
236 while (!stack.empty()) {
237 int u = stack.back();
238 stack.pop_back();
239 if (!live[static_cast<size_t>(u)] || visited[static_cast<size_t>(u)]) continue;
240 visited[static_cast<size_t>(u)] = 1;
241 groupOf[static_cast<size_t>(u)] = int(groups.size());
242 grp.nodes.push_back(u);
243 const auto &nd = g.node(u);
244 const int inputs[5] = {nd.in0, nd.in1, nd.in2, nd.in3, nd.in4};
245 for (int k = 0; k < 5; ++k) {
246 const int p = inputs[k];
247 if (p >= 0 && live[static_cast<size_t>(p)] &&
249 consumerCount[static_cast<size_t>(p)] == 1) {
250 stack.push_back(p);
251 }
252 }
253 }
254 std::stable_sort(grp.nodes.begin(), grp.nodes.end(),
255 [&](int a, int b) { return positionOf(order, a) < positionOf(order, b); });
256 absorbed.push_back(0);
257 groups.push_back(std::move(grp));
258 }
259
260 // 3b. remaining nodes -> single-op groups
261 for (int id : order) {
262 if (!live[static_cast<size_t>(id)] || visited[static_cast<size_t>(id)]) continue;
263 // Placeholders and consts are data nodes (group inputs), never groups.
264 if (g.node(id).type == OpType::Placeholder || g.node(id).type == OpType::Const) continue;
265 visited[static_cast<size_t>(id)] = 1;
266 groupOf[static_cast<size_t>(id)] = int(groups.size());
267 FusedGroup grp;
268 grp.outputNode = id;
269 grp.kind = kindForOp(g.node(id).type);
270 fillGroupAttrs(grp, g, id);
271 grp.nodes.push_back(id);
272 absorbed.push_back(0);
273 groups.push_back(std::move(grp));
274 }
275
276 // 3c. matmul / conv bias + activation epilogue fusion
277 for (size_t gi = 0; gi < groups.size(); ++gi) {
278 FusedGroup &grp = groups[gi];
279 if (grp.kind != GroupKind::MatMul && grp.kind != GroupKind::Conv1d &&
280 grp.kind != GroupKind::Conv2d)
281 continue;
282 const int m = grp.outputNode;
283 // exactly one consumer
284 std::vector<int> consumers;
285 for (int id : order) {
286 const auto &nd = g.node(id);
287 const int inputs[5] = {nd.in0, nd.in1, nd.in2, nd.in3, nd.in4};
288 for (int k = 0; k < 5; ++k)
289 if (inputs[k] == m) consumers.push_back(id);
290 }
291 if (consumers.size() != 1) continue;
292 const int c = consumers[0];
293 const size_t egIdx = static_cast<size_t>(groupOf[static_cast<size_t>(c)]);
294 if (egIdx >= groups.size() || absorbed[egIdx]) continue;
295 FusedGroup &eg = groups[egIdx];
296 if (eg.kind != GroupKind::Elementwise) continue;
297 if (consumerCount[static_cast<size_t>(c)] != 1 && c != outputNode) continue;
298
299 // Validate the chain: single-consumer elementwise ops starting at m.
300 bool ok = true;
301 int biasNode = -1;
302 int prev = m;
303 const auto &mn = g.node(m);
304 for (int u : eg.nodes) {
305 const auto &nd = g.node(u);
306 if (!isEpilogueOp(nd.type)) {
307 ok = false;
308 break;
309 }
310 if (nd.in0 != prev) {
311 ok = false;
312 break;
313 }
314 if (nd.type == OpType::Add) {
315 const int other = nd.in1;
316 if (other < 0 || g.node(other).type != OpType::Const) {
317 ok = false;
318 break;
319 }
320 const auto &bn = g.node(other);
321 const bool biasOk = (bn.rank == 1 && bn.dims[0] == mn.dims[mn.rank - 1]) ||
322 (bn.rank == 2 && bn.dims[0] == 1 &&
323 bn.dims[1] == mn.dims[mn.rank - 1]);
324 if (!biasOk) {
325 ok = false;
326 break;
327 }
328 if (biasNode == -1) biasNode = other;
329 }
330 if (nd.in1 >= 0 && nd.type != OpType::Add && nd.in1 != u && nd.in1 != prev) {
331 ok = false;
332 break;
333 }
334 prev = u;
335 }
336 if (!ok) continue;
337
338 // Merge: absorb the elementwise group into the matmul/conv group.
339 grp.nodes.insert(grp.nodes.end(), eg.nodes.begin(), eg.nodes.end());
340 grp.epilogue = eg.nodes;
341 grp.biasNode = biasNode;
342 grp.outputNode = eg.outputNode;
343 for (int u : eg.nodes) groupOf[static_cast<size_t>(u)] = int(gi);
344 absorbed[egIdx] = 1;
345 }
346
347 // 4. collect external inputs per group (first-use order)
348 for (size_t gi = 0; gi < groups.size(); ++gi) {
349 if (absorbed[gi]) continue;
350 FusedGroup &grp = groups[gi];
351 std::vector<char> inGroup(static_cast<size_t>(n), 0);
352 for (int u : grp.nodes) inGroup[static_cast<size_t>(u)] = 1;
353 grp.inputs.clear();
354 for (int u : grp.nodes) {
355 const auto &nd = g.node(u);
356 const int inputs[5] = {nd.in0, nd.in1, nd.in2, nd.in3, nd.in4};
357 for (int k = 0; k < 5; ++k) {
358 const int p = inputs[k];
359 if (p < 0 || inGroup[static_cast<size_t>(p)]) continue;
360 // Keep one entry per input *slot* (not per node): kernels bind by
361 // position, so the same node feeding two slots (q=k=v, where(a,a,b))
362 // must appear twice.
363 grp.inputs.push_back(p);
364 }
365 }
366 std::stable_sort(grp.nodes.begin(), grp.nodes.end(),
367 [&](int a, int b) { return positionOf(order, a) < positionOf(order, b); });
368 }
369
370 // 5. memory planning
371 OptimizedGraph opt;
372 opt.order = order;
373 opt.outputNode = outputNode;
374 opt.nodeSlot.assign(static_cast<size_t>(n), -1);
375
376 // execution order of groups
377 std::vector<size_t> exec;
378 for (size_t gi = 0; gi < groups.size(); ++gi)
379 if (!absorbed[gi]) exec.push_back(gi);
380 std::stable_sort(exec.begin(), exec.end(), [&](size_t a, size_t b) {
381 return positionOf(order, groups[a].outputNode) < positionOf(order, groups[b].outputNode);
382 });
383
384 // group index of each node (for liveness)
385 std::vector<int> groupIndexByNode(static_cast<size_t>(n), -1);
386 for (size_t gi = 0; gi < groups.size(); ++gi)
387 if (!absorbed[gi])
388 for (int u : groups[gi].nodes) groupIndexByNode[static_cast<size_t>(u)] = int(gi);
389
390 // Alias (reshape/flatten/cast) outputs share their producer's buffer. A
391 // consumer of the alias keeps the *producer's* buffer alive, so liveness
392 // must resolve through alias chains before computing last-use.
393 std::vector<int> realProducer(static_cast<size_t>(n), -1);
394 for (int id = 0; id < n; ++id) {
395 int cur = id;
396 while (cur >= 0 && (g.node(cur).type == OpType::Reshape ||
397 g.node(cur).type == OpType::Flatten ||
398 g.node(cur).type == OpType::Cast))
399 cur = g.node(cur).in0;
400 realProducer[static_cast<size_t>(id)] = cur;
401 }
402
403 // consumer group per group (last use)
404 std::vector<int> lastUse(exec.size(), -1);
405 for (size_t gi = 0; gi < exec.size(); ++gi) {
406 const FusedGroup &grp = groups[exec[gi]];
407 for (int p : grp.inputs) {
408 const int real = realProducer[static_cast<size_t>(p)];
409 const int prod = real >= 0 ? groupIndexByNode[static_cast<size_t>(real)] : -1;
410 if (prod < 0) continue;
411 const auto it = std::find(exec.begin(), exec.end(), size_t(prod));
412 if (it == exec.end()) continue;
413 const size_t pIdx = static_cast<size_t>(it - exec.begin());
414 lastUse[pIdx] = std::max(lastUse[pIdx], int(gi));
415 }
416 if (grp.outputNode == outputNode && lastUse[gi] < 0) lastUse[gi] = int(gi);
417 }
418
419 struct Slot {
420 int capacity = 0;
421 int freeAt = 0;
422 };
423 std::vector<Slot> slots;
424 std::vector<char> persistent(static_cast<size_t>(n), 0);
425
426 auto allocate = [&](int nodeId, int currentTime, int freeAt) {
427 if (opt.nodeSlot[static_cast<size_t>(nodeId)] >= 0) return; // already allocated
428 const int size = g.node(nodeId).size;
429 int best = -1;
430 for (size_t s = 0; s < slots.size(); ++s) {
431 if (slots[s].capacity >= size && slots[s].freeAt <= currentTime) {
432 best = int(s);
433 break;
434 }
435 }
436 if (best < 0) {
437 best = int(slots.size());
438 slots.push_back({size, freeAt});
439 } else {
440 slots[static_cast<size_t>(best)].freeAt = freeAt;
441 }
442 opt.nodeSlot[static_cast<size_t>(nodeId)] = best;
443 };
444
445 // persistent nodes: placeholders, consts, final output
446 for (int id : order) {
447 const auto &nd = g.node(id);
448 if (nd.type == OpType::Placeholder || nd.type == OpType::Const) {
449 persistent[static_cast<size_t>(id)] = 1;
450 allocate(id, 0, 1 << 30);
451 }
452 }
453 persistent[static_cast<size_t>(outputNode)] = 1;
454
455 // group outputs
456 for (size_t gi : exec) {
457 FusedGroup &grp = groups[gi];
458 const int out = grp.outputNode;
459 if (grp.kind == GroupKind::Alias) {
460 const auto &nd = g.node(out);
461 if (nd.in0 < 0) throw eve::Exception("optimizeGraph: alias without input");
462 const int srcSlot = opt.nodeSlot[static_cast<size_t>(nd.in0)];
463 if (srcSlot < 0) throw eve::Exception("optimizeGraph: alias input without slot");
464 opt.nodeSlot[static_cast<size_t>(out)] = srcSlot;
465 continue;
466 }
467 if (persistent[static_cast<size_t>(out)]) {
468 allocate(out, 0, 1 << 30);
469 } else {
470 const size_t giPos = static_cast<size_t>(std::find(exec.begin(), exec.end(), gi) -
471 exec.begin());
472 allocate(out, int(giPos), lastUse[giPos] + 1);
473 }
474 }
475
476 opt.slotSize.resize(slots.size());
477 for (size_t s = 0; s < slots.size(); ++s) opt.slotSize[s] = slots[s].capacity;
478 for (size_t s = 0; s < slots.size(); ++s)
479 if (slots[s].freeAt >= (1 << 30)) opt.persistentSlots.push_back(int(s));
480
481 // drop absorbed (fused-away) groups from the final program
482 std::vector<FusedGroup> finalGroups;
483 std::vector<int> oldToNew(groups.size(), -1);
484 for (size_t i = 0; i < groups.size(); ++i)
485 if (!absorbed[i]) {
486 oldToNew[i] = int(finalGroups.size());
487 finalGroups.push_back(std::move(groups[i]));
488 }
489 opt.groups = std::move(finalGroups);
490 opt.groupOrder.reserve(exec.size());
491 for (size_t gi : exec) {
492 const int mapped = oldToNew[gi];
493 if (mapped >= 0) opt.groupOrder.push_back(mapped);
494 }
495 return opt;
496}
497
499 int count = 0;
500 for (const auto &g : opt.groups)
501 if (g.kind != GroupKind::Alias) ++count;
502 return count;
503}
504
505} // namespace eve::tensor
std::string id
float u
Definition Grass.cpp:234
glm::vec3 n
Definition Grass.cpp:64
const Graph & graph
uint32_t a
uint32_t b
uint32_t c
int idx
glm::vec4 p[6]
int v
float m[16]
uint32_t s
Definition Weather.cpp:28
const GraphNode & node(int id) const
Definition Graph.h:109
int nodeCount() const
Definition Graph.h:111
static constexpr int kMaxRank
Definition Tensor.h:45
void binaryOp(OpType type, const float *a, const int *aDims, int aRank, const float *b, const int *bDims, int bRank, float *out, const int *outDims, int outRank)
bool isElementwiseOp(OpType t)
void unaryOp(OpType type, const float *in, int count, float *out, float s0, float s1)
int groupKernelCount(const OptimizedGraph &opt)
OptimizedGraph optimizeGraph(const Graph &graph, int outputNode)
std::vector< int > epilogue
Definition Optimizer.h:61
std::vector< int > nodes
Definition Optimizer.h:47
std::vector< int > inputs
Definition Optimizer.h:49
std::vector< int > order
Definition Optimizer.h:74
std::vector< int > slotSize
Definition Optimizer.h:82
std::vector< int > persistentSlots
Definition Optimizer.h:84
std::vector< int > nodeSlot
Definition Optimizer.h:80
std::vector< int > groupOrder
Definition Optimizer.h:78
std::vector< FusedGroup > groups
Definition Optimizer.h:76