You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TensorFlow GradientTape.jacobian() GPU显存不足,双RTX3090如何利用?

解决TensorFlow 2中双GPU计算大矩阵Jacobian的显存不足问题

核心问题分析

计算56×56矩阵对模型可训练变量的Jacobian时,单GPU无法承载超大尺寸的梯度张量(报错中的[3136,56,8,8,64]张量),可通过双GPU显存拆分和Jacobian计算优化两类方案解决。


一、利用双GPU扩容显存的具体方案

1. 数据并行拆分Jacobian计算任务

将56×56矩阵的元素拆分到两个GPU上分别计算梯度,最后合并结果。以下是手动指定设备分片的实现示例:

import tensorflow.compat.v2 as tf
import gc

# 保留原模型定义
class NN(tf.keras.Model):
  """The convolutional network used to compute the agent's policy."""
  def __init__(self,
               num_actions,
               name = None,
               dropout = 0.0,
               rand_conv = False,
               projection = True,
               **kwargs):
    super(NN, self).__init__(**kwargs)
    self.kwargs = kwargs
    self._dropout = dropout
    self._num_actions = num_actions
    self.rand_conv = None
    self._projection = projection
    activation_fn = tf.keras.activations.relu
    self.conv0 = tf.keras.layers.Conv2D(
        32, [8, 8],
        strides=4,
        padding='same',
        activation=activation_fn,
        name='Conv')
    self.conv1 = tf.keras.layers.Conv2D(
        64, [4, 4],
        strides=2,
        padding='same',
        activation=activation_fn,
        name='Conv1')
    self.conv2 = tf.keras.layers.Conv2D(
        64, [3, 3],
        strides=1,
        padding='same',
        activation=activation_fn,
        name='Conv2')
    self.flatten = tf.keras.layers.Flatten()
    self.dense0 = tf.keras.layers.Dense(256, activation=activation_fn)
    self.dense1 = tf.keras.layers.Dense(64, activation=activation_fn)
    self.dense2 = tf.keras.layers.Dense(num_actions, name='fully_connected')

  @tf.function
  def call(self, state, training=True):
    x = self.representation(state, projection=False)
    if training:
      x = tf.nn.dropout(x, rate=self._dropout)
    x = self.dense2(x)
    if self._num_actions == 1:
      x = tf.squeeze(x, axis=-1)
    return x

  def representation(self, state, projection=True):
    x = tf.cast(state, tf.float32)
    if self.rand_conv is not None:
      x = self.rand_conv(x)
    x = self.conv0(x)
    x = self.conv1(x)
    x = self.conv2(x)
    x = self.flatten(x)
    x = self.dense0(x)
    if projection and self._projection:
      x = self.dense1(x)
    return x

nn_model = NN(num_actions=2, dropout=0.0, rand_conv=True, projection=True)
fake1 = tf.random.uniform([56,60,60,2])
fake2 = tf.random.uniform([56,60,60,2])

# 拆分矩阵为前后两部分,分配到不同GPU计算
split_idx = 28
jacobian_parts = []

# GPU0计算前28行的Jacobian
with tf.device('/GPU:0'):
    with tf.GradientTape(persistent=True) as tape0:
        tape0.watch(nn_model.trainable_variables)
        out1 = nn_model.representation(fake1)
        out2 = nn_model.representation(fake2)
        cos = tf.keras.losses.CosineSimilarity(axis=2, reduction=tf.keras.losses.Reduction.NONE)
        m_part0 = cos(tf.expand_dims(out1[:split_idx], 1), tf.expand_dims(out2, 0))
    jac_part0 = tape0.jacobian(m_part0, nn_model.trainable_variables)
    jacobian_parts.append(jac_part0)
    del tape0, m_part0
    gc.collect()

# GPU1计算后28行的Jacobian
with tf.device('/GPU:1'):
    with tf.GradientTape(persistent=True) as tape1:
        tape1.watch(nn_model.trainable_variables)
        out1 = nn_model.representation(fake1)
        out2 = nn_model.representation(fake2)
        cos = tf.keras.losses.CosineSimilarity(axis=2, reduction=tf.keras.losses.Reduction.NONE)
        m_part1 = cos(tf.expand_dims(out1[split_idx:], 1), tf.expand_dims(out2, 0))
    jac_part1 = tape1.jacobian(m_part1, nn_model.trainable_variables)
    jacobian_parts.append(jac_part1)
    del tape1, m_part1
    gc.collect()

# 合并两部分结果
full_jacobian = []
for j0, j1 in zip(jacobian_parts[0], jacobian_parts[1]):
    full_jac = tf.concat([j0, j1], axis=0)
    full_jacobian.append(full_jac)

2. 模型并行:将模型层分配到不同GPU

把模型的不同层拆分到两个GPU上,让模型参数和中间计算结果分散到两张卡的显存中,降低单卡负载:

class NN(tf.keras.Model):
    def __init__(self, num_actions, **kwargs):
        super(NN, self).__init__(**kwargs)
        self._dropout = 0.0
        self._num_actions = num_actions
        self.rand_conv = None
        self._projection = True
        activation_fn = tf.keras.activations.relu
        
        # 卷积层分配到GPU0
        with tf.device('/GPU:0'):
            self.conv0 = tf.keras.layers.Conv2D(32, [8,8], strides=4, padding='same', activation=activation_fn)
            self.conv1 = tf.keras.layers.Conv2D(64, [4,4], strides=2, padding='same', activation=activation_fn)
        # 后续层分配到GPU1
        with tf.device('/GPU:1'):
            self.conv2 = tf.keras.layers.Conv2D(64, [3,3], strides=1, padding='same', activation=activation_fn)
            self.flatten = tf.keras.layers.Flatten()
            self.dense0 = tf.keras.layers.Dense(256, activation=activation_fn)
            self.dense1 = tf.keras.layers.Dense(64, activation=activation_fn)
            self.dense2 = tf.keras.layers.Dense(num_actions)
    
    # 原call和representation方法保持不变

二、Jacobian计算的显存优化(配合双GPU效果更佳)

1. 逐元素/逐批次计算Jacobian

避免一次性计算整个矩阵的Jacobian,循环处理小批次元素,每次只占用部分显存:

jacobian_list = []
# 逐行计算Jacobian
for i in range(56):
    with tf.GradientTape() as tape:
        tape.watch(nn_model.trainable_variables)
        out1 = nn_model.representation(fake1)
        out2 = nn_model.representation(fake2)
        cos = tf.keras.losses.CosineSimilarity(axis=2, reduction=tf.keras.losses.Reduction.NONE)
        m = cos(tf.expand_dims(out1, 1), tf.expand_dims(out2, 0))
        row_element = m[i]
    jac = tape.jacobian(row_element, nn_model.trainable_variables)
    jacobian_list.append(jac)
# 整理为完整Jacobian张量
full_jacobian = tf.stack(jacobian_list, axis=0)

2. 启用混合精度训练

将部分张量转为float16格式,大幅降低显存占用,同时不影响计算精度:

tf.keras.mixed_precision.set_global_policy('mixed_float16')
# 之后初始化模型并执行计算流程

三、额外显存清理技巧

  • 每次计算后手动删除无用张量,并调用gc.collect()释放内存
  • 禁用TensorFlow显存预分配,让显存按需使用:
    gpus = tf.config.list_physical_devices('GPU')
    for gpu in gpus:
        tf.config.experimental.set_memory_growth(gpu, True)
    

内容的提问来源于stack exchange,提问作者Dibbla

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 10:25:04