MxNet源码学习--调度引擎

threaded_engine.h & threaded_engine.cc:
    核心就是分辨出哪些操作有变量读写冲突,让没有冲突的操作可以同时调度执行;让有冲突的操作不被同时调度执行

ThreadedVar的核心:
    num_pending_reads_:执行中的读操作的引用计数(-1表示正在执行写操作);
    等待队列(单链表实现)(存放等待中的操作,队头pending_write_可能在执行也可能在等待):pending_write_一般是队头,head_是队尾
    pending_write_:当前在执行或者等待中的第一个写操作;肯定是队头;

一个操作在执行之前,要把其用到的所有只读变量和可写变量,都“加依赖”(AppendReadDependency或AppendWriteDependency);
加依赖:该变量当前如果可以交给该操作,则该变量的引用计数加1(如果是写操作则引用计数置为-1),并把该操作的依赖计数减1;
              如果不可以,则把该操作push到该变量的等待队列里;
一个操作在执行之后,要把其用到的所有只读变量和可写变量,都“减依赖”(CompleteReadDependency或CompleteWriteDependency);
减依赖:只读变量:该变量的引用计数减1(当读计数减到0,则把自己交给等待队列头部的写操作);
              可写变量:如果当前和下一个写操作之间有读操作,则把自己交给这些读操作;如果没有,则把自己交给下一个写操作;

操作的引用计数:

一个操作创建时,把引用计数设成用到的所有变量个数+1,创建完成时-1,这个1是为了防止创建过程中该计数减到0被执行;

操作上的引用计数表示该操作还有多少变量不ready,当减到0时,立即获得执行权利;

PushAsync-->Push:对该操作的所有变量“加依赖”;引擎执行/等待中操作+1(pending_)
PushSync: 把该操作放入bulk里(bulk满了会调用一次PushAsync来批量执行)
OnCompleteStatic-->OnComplete: 一个操作在执行之后,要把其用到的所有只读变量和可写变量,都“减依赖”;引擎执行/等待中操作-1(pending_)

操作的执行:Push or OnComplete里交给“减依赖”的回调函数-->PushToExecute(负责调度的子引擎来实现)-->ExecuteOprBlock

BulkFlush: 先把变量去重;再调用PushAsync一次实现整个bulk这批操作的执行;

释放操作的内存(DeleteOperator):把该操作所有变量都认为是可写变量,通过PushAsync来实现(Why?)
释放变量的内存(DeleteVariable):把该变量var设成可写变量,通过PushAsync来实现
等待变量直到可读(WaitForVar):把该变量var设成只读变量,通过PushAsync来实现signal;
等待引擎闲置(没有执行中的操作,没有任何变量在被读写)(WaitForAll):wait在信号量上直到pending_为0

隐藏的事实:

       Var的操作等待队列,要么队头是写操作,要么队列为空;
       Var的操作等待队列,按照严格先进先出的顺序来走的,如果队列中有等待中的写操作,则读操作不会执行而是排队;2个写操作之间的读操作会在第2个写操作之前执行;即既不是写优先,也不是读优先;


threaded_engine_perdevice.cc

每个device,对应一个固定数量线程的线程池,对应一个任务队列;
cpu_priority_worker_: 默认一共有4个线程;处理CPU上的高优先级操作;
cpu_normal_workers_: 每个CPU-core上,默认有1个线程;处理CPU上的普通操作;
gpu_priority_workers_: 每个GPU卡上,默认有2个CPU线程(每个对应一个stream),处理GPU上的高优先级操作;
gpu_normal_workers_: 每个GPU卡上,默认有2个CPU线程(每个对应一个stream),处理GPU上的普通操作;
gpu_copy_workers_: 每个GPU卡上,默认有2个CPU线程(每个对应一个stream),处理CPU和GPU之间的Copy操作;

细节:无论CPU还是GPU,遇到删除变量的操作,因为能尽早释放内存,所以放到队列头部使其优先级最高;

我的思考:gpu_copy_workers_可以设置2个,一个FromHostToGPU,一个FromGPUToHost,充分利用PCIE总线的双向带宽


OpenMP在这里干啥??

 

threaded_engine_pooled.cc
CPU和CPU任务共用1个任务队列;显存内存Copy任务使用1个任务队列;
16个线程去做CPU和GPU计算任务(从那1个任务队列里Pop);1个线程去做显存内存Copy任务;
每个GPU,开16个计算stream, 轮转式使用;每个GPU,开1个io-stream;
疑问:执行GPU任务时,是异步执行还是同步执行?如果异步执行,无论多少个线程,都是狂Pop任务队列,所有stream挤压巨多;如果同步执行,那所有GPU上通知只能有16个计算任务在执行;

thread_pool.h
很简单,就启动N个线程;
创建ThreadPool的线程先wait,等Worker线程认为ready了,再signal自己的event;ThreadPool的所有event都被signal之后,才继续往下走(认为线程池创建完毕)
SetReadyOnDestroy这个对象是析构时signal,目的是保证异常安全;

 

张元鹏的调度引擎笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值