多线程迭代器

自己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最好单独开一个变量来存储返回值:

  1. 后续内部自定义调用更灵活
  2. 线程相关

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__(),内存搞定。(函数结束没有结束线程应该是没问题的)

posted @ 2017-06-21 15:36  rotxin  阅读(1688)  评论(0编辑  收藏  举报