多线程迭代器
自己hack的迭代器总觉得卡,可能是两个处理器之间工作不连贯,batch size高了会使CPU上下起伏,卡顿(看着流水图也心塞),低了GPU的Utilization不高。所以最好的方案应该就是多线程了。
后续评论(正文请忽略)
后面再来估计,似乎官方的版本也是没有多线程的(比如mx.img.ImageIter?),只是计算引擎在那里使人产生错觉,或者是在调用某些特别的函数时,在某处调用了多线程。(待验证)
看了下,似乎正好有个通用interface。
class mxnet.io.PrefetchingIter(iters, rename_data=None, rename_label=None)
Performs pre-fetch for other data iterators.
This iterator will create another thread to perform iter_next and then store the data in memory. It potentially accelerates the data read, at the cost of more memory usage.
Parameters:
iters (DataIter or list of DataIter) – The data iterators to be pre-fetched.
rename_data (None or list of dict) – The i-th element is a renaming map for the i-th iter, in the form of {‘original_name’ : ‘new_name’}. Should have one entry for each entry in iter[i].provide_data.
rename_label (None or list of dict) – Similar to rename_data.
Examples
>>> iter1 = mx.io.NDArrayIter({'data':mx.nd.ones((100,10))}, batch_size=25)
>>> iter2 = mx.io.NDArrayIter({'data':mx.nd.ones((100,10))}, batch_size=25)
>>> piter = mx.io.PrefetchingIter([iter1, iter2],
... rename_data=[{'data': 'data_1'}, {'data': 'data_2'}])
>>> print(piter.provide_data)
[DataDesc[data_1,(25, 10L),<type 'numpy.float32'>,NCHW],
DataDesc[data_2,(25, 10L),<type 'numpy.float32'>,NCHW]]
如果要更多的线程,估计可以将迭代器反复迭代。
NOTE
上面的结论还没试。
试了下,CPU的波动问题似乎没有得到解决,不过从GPU的utilization来看,这部分应该是有效的。
21 Jun, 2017 再记
前面写了几段发现还不适合发布,最近发现些其他问题,想到这还没完结,正好放这了。
Previous Note
前面提到的PrefetchingIter最好单独开一个变量来存储返回值:
- 后续内部自定义调用更灵活
- 线程相关
第2点还没有详细的试验,但这样做肯定是安全的,并且至少第一点是一个优势。
Concern
谈谈新的问题。
通常多线程成为噩梦的重要原因是同步is prone to bugs,这在以传递ref为特色的设计里面尤为使人忧虑。比如这段测试:
import mxnet as mx
import numpy as np
m_ = np.random.randint(-10,10,(4,5,6,7))
m= mx.nd.array(m_)
m_it = mx.io.PrefetchingIter(mx.io.NDArrayIter(m))
d=m_it.next().data[0]
m[:]=0 #-> 0....
d.asnumpy().sum() # dangerous !!!!
# 0
Solution
显然,使用强制拷贝是最合理的:
import mxnet as mx
import numpy as np
m_ = np.random.randint(-10,10,(4,5,6,7))
m= mx.nd.array(m_)
#m_it = mx.io.NDArrayIter(m)
m_it = mx.io.PrefetchingIter(mx.io.NDArrayIter(m))
d=m_it.next().data[0].copy()
m[:]=0 #-> 0....
d.asnumpy().sum()
# -118.0
Followup
另一个附带的问题是,拷贝的目的地在哪:
import mxnet as mx
import numpy as np
m_ = np.random.randint(-10,10,(4,5,6,7))
m= mx.nd.array(m_,mx.cpu(1))
m.copy().context
# cpu(1)
一个称心的返回值 😃
17 July, 2017 记
内存空间释放重利用
在空间管理上还存在一些问题。看github上的讨论,意思是mxnet将内存作为池来管理,所以即使释放了内存也不会从nvidia上看到变化。(其中一个问题是怎样释放掉一个变量,发现直接赋值为None可以解决问题)
但还是遇到一些问题,比如下面这段:
import mxnet as mx
def test():
m=mx.nd.zeros((999,999,550),mx.gpu()) # 4GB 的 total dedicated memory
return mx.io.NDArrayIter(m,batch_size=1)
用下面这个这段可以顺利运行:
from test import test, mx
import time
it_ = test()
it_ = None
time.sleep(2) # 似乎要这样运作一下
it_ = test() # 没问题
但这段就不行了:
from test import test, mx
import time
it_ = test()
it = mx.io.PrefetchingIter(it_)
it_ = None
it =None
time.sleep(2)
it_ = test()
it = mx.io.PrefetchingIter(it_) # 提示 Out of Memory
检查io.py时发现一个可能行的方案:
from test import test, mx
import time
it_ = test()
it = mx.io.PrefetchingIter(it_)
it.__del__() # 加入这个
it_ = None
it =None
time.sleep(2)
it_ = test()
it = mx.io.PrefetchingIter(it_) # 没有问题!
21 Jul, 2017 记
关于 __del__()与内存
另一最近遇到的一个例子是example/ssd里面的。
需要把图片里面的目标检测出来。使用的是for来一次次读,然后检测的结构。过了一会发现内存上去了,后面就被killed掉了,于是只好过一段时间自己中断掉,然后重新启动。后面发现可能要长期使用,这样手工中断就有些吃不消了,又不想跑到里面去改迭代器。跟着里面走了一会儿,发现出现了PrefetchingIter
(于是乎,陡然间想起多年前的寒假,在某kernel里面讲到出现系统kill的情况),于是在这段函数结束前,调用__del__()
,内存搞定。(函数结束没有结束线程应该是没问题的)