CPU方面的性能瓶颈

时间:2018-07-10 08:36:57

标签: tensorflow keras

我正在研究语义分割架构。我需要加快培训速度,但不知道从何而来。

一般信息

  • 形状的图像(512,512,3)
  • 可用4个GPU GeForce GTX 1080 11 GB GPU内存
  • 提供1个CPU英特尔®至强®CPU E5-2637 v4#3.50GHz
  • 足够的内存
  • 我用Keras
  • 我使用轻量级数据预处理(主要是裁剪,没有太多数据扩充)

我尝试了有关数据加载的不同方法,但是每一次瓶颈似乎都是CPU而不是GPU。我运行nvidia-smihtop来查看利用率。

到目前为止我尝试过的事情:

  • Keras +具有8个工作器和1个GPU的自定义DataGenerator model.fit_generator(generator=training_generator,use_multiprocessing=True, workers=8)

  • Keras + tf.data.dataset,具有从原始图像加载的数据 model.fit(training_dataset.make_one_shot_iterator(),...)

    我尝试了两种预取方式:
    dataset = dataset.prefetch(tf.contrib.data.AUTOTUNE)
    dataset = dataset.apply(tf.contrib.data.prefetch_to_device('/gpu:0'))

  • Keras + tf.data.dataset,数据从tf.Records加载
    =>下一个选项。

发现

  • 由于开销计算占用了CPU,因此使用多个GPU(使用Keras相当容易)会减慢训练速度。
  • 令人惊讶的是,普通的DataGenerator方法(没有tf.data.dataset)是目前最快的方法。
  • 每种方法在很短的时间内
  • GPU利用率的确提高了100%。但有时也是0%。

现在,我的处理链看起来像这样:

磁盘上的数据-> CPU将数据加载到RAM中-> CPU进行数据预处理-> CPU将数据移至GPU-> GPU执行训练步骤

因此,加快培训速度的唯一方法是预先进行所有预处理,然后将文件保存到磁盘上(数据扩充将非常庞大)。然后使用tf.Records有效地加载文件。

您还有其他提高培训速度的想法吗?

更新

我已经用两种模型测试了管道。

简单模型

enter image description here

复杂模型

enter image description here

效果结果

我训练了3个时期的2个模型,每个时期140步(批量大小= 3)。 这是结果。

  1. 原始图像数据=> Keras.DataGenerator
    简单模型:126秒
    复杂模型:154秒

  2. 原始图像数据=> tf.data.datasets
    简单模型:208s
    复杂模型:215秒

DataGenerator

助手功能

def load_image(self,path):
    image = cv2.cvtColor(cv2.imread(path,-1), cv2.COLOR_BGR2RGB)
    return image

主要部分

#Collect a batch of images on the CPU step by step (probably the bottlebeck of the whole computation)
for i in range(len(image_filenames_tmp)):
    #print(image_filenames_tmp[i])
    #print(label_filenames_tmp[i])
    input_image = self.load_image(image_filenames_tmp[i])[: self.shape[0], : self.shape[1]]
    output_image = self.load_image(label_filenames_tmp[i])[: self.shape[0], : self.shape[1]]

    # Prep the data. Make sure the labels are in one-hot format
    input_image = np.float32(input_image) / 255.0
    output_image = np.float32(self.one_hot_it(label=output_image, label_values=label_values))

    input_image_batch.append(np.expand_dims(input_image, axis=0))
    output_image_batch.append(np.expand_dims(output_image, axis=0))

    input_image_batch = np.squeeze(np.stack(input_image_batch, axis=1))
    output_image_batch = np.squeeze(np.stack(output_image_batch, axis=1))            


return input_image_batch, output_image_batch

tf.data.dataset

助手功能

def preprocess_fn(train_image_filename, train_label_filename):
'''A transformation function to preprocess raw data
into trainable input. '''
     x = tf.image.decode_png(tf.read_file(train_image_filename))
     x = tf.image.convert_image_dtype(x,tf.float32,saturate=False,name=None)

     x = tf.image.resize_image_with_crop_or_pad(x,512,512)

     y = tf.image.decode_png(tf.read_file(train_label_filename))
     y = tf.image.resize_image_with_crop_or_pad(y,512,512)

     class_names, label_values = get_label_info(csv_path)

     semantic_map = []
     for colour in label_values:
         class_map = tf.reduce_all(tf.equal(y, colour), axis=-1)
         semantic_map.append(class_map)
         semantic_map = tf.stack(semantic_map, axis=-1)
         # NOTE cast to tf.float32 because most neural networks operate in float32.
      semantic_map = tf.cast(semantic_map, tf.float32)       

      return x, semantic_map

主要部分

dataset = tf.data.Dataset.from_tensor_slices((train_image_filenames, train_label_filenames))

dataset = dataset.apply(tf.contrib.data.map_and_batch(
            preprocess_fn, batch_size,
            num_parallel_batches=4,  # cpu cores
            drop_remainder=True if is_training    
dataset = dataset.repeat()
dataset = dataset.prefetch(tf.contrib.data.AUTOTUNE) # automatically picks best buffer_size

3 个答案:

答案 0 :(得分:3)

您的数据处理管道看起来如何?您是否考虑过省略一些可能太昂贵的步骤?您的数据如何存储?是按需加载的普通图像文件,还是您之前已将它们预加载到内存?通常,加载JPG / PNG图片非常昂贵。

如果在max_queue_size中增加model.fit_generator(),可以看到任何改进吗?

最后,您是否可以通过例如仅生成几千个批次并衡量每个批次的时间来基准测试数据处理管道实际上有多快?

除此之外,我自己的经验是,当您的模型相对较小/计算不昂贵时,可能会发现GPU使用率较低。由于必须在批次之间将新数据馈入GPU,因此您实际上无法避免开销。如果此开销与单次通过的实际计算时间之间的比率较高,您可能会发现您的总体GPU使用率相对较低,甚至常常获得0%的值。

编辑: 您能否为我们提供有关所使用模型的更多信息,尤其是它主要包含哪些层。例如,相对较小的CNN的单遍计算时间可能太短,以至于批之间重新馈送GPU使用的时间要比实际计算花费的时间更多。

更新: 在添加了有关处理管道的更多信息之后,我想说的主要瓶颈是PNG图像的加载和解码。 PNG解压缩(甚至压缩得更多)通常非常昂贵(根据this来源,它比JPEG约高5倍)。要检查这一假设,您可以通过测量每个处理步骤(解码,调整大小,裁剪等)需要多少时间以及主要的贡献者来描述您的处理管道。

现在有很多方法可以优化您的处理管道:

  • 似乎您加载了具有不同图像尺寸的未经处理的普通PNG图像。您至少可以将每个图像文件的大小调整为最终大小。这样可以节省存储空间,并减少加载/解码的开销。
  • 改为使用JPEG。如果是“真实世界”图像,则JPEG和PNG之间应该有明显的质量差异,但是JPEG占用的空间更少,解码的成本也要便宜得多。
  • 如果有足够的存储空间,则可以将整批图像保存为最终格式的压缩Numpy数组。这样可能会占用更多空间,但也会大大减少加载时间。

答案 1 :(得分:1)

您对处理链是正确的。

根据我的经验,可以大大提高性能的是并行进行数据加载(例如,如果来自远程数据库)和数据预处理。

这样,您可以在训练时继续处理下一批的数据,理想情况下,在GPU上完成最后一个训练步骤后,就可以准备好下一批的处理数据。

如果与非常快速的培训步骤相比,您的预处理非常繁重,则可能不会使性能提高很多。然后我会说你最好的选择是同时移动预处理器,例如GPU。通过使用CUDA。

编辑: 如果那没有帮助,我建议进行更深入的分析。如果确实是处理的一部分,请考虑如何加快速度,或者将列表而不是numpy用于数组操作是一个容易的问题。 最后,您唯一的选择是保存预处理的数据,而不是在运行时进行计算。另一种解决方案可能是在第一次处理后将其缓存(取决于您拥有的内存量)。

答案 2 :(得分:1)

我正在处理类似的问题,并且尝试优化管道是一项艰巨的任务。 使用horovod代替keras multi-gpu使我几乎线性加速,而keras multi-gpu却没有: https://medium.com/omnius/keras-horovod-distributed-deep-learning-on-steroids-94666e16673d

tf.dataset绝对是正确的方法。您可能还希望进行随机操作以更好地泛化。

另一件对我有很大帮助的事情是预先调整图像大小,并使用np.save()将其保存为.npy文件。它们需要更多的空间来保存,但读取速度要快一个数量级。我使用tf.py_func()将numpy操作转换为张量(由于python GIL而无法并行化)

Nvidia最近发布了DALI。它可以在GPU上进行增强,这绝对是未来的发展之路。对于简单的分类任务,它可能已经具有所需的所有功能。