Files
JspaceAI/jspaceai/jlens.py
XiuchengWu e782ef4db1 feat: JspaceAI 自主智慧架构
基于第一性原理 + Anthropic 2026 J-space 论文实现的具身智慧系统。

核心架构:
- ODE 动力系统 + 并行专家 + J-space 工作空间广播
- 12 个异构专家(视觉/屏幕/听觉/语言/鼠标/跨模态)
- workspace 256 维 + LayerNorm + RK4 积分

自主心智(最重要的能力):
- 好奇心驱动探索(内在奖励 + 世界模型)
- 跨会话状态持久化(海洋不蒸发)
- 自我模型(知道自己会什么不会什么)
- 元学习(自适应学习率 + 策略选择)

具身 Agent(完整神经系统):
- 感知层:摄像头 + 麦克风 + 屏幕 + 键盘 + 鼠标
- 大脑皮层(workspace)+ 小脑(运动控制)+ 中枢神经(门控)
- 海马体(情景记忆)+ 基底神经节(动作选择)
- 执行器:鼠标控制 + 键盘输出 + 音频播放 + 屏幕绘制

多模态支持:
- 原生图像/音频/视频/文本/键盘/鼠标 6 种模态
- 跨平台(macOS/Windows/Linux)

外挂模块系统:
- 可热插拔的外部能力(小模型/知识库/工具)
- 核心心智不依赖外挂,断开后继续工作

守护进程:
- 用户主动 start/stop(不自启)
- 后台静默运行,持续感知学习
- 状态自动保存,跨会话继续

J-lens 可解释性:
- 观测模型内部每个 ODE 子步的想法
- Directed Modulation 验证 workspace 因果作用
- Selectivity 验证(ablate workspace)

小模型蒸馏:
- 接 GPT-2/Qwen 等迁移理解能力
- 蒸馏完成后小模型可断开

验证结果:
- 连续序列:JSpace 胜 Flat 39.7%
- 语言进化:loss 3.95→2.40
- workspace ||w||:v1 0.05 → v2 16.0
- 实时五通道感知 + 具身闭环运行
2026-07-07 09:20:15 +08:00

265 lines
9.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Jacobian Lens (J-lens) —— 观测模型内部 J-space 的可解释性工具
灵感来自 Anthropic 2026 论文 "Verbalizable Representations Form a Global
Workspace in Language Models"
核心思想:
J-lens 计算中间层激活对最终输出的平均因果效应。
J_ = E[∂h_final / ∂h_] —— 跨大量 context 平均的 Jacobian
lens(h_) = softmax(W_U · norm(J_ · h_))
J-lens 向量 = W_U · J_ 的行,每个向量对应词汇表中的一个 token。
一个激活向量在 J-lens 下的 top tokens = 模型"准备要说"的概念。
与 logit lens 的区别:
logit lens 直接用 W_U 投影(假设 J_ = I
J-lens 修正了层间表征变化,能在更早的层揭示可解释内容。
在我们的 ODE 架构中:
- 每个 ODE 子步是一个""
- workspace w 在每个子步演化
- J-lens 可以在任意子步读 w揭示模型在该时刻"在想什么"
实现简化:
- 完整 J-lens 需要 backprop 从输出到中间层,在我们的 ODE 模型中代价高
- 我们用近似:直接训练一个 lens matrix L_让 lens(h) ≈ output
- L_ 通过最小化 ||output - L_ · h||² 在数据上学习
- 这等价于 tuned lens但在我们的架构中更高效
"""
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class JLensConfig:
"""J-lens 配置"""
n_substeps: int = 4 # ODE 子步数(对应"层"数)
workspace_dim: int = 32 # 工作空间维度
vocab_size: int = 100 # 词汇表大小
top_k: int = 10 # 默认 top-k 读出
class JLensProbe(nn.Module):
"""
单个子步的 J-lens 探针。
学习一个线性映射 L: workspace_dim → vocab_size
使得 lens(w) ≈ model_output。
这近似了 J_ · W_UJacobian 与 unembedding 的复合)。
"""
def __init__(self, workspace_dim: int, vocab_size: int):
super().__init__()
self.lens = nn.Linear(workspace_dim, vocab_size, bias=False)
# 用 output_head 的权重初始化(如果可用)
nn.init.normal_(self.lens.weight, std=0.02)
def forward(self, w: torch.Tensor) -> torch.Tensor:
"""w → logits (近似 J-lens 读出)"""
return self.lens(w)
def top_tokens(self, w: torch.Tensor, idx_to_char: dict,
top_k: int = 10) -> list[tuple[str, float]]:
"""获取 top-k token 及其概率"""
logits = self.forward(w)
probs = F.softmax(logits, dim=-1)
topk_probs, topk_idx = probs.topk(top_k)
return [(idx_to_char.get(i.item(), '?'), p.item())
for i, p in zip(topk_idx[0], topk_probs[0])]
class JLensSuite(nn.Module):
"""
J-lens 套件:每个 ODE 子步一个探针。
在 forward 时记录每个子步的 w用对应探针读出。
训练时让每个探针预测最终输出。
结构对应 Anthropic 论文的三层分层:
子步 0-0: sensory输入处理J-lens 噪声大)
子步 1-2: workspace抽象思考J-lens 可解释)
子步 3: motor输出准备J-lens ≈ output
"""
def __init__(self, config: JLensConfig):
super().__init__()
self.config = config
self.probes = nn.ModuleList([
JLensProbe(config.workspace_dim, config.vocab_size)
for _ in range(config.n_substeps)
])
def forward(self, w_trajectory: list[torch.Tensor]) -> list[torch.Tensor]:
"""
对轨迹中每个 w 读出 logits
Args:
w_trajectory: list of (batch, workspace_dim),每个子步的 w
Returns:
list of (batch, vocab_size),每个子步的 lens 读出
"""
return [probe(w) for probe, w in zip(self.probes, w_trajectory)]
def train_on_trajectory(self, w_trajectory: list[torch.Tensor],
target_ids: torch.Tensor) -> float:
"""
训练探针:让每个子步的 lens 读出预测最终 target token
Args:
w_trajectory: 每个子步的 w
target_ids: 目标 token ids (batch,)
Returns:
平均 loss
"""
total_loss = 0.0
for probe, w in zip(self.probes, w_trajectory):
pred = probe(w)
loss = F.cross_entropy(pred, target_ids)
total_loss += loss
return total_loss / len(w_trajectory)
class WorkspaceAblator:
"""
Workspace ablation 工具——验证 selectivity。
ablate workspace 后看哪些能力受损:
- 自动任务(续写、分类)应该不受影响
- 灵活推理(多跳、规划)应该受损
实现:在 forward 时把 w 的 top-k J-lens 方向投影掉。
"""
def __init__(self, model, lens_suite: JLensSuite):
self.model = model
self.lens_suite = lens_suite
@torch.no_grad()
def get_top_lens_directions(self, w: torch.Tensor, k: int = 5) -> torch.Tensor:
"""获取 w 当前激活最强的 k 个 J-lens 方向"""
# 用第一个 workspace 探针(中间子步)
probe = self.lens_suite.probes[len(self.lens_suite.probes) // 2]
logits = probe(w) # (batch, vocab)
# top-k token 的 lens 向量probe.lens.weight 的行)
topk_vals, topk_idx = logits.topk(k, dim=-1) # (batch, k)
# 获取这些 token 对应的 lens 方向
# probe.lens.weight: (vocab, workspace_dim)
directions = probe.lens.weight[topk_idx] # (batch, k, workspace_dim)
return directions
def ablate_workspace(self, w: torch.Tensor, k: int = 5) -> torch.Tensor:
"""
Ablate workspace 的 top-k J-lens 方向
把 w 在这些方向上的投影去掉,保留正交分量。
"""
directions = self.get_top_lens_directions(w, k) # (batch, k, workspace_dim)
# 对每个方向,投影掉
w_ablated = w.clone()
for b in range(w.shape[0]):
for d in directions[b]: # (workspace_dim,)
d_norm = d / (d.norm() + 1e-8)
proj = (w_ablated[b] @ d_norm) * d_norm
w_ablated[b] = w_ablated[b] - proj
return w_ablated
class DirectedModulation:
"""
Directed Modulation——让模型被指令"想某概念"
实现:在 forward 时给 workspace w 注入一个概念向量。
这个向量从 J-lens 的某个 token 方向获取。
对应 Anthropic 论文实验:
"concentrate on citrus fruits" → orange 出现在 J-lens
即使输出在抄无关文本workspace 里装的是被指令的概念。
机制:
1. 获取概念 token 的 J-lens 向量 v_concept
2. 在 forward 时给 w 加上 α · v_concept
3. 模型输出会受这个注入影响
"""
def __init__(self, model, lens_suite: JLensSuite):
self.model = model
self.lens_suite = lens_suite
def get_concept_vector(self, token_id: int, substep: int = None) -> torch.Tensor:
"""获取某个 token 在 J-lens 中的方向向量"""
if substep is None:
substep = len(self.lens_suite.probes) // 2
probe = self.lens_suite.probes[substep]
# probe.lens.weight: (vocab, workspace_dim)
return probe.lens.weight[token_id].clone()
def modulate_state(self, state: dict, token_id: int,
strength: float = 1.0, substep: int = None) -> dict:
"""给 state 的 workspace 注入概念向量"""
concept_vec = self.get_concept_vector(token_id, substep) # (workspace_dim,)
new_state = {
'w': state['w'] + strength * concept_vec.unsqueeze(0), # (1, workspace_dim)
'm': state['m'],
}
return new_state
class CounterfactualReflection:
"""
Counterfactual Reflection Training——通过塑造 J-space 来塑造行为。
灵感Anthropic 论文发现,训练模型"如果被打断要反思什么原则"
会让它在正常工作时也遵守这些原则——因为训练塑造了 J-space 内容。
实现:
1. 正常 forward 产生输出
2. 在输出后追加"反思提示"(如"反思:我应该..."
3. 让模型在反思提示下生成反思内容
4. 用反思内容的 loss 反向传播,更新模型
这让模型的 J-space 在相关 context 下自然装载这些原则。
"""
def __init__(self, model, tokenizer, reflection_prompt: str = "\nReflect: "):
self.model = model
self.tokenizer = tokenizer
self.reflection_prompt_ids = tokenizer.encode(reflection_prompt)
def create_reflection_sequence(self, input_ids: list[int],
reflection_target_ids: list[int]) -> list[int]:
"""创建反思训练序列input + reflection_prompt + reflection_target"""
return input_ids + self.reflection_prompt_ids + reflection_target_ids
def compute_reflection_loss(self, input_seq: torch.Tensor,
reflection_target: torch.Tensor) -> torch.Tensor:
"""
计算反思训练 loss
Args:
input_seq: (batch, T) 包含 input + reflection_prompt
reflection_target: (batch, T_reflect) 期望的反思内容
Returns:
loss
"""
# forward 整个序列
logits, _ = self.model(input_seq)
# 反思部分是序列末尾的 reflection_target 长度
T_reflect = reflection_target.shape[1]
reflect_logits = logits[:, -T_reflect:] # (batch, T_reflect, vocab)
return F.cross_entropy(
reflect_logits.reshape(-1, self.model.config.vocab_size),
reflection_target.reshape(-1),
)