TensorFlow的图切割模块——Graph Partitioner
背景
功能描述
Graph Partition切割流程

第一步——分析构建Control Flow相关信息
GraphInfo g_info;
if (!opts.control_flow_added) {
// Add the "code" for distributed execution of control flow. Code is
// added only for the frames that are placed on multiple devices. The
// new graph is an equivalent transformation of the original graph and
// has the property that it can be subsequently partitioned arbitrarily
// (down to the level of individual device) for distributed execution.
status = AddControlFlow(opts, g, &g_info);
if (!status.ok()) return status;
}
第二步——构建Op的Input和Output Memory类型信息
// MemoryType is used to describe whether input or output Tensors of
// an OpKernel should reside in "Host memory" (e.g., CPU memory) or
// "Device" Memory (CPU memory for CPU devices, GPU memory for GPU
// devices).
enum MemoryType {
DEVICE_MEMORY = ,
HOST_MEMORY = ,
};
#define REGISTER_GPU_KERNEL(type) \
REGISTER_KERNEL_BUILDER(Name("Reshape") \
.Device(DEVICE_GPU) \
.HostMemory("shape") \
.TypeConstraint<type>("T") \
.TypeConstraint<int32>("Tshape"), \
ReshapeOp); \
REGISTER_KERNEL_BUILDER(Name("Reshape") \
.Device(DEVICE_GPU) \
.HostMemory("shape") \
.TypeConstraint<type>("T") \
.TypeConstraint<int64>("Tshape"), \
ReshapeOp);
上面的宏显示,虽然Reshape Op确实在GPU上有注册的实现版本,但是它依然要使用HostMemory。另外,某些Tensor的类型也决定了其是否可以被放置到Device Memory上,一般情况下float类型的数据对于计算设备是非常友好的,而String类型就不是这样,所以在types.cc文件中规定了一些强制被放在HostMemory的数据类型,如下代码所示。
bool DataTypeAlwaysOnHost(DataType dt) {
// Includes DT_STRING and DT_RESOURCE.
switch (dt) {
case DT_STRING:
case DT_STRING_REF:
case DT_RESOURCE:
return true;
default:
return false;
}
}
第三步——对原图进行分析,并产出切割后的多个子图

// Check whether there is already a send/recv pair transferring
// the same tensor/control from the src to dst partition.
const bool on_host = IsDstInputOnHost(edge, g_info);
DupRecvKey key{src->id(), edge->src_output(), dst_graph, on_host};
auto iter = dup_recv.find(key);
if (iter != dup_recv.end()) {
// We found one. Reuse the data/control transferred already.
const string& recv_node_name = iter->second.recv->name();
if (edge->IsControlEdge()) {
AddInput(dst_def, recv_node_name, Graph::kControlSlot);
} else {
AddInput(dst_def, recv_node_name, );
}
ref_control_inputs.push_back(recv_node_name); // We want the start_time for the recv to be the smallest of the start
// times of it's consumers. So we update this whenever we use a recv,
// and write it out to the attribute at the end of the subroutine
if (iter->second.start_time > recv_start_time) {
iter->second.start_time = recv_start_time;
}
continue;
}
const FunctionLibraryDefinition* flib_def = opts.flib_def;
if (flib_def == nullptr) {
flib_def = &g->flib_def();
} // Set versions, function library and send/recv incarnation.
for (auto& it : *partitions) {
GraphDef* gdef = &it.second;
*gdef->mutable_versions() = g->versions();
// Prune unreachable functions from `flib_def` before adding them to `gdef`.
*gdef->mutable_library() = flib_def->ReachableDefinitions(*gdef).ToProto(); // Traverse the graph to fill every send/recv op's incarnation
// information.
SetIncarnation(opts, gdef);
}
Send和Recv节点对插入的三种情况
在代码中,声明插入Send和Recv节点的代码段非常简单,如下所示。
// Need to split edge by placing matching send/recv nodes on
// the src/dst sides of the edge.
NodeDef* send = AddSend(opts, g_info, src_graph, edge, send_from,
send_start_time, &status);
if (!status.ok()) return status; NodeDef* real_recv = nullptr;
NodeDef* recv =
AddRecv(opts, g_info, dst_graph, edge, &real_recv, &status);
if (!status.ok()) return status;
但是对于不同的情况却有着丰富的处理逻辑,所以下面在展示示意图的同时,会将相关的代码段摘出来做展示。
在同一个Device上插入Send和Recv节点对
因为同一个Device上的Send和Recv节点在执行过程中实际上Memory Copy,而Recv的kernel又是异步的,所以需要有一种机制保证保证Recv一定要在Send之后执行,因此需要在Send和Recv之间插入一个Control Edge,从图的依赖上保证它们的执行顺序。

这个过程的关键是在插入Send和Recv节点之后,需要插入额外的Control Edge,代码如下。
// Fix up the control flow edge.
// NOTE(yuanbyu): 'real_recv' must be the real recv node.
if (src_graph == dst_graph) {
// For same device send/recv, add a control edge from send to recv.
// This prevents the asynchronous recv kernel from being scheduled
// before the data is available.
AddInput(real_recv, send->name(), Graph::kControlSlot);
}
跨Device根据DataFlow插入Send和Recv节点对

跨Device根据ControlFlow插入Send和Recv节点对

NodeDefBuilder::NodeOut send_from;
if (edge->IsControlEdge()) {
// Insert a dummy const node that will generate a tiny
// data element to be sent from send to recv.
VLOG() << "Send/Recv control: " << src->assigned_device_name() << "["
<< src->name() << "] -> " << dst->assigned_device_name() << "["
<< dst->name() << "]";
NodeDef* dummy = AddDummyConst(opts, src_graph, edge, &status);
if (!status.ok()) return status;
// Set the start time for this dummy node.
if (opts.scheduling_for_recvs) {
AddNodeAttr("_start_time", send_start_time, dummy);
}
AddInput(dummy, src->name(), Graph::kControlSlot);
send_from.Reset(dummy->name(), , DT_FLOAT);
} else {
send_from.Reset(src->name(), edge->src_output(), EdgeType(edge));
}
Indentity即相关依赖的插入逻辑被写在了AddRecv中,下面展示了这个片段。
// Add the cast node (from cast_dtype to dtype) or an Identity node.
if (dtype != cast_dtype) {
const string cast_op = (host_memory) ? "_HostCast" : "Cast";
NodeDefBuilder cast_builder(opts.new_name(src->name()), cast_op);
cast_builder.Attr("DstT", dtype);
cast_builder.Device(dst->assigned_device_name())
.Input(recv->name(), , cast_dtype);
NodeDef* cast = gdef->add_node();
*status = cast_builder.Finalize(cast);
if (!status->ok()) return nullptr;
return cast;
} else if (edge->IsControlEdge()) {
// An Identity is only needed for control edges.
NodeDefBuilder id_builder(opts.new_name(src->name()), "Identity");
id_builder.Device(dst->assigned_device_name())
.Input(recv->name(), , cast_dtype);
NodeDef* id = gdef->add_node();
*status = id_builder.Finalize(id);
if (!status->ok()) return nullptr;
return id;
} else {
return recv;
}
关于使用bfloat16压缩通信
TensorFlow支持通过使用bfloat16减少通信量,虽然bfloat16理论上是有损精度的,但是大量的实践证明这个精度损失是基本感知不到的。bfloat16的通信功能可以通过以下配置项打开,只要在创建Session时传入打开该功能的config即可。
graph_options = tf.GraphOptions(enable_bfloat16_sendrecv=True)
session_config = tf.ConfigProto(gpu_options=gpu_options)
总结
TensorFlow的图切割模块——Graph Partitioner的更多相关文章
- Tensorflow中的图(tf.Graph)和会话(tf.Session)详解
Tensorflow中的图(tf.Graph)和会话(tf.Session) Tensorflow编程系统 Tensorflow工具或者说深度学习本身就是一个连贯紧密的系统.一般的系统是一个自治独立的 ...
- 图数据库 Nebula Graph 的数据模型和系统架构设计
Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,而且能够提供极高的 ...
- TensorFlow框架(1)之Computational Graph详解
1. Getting Start 1.1 import TensorFlow应用程序需要引入编程架包,才能访问TensorFlow的类.方法和符号.如下所示的方法: import tensorflow ...
- tensorflow 优化图
当我们把训练好的tensorflow训练图拿来进行预测时,会有多个训练时生成的节点,这些节点是不必要的,我们需要在预测的时候进行删除. 下面以bert的图为例,进行优化 def optimize_gr ...
- GraphX 在图数据库 Nebula Graph 的图计算实践
不同来源的异构数据间存在着千丝万缕的关联,这种数据之间隐藏的关联关系和网络结构特性对于数据分析至关重要,图计算就是以图作为数据模型来表达问题并予以解决的过程. 一.背景 随着网络信息技术的飞速发展,数 ...
- 初识分布式图数据库 Nebula Graph 2.0 Query Engine
摘要:本文主要介绍 Query 层的整体结构,并通过一条 nGQL 语句来介绍其通过 Query 层的四个主要模块的流程. 一.概述 分布式图数据库 Nebula Graph 2.0 版本相比 1.0 ...
- 【转载】利用Unity自带的合图切割功能将合图切割成子图
虽然目前网上具有切割合图功能的工具不少,但大部分都是自动切割或者根据plist之类的合图文件切割的, 这种切割往往不可自己微调或者很难维调,导致效果不理想. 今天逛贴吧发现了一位网友写的切割合图插件很 ...
- c/c++ 有向无环图 directed acycline graph
c/c++ 有向无环图 directed acycline graph 概念: 图中点与点之间的线是有方向的,图中不存在环.用邻接表的方式,实现的图. 名词: 顶点的入度:到这个顶点的线的数量. 顶点 ...
- 图:无向图(Graph)基本方法及Dijkstra算法的实现 [Python]
一般来讲,实现图的过程中需要有两个自定义的类进行支撑:顶点(Vertex)类,和图(Graph)类.按照这一架构,Vertex类至少需要包含名称(或者某个代号.数据)和邻接顶点两个参数,前者作为顶点的 ...
随机推荐
- spring BeanPostProcessor
BeanPostProcessor spring使用BeanPostProcessor接口来处理生命周期的回调 BeanPostProcessor接口定义的两个方法,分别在bean的(实例化配置和初始 ...
- 《代码不朽:编写可维护软件的10大要则(C#版)》读后感
本书作者Joost Visser,译者张若飞.本书讲解了编写可维护代码的10个要则,从目录就可以看出这10点分别是: 编写短小的代码单元(15行以内,在大部分情况下还是能实现的,但是当我们使用Linq ...
- android-基础编程-ViewPager
ViewPager android 提供的基础V4包,android studio 导入gradle compile 'com.android.support:support-v4:25.0.0' 1 ...
- WSGI协议以及对服务器的影响
下面的内容纯属个人学习心得,如果对于我的观点有疑问,敬请留言,我将虚心向大牛学习. WSGI的全称是WEB SERVICE GATEWAY INTERFACE.WSGI 不是服务器,不是API,也不是 ...
- Apache Tomcat Eclipse Integration
An Illustrated Quick Start Guide Apache Tomcat makes hosting your applications easy. The Eclipse IDE ...
- vscode配置git及码云
1.将代码放到码云 到码云里新建一个仓库,完成后码云会有一个命令教程按上面的来就行了 码云中的使用教程: Git 全局设置: git config --global user.name "A ...
- PYTHON进阶(4)
学习内容: 1.html简介 2.html标签 3.堡垒机 一.html简介 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语 ...
- hdu 1086 You can Solve a Geometry Problem too [线段相交]
题目:给出一些线段,判断有几个交点. 问题:如何判断两条线段是否相交? 向量叉乘(行列式计算):向量a(x1,y1),向量b(x2,y2): 首先我们要明白一个定理:向量a×向量b(×为向量叉乘),若 ...
- div+css+position实现简单的纵向导航栏
完成效果: 这应该是很简单的纵向导航栏了. OK,进入正题 首先,我们看上面的效果图可以分析得出,要实现这个效果需要用到的技术点 1.hover 2.position 3.布局 我认为在这个效果的实现 ...
- jupyter-notebook快捷键
Jupyter Notebook 的快捷键 Jupyter Notebook 有两种键盘输入模式.编辑模式,允许你往单元中键入代码或文本:这时的单元框线是绿色的.命令模式,键盘输入运行程序命令:这时的 ...