mxnet:如何设置自定义mx.io.DataIter进行预取?

时间:2018-12-07 21:26:27

标签: python iterator gpu mxnet prefetch

我的mxnet脚本可能受到将数据加载到GPU的I / O的限制,我正在尝试通过预取来加快速度。问题是我不知道如何使用自定义数据迭代器进行预取。

我的第一个假设/希望是,设置self.preprocess_threads和self.prefetch_buffer的值就足够了,就像我看到的here这样的迭代器(例如{{1 }}。但是,当我这样做时,在设置这些变量之前,我发现相对于脚本而言,性能没有任何变化,因此显然设置这些设置无效。

然后我注意到,除了我为其实现了子类mxnet.io.ImageRecordUInt8Iter的基类之外,还存在类mx.io.PrefetchingIter 。我找到了这个documentation,但是我找不到任何示例,对于何时何地需要发生什么我有些困惑。但是,我不清楚如何使用它。例如。我看到除了mx.io.DataIter之外,它还有一个next()方法,该方法只是说“移至下一批”。这到底是什么意思?不生产就“移动”到下一批是什么意思?我发现了该类的source code,并通过简短的阅读,似乎需要多个迭代器并为每个迭代器创建一个线程。对于我当前的设计,这可能不起作用,因为我确实希望使用多个线程从同一个迭代器中预取。

这是我要通过自定义数据迭代器执行的操作

  1. 我维护一个全局iter_next(),在该数据可用时我会在其上弹出数据
  2. 我通过运行(通过multiprocessing.Queue)命令行脚本来生成该数据,该脚本执行一个c ++二进制文件,该文件会生成一个multiprocessing文件
  3. 我打开numpy文件并将其内容加载到内存中,对其进行处理,然后将处理后的位放在全局numpy
  4. 我的自定义迭代器提取此队列,并在队列为空时启动更多作业以产生更多数据。

这是我的代码:

multiprocessing.Queue

然后我尝试制作这些迭代器之一以及像这样的预取迭代器:

def launchJobForDate(date_str):
### this is a function that gets called via multiprocessing
### to produce new data by calling a c++ binary
### whenever data queue is empty so that we need to produce more data
    try:
        f = "testdata/data%s.npy"%date_str
        if not os.path.isfile(f):
            cmd = CMD % ( date_str, JSON_FILE, date_str, date_str, date_str)
            while True:
                try:
                    output = subprocess.check_output(cmd, shell=True)
                    break
                except:
                    pass
        while True:
            try:
                d = np.load(f)
                break
            except:
                pass
        data_queue.put((d, date_str))
    except Exception as ex:
        print("launchJobForDate: ERROR ", ex)

class ProduceDataIter(mx.io.DataIter):
    @staticmethod
    def processData(d, time_steps, num_inputs):
       try: 
            ...processes data...
            return [z for z in zip(bigX, bigY, bigEvalY, dates)]
        except Exception as ex:
            print("processData: ERROR ", ex)

    def __init__(self, num_mgrs, end_date_str):
        ## iter stuff
        self.preprocess_threads = 4
        self.prefetch_buffer = 1

        ## set up internal data to preserve state
        ## and make a list of dates for which to run binary

    @property
    def provide_data(self):
        return [mx.io.DataDesc(name='seq_var', 
                               shape=(args_batch_size * GPU_COUNT, 
                                      self.time_steps, 
                                      self.num_inputs), 
                               layout='NTC')]

    @property
    def provide_label(self):
        return [mx.io.DataDesc(name='bd_return', 
                                shape=(args_batch_size * GPU_COUNT)),             
                mx.io.DataDesc(name='bd_return', 
                                shape=(args_batch_size * GPU_COUNT, num_y_cols)), 
                mx.io.DataDesc(name='date', 
                               shape=(args_batch_size * GPU_COUNT))]                 


    def __next__(self):
        try:
            z = self.z.pop(0)       
            data = z[0:1]
            label = z[1:]
            return mx.io.DataBatch(data, label) 
        except Exception as ex:
            ### if self.z (a list) has no elements to pop we need
            ### to get more data off the queue, process it, and put it
            ### on self.x so it's ready for calls to __next__()
            while True:
                try:
                    d = data_queue.get_nowait()
                    processedData = ProduceDataIter.processData(d, 
                                                            self.time_steps, 
                                                            self.num_inputs)
                    self.z.extend(processedData)
                    counter_queue.put(counter_queue.get() - 1)

                    z = self.z.pop(0)
                    data = z[0:1]
                    label = z[1:]
                    return mx.io.DataBatch(data, label)

                except queue.Empty:
                    ...this is where new jobs to produce new data and put them 
                    ...on the queue would happen if nothing is left on the queue

问题在于,mgr = ProcessMgr(2, end_date_str) mgrOuter = mx.io.PrefetchingIter([mgr]) 会在第一次调用mgrOuter时立即抛出StopIteration,而不会像我想的那样调用__next__()

最后,我还注意到mgr.__next__()有一个seems like it might handle prefetchinggluon对象,但是在这种情况下,它似乎还假设基础数据来自DataLoader其布局是有限且不变的(基于它是根据Dataset实现的,该索引带有一个索引)。因此,考虑到我作为训练输入生成的数据的动态类队列性质,我似乎并没有追求这种选择。

我的问题是:

  • 我该如何修改上面的代码,以便对我的自定义迭代器进行预取?
  • 在哪里可以找到有关mx.io.PrefetchingIter的工作方式的示例或更详细的文档?
  • 我还有其他策略需要通过自定义迭代器从我的GPU中获得更多性能吗?目前,它们仅以大约50%的容量运行,并且增大(或减小)批量大小并不会改变这一点。我还能转动其他哪些旋钮来提高GPU使用效率?

感谢您的反馈和建议。

1 个答案:

答案 0 :(得分:0)

正如您已经提到的,gluon DataLoader提供了预取。在自定义DataIterator中,您将Numpy数组用作输入。因此,您可以执行以下操作:

f = "testdata/data%s.npy"%date_str
data = np.load(f)
train = gluon.data.ArrayDataset(mx.nd.array(data))
train_iter = gluon.data.DataLoader(train, shuffle=True, num_workers=4, batch_size=batch_size, last_batch='rollover')

由于您是动态创建数据的,因此可以尝试在每个时期重置DataLoader并加载新的Numpy数组。 如果GPU利用率仍然很低,请尝试增加batch_size和num_workers。另一个问题也可能是数据集的大小。重置DataLoader会影响性能,因此拥有更大的数据集会增加一个纪元的时间,从而提高性能。