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

在JAX中高效实现可变长度参数化矩阵链与向量的乘法运算

高效实现变长索引矩阵链乘与梯度计算(JAX)

核心思路

抛弃冗余的填充操作,利用JAX原生的序列处理能力,针对每个变长索引序列单独执行矩阵链乘,再通过批量映射完成多序列计算,同时保留自动微分的兼容性。

具体实现方案

方案1:单序列scan + 批量vmap(无填充,效率最优)

先将字符串形式的门控序列转换为索引数组,对单个序列用scan完成矩阵乘,再通过vmap批量处理所有序列,完全避免无效计算。

修改后的完整代码:

import jax
import jax.numpy as jnp
from jax.scipy.linalg import expm

sigma0 = jnp.eye(2)
sigmax = jnp.array([[0, 1], [1, 0]])
sigmay = jnp.array([[0, -1j], [1j, 0]])
sigmaz = jnp.array([[1, 0], [0, -1]])
sigma = jnp.array([sigmax, sigmay, sigmaz])

def gates_func(params):
    theta = params["theta"]
    epsilon = params["epsilon"]

    n = jnp.array([jnp.cos(theta), 0, jnp.sin(theta)])
    omega = jnp.pi / 2 * (1 + epsilon)
    X90 = expm(-1j * omega * jnp.einsum("i,ijk->jk", n, sigma) / 2)

    # 返回按索引映射的门控数组:0对应X90,1对应Z90
    return jnp.array([X90, expm(-1j * jnp.pi / 2 * sigmaz / 2)])

# 处理单个序列的函数:输入门控数组、序列索引、初始向量,返回最终结果
def process_single_seq(gates, seq_indices, init_vec):
    def step(carry, idx):
        return gates[idx] @ carry, None
    final_vec, _ = jax.lax.scan(step, init_vec, seq_indices)
    return jnp.abs(final_vec[0])

def multiply_out(params):
    gates = gates_func(params)
    # 将字符串序列转为索引数组
    gate_indices_list = [
        jnp.array([0, 0]),
        jnp.array([0, 1]),
        jnp.array([1, 0]),
        jnp.array([0, 1, 0])
    ]
    init_vec = jnp.array([1.0, 0.0], dtype=jnp.complex128)
    
    # 批量处理所有序列
    results = jnp.array([process_single_seq(gates, seq, init_vec) for seq in gate_indices_list])
    return results

# 测试计算与梯度
params = dict(theta=-0.0, epsilon=0.001)
print(multiply_out(params))
grad_fn = jax.grad(multiply_out)
print(grad_fn(params))

方案2:掩码式填充(兼容统一批量处理)

如果需要统一的张量格式,可通过掩码标记有效计算步骤,跳过填充位置的无效操作:

def multiply_out_masked(params):
    gates = gates_func(params)
    gate_indices_list = [
        jnp.array([0, 0]),
        jnp.array([0, 1]),
        jnp.array([1, 0]),
        jnp.array([0, 1, 0])
    ]
    max_len = max(len(seq) for seq in gate_indices_list)
    # 填充序列并生成掩码(1为有效步骤,0为填充)
    padded_seqs = jnp.array([jnp.pad(seq, (0, max_len - len(seq)), mode="constant", constant_values=-1) for seq in gate_indices_list])
    masks = jnp.array([jnp.pad(jnp.ones(len(seq)), (0, max_len - len(seq)), mode="constant") for seq in gate_indices_list])
    init_vec = jnp.array([1.0, 0.0], dtype=jnp.complex128)
    
    def step(carry, inputs):
        seq_idx, mask = inputs
        # 掩码为真时执行矩阵乘,否则保持当前值
        new_carry = jax.lax.select(mask.astype(bool), gates[seq_idx] @ carry, carry)
        return new_carry, None
    
    # 批量扫描所有序列
    final_vecs, _ = jax.vmap(lambda seq, mask: jax.lax.scan(step, init_vec, (seq, mask))[0])(padded_seqs, masks)
    results = jnp.abs(final_vecs[:, 0])
    return results

方案对比

  • 方案1完全无填充,每个序列仅计算实际长度的步骤,适配长尾分布场景,计算效率最高
  • 两种方案均保留JAX自动微分能力,可正常对参数p求梯度
  • 代码结构简洁,易扩展门控类型或新增序列

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:00:06