GRPC++
简介
在大规模训练场景下,用户使用大量worker和ps,导致训练任务通信,数据拷贝等带来很大的开销。原生Tensorflow使用GRPC作为通信协议,在大规模场景下难以满足用户需求的训练性能。
针对上述问题,DeepRec提供了GRPC++支持更大规模的训练任务。通过Sharing Nothing架构、BusyPolling机制、用户态零拷贝、Send/Recv融合等多种优化实现,极大的降低了E2E的通信延时,数倍的提高了Server的吞吐能力,从而可以在DeepRec上支持更大的训练规模和更优的训练性能,在一些典型的业务场景上相比较原生的Tensorflow大幅提升了性能。
接口介绍
为了方便用户使用,开启GRPC++功能和使用GRPC一样简单,只需要配置Protocol
字段即可。
在一些场景下,特别是Send/Recv算子特别多时,将Send/Recv算子fuse起来提升性能,具体的是通过在config
中配置tensor_fuse
字段来启动此功能,默认不开启此功能。
注:在grpc协议下也能使用tensor_fuse功能。
Configure GRPC++
GRPC++使用了seastar做为底层的通信库,同时保留了GRPC的接口连接(用于MasterSession),这样需要为seastar配置一组ports。使用GRPC++需要在执行目录下配置.endpoint_map文件,格式如下:
127.0.0.1:3333=127.0.0.1:5555
127.0.0.1:4444=127.0.0.1:6666
其中worker0的GRPC ip/port为127.0.0.1:3333,那么配置的对应节点的seastar port为5555的配置方法为127.0.0.1:3333=127.0.0.1:5555 其中ps0的GRPC ip/port为127.0.0.1:4444,那么配置的对应节点的seastar port为6666的配置方法为127.0.0.1:3333=127.0.0.1:6666
对应的TF_CONFIG的配置中仍然使用的是GRPC的ip/port进行描述。
MonitoredTrainingSession训练
cluster = tf.train.ClusterSpec({"ps": ps_hosts, "worker": worker_hosts})
server = tf.train.Server(cluster,
job_name=FLAGS.job_name,
task_index=FLAGS.task_index,
protocol="grpc++") # 配置 protocol
...
with tf.train.MonitoredTrainingSession(
master=target,
config=tf.ConfigProto(tensor_fuse=True, # 开启send/recv tensor融合功能
...),
) as mon_sess:
...
Estimator训练
Estimator中使用GRPC++,需要通过RunConfig
来配置protocol="grpc++"
:
session_config = tf.ConfigProto(
tensor_fuse=True, # 开启send/recv tensor融合功能
inter_op_parallelism_threads=16,
intra_op_parallelism_threads=16)
run_config = tf.estimator.RunConfig(model_dir=model_dir,
save_summary_steps=train_save_summary_steps,
protocol="grpc++", # 配置 protocol
session_config=session_config) # 配置config
...
classifier = tf.estimator.Estimator(
model_fn=model_fn,
params={...},
config=run_config) # 配置 run_config
注意: PS/Worker模式下使用estimator,一定不要使用ParameterServerStrategy。会导致这里的RunConfig的protocol不生效。
最佳实践
GRPC++中除了上述的配置参数之外,还提供了一些环境变量来供用户对性能进行调优。
os.environ['WORKER_ENABLE_POLLING'] = "False"
os.environ['PS_ENABLE_POLLING'] = "False"
对于第一组参数:表示是否需要对通信线程进行polling。
"WORKER_ENABLE_POLLING"
一般都配置成"False"
。"PS_ENABLE_POLLING"
在混部集群下配置为"False"
,如果是独立集群或者CPU隔离做的比较好,可以设置为"True"
。
os.environ['NETWORK_PS_CORE_NUMBER'] = "8"
os.environ['NETWORK_WORKER_CORE_NUMBER'] = "2"
对于第二组参数:表示分配多少个通信线程。为何此处环境变量名是XX_CORE_NUMBER,因为如果通信线程使用polling,那么需要完整占用一个core。
需要结合任务实际分配的CPU core数量以及是否需要开启上述polling功能来确定。
目前NETWORK_PS_CORE_NUMBER的默认值是Min(8, connections), 取最小连接数和8中较小的一个,默认连接数不是最佳的配置,建议调整该参数。 目前NETWORK_WORKER_CORE_NUMBER的默认值是Min(2, connections), 取最小连接数和2中较小的一个,默认连接数不是最佳的配置,建议调整该参数。
对于worker,一般取2-4。因为PS的数量一般是很少的,总的连接数不会太多,2-4个线程足够使用。
对于PS,对于比较大的规模(100-几百数量级别),一般取8-10足够(假设分配24个CPU core),具体也需要看模型的计算复杂度以及分配的CPU Core数做相应的微调。对于较小或者较大的规模,相应的数量可以减少或增加。
os.environ["WORKER_DISABLE_PIN_CORES"] = "True"
os.environ["PS_DISABLE_PIN_CORES"] = "True"
对于第三组参数:表示通信线程是否需要绑核。
DeepRec默认不绑核(此处仅针对seastar的通信线程),用户在独占机器下可以尝试开启此功能。