当前位置: 首页 > 知识库问答 >
问题:

为什么TensorFlow 2比TensorFlow 1慢得多?

王航
2023-03-14

许多用户认为这是切换到 Pytorch 的原因,但我还没有找到牺牲最重要的实际质量、速度来换取急切执行的理由/解释。

下面是代码基准测试性能,TF1与TF2-TF1的运行速度从47%到276%不等。

我的问题是:在图形或硬件级别,是什么导致了如此显着的减速?

寻找详细的答案-我已经熟悉广泛的概念。相关Git

规格:CUDA 10.0.130、cuDNN 7.4.2、Python 3.7.4、Windows 10、GTX 1070

基准测试结果:

更新:禁用以下代码的急切执行没有帮助。然而,行为是不一致的:有时在图形模式下运行会有很大帮助,其他时候相对于急切运行较慢。

基准代码

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

使用的功能:

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model
    
def make_data(batch_shape):
    return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))

共有2个答案

方奕
2023-03-14

此答案:旨在提供该问题的详细图形/硬件级描述 - 包括 TF2 与 TF1 列车循环、输入数据处理器以及 Eager vs. Graph 模式执行。有关问题摘要

性能判断:有时一个更快,有时另一个更快,具体取决于配置。就TF2与TF1而言,它们的平均水平相当,但确实存在显着的基于配置的差异,TF1胜过TF2的频率高于TF2,反之亦然。请参阅下面的“基准测试”。

EAGER VS. GRAPH:对一些人来说,整个答案的实质是:根据我的测试,TF2的渴望比TF1慢。细节进一步下降。

两者之间的根本区别在于:图形主动建立了一个计算网络,并在“被告知”时执行——而伊格尔在创建时执行所有内容。但故事只从这里开始:

>

  • Eager 并非没有 Graph,实际上可能主要是 Graph,与预期相反。它在很大程度上是什么,是执行图形 - 这包括模型

    渴望在执行时重建自己的图的一部分;图形未完全构建的直接后果 - 请参阅探查器结果。这会产生计算开销。

    急则慢,输入为Numpy;根据此Git评论

    TF2急切比TF1急切慢——这是…出乎意料的。参见下面的基准测试结果。差异从可以忽略不计到显著,但是一致的。不确定为什么会这样——如果TF开发人员澄清,将更新答案。

    TF2 vs. TF1:引用TF dev的相关部分,Q. Scott Zhu的回应- w/ bit我的重点

    在eager中,运行时需要执行ops并为python代码的每一行返回数值。单步执行的性质导致它很慢。

    在TF2中,Keras利用tf.function来构建用于训练、评估和预测的图。我们称之为模型的“执行函数”。在TF1中,“执行函数”是一个函数图,它共享一些常见的组件作为TF函数,但有不同的实现。

    在此过程中,我们不知何故为 train_on_batch()、test_on_batch() 和 predict_on_batch() 留下了不正确的实现。它们在数值上仍然是正确的,但x_on_batch的执行函数是一个纯 python 函数,而不是一个 tf.function 包装的 python 函数。这将导致缓慢

    在TF2中,我们将所有输入数据转换为tf.data.Dataset,通过它我们可以统一我们的执行函数来处理单一类型的输入。数据集转换中可能会有一些开销,我认为这是一次性开销,而不是每批成本

    以上最后一段的最后一句,以及以下段落的最后一条:

    为了克服急切模式下的缓慢,我们有@tf.function,它会把python函数变成一个图。当像np数组一样馈送数值时,tf.function的主体被转换成静态图,被优化,并返回最终值,这是快速的,应该具有与TF1图模式相似的性能。

    我不同意——根据我的分析结果,它显示急切的输入数据处理比图形的慢得多。此外,特别是不确定tf.data.Dataset,但急切确实重复调用多个相同的数据转换方法——请参见分析器。

    最后,dev的链接提交:支持Keras v2循环的大量更改。

    训练循环:根据(1)急切与图形;(2)输入数据格式,训练将继续进行不同的训练循环-在TF2中,_select_training_loop(),training.py,其中之一:

    training_v2.Loop()
    training_distributed.DistributionMultiWorkerTrainingLoop(
                  training_v2.Loop()) # multi-worker mode
    # Case 1: distribution strategy
    training_distributed.DistributionMultiWorkerTrainingLoop(
                training_distributed.DistributionSingleWorkerTrainingLoop())
    # Case 2: generator-like. Input is Python generator, or Sequence object,
    # or a non-distributed Dataset or iterator in eager execution.
    training_generator.GeneratorOrSequenceTrainingLoop()
    training_generator.EagerDatasetOrIteratorTrainingLoop()
    # Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
    # in graph mode (since they generate symbolic tensors).
    training_generator.GeneratorLikeTrainingLoop() # Eager
    training_arrays.ArrayLikeTrainingLoop() # Graph
    

    每个方法都以不同的方式处理资源分配,并对性能产生影响

    Train Loops:< code > fit vs < code > train _ on _ batch ,< code > keras vs < code > TF . keras :四者中的每一个都使用不同的train Loops,尽管可能不是所有可能的组合。例如,< code > keras ' < code > fit 使用一种形式的< code>fit_loop,例如< code > training _ arrays . fit _ loop(),其< code>train_on_batch可以使用< code>K.function()。< code>tf.keras有一个更复杂的层次结构,在上一节中有部分描述。

    训练循环:文档——一些不同执行方法的相关源文档字符串:

    与其他 TensorFlow 操作不同,我们不会将 python 数值输入转换为张量。此外,为每个不同的python数值生成一个新图形

    函数为每个唯一的输入形状和数据类型集实例化一个单独的图形。

    单个tf.function对象可能需要映射到引擎盖下的多个计算图。这应该仅作为性能可见(跟踪图具有非零计算和内存成本)

    输入数据处理器:与上述类似,处理器是根据运行时配置(执行模式、数据格式、分发策略)设置的内部标志逐案选择的。最简单的情况是 Eager,它直接与 Numpy 数组一起工作。有关一些具体示例,请参阅此答案。

    模型大小,数据大小:

    • 是决定性的;没有一种配置可以凌驾于所有模型之上

    基准:磨碎的肉。——Word文档--Excel电子表格

    术语:

    • %-少的数字都是秒
    • %计算为(1-longer_time/shorter_time)*100;基本原理:我们对一个比另一个快的因素感兴趣;短/长实际上是一个非线性关系,对于直接比较没有用
    • %符号确定:
      • TF2 vs TF1:如果TF2更快
      • GvE(图形与急切):如果图形更快

      分析器:

      分析器 - 说明:Spyder 3.3.6 IDE 分析器。

      >

    • 一些功能在其他功能的嵌套中重复;因此,很难追踪“数据处理”和“训练”功能之间的精确分离,因此会有一些重叠,正如最后一个结果所示。

      %数字计算w. r. t.运行时减去构建时间

      构建时间通过对所有(唯一)运行时(调用 1 或 2 次)求和来计算

      训练时间是通过将所有(唯一的)运行时(与迭代次数相同的次数)及其嵌套的一些运行时相加而计算的

      不幸的是,函数是根据它们的原始名称进行分析的(即_func=func将分析为func),它在构建时混合-因此需要排除它

      测试环境:

        < li >在最少后台任务运行的情况下在底部执行的代码 < li >在计时迭代之前,GPU已经“预热”了几次迭代,如本文所述 < li>CUDA 10.0.130,cuDNN 7.6.0,TensorFlow 1.14.0,

      方法:

      • 基准“小”、“中”,

      限制:一个“完整”的答案可以解释每一个可能的火车环路

      法典:

      import numpy as np
      import tensorflow as tf
      import random
      from termcolor import cprint
      from time import time
      
      from tensorflow.keras.layers import Input, Dense, Conv1D
      from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
      from tensorflow.keras.models import Model
      from tensorflow.keras.optimizers import Adam
      import tensorflow.keras.backend as K
      #from keras.layers import Input, Dense, Conv1D
      #from keras.layers import Dropout, GlobalAveragePooling1D
      #from keras.models import Model 
      #from keras.optimizers import Adam
      #import keras.backend as K
      
      #tf.compat.v1.disable_eager_execution()
      #tf.enable_eager_execution()
      
      def reset_seeds(reset_graph_with_backend=None, verbose=1):
          if reset_graph_with_backend is not None:
              K = reset_graph_with_backend
              K.clear_session()
              tf.compat.v1.reset_default_graph()
              if verbose:
                  print("KERAS AND TENSORFLOW GRAPHS RESET")
      
          np.random.seed(1)
          random.seed(2)
          if tf.__version__[0] == '2':
              tf.random.set_seed(3)
          else:
              tf.set_random_seed(3)
          if verbose:
              print("RANDOM SEEDS RESET")
      
      print("TF version: {}".format(tf.__version__))
      reset_seeds()
      
      def timeit(func, iterations, *args, _verbose=0, **kwargs):
          t0 = time()
          for _ in range(iterations):
              func(*args, **kwargs)
              print(end='.'*int(_verbose))
          print("Time/iter: %.4f sec" % ((time() - t0) / iterations))
      
      def make_model_small(batch_shape):
          ipt   = Input(batch_shape=batch_shape)
          x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
          x     = GlobalAveragePooling1D()(x)
          x     = Dropout(0.5)(x)
          x     = Dense(64, activation='relu')(x)
          out   = Dense(1,  activation='sigmoid')(x)
          model = Model(ipt, out)
          model.compile(Adam(lr=1e-4), 'binary_crossentropy')
          return model
      
      def make_model_medium(batch_shape):
          ipt = Input(batch_shape=batch_shape)
          x = ipt
          for filters in [64, 128, 256, 256, 128, 64]:
              x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
          x     = GlobalAveragePooling1D()(x)
          x     = Dense(256, activation='relu')(x)
          x     = Dropout(0.5)(x)
          x     = Dense(128, activation='relu')(x)
          x     = Dense(64,  activation='relu')(x)
          out   = Dense(1,   activation='sigmoid')(x)
          model = Model(ipt, out)
          model.compile(Adam(lr=1e-4), 'binary_crossentropy')
          return model
      
      def make_model_large(batch_shape):
          ipt   = Input(batch_shape=batch_shape)
          x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
          x     = Conv1D(128, 200, strides=1, padding='valid')(x)
          for _ in range(40):
              x = Conv1D(256,  12, strides=1, padding='same')(x)
          x     = Conv1D(512,  20, strides=2, padding='valid')(x)
          x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
          x     = Conv1D(256,   1, strides=1, padding='valid')(x)
          x     = GlobalAveragePooling1D()(x)
          x     = Dense(256, activation='relu')(x)
          x     = Dropout(0.5)(x)
          x     = Dense(128, activation='relu')(x)
          x     = Dense(64,  activation='relu')(x)    
          out   = Dense(1,   activation='sigmoid')(x)
          model = Model(ipt, out)
          model.compile(Adam(lr=1e-4), 'binary_crossentropy')
          return model
      
      def make_data(batch_shape):
          return np.random.randn(*batch_shape), \
                 np.random.randint(0, 2, (batch_shape[0], 1))
                 
      def make_data_tf(batch_shape, n_batches, iters):
          data = np.random.randn(n_batches, *batch_shape),
          trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
          return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)
      
      batch_shape_small  = (32, 140,   30)
      batch_shape_medium = (32, 1400,  30)
      batch_shape_large  = (32, 14000, 30)
      
      batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
      make_model_fns = make_model_small, make_model_medium, make_model_large
      iterations = [200, 100, 50]
      shape_names = ["Small data",  "Medium data",  "Large data"]
      model_names = ["Small model", "Medium model", "Large model"]
      
      def test_all(fit=False, tf_dataset=False):
          for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
              for batch_shape, shape_name in zip(batch_shapes, shape_names):
                  if (model_fn is make_model_large) and (batch_shape == batch_shape_small):
                      continue
                  reset_seeds(reset_graph_with_backend=K)
                  if tf_dataset:
                      data = make_data_tf(batch_shape, iters, iters)
                  else:
                      data = make_data(batch_shape)
                  model = model_fn(batch_shape)
      
                  if fit:
                      if tf_dataset:
                          model.train_on_batch(data.take(1))
                          t0 = time()
                          model.fit(data, steps_per_epoch=iters)
                          print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                      else:
                          model.train_on_batch(*data)
                          timeit(model.fit, iters, *data, _verbose=1, verbose=0)
                  else:
                      model.train_on_batch(*data)
                      timeit(model.train_on_batch, iters, *data, _verbose=1)
                  cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
                  del model
      
      test_all(fit=True, tf_dataset=False)
      

  • 太叔景曜
    2023-03-14

    更新8/ 17 30/2020: TF 2.3终于做到了:所有案例的运行速度都与之前的任何版本一样快,或者明显更快。

    进一步说,我之前的更新对TF不公平;我的GPU是罪魁祸首,最近一直过热。如果您看到迭代次数的上升趋势图,这是一个可靠的征兆。最后,请参见开发人员对Eager vs Graph的说明。

    这可能是我最后一次更新这个答案。你的模型速度的真实数据只能由你自己在你的设备上找到。

    2020年5月19日更新:TF 2.2,使用相同的测试:在渴望速度上只有微小的改进。下面是大型-大型Numpy < code > train _ on _ batch 情况的图,x轴是连续拟合迭代;我的GPU没有接近其全部容量,所以怀疑它的节流,但随着时间的推移,迭代确实变得越来越慢。

    如上所述,Graph 和 Eager 分别比 TF1 慢 1.56 倍和 1.97 倍。不确定我会进一步调试它,因为我正在考虑根据 TensorFlow 对自定义/低级功能的支持不佳切换到 Pytorch。但是,我确实打开了一个问题以获取开发人员的反馈。

    2020年2月18日更新:我每晚都坐冷板凳2.1和2.1;结果喜忧参半。除了一个配置(模型

    此外,对于我测试的大型模型,Graph 和 Eager 之间存在极端的可重复性差异 - 无法通过随机性/计算并行性来解释。我目前无法为每个时间限制提供这些声明的可重现代码,因此我强烈建议您针对自己的模型进行测试。

    尚未就这些问题发表Git文章,但我确实对原文发表了评论,目前还没有回应。一旦取得进展,我将更新答案。

    裁决:如果你知道自己在做什么,那就不是了。但如果你不知道,它可能会让你付出很多代价——平均几次GPU升级,最坏的情况下是多个GPU升级。

    此答案:旨在提供问题的高级描述,以及如何根据您的需求决定培训配置的指导原则。有关详细的低级描述,包括使用的所有基准测试结果代码,请参阅我的另一个答案。

    如果我学到任何信息,我将更新我的答案/更多信息 - 可以收藏/“加星”这个问题以供参考。

    问题摘要:正如TensorFlow开发人员Q. Scott Zhu所证实的那样,将开发重点放在热切执行上

    然而,事情要复杂得多。不仅仅是TF1与TF2,导致列车速度显著差异的因素还包括:

    1. TF2与TF1的比较
    2. 急切与图形模式
    3. <code>keras</code>与<code>tf。keras
    4. <code>numpy</code>与<code>tf.data。数据集与…
    5. train_on_batch()fit()<-code>
    6. GPU与CPU的对比
    7. model(x)模型。预测(x)

    不幸的是,以上几乎没有一个是相互独立的,每一个相对于另一个至少可以增加一倍的执行时间。幸运的是,你可以通过一些捷径系统地确定什么最有效——正如我将要展示的。

    我应该做什么?目前,唯一的方法是——对您的特定模型、数据和硬件进行实验。没有一种配置总是最有效的——但是有做和不做来简化您的搜索:

    • train_on_batch()Numpytf.kerasTF1急切/图形
    • train_on_batch()Numpytf.kerasTF2图
    • 配合()Numpytf.kerasTF1/TF2图形大模型

    >

  • Fit() Numpy Keras for Small

    fit()numpy TF1/TF2急切

    train_on_batch() numpy keras TF1 Eager

    【主要】<code>tf.python。keras;它的运行速度可以慢10-100倍,而且有很多bug;更多信息

      < li >这包括< code >层 、< code >模型 、< code >优化器,

    参考我的另一个答案底部的代码,获得一个基准设置的例子。上面的列表主要基于另一个答案中的“基准”表。

    上述注意事项的局限性

      < li >这个问题的题目是“为什么TF2比TF1慢很多?”,虽然它的身体明确关注训练,但问题并不仅限于它;推理也是如此,即使在相同的TF版本、导入、数据格式等情况下,也会有很大的速度差异。-看到这个回答。 < li > RNNs可能会显著改变另一个答案中的数据网格,因为它们在TF2得到了改进 < li >模型主要使用< code>Conv1D和< code >密集 -无rnn、稀疏数据/目标、4/5D输入,

    为什么TF2为了急切的执行而牺牲了最实用的品质——速度?显然,它没有——图表仍然可用。但如果问题是“为什么渴望”:

    • 出色的调试:您可能会遇到大量问题:“我如何获得中间层输出”或“我如何检查权重”;对于渴望,它(几乎)与._dict_一样简单。相比之下,Graph需要熟悉特殊的后端函数,这大大简化了整个调试过程

    如何启用/禁用 EAGER?

    tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
    tf.compat.v1.disable_eager_execution() # TF2; above holds
    

    在TF2具有误导性;看这里。

    附加信息:

    • 小心TF2中的_on_batch()方法;根据TF开发人员的说法,他们仍然使用较慢的实现,但不是故意的 - 即要修复它。有关详细信息,请参阅其他答案。

    对TENSORFLOW DEVS的请求:

      < li> 请修复< code>train_on_batch(),以及迭代调用< code>fit()的性能方面;定制火车环对很多人来说很重要,尤其是对我。 < li> 添加有关这些性能差异的文档/文档字符串,以便用户了解。 < li> 提高一般执行速度,防止偷窥者跳到Pytorch。

    感谢:感谢

    • Q. TensorFlow开发人员斯科特·朱对此事的详细澄清。
    • P. Andrey分享有用的测试和讨论。

    更新:

    >

  • 11/14/19-发现了一个模型(在我的真实应用程序中),对于所有带有/Numpy输入数据的*配置,它在TF2上运行得更慢。差异范围为13-19%,平均为17%。然而,kerastf.keras之间的差异更为明显:18-40%,平均为32%(两者都是TF1

    19年11月17日-开发人员在最近的一次提交中更新了on_batch()方法,声明速度有所提高-将在TF 2.1中发布,或者现在以tf-nigh的形式提供。由于我无法让后者运行,将延迟板凳到2.1。

    2/20/20 - 预测性能也值得板凳;例如,在 TF2 中,CPU 预测时间可能涉及周期性峰值

  •  类似资料:
    • 问题内容: 我有一个简单的任务:计算每个字母在一个字符串中出现的次数。我已经使用了它,但是在一个论坛上我看到了使用/比每个字母都要慢得多的信息。我认为它只能在字符串中进行一次遍历,而解决方案则必须遍历该字符串四次(在这种情况下)。为什么这么慢? 问题答案: 允许您计算任何可哈希对象,而不仅仅是子字符串。两种解决方案都是-time。您的测量结果表明,迭代和散列单个字符的开销大于运行4倍。 可以 使用

    • 下面的代码将简单的值持有者映射到一个对象,它在Java中的运行速度比使用XCode 7 beta3“最快、积极的优化[-ofast]”的Objective-C快15倍以上。在Java中,我可以获得超过280m/sec的查找,但在objc示例中只有大约19m。(我在这里发布了相应的Java代码,因为这是作为一个Swift比较开始的:Swift Dictionary即使经过优化也很慢:是否不断保留/发

    • 问题内容: 我生成了x的两个矩阵: 第一矩阵:和。 第二矩阵:和。 使用以下代码,第一个矩阵花费了8.52秒完成: 使用此代码,第二个矩阵花费了259.152秒来完成: 运行时间显着不同的原因是什么? 正如评论所说,仅打印需要秒,而给。 正如其他指出它对他们正常工作的人一样,例如,我尝试了Ideone.com,这两段代码以相同的速度执行。 测试条件: 我从 Netbeans 7.2 运行了此测试,

    • 我昨天对一个答案发表了评论,其中有人在正则表达式中使用了,而不是或。我说使用范围或数字说明符可能比使用字符集更快。 我决定今天测试一下,并惊讶地发现(至少在C#regex引擎中)似乎比其他两个似乎没有太大区别的任何一个都慢。这是我的测试输出超过10000个随机字符串,其中包含1000个随机字符,其中5077个实际上包含一个数字: 这对我来说是一个惊喜,有两个原因,如果有人能解释一下,我会很感兴趣:

    • 我发现 比 Python 2 和 3 中的函数慢。 Python 2 蟒蛇 3 为什么<code>max</code>(<code>O(n)</code>)比<code>sort</code>函数(<code<O(nlogn)</code>)慢?