将 Keras 模型应用于符号张量的 TF2.0 内存泄漏
TF2.0 Memory Leak From Applying Keras Model to Symbolic Tensor
tldr:我的实现的内存使用显然随着通过它的样本数量而增长,但应该没有network/sample feeding 关心到目前为止通过了多少样本。
当通过函数 API 创建的自定义 Keras 模型传递大量高维数据时,我观察到我假设 GPU 内存使用量持续增长 随着观察到的实例数量的增加。以下是通过网络传递样本的过程的最小示例:
sequence_length = 100
batch_size = 128
env = gym.make("ShadowHand-v1")
_, _, joint = build_shadow_brain(env, bs=batch_size)
optimizer: tf.keras.optimizers.Optimizer = tf.keras.optimizers.SGD()
start_time = time.time()
for t in tqdm(range(sequence_length), disable=False):
sample_batch = (
tf.random.normal([batch_size, 1, 200, 200, 3]),
tf.random.normal([batch_size, 1, 48]),
tf.random.normal([batch_size, 1, 92]),
tf.random.normal([batch_size, 1, 7])
)
with tf.GradientTape() as tape:
out, v = joint(sample_batch)
loss = tf.reduce_mean(out - v)
grads = tape.gradient(loss, joint.trainable_variables)
optimizer.apply_gradients(zip(grads, joint.trainable_variables))
joint.reset_states()
print(f"Execution Time: {time.time() - start_time}")
我知道这样一个事实,即给定批量大小,这是一个很大的样本,但是如果它实际上对我的 GPU 来说太大了,我预计会立即出现 OOM 错误,并且我还假设 6GB VRAM 其实够用了。那是因为只有在33次之后才出现OOM错误,让我怀疑内存使用量越来越大。
请参阅以下我的模型的 Keras 摘要:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
visual_input (InputLayer) [(32, None, 200, 200 0
__________________________________________________________________________________________________
proprioceptive_input (InputLaye [(32, None, 48)] 0
__________________________________________________________________________________________________
somatosensory_input (InputLayer [(32, None, 92)] 0
__________________________________________________________________________________________________
time_distributed (TimeDistribut (None, None, 64) 272032 visual_input[0][0]
__________________________________________________________________________________________________
time_distributed_1 (TimeDistrib (None, None, 8) 848 proprioceptive_input[0][0]
__________________________________________________________________________________________________
time_distributed_2 (TimeDistrib (None, None, 8) 3032 somatosensory_input[0][0]
__________________________________________________________________________________________________
concatenate (Concatenate) (None, None, 80) 0 time_distributed[0][0]
time_distributed_1[0][0]
time_distributed_2[0][0]
__________________________________________________________________________________________________
time_distributed_3 (TimeDistrib (None, None, 48) 3888 concatenate[0][0]
__________________________________________________________________________________________________
time_distributed_4 (TimeDistrib (None, None, 48) 0 time_distributed_3[0][0]
__________________________________________________________________________________________________
time_distributed_5 (TimeDistrib (None, None, 32) 1568 time_distributed_4[0][0]
__________________________________________________________________________________________________
time_distributed_6 (TimeDistrib (None, None, 32) 0 time_distributed_5[0][0]
__________________________________________________________________________________________________
goal_input (InputLayer) [(32, None, 7)] 0
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (32, None, 39) 0 time_distributed_6[0][0]
goal_input[0][0]
__________________________________________________________________________________________________
lstm (LSTM) (32, 32) 9216 concatenate_1[0][0]
__________________________________________________________________________________________________
dense_10 (Dense) (32, 20) 660 lstm[0][0]
__________________________________________________________________________________________________
dense_11 (Dense) (32, 20) 660 lstm[0][0]
__________________________________________________________________________________________________
activation (Activation) (32, 20) 0 dense_10[0][0]
__________________________________________________________________________________________________
activation_1 (Activation) (32, 20) 0 dense_11[0][0]
__________________________________________________________________________________________________
concatenate_2 (Concatenate) (32, 40) 0 activation[0][0]
activation_1[0][0]
__________________________________________________________________________________________________
dense_12 (Dense) (32, 1) 33 lstm[0][0]
==================================================================================================
Total params: 291,937
Trainable params: 291,937
Non-trainable params: 0
__________________________________________________________________________________________________
如您所见,该网络中有一个 LSTM 层。它通常应该是有状态的,但我已经将其关闭,因为我认为问题不知何故就在那里。事实上,我已经尝试了以下方法,但没有解决问题
- 状态的转变
- 完全删除 LSTM
- 不计算任何梯度
- 在每个实例后重建模型
关于问题的潜在原因,我的想法现在已经结束。
我也将 进程强制到 CPU 并检查了标准内存(这里没有发生 OOM,因为我的 RAM 比 VRAM 多得多) .有趣的是,内存使用量上下跳跃,但呈上升趋势。对于每个实例,大约占用 2GB 内存,但是在获取下一个样本之前释放内存时,只会释放比占用内存少大约 200MB 的内存。
编辑 1: 如评论中所述,问题可能是在输入上调用模型会添加到计算图中。但是我不能使用 joint.predict()
因为我需要计算梯度。
编辑 2: 我更仔细地监控了内存的增长,实际上发生的是每次迭代都保留了一些内存,正如您在此处看到的前 9 个步骤:
0: 8744054784
1: 8885506048
2: 9015111680
3: 9143611392
4: 9272619008
5: 9405591552
6: 9516531712
7: 9647988736
8: 9785032704
这是在批量大小为 32 的情况下完成的。一个 sample_batch
的大小是 256 * (200 * 200 * 3 + 48 + 92 + 7) * 32 = 984244224
位(精度是 float32
),这或多或少表明确实存在问题是当通过网络传递样本时,样本被添加到图中,因为它是象征性的,正如@MatiasValdenegro 所建议的那样。所以我想这个问题现在可以归结为 "how to make a tensor non-symbolic" 如果那是一回事。
免责声明:我知道您无法使用给定的代码重现问题,因为缺少组件,但我无法在此处提供完整的项目代码。
我花了一些时间,但我现在已经解决了这个问题。正如我之前编辑过的问题:问题是 Keras 的功能 API 似乎将每个样本添加到计算图中,而不删除迭代后我们不再需要的输入。似乎没有明确删除它的简单方法,但是 tf.function
装饰器可以解决问题。
以我上面的代码为例,可以按如下方式应用:
sequence_length = 100
batch_size = 256
env = gym.make("ShadowHand-v1")
_, _, joint = build_shadow_brain(env, bs=batch_size)
plot_model(joint, to_file="model.png")
optimizer: tf.keras.optimizers.Optimizer = tf.keras.optimizers.SGD()
@tf.function
def _train():
start_time = time.time()
for _ in tqdm(range(sequence_length), disable=False):
sample_batch = (tf.convert_to_tensor(tf.random.normal([batch_size, 4, 224, 224, 3])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 48])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 92])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 7])))
with tf.GradientTape() as tape:
out, v = joint(sample_batch, training=True)
loss = tf.reduce_mean(out - v)
grads = tape.gradient(loss, joint.trainable_variables)
optimizer.apply_gradients(zip(grads, joint.trainable_variables))
print(f"Execution Time: {time.time() - start_time}")
_train()
也就是说,训练循环可以在带有 tf.function
装饰器的函数中传送。这意味着训练将以图形模式执行,出于某种原因,这消除了问题,很可能是因为图形将在函数结束后被转储。有关 tf.function
的更多信息,请参阅有关该主题的 TF2.0 Guide。
tldr:我的实现的内存使用显然随着通过它的样本数量而增长,但应该没有network/sample feeding 关心到目前为止通过了多少样本。
当通过函数 API 创建的自定义 Keras 模型传递大量高维数据时,我观察到我假设 GPU 内存使用量持续增长 随着观察到的实例数量的增加。以下是通过网络传递样本的过程的最小示例:
sequence_length = 100
batch_size = 128
env = gym.make("ShadowHand-v1")
_, _, joint = build_shadow_brain(env, bs=batch_size)
optimizer: tf.keras.optimizers.Optimizer = tf.keras.optimizers.SGD()
start_time = time.time()
for t in tqdm(range(sequence_length), disable=False):
sample_batch = (
tf.random.normal([batch_size, 1, 200, 200, 3]),
tf.random.normal([batch_size, 1, 48]),
tf.random.normal([batch_size, 1, 92]),
tf.random.normal([batch_size, 1, 7])
)
with tf.GradientTape() as tape:
out, v = joint(sample_batch)
loss = tf.reduce_mean(out - v)
grads = tape.gradient(loss, joint.trainable_variables)
optimizer.apply_gradients(zip(grads, joint.trainable_variables))
joint.reset_states()
print(f"Execution Time: {time.time() - start_time}")
我知道这样一个事实,即给定批量大小,这是一个很大的样本,但是如果它实际上对我的 GPU 来说太大了,我预计会立即出现 OOM 错误,并且我还假设 6GB VRAM 其实够用了。那是因为只有在33次之后才出现OOM错误,让我怀疑内存使用量越来越大。
请参阅以下我的模型的 Keras 摘要:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
visual_input (InputLayer) [(32, None, 200, 200 0
__________________________________________________________________________________________________
proprioceptive_input (InputLaye [(32, None, 48)] 0
__________________________________________________________________________________________________
somatosensory_input (InputLayer [(32, None, 92)] 0
__________________________________________________________________________________________________
time_distributed (TimeDistribut (None, None, 64) 272032 visual_input[0][0]
__________________________________________________________________________________________________
time_distributed_1 (TimeDistrib (None, None, 8) 848 proprioceptive_input[0][0]
__________________________________________________________________________________________________
time_distributed_2 (TimeDistrib (None, None, 8) 3032 somatosensory_input[0][0]
__________________________________________________________________________________________________
concatenate (Concatenate) (None, None, 80) 0 time_distributed[0][0]
time_distributed_1[0][0]
time_distributed_2[0][0]
__________________________________________________________________________________________________
time_distributed_3 (TimeDistrib (None, None, 48) 3888 concatenate[0][0]
__________________________________________________________________________________________________
time_distributed_4 (TimeDistrib (None, None, 48) 0 time_distributed_3[0][0]
__________________________________________________________________________________________________
time_distributed_5 (TimeDistrib (None, None, 32) 1568 time_distributed_4[0][0]
__________________________________________________________________________________________________
time_distributed_6 (TimeDistrib (None, None, 32) 0 time_distributed_5[0][0]
__________________________________________________________________________________________________
goal_input (InputLayer) [(32, None, 7)] 0
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (32, None, 39) 0 time_distributed_6[0][0]
goal_input[0][0]
__________________________________________________________________________________________________
lstm (LSTM) (32, 32) 9216 concatenate_1[0][0]
__________________________________________________________________________________________________
dense_10 (Dense) (32, 20) 660 lstm[0][0]
__________________________________________________________________________________________________
dense_11 (Dense) (32, 20) 660 lstm[0][0]
__________________________________________________________________________________________________
activation (Activation) (32, 20) 0 dense_10[0][0]
__________________________________________________________________________________________________
activation_1 (Activation) (32, 20) 0 dense_11[0][0]
__________________________________________________________________________________________________
concatenate_2 (Concatenate) (32, 40) 0 activation[0][0]
activation_1[0][0]
__________________________________________________________________________________________________
dense_12 (Dense) (32, 1) 33 lstm[0][0]
==================================================================================================
Total params: 291,937
Trainable params: 291,937
Non-trainable params: 0
__________________________________________________________________________________________________
如您所见,该网络中有一个 LSTM 层。它通常应该是有状态的,但我已经将其关闭,因为我认为问题不知何故就在那里。事实上,我已经尝试了以下方法,但没有解决问题
- 状态的转变
- 完全删除 LSTM
- 不计算任何梯度
- 在每个实例后重建模型
关于问题的潜在原因,我的想法现在已经结束。
我也将 进程强制到 CPU 并检查了标准内存(这里没有发生 OOM,因为我的 RAM 比 VRAM 多得多) .有趣的是,内存使用量上下跳跃,但呈上升趋势。对于每个实例,大约占用 2GB 内存,但是在获取下一个样本之前释放内存时,只会释放比占用内存少大约 200MB 的内存。
编辑 1: 如评论中所述,问题可能是在输入上调用模型会添加到计算图中。但是我不能使用 joint.predict()
因为我需要计算梯度。
编辑 2: 我更仔细地监控了内存的增长,实际上发生的是每次迭代都保留了一些内存,正如您在此处看到的前 9 个步骤:
0: 8744054784
1: 8885506048
2: 9015111680
3: 9143611392
4: 9272619008
5: 9405591552
6: 9516531712
7: 9647988736
8: 9785032704
这是在批量大小为 32 的情况下完成的。一个 sample_batch
的大小是 256 * (200 * 200 * 3 + 48 + 92 + 7) * 32 = 984244224
位(精度是 float32
),这或多或少表明确实存在问题是当通过网络传递样本时,样本被添加到图中,因为它是象征性的,正如@MatiasValdenegro 所建议的那样。所以我想这个问题现在可以归结为 "how to make a tensor non-symbolic" 如果那是一回事。
免责声明:我知道您无法使用给定的代码重现问题,因为缺少组件,但我无法在此处提供完整的项目代码。
我花了一些时间,但我现在已经解决了这个问题。正如我之前编辑过的问题:问题是 Keras 的功能 API 似乎将每个样本添加到计算图中,而不删除迭代后我们不再需要的输入。似乎没有明确删除它的简单方法,但是 tf.function
装饰器可以解决问题。
以我上面的代码为例,可以按如下方式应用:
sequence_length = 100
batch_size = 256
env = gym.make("ShadowHand-v1")
_, _, joint = build_shadow_brain(env, bs=batch_size)
plot_model(joint, to_file="model.png")
optimizer: tf.keras.optimizers.Optimizer = tf.keras.optimizers.SGD()
@tf.function
def _train():
start_time = time.time()
for _ in tqdm(range(sequence_length), disable=False):
sample_batch = (tf.convert_to_tensor(tf.random.normal([batch_size, 4, 224, 224, 3])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 48])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 92])),
tf.convert_to_tensor(tf.random.normal([batch_size, 4, 7])))
with tf.GradientTape() as tape:
out, v = joint(sample_batch, training=True)
loss = tf.reduce_mean(out - v)
grads = tape.gradient(loss, joint.trainable_variables)
optimizer.apply_gradients(zip(grads, joint.trainable_variables))
print(f"Execution Time: {time.time() - start_time}")
_train()
也就是说,训练循环可以在带有 tf.function
装饰器的函数中传送。这意味着训练将以图形模式执行,出于某种原因,这消除了问题,很可能是因为图形将在函数结束后被转储。有关 tf.function
的更多信息,请参阅有关该主题的 TF2.0 Guide。