計算圖是TensorFlow領(lǐng)域模型的核心丰嘉。本文通過對計算圖領(lǐng)域模型的梳理驼抹,講述計算圖構(gòu)造的基本原理。
邊
Edge
持有前驅(qū)節(jié)點與后驅(qū)節(jié)點丈甸,從而實現(xiàn)了計算圖的連接渔伯,也是計算圖前向遍歷顶霞,后向遍歷的銜接點。
邊上的數(shù)據(jù)以Tensor的形式傳遞咱旱,Tensor的標識由源節(jié)點的名稱确丢,及其所在邊的src_output
唯一確定。也就是說吐限,tensor_id = op_name:src_output
src_output與dst_input
Edge
持有兩個重要的屬性:
-
src_output
:表示該邊為前驅(qū)節(jié)點的第src_output
條輸出邊鲜侥; -
dst_input
:表示該邊為后驅(qū)節(jié)點的第dst_input
條輸入邊。
例如诸典,存在兩個前驅(qū)節(jié)點s1, s2
描函,都存在兩條輸出邊;存在兩個后驅(qū)節(jié)點d1, d2
狐粱,都存在兩條輸入邊舀寓。
控制依賴
計算圖中存在兩類邊,
- 普通邊:用于承載Tensor肌蜻,常用實線表示互墓;
- 控制依賴:控制節(jié)點的執(zhí)行順序,常用虛線表示蒋搜。
特殊地篡撵,控制依賴邊,其src_output, dst_input
都為-1(Graph::kControlSlot)
豆挽,暗喻控制依賴邊不承載任何數(shù)據(jù)育谬,僅僅表示計算的依賴關(guān)系。
bool Edge::IsControlEdge() const {
return src_output_ == Graph::kControlSlot;
}
節(jié)點
Node
(節(jié)點)持有零條或多條輸入/輸出的邊帮哈,分別使用in_edges, out_edges
表示膛檀。另外,Node持有NodeDef, OpDef
娘侍。其中咖刃,NodeDef
持有設(shè)備分配信息,及其OP的屬性值集合私蕾;OpDef
持有OP的元數(shù)據(jù)僵缺。
輸入邊
在輸入邊的集合中按照索引線性查找,當節(jié)點輸入的邊比較多時踩叭,可能會成為性能的瓶頸磕潮。依次類推,按照索引查找輸出邊容贝,算法相同自脯。
Status Node::input_edge(int idx, const Edge** e) const {
for (auto edge : in_edges()) {
if (edge->dst_input() == idx) {
*e = edge;
return Status::OK();
}
}
return errors::NotFound("not found input edge ", idx);
}
前驅(qū)節(jié)點
首先通過idx
索引找到輸入邊,然后通過輸入邊找到前驅(qū)節(jié)點斤富。依次類推膏潮,按照索引查找后驅(qū)節(jié)點,算法相同满力。
Status Node::input_node(int idx, const Node** n) const {
const Edge* e;
TF_RETURN_IF_ERROR(input_edge(idx, &e));
if (e == nullptr) {
*n = nullptr;
} else {
*n = e->src();
}
return Status::OK();
}
圖
Graph
(計算圖)就是節(jié)點與邊的集合焕参,領(lǐng)域模型何其簡單轻纪。計算圖是一個DAG圖,計算圖的執(zhí)行過程將按照DAG的拓撲排序叠纷,依次啟動OP的運算刻帚。其中,如果存在多個入度為0的節(jié)點涩嚣,TensorFlow運行時可以實現(xiàn)并發(fā)崇众,同時執(zhí)行多個OP的運算,提高執(zhí)行效率航厚。
空圖
計算圖的初始狀態(tài)顷歌,并非是一個空圖。實現(xiàn)添加了兩個特殊的節(jié)點:Source與Sink節(jié)點幔睬,分別表示DAG圖的起始節(jié)點與終止節(jié)點眯漩。其中,Source的id為0溪窒,Sink的id為1坤塞;依次論斷,普通OP節(jié)點的id將大于1澈蚌。
另外摹芙,Source與Sink之間,通過連接「控制依賴」的邊宛瞄,保證計算圖的執(zhí)行始于Source節(jié)點浮禾,終于Sink節(jié)點。它們之前連接的控制依賴邊份汗,其src_output, dst_input
值都為-1盈电。
習慣上,僅包含Source與Sink節(jié)點的計算圖也常常稱為空圖杯活。
Node* Graph::AddEndpoint(const char* name, int id) {
NodeDef def;
def.set_name(name);
def.set_op("NoOp");
Status status;
Node* node = AddNode(def, &status);
TF_CHECK_OK(status);
CHECK_EQ(node->id(), node_id);
return node;
}
Graph::Graph(const OpRegistryInterface* ops)
: ops_(ops), arena_(8 << 10 /* 8kB */) {
auto src = AddEndpoint("_SOURCE", kSourceId);
auto sink = AddEndpoint("_SINK", kSinkId);
AddControlEdge(src, sink);
}
非空圖
在前端匆帚,用戶使用OP構(gòu)造器,將構(gòu)造任意復雜度的計算圖旁钧。對于運行時吸重,無非就是將用戶構(gòu)造的計算圖通過控制依賴的邊與Source/Sink節(jié)點連接,保證計算圖執(zhí)行始于Source節(jié)點歪今,終于Sink節(jié)點嚎幸。
添加邊
計算圖的構(gòu)造過程非常簡單,首先通過Graph::AddNode
在圖中放置節(jié)點寄猩,然后再通過Graph::AddEdge
在圖中放置邊嫉晶,實現(xiàn)節(jié)點之間的連接。
const Edge* Graph::AllocEdge() const {
Edge* e = nullptr;
if (free_edges_.empty()) {
e = new (arena_.Alloc(sizeof(Edge))) Edge;
} else {
e = free_edges_.back();
free_edges_.pop_back();
}
e->id_ = edges_.size();
return e;
}
const Edge* Graph::AddEdge(Node* source, int x, Node* dest, int y) {
auto e = AllocEdge();
e->src_ = source;
e->dst_ = dest;
e->src_output_ = x;
e->dst_input_ = y;
CHECK(source->out_edges_.insert(e).second);
CHECK(dest->in_edges_.insert(e).second);
edges_.push_back(e);
edge_set_.insert(e);
return e;
}
添加控制依賴邊,則可以轉(zhuǎn)發(fā)調(diào)用Graph::AddEdge
實現(xiàn)替废。
const Edge* Graph::AddControlEdge(Node* src, Node* dst) {
return AddEdge(src, kControlSlot, dst, kControlSlot);
}
開源技術(shù)書
https://github.com/horance-liu/tensorflow-internals