feat: 图变换相关类分到单独文件中,并模板化。

划分子图时提供一个模板化的子图类型。

Signed-off-by: YdrMaster <ydrml@hotmail.com>
This commit is contained in:
YdrMaster 2023-04-14 10:57:52 +08:00
parent 59a46f3ff9
commit 34f7d7e9ed
8 changed files with 199 additions and 198 deletions

View File

@ -9,7 +9,7 @@
## 代码解析
重要的文件是 [tensor.h](src/tensor.h) 和 [graph.h](src/graph.h)。
重要的文件是 [tensor.h](src/tensor.h)、[graph.h](src/graph.h) 和 [mutation.h](src/mutation.h)。
tensor.h 提供了这个图表示中张量的定义。张量的结构由形状、数据类型和数据组成,并储存了在每个图上和算子的连接关系。张量的所有权由所有连接到张量的算子共享,因此其存在的唯一形式就是 `std::shared_ptr`,脱离共享所有权智能指针是没有意义的。通过向工厂函数直接传递形状、数据类型和数据,直接构造智能指针的张量,一旦所有连接到张量的算子释放,算子也被释放。
@ -19,6 +19,6 @@ tensor.h 提供了这个图表示中张量的定义。张量的结构由形状
同时,`Unigraph` 具有只增性。只能向其中增加算子,必须以拓扑序,不能移除,也不能修改算子的顺序。因此,算子在图中的序号是唯一的,每个图则持有一个唯一的 ID。因此可以用 ID 来指示图,用序号指示算子(`OpRef`);用序号指示算子,再用序号指示张量(`TensorPos`)。图必须整体销毁,销毁时,其中所有算子控制的所有张量连接也会同时销毁。因此,不必维持不可独立存在的所有权关系。
`Partition`、`Mutation` 和 `Rating` 三个类用于支持图的规则优化。这三个类本质是一样的,这种定义是为了对优化的不同阶段实现编译时的约束——一轮优化必须按划分→突变→评价的顺序依次执行每个操作一次。
mutation.h 的 `Partition`、`Mutation` 和 `Rating` 三个类用于支持图的规则优化。这三个类本质是一样的,这种定义是为了对优化的不同阶段实现编译时的约束——一轮优化必须按划分→突变→评价的顺序依次执行每个操作一次。
这些类中保存的是一个 `Mutant` 的二维数组。每个 `Mutant` 是子图的一种突变体,存储了子图结构和评分。内层数组表示每个子图的多个变体,外层数组表示每张图的多个子图。显然,`Partition` 输入完整的图,并构建外层数组的结构,`Mutation` 将填充内层数组。`Rating` 填充每个突变体的得分,然后从高到低排序。接下来可以用序号向量指导图的重建。

View File

@ -13,6 +13,7 @@ struct Data {
// #ifdef USE_CUDA
// void *gpu_data;
// #endif
// #ifdef USE_BANG
// void *mlu_data;
// #endif

View File

@ -1,5 +1,4 @@
#include "graph.h"
#include <unordered_set>
static size_t GRAPH_ID = 1;
@ -65,84 +64,3 @@ OpRef Unigraph::push_operator( // fmt: new line
});
return ans;
}
bool Mutant::operator<(Mutant const &others) const {
return this->score < others.score;
}
bool Mutant::operator>(Mutant const &others) const {
return this->score > others.score;
}
Mutant::Mutant(Unigraph &&g) : graph(std::move(g)) {}
Mutant::Mutant(Mutant &&others) : graph(std::move(others.graph)) {}
Mutant &Mutant::operator=(Mutant &&others) {
if (this != &others)
this->graph = std::move(others.graph);
return *this;
}
template <class t> Vec<size_t> list_size(Vec<Vec<t>> const &list) {
Vec<size_t> ans(list.size());
std::transform(list.begin(), list.end(), ans.begin(),
[](const auto &e) { return e.size(); });
return ans;
}
Partition::Partition(Unigraph &&g, Func const &f) {
auto mutant = f(std::move(g));
for (auto &sub : mutant)
this->mutant.emplace_back().emplace_back(std::move(sub));
}
Vec<size_t> Partition::size() const { return list_size(mutant); }
Mutation::Mutation(Partition &&p, Func const &f) : mutant(std::move(p.mutant)) {
for (auto &sub : mutant)
for (auto &m : f(sub.front().graph))
sub.emplace_back(std::move(m));
}
Vec<size_t> Mutation::size() const { return list_size(mutant); }
Rating::Rating(Mutation &&m, Func const &f) : mutant(std::move(m.mutant)) {
for (auto &sub : mutant) {
auto sum = 0.0f;
for (auto &c : sub)
sum += (c.score = f(c.graph));
sum = std::abs(sum);
for (auto &c : sub)
c.score /= sum;
std::sort(sub.begin(), sub.end(), std::greater<Mutant>());
}
}
Vec<size_t> Rating::size() const { return list_size(mutant); }
Unigraph Rating::build(Vec<size_t> const &indices) const {
const auto size = indices.size();
if (size != mutant.size())
throw "indices size wrong";
Unigraph ans;
for (size_t i = 0; i < size; ++i)
for (const auto &op : mutant.at(i).at(indices[i]).graph.operators)
ans.push_operator(op.op_type, op.inputs, op.outputs);
return ans;
}
Vec<Unigraph> split_each(Unigraph &&g) {
Vec<Unigraph> ans;
for (auto &op : g.operators)
ans.emplace_back().push_operator(op.op_type, op.inputs, op.outputs);
return ans;
}
float memory_usage(Unigraph const &g) {
std::unordered_set<size_t> mark;
uintptr_t memory;
for (const auto &op : g.operators)
for (const auto &t : op.outputs)
if (mark.insert(reinterpret_cast<uintptr_t>(t.get())).second)
memory += t->size();
return 1e6f / static_cast<float>(memory);
}

View File

@ -1,9 +1,7 @@
#pragma once
#include "data.h"
#include "op_type.h"
#include "tensor.h"
#include <functional>
/// @brief a struct to represent an operator in the computation graph.
/// The ownership of an `Operator` belongs to one `Unigraph`.
@ -51,110 +49,3 @@ struct Unigraph {
Vec<Arc<Tensor>> outputs //
);
};
/// @brief A candidate subgraph mutant.
struct Mutant {
/// @brief The mutated subgraph.
Unigraph graph;
/// @brief A score representing the quality of the mutant.
float score;
Mutant(Unigraph &&);
Mutant(Mutant const &) = delete;
Mutant(Mutant &&);
Mutant &operator=(Mutant const &) = delete;
Mutant &operator=(Mutant &&);
bool operator<(Mutant const &others) const;
bool operator>(Mutant const &others) const;
};
class Mutation;
class Rating;
/// @brief Partitioned subgraphs.
struct Partition {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<Vec<Mutant>> mutant;
friend Mutation;
public:
/// @brief A functional object that takes an unpartitioned graph as input
/// and returns a vector of partitioned subgraphs.
using Func = std::function<Vec<Unigraph>(Unigraph &&)>;
/// @brief Constructs a partitioned graph from an unpartitioned graph
/// using a partitioning function.
/// @param arg0 An unpartitioned graph.
/// @param arg1 A function that takes an unpartitioned graph as input
/// and returns a vector of partitioned subgraphs.
Partition(Unigraph &&, Func const &);
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const;
};
/// @brief Generates mutants for every subgraph.
class Mutation {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<Vec<Mutant>> mutant;
friend Rating;
public:
/// @brief A functional object that takes a subgraph as input
/// and returns a vector of mutated graphs.
using Func = std::function<Vec<Unigraph>(Unigraph const &)>;
/// @brief Mutates every subgraph in a partitioned graph.
/// @param arg0 The partitioned graph to be mutated.
/// @param arg1 A function that takes a subgraph as input
/// and returns a vector of mutated graphs.
Mutation(Partition &&, Func const &);
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const;
};
/// @brief Rates each subgraph mutant.
class Rating {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<Vec<Mutant>> mutant;
public:
/// @brief A functional object that takes a mutated subgraph as input
/// and returns its score.
using Func = std::function<float(Unigraph const &)>;
/// @brief Rates every mutated subgraph with a `Rating::Func`.
/// @param arg0 The mutated subgraphs to be rated.
/// @param arg1 A function that takes a mutated subgraph as input
/// and returns its score.
Rating(Mutation &&, Func const &);
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const;
/// @brief Builds `Unigraph` from the subgraphs
/// with specified indices.
/// @param arg0 Subgraph indices.
/// @return Merged `Unigraph`.
Unigraph build(Vec<size_t> const &) const;
};
/// @brief Splits a graph into subgraphs, where each subgraph contains
/// only one operator.
/// @param arg0 An unpartitioned graph.
/// @return A vector of individual subgraphs.
Vec<Unigraph> split_each(Unigraph &&);
/// @brief Calculates the memory usage of a graph.
/// @param arg0 The graph.
/// @return The reciprocal of the total memory usage of the graph in bytes.
float memory_usage(Unigraph const &);

21
try/src/mutation.cc Normal file
View File

@ -0,0 +1,21 @@
#include "mutation.h"
#include <unordered_set>
Vec<std::pair<Unigraph, SingleOperator>> split_each(Unigraph &&g) {
Vec<std::pair<Unigraph, SingleOperator>> ans;
for (auto &op : g.operators) {
auto &[g, t] = ans.emplace_back();
g.push_operator(op.op_type, op.inputs, op.outputs);
}
return ans;
}
float memory_usage(Unigraph const &g) {
std::unordered_set<size_t> mark;
uintptr_t memory;
for (const auto &op : g.operators)
for (const auto &t : op.outputs)
if (mark.insert(reinterpret_cast<uintptr_t>(t.get())).second)
memory += t->size();
return 1e6f / static_cast<float>(memory);
}

167
try/src/mutation.h Normal file
View File

@ -0,0 +1,167 @@
#pragma once
#include "graph.h"
#include <functional>
/// @brief A candidate subgraph mutant.
struct Mutant {
/// @brief The mutated subgraph.
Unigraph graph;
/// @brief A score representing the quality of the mutant.
float score;
Mutant(Unigraph &&g) : graph(std::move(g)) {}
Mutant(Mutant const &) = delete;
Mutant(Mutant &&others) : graph(std::move(others.graph)) {}
Mutant &operator=(Mutant const &) = delete;
Mutant &operator=(Mutant &&others) {
if (this != &others)
this->graph = std::move(others.graph);
return *this;
}
};
/// @brief A subgraph partition with `PartitionType`, will be mutated into
/// multiple `Mutant`s.
/// @tparam PartitionType To partition this subgraph.
template <class PartitionType> struct SubGraph {
Vec<Mutant> mutants;
PartitionType type;
};
template <class t> Vec<size_t> list_size(Vec<Vec<t>> const &);
template <class PartitionType> class Mutation;
template <class PartitionType> class Rating;
/// @brief Partitioned subgraphs.
template <class PartitionType> struct Partition {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<SubGraph<PartitionType>> parts;
friend Mutation<PartitionType>;
public:
/// @brief A functional object that takes an unpartitioned graph as input
/// and returns a vector of partitioned subgraphs.
using Func =
std::function<Vec<std::pair<Unigraph, PartitionType>>(Unigraph &&)>;
/// @brief Constructs a partitioned graph from an unpartitioned graph
/// using a partitioning function.
/// @param g An unpartitioned graph.
/// @param f A function that takes an unpartitioned graph as input
/// and returns a vector of partitioned subgraphs.
Partition(Unigraph &&g, Func const &f) {
for (auto &[g_, t] : f(std::move(g))) {
auto &sub = this->parts.emplace_back();
sub.mutants.emplace_back(std::move(g_));
sub.type = std::move(t);
}
}
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const { return list_size(parts); }
};
/// @brief Generates mutants for every subgraph.
template <class PartitionType> class Mutation {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<SubGraph<PartitionType>> parts;
friend Rating<PartitionType>;
public:
/// @brief A functional object that takes a subgraph as input
/// and returns a vector of mutated graphs.
using Func =
std::function<Vec<Unigraph>(Unigraph const &, PartitionType const &)>;
/// @brief Mutates every subgraph in a partitioned graph.
/// @param p The partitioned graph to be mutated.
/// @param f A function that takes a subgraph as input
/// and returns a vector of mutated graphs.
Mutation(Partition<PartitionType> &&p, Func const &f)
: parts(std::move(p.parts)) {
for (auto &sub : parts)
for (auto &m : f(sub.mutants.front().graph, sub.type))
sub.mutants.emplace_back(std::move(m));
}
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const { return list_size(parts); }
};
/// @brief Rates each subgraph mutant.
template <class PartitionType> class Rating {
/// @brief 2D vector of `Mutant` instances for each partitioned subgraph.
Vec<SubGraph<PartitionType>> parts;
public:
/// @brief A functional object that takes a mutated subgraph as input
/// and returns its score.
using Func = std::function<float(Unigraph const &)>;
/// @brief Rates every mutated subgraph with a `Rating::Func`.
/// @param m The mutated subgraphs to be rated.
/// @param f A function that takes a mutated subgraph as input
/// and returns its score.
Rating(Mutation<PartitionType> &&m, Func const &f)
: parts(std::move(m.parts)) {
for (auto &sub : parts) {
auto sum = 0.0f;
for (auto &c : sub.mutants)
sum += (c.score = f(c.graph));
sum = std::abs(sum);
for (auto &c : sub.mutants)
c.score /= sum;
std::sort(
sub.mutants.begin(), sub.mutants.end(),
[](auto const &a, auto const &b) { return a.score > b.score; });
}
}
/// @brief Returns mutant vector size.
/// @return 2D vector size.
Vec<size_t> size() const { return list_size(parts); }
/// @brief Builds `Unigraph` from the subgraphs
/// with specified indices.
/// @param indices Subgraph indices.
/// @return Merged `Unigraph`.
Unigraph build(Vec<size_t> const &indices) const {
const auto size = indices.size();
if (size != parts.size())
throw "indices size wrong";
Unigraph ans;
for (size_t i = 0; i < size; ++i)
for (const auto &op :
parts.at(i).mutants.at(indices[i]).graph.operators)
ans.push_operator(op.op_type, op.inputs, op.outputs);
return ans;
}
};
/// @brief Partition every operator as a `Unigraph`.
struct SingleOperator {};
/// @brief Splits a graph into subgraphs, where each subgraph contains
/// only one operator.
/// @param arg0 An unpartitioned graph.
/// @return A vector of individual subgraphs.
Vec<std::pair<Unigraph, SingleOperator>> split_each(Unigraph &&);
/// @brief Calculates the memory usage of a graph.
/// @param arg0 The graph.
/// @return The reciprocal of the total memory usage of the graph in bytes.
float memory_usage(Unigraph const &);
template <class t> Vec<size_t> list_size(Vec<SubGraph<t>> const &list) {
Vec<size_t> ans(list.size());
std::transform(list.begin(), list.end(), ans.begin(),
[](const auto &e) { return e.mutants.size(); });
return ans;
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <cstdint>
enum class OpType : uint16_t {
Abs,
Acos,

View File

@ -1,4 +1,4 @@
#include "../src/graph.h"
#include "../src/mutation.h"
#include <iostream>
#include <unordered_set>
@ -26,10 +26,11 @@ int main() {
{c} // outputs
);
auto p = Partition(std::move(g), split_each);
auto m = Mutation(std::move(p),
[](const auto &g) { return Vec<Unigraph>{}; });
auto r = Rating(std::move(m), memory_usage);
auto p = Partition<SingleOperator>(std::move(g), split_each);
auto m = Mutation<SingleOperator>(
std::move(p),
[](const auto &g, const auto &t) { return Vec<Unigraph>{}; });
auto r = Rating<SingleOperator>(std::move(m), memory_usage);
auto ans = r.build(Vec<size_t>(r.size().size(), 0));
return 0;