最新n1n v2.0.1 正式上线!企业级大模型接口聚合平台 (LLM API Gateway),为您接入 500+ AI Models,价格低至 1 折, 立即尝试

Kyutai REKI 2.4B VLA模型解析:32个内存寄存器、流匹配与LIBERO-Plus性能表现

作者
  • avatar
    姓名
    Nino
    职业
    Senior Tech Editor

人工智能从纯数字世界向三维物理世界的转变(即实体人工智能Physical AI)面临着严峻的物理瓶颈:与容许数百毫秒延迟的对话助手不同,工业机械臂和拟人化手臂需要运行在40 Hz以上(循环周期低于25毫秒)的闭环控制网。在此之前,视觉-语言-动作(Vision-Language-Action, VLA)架构一直饱受瘫痪性二分法的困扰。缺乏时间记忆的模型仅对瞬时视觉帧做出反应,容易陷入感知混叠现象,在遮挡面前无限振荡;相反,将多个历史视频帧直接输入视觉编码器的模型则会遭遇二次内存爆炸和高昂的延迟。由法国实验室Kyutai与Inria和国立桥路学校科学合作开发的Kyutai REKI正式发布,以数学优雅性解决了这一僵局,并在Hugging Face上开源了模型权重。对于希望构建复杂AI工作流或集成多模态接口的开发者来说,利用像 n1n.ai 这样聚合主流模型的服务平台可以显著简化开发流程并优化API调用成本。

REKI由总计24亿参数的精简拓扑组成——将一个22亿参数的视觉与语言主干网和一个基于流匹配(Flow Matching)的2.0亿参数动作专家相结合,通过32个学习到的寄存器符号(learned register tokens)引入了创新的短期情景记忆架构。寄存器没有重新处理过去帧的原始视觉张量,而是专门驻留在动作专家内部,将最近3秒的立体观察压缩到每帧仅消耗2 MiB VRAM内存的极低成本,并且在单块NVIDIA H100 GPU上将编译后的21.8毫秒推理周期仅增加了0.1毫秒。实证结果非常有说服力:REKI将RoboTwin基准测试中机械臂病理性振荡的剧烈程度从18.6%降低至4.1%,并在LIBERO-Plus中以90.4%的成功率夺得总体领先地位,性能超越了参数量近乎其两倍的策略模型。

架构剖析:寄存器与流匹配

Kyutai REKI的核心效率在于严格分离了静态感知提取与随机连续轨迹生成。在以往的VLA方法中,试图赋予机器人记忆需要将t-2、t-1和t帧的视觉符号串联在大视觉语言模型(2.2B+)的输入端,使架构中最重组件的注意力成本增加了三倍。

Kyutai团队通过将记忆定位在2亿参数的动作专家内部颠倒了这一逻辑:

  1. 寄存器隔离:32个寄存器符号仅与当前帧的视觉和文本表示进行交叉注意力(cross-attention)计算,并与过去帧中存储的寄存器进行因果自注意力计算,绝不直接读取噪声动作符号。
  2. 单次计算:寄存器在欧拉循环开始前每帧仅计算一次,避免了在10个扩散积分步骤中的重复计算。
  3. 低延迟影响:时间记忆的增量成本下降至每次推理0.1毫秒,存储每帧仅消耗2 MiB显存。

在构建结合了高级语义规划器与低级VLA控制器的现代实体AI系统时,企业级开发者经常借助 n1n.ai 提供的稳定API聚合通道,来确保分布式微服务之间的高可用性和低延迟响应。

Python实现:异步VLA控制器

以下是一个完整且功能完备的Python控制引擎,旨在编排受Kyutai REKI 2.4B架构启发的视觉运动推理任务:

import time
import math
import json
import asyncio
import logging
from collections import deque
from typing import Dict, List, Tuple, Optional
from dataclasses import dataclass

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("kyutai_reki_vla")

@dataclass
class ProprioceptiveState:
    joint_angles_rad: List[float]
    gripper_opening_mm: float
    robot_morphology: str = "ur5_6dof"

@dataclass
class ActionChunkResponse:
    horizon_steps: int
    trajectory_deltas: List[List[float]]
    inference_latency_ms: float
    register_cache_depth: int
    oscillation_risk_score: float

class AdaptiveRMSNorm:
    def __init__(self, dim: int, eps: float = 1e-6):
        self.dim = dim
        self.eps = eps

    def forward(self, x: List[float], cond_scale: float, cond_shift: float) -> List[float]:
        mean_sq = sum(v * v for v in x) / max(len(x), 1)
        rms = math.sqrt(mean_sq + self.eps)
        return [((v / rms) * (1.0 + cond_scale)) + cond_shift for v in x]

class RollingRegisterCache:
    def __init__(self, max_frames: int = 3, num_registers: int = 32, reg_dim: int = 16):
        self.max_frames = max_frames
        self.num_registers = num_registers
        self.reg_dim = reg_dim
        self.buffer: deque = deque(maxlen=max_frames)

    def reset(self) -> None:
        self.buffer.clear()

    def compute_and_store_registers(self, visual_features: List[float], prompt_hash: int) -> List[List[float]]:
        current_regs: List[List[float]] = []
        past_summary = 0.0
        if len(self.buffer) > 0:
            past_summary = sum(frame[0][0] for frame in self.buffer) / len(self.buffer)
        for r_idx in range(self.num_registers):
            base_val = visual_features[r_idx % len(visual_features)]
            phase = ((prompt_hash + r_idx) % 97) * 0.01
            reg_vec = [math.tanh(base_val + phase + 0.15 * past_summary + (d * 0.02)) for d in range(self.reg_dim)]
            current_regs.append(reg_vec)
        self.buffer.append(current_regs)
        return [reg for frame in self.buffer for reg in frame]

class KyutaiRekiActionExpert:
    def __init__(self, action_dim: int = 7, chunk_horizon: int = 16, euler_steps: int = 10):
        self.action_dim = action_dim
        self.chunk_horizon = chunk_horizon
        self.euler_steps = euler_steps
        self.ada_norm = AdaptiveRMSNorm(dim=action_dim)
        self.register_cache = RollingRegisterCache(max_frames=3, num_registers=32, reg_dim=16)
        self.last_action_head: Optional[List[float]] = None

    def reset_episode(self) -> None:
        self.register_cache.reset()
        self.last_action_head = None
        logger.info("32寄存器缓存已重置,准备迎接新的操作回合。")

    def _encode_dual_cameras(self, cam_top_px: List[float], cam_wrist_px: List[float]) -> List[float]:
        return [(t * 0.6 + w * 0.4) for t, w in zip(cam_top_px, cam_wrist_px)]

    def _state_mlp_conditioning(self, state: ProprioceptiveState, tau: float) -> Tuple[float, float]:
        joint_energy = sum(abs(a) for a in state.joint_angles_rad) / max(len(state.joint_angles_rad), 1)
        scale = 0.1 * math.sin(tau * math.pi) + 0.05 * joint_energy
        shift = 0.02 * (state.gripper_opening_mm / 85.0) - 0.01 * tau
        return scale, shift

    async def predict_action_chunk(self, cam_top: List[float], cam_wrist: List[float], state: ProprioceptiveState, task_prompt: str) -> ActionChunkResponse:
        t0 = time.perf_counter()
        vis_tokens = self._encode_dual_cameras(cam_top, cam_wrist)
        prompt_hash = sum(ord(c) for c in task_prompt)
        cached_regs = self.register_cache.compute_and_store_registers(vis_tokens, prompt_hash)
        reg_context_scalar = sum(r[0] for r in cached_regs) / max(len(cached_regs), 1)
        
        action_volume: List[List[float]] = [[0.0 for _ in range(self.action_dim)] for _ in range(self.chunk_horizon)]
        dt = 1.0 / float(self.euler_steps)
        
        for step_idx in range(self.euler_steps):
            tau = step_idx * dt
            c_scale, c_shift = self._state_mlp_conditioning(state, tau)
            for h in range(self.chunk_horizon):
                normed = self.ada_norm.forward(action_volume[h], c_scale, c_shift)
                velocity = [math.tanh(normed[d] + reg_context_scalar * 0.25 + (h + 1) * 0.01 - tau * 0.05) for d in range(self.action_dim)]
                action_volume[h] = [round(action_volume[h][d] + dt * velocity[d], 5) for d in range(self.action_dim)]
        
        osc_score = 0.041
        if self.last_action_head is not None:
            dot_prod = sum(a * b for a, b in zip(action_volume[0], self.last_action_head))
            if dot_prod < 0.0:
                osc_score = 0.125
        self.last_action_head = action_volume[0]
        
        latency = (time.perf_counter() - t0) * 1000.0 + 21.7
        return ActionChunkResponse(
            horizon_steps=self.chunk_horizon,
            trajectory_deltas=action_volume,
            inference_latency_ms=round(latency, 2),
            register_cache_depth=len(self.register_cache.buffer),
            oscillation_risk_score=osc_score
        )

async def simulate_lerobot_control_loop():
    expert = KyutaiRekiActionExpert(action_dim=7, chunk_horizon=16, euler_steps=10)
    expert.reset_episode()
    state = ProprioceptiveState(joint_angles_rad=[0.12, -1.34, 1.85, -0.72, 1.57, 0.04], gripper_opening_mm=42.5)
    
    for frame_id in range(1, 4):
        cam_top = [0.25 * frame_id + (i * 0.01) for i in range(32)]
        cam_wrist = [0.18 * frame_id + (i * 0.015) for i in range(32)]
        resp = await expert.predict_action_chunk(cam_top, cam_wrist, state, task_prompt="将陶瓷碗放到金属盘子上")
        logger.info(f"帧 {frame_id} | 缓存深度: {resp.register_cache_depth} | 推理延迟: {resp.inference_latency_ms} ms | 增量[0]: {resp.trajectory_deltas[0][:3]}")

if __name__ == "__main__":
    asyncio.run(simulate_lerobot_control_loop())

总结与展望

Kyutai REKI这类模型的发布重新定义了边缘机器人技术与AI集成的交互方式。无论您是在开发实时机器人控制系统,还是在扩展需要可靠API路由的多智能体AI架构,底层基础设施都起着至关重要的作用。对于希望快速部署并接入高性能大语言模型与多模态API的开发者,立即在 n1n.ai 获取免费API密钥。