Li Xinqi
Li Xinqi
有用户希望加一个严格模式,要求用户手动做所有数据类型转换,否则报错。这样用户可以自己精确控制。
使用oneflow.async.thread将write_metrics放置在临时线程里,这样能加速eager的性能。加速效果大概是 | 测试模型 | 计算设备 | 单卡batch_size | 分布式情况 | oneflow性能 | pytorch性能 | | ---------- | -------- | -------------- | ---------- | --------------- | -------------- | | bert-large |...
优化scalar_tensor.item的性能,使其耗时从pytorch的2-3倍降到pytorch的70%-80%。 ## 优化手段 ### 优化scalar_tensor.item的python层代码 原本的scalar_tensor.item是用python代码实现的:`scalar_tensor.cpu().numpy()[0]`。不论是tensor.cpu,tensor.numpy还是numpy.__getitem__,对于scalar_tensor.item都过于重度,因为scalar_tensor.item整个过程描述起来非常简单:从tensor内存中获取一个标量。显然,用一个专用的c++ api来加速这个过程是很合适的。 ### 优化scalar_tensor.item的main线程c++代码 旧版scalar_tensor.item的c++代码最终会调用AccessBlobByCallback指令。该指令构建的成本大概4-7个us,主要是一系列对象的创建成本,但其实这个构建成本可以通过复用指令对象减少到纳秒级。 上述所说的复用指令对象并不是直接复用AccessBlobByCallback指令,而是复用新设计的SyncRead指令。可以复用的根本原因是当前线程同一时间只会提交一条SyncRead指令给vm(因为是同步,main线程处理下一次SyncRead的时候,上一次SyncRead一定已经执行完)。SyncRead指令复用的方式可以采用简单的thread_local变量,确保main线程从python进入c++到最终提交指令到vm之间,不会有任何堆对象的创建。 ### 加速SyncRead的执行 SyncRead与AccessBlobByCallback的基本逻辑大致相同,但是由于前者专用于小数据的拷贝,我们可以用临时的pined_memory做cudaAsyncMemcpy。这种方式相比于AccessBlobByCallback所用的pageable memory,能缩短cudaAsyncMemcpy操作大概5us的时间,因为省去了同步操作。 ### 加速main线程的唤醒 旧版AccessBlobByCallback使用BlockingThenBusy来让worker线程唤醒main线程,具体做法是在cudaAsyncMemcpy之前通过BlockingThenBusy.blocking_cnt唤醒main线程,让main线程进入对BlockingThenBusy.spin_cnt的忙等,cudaAsyncMemcpy之后再对BlockingThenBusy.spin_cnt减一,让main线程执行BlockingThenBusy.spin_cnt之后的代码。这么做是为了用blocking wait减少cpu的空耗,而用busy wait加速main线程的响应。 上述过程中,worker线程调用posix接口pthread_cond_broadcast唤醒main线程。但是这个pthread_cond_broadcast也是有时间开销的,大概在1us-4us。这个开销同样可以想办法去掉,具体方法是main线程如果发觉vm上指令非常少(比如少于3条),它可以相信这些指令会在很快执行完,所以main线程可以选择不做blocking wait。这样main线程就不会进入睡眠,而worker线程也就不用花1us-4us来执行pthread_cond_broadcast。值得一提的是,原本的BlockingThenBusy不能帮助worker线程跳过pthread_cond_broadcast,本pr重构了BlockingThenBusy的内部实现以达成这一目的。 ### 加速worker线程的响应 旧版worker线程的工作模式是blocking wait方式等待指令,执行完指令后进入下次迭代的blocking wait。这种方式下,指令从scheduler线程到worker线程的切换开销大概在2-7us的样子,这对流水很弱的eager代码非常不利。 我们可以尝试让worker线程不要那么着急睡眠的方式工作,让它在每次执行完一条执行之后在线(使用std::this_thread::yield)等待大概200us,这段时间足够让main线程把后续的eager op准备好,穿过scheduler线程,worker线程在线接到任务就立刻开始干,这样就能省去线程切换的2-7us。
多进程分离编译。 核心思路:1)分发job而不是分发plan;2)在master上为每个task分配好task_id,然后分发给各个worker,worker进程在编译的时候直接使用这些task_id;3)regst_desc_id/mem_block_id/chunk_id在分配时分按照rank分段,保证不同rank上的plan肯定不会发生id冲突。
解决芯片算子api内部的fork死锁问题。 昇腾算子api的内部会起多线程,在每个线程里有可能调用fork,这是目前Master没法处理的情况。根本原因是atfork时候依赖了SyncVmMode,这是一个必须手工设置的变量,昇腾算子显然不知道在新建线程的时候需要设置SyncVmMode::kEnable,这才导致了死锁。 本pr让atfork不再依赖静态的SyncVmMode,而是依赖动态的boolean类型的VmNeedSync标记,这个标记会在VirtualMachine::Receive函数内实际发指令到vm的时刻才会设置。如果当前线程从未发过vm的指令,显然是不需要同步的。
重构ONEFLOW_VM_COMPUTE_ON_WORKER_THREAD,使其默认值为false,同时只对默认的worker thread 0生效。
Odb
odb模块方便oneflow的调试,需要在gdb环境运行。目前仅用于交互式代码讲解。 ## 准备带odb的oneflow 需要编译oneflow的odb分支,编译时记得采用-DCMAKE_BUILD_TYPE=RelWithDebInfo,这种编译模式不会损失性能,在gdb里运行也有够用的debug信息。 ### 在gdb里运行python3 ```bash ONEFLOW_VM_COMPUTE_ON_WORKER_THREAD=0 ONEFLOW_VM_THREAD_MIN_ONLINE_MICROSECONDS=0 gdb -ex='source oneflow/tools/gdb/odb' -ex=r python3 ``` 注意,上述命令中的选项-ex='source oneflow/tools/gdb/odb'会寻找当前目录下的oneflow/tools/gdb/odb文件,所以请在oneflow repo所在目录的外层目录执行本条命令。 此时,我们进入了gdb模式下的Python交互界面 ``` Starting program: /home/lixinqi/miniconda3/envs/oneflow-dev-clang10-v2/bin/python3 [Thread debugging using libthread_db enabled] Using host...
本pr尝试解决 https://github.com/Oneflow-Inc/OneTeam/issues/1782 里的问题。
Ssp
delay tick op/kernel/task/actor
减少反复infer 完全相同的 op_node。 1) symbol 化op_conf, blob_conf, job_conf 2) symbol化op infer conf 3) 每次infer完更新全局缓存,供下次使用。