
本文介绍在 JAX 中对已预先 vmapped 的函数(输入输出均为 (M, N) 形状)计算逐行雅可比矩阵(即输出 (M, N, N))的规范、清晰且内存友好的方法,避免 hacky 实现或高内存开销。
本文介绍在 jax 中对**已预先 vmapped 的函数**(输入输出均为 `(m, n)` 形状)计算逐行雅可比矩阵(即输出 `(m, n, n)`)的规范、清晰且内存友好的方法,避免 hacky 实现或高内存开销。
在 JAX 中,jax.jacobian 默认作用于标量或向量输入,并沿其完整第一维展开求导;而当目标函数本身已是 vmap 化(如批量矩阵乘法),直接对其调用 jax.jacobian 会导致错误维度广播或冗余计算。此时核心思路是:将雅可比计算“下沉”到单样本层面,再用 vmap 批量执行——这既符合 JAX 的函数式与可组合设计哲学,也保证了计算效率与内存可控性。
最推荐的做法是显式构造一个单样本包装函数,再对其 vmap 雅可比:
import jax
from jax import numpy as jnp
rng = jax.random.PRNGKey(42)
A = jax.random.normal(rng, shape=(128, 16, 16))
def already_vmapped(x, A):
vmap_mul = jax.vmap(jnp.matmul)
return vmap_mul(A, x) # x: (128, 16) → out: (128, 16)
# ✅ 推荐方案:语义清晰、易于调试、内存友好
def f_single_batch(x, A):
# 将单行 x 和对应 A 的单个矩阵升维为 batch=1
return already_vmapped(x[None, :], A[None, :]).squeeze(0)
# 对每一行独立计算雅可比,自动并行化
jacobian_fn = jax.vmap(jax.jacobian(f_single_batch, argnums=0))
x = jnp.ones((128, 16))
J = jacobian_fn(x, A) # shape: (128, 16, 16)
print(J.shape) # (128, 16, 16)
print(jnp.allclose(J, A)) # True —— 因 matmul 对 x 的雅可比即为 A该方案优势显著:
-
语义明确:
f_single_batch明确表达了“还原至单样本语义”的意图; -
调试友好:可单独测试
f_single_batch与jax.jacobian(f_single_batch),验证单样本行为; -
内存高效:无全批量雅可比(
(M, N, M, N))的中间张量,仅生成(M, N, N)结果; -
可扩展性强:支持
argnums=(0, 1)多参数求导,或嵌套vmap/pmap分布式场景。
⚠️ 注意事项:
- 切勿直接对
already_vmapped调用jax.jacobian后用jnp.diagonal提取——这会先构建(M, N, M, N)全雅可比矩阵,内存复杂度达 O(M²N²),极易 OOM; - 若
already_vmapped内部含不可微操作(如jnp.where条件分支、jnp.argmax),需确保f_single_batch中的梯度流仍可追踪(必要时使用jax.custom_vjp); - 对于极高维
N(如 >1000),可考虑结合jax.checkpoint(即jax.remat)减少反向传播内存峰值。
总结:面对“已 vmapped 函数求逐行雅可比”这一常见需求,显式降维 + vmap(jacobian) 是 JAX 生态中最自然、稳健且可维护的解法。它不依赖底层实现细节,与 jit、pmap 等变换正交兼容,是符合 JAX 最佳实践的工程化选择。

















