ljsysfurry's picture
Upload folder using huggingface_hub
b31d9bd verified
Raw History Blame Contribute Delete
36 kB
"""
AgentFrame 四层融合实现
=======================
认知层 × 路由层 × 存储层 × 物理层
设计: agentframe_four_layer_blueprint.md
实现: numpy (可 CPU 验证), 接口兼容 torch 迁移
层:
L1 MetaCog — 认知层: 任务分解/置信度/信息缺口
L2 LandmarkRouter — 路由层: landmark 摘要 + 分层软max + Q-Cal
L3 AbsorbedMLA — 存储层: 吸收式 MLA + 分层量化 (28.4×)
L4 KVPager — 物理层: 热/温/冷三级换页 + 预测性驱逐
"""
import numpy as np
import math
from dataclasses import dataclass, field
from typing import Optional, Literal
# ============================================================
# L3 存储层: 吸收式 MLA + 分层量化 (核心, 自研)
# ============================================================
@dataclass
class CompressedKV:
"""压缩后的 KV 块 (存储层输出, 物理层/路由层操作对象)"""
chunk_id: int
latent: np.ndarray # [576] 潜在向量 (kv_lora_rank 512 + k_pe 64)
quant_bits: int # 16/8/4
heat: float = 0.5 # 热度 (物理层)
last_access: float = 0.0 # 最后访问时间
size_bytes: int = 0 # 实际字节数
importance: float = 0.5 # 重要性 (遗忘曲线用, 0-1)
access_count: int = 0 # 访问次数 (遗忘曲线用)
max_heat: float = 0.0 # 历史峰值热度 (LFRU 滞回用, 防抖)
# ===== 真 INT4 打包 (colibrì quant.h 移植) =====
q4: Optional[np.ndarray] = None # INT4 打包字节 [288] (真实存储)
scales: Optional[np.ndarray] = None # per-channel scale [n_ch] (真实存储)
# ===== 自适应 Top-K 精度保护 (3-Agent 讨论室产出 + 实测迭代) =====
protected: bool = False # 是否为 Top-K 高精度块 (路由层标记)
reversible: bool = False # 启用保护机制
# ===== Sector-Block-Module 分层组织 (用户设计 + Pro 讨论完善) =====
sector_id: int = -1 # 所在区 (16 token)
block_id: int = -1 # 所在大块 (256 token)
module_id: int = -1 # 所在模组 (1024 token)
value_hist: Optional[np.ndarray] = None # 块内价值直方图 (两阶段驱逐用)
class HierarchicalKV:
"""
Sector-Block-Module 分层 KV 组织 (用户设计 + Pro 讨论完善)
============================================================
结构: 16 token(带位置) = 1 Sector
16 Sector = 1 Block (256 token)
4 Block = 1 Module (1024 token)
用途 (Pro 讨论结论):
✅ 存储骨架: 定位/索引/并行计算 (硬件对齐)
✅ 驱逐决策: 两阶段价值分配 (每块分位数直方图 + 全局配额 + 块内排序)
✅ 量化粒度: per-Sector (对齐 GPU warp)
✅ 换页单元: per-Block (热/温/冷)
"""
SECTOR_SIZE = 16 # token 数
BLOCK_SECTORS = 16 # 每 Block 的 Sector 数
MODULE_BLOCKS = 4 # 每 Module 的 Block 数
BLOCK_SIZE = SECTOR_SIZE * BLOCK_SECTORS # 256
MODULE_SIZE = BLOCK_SIZE * MODULE_BLOCKS # 1024
@staticmethod
def addr(token_idx: int) -> tuple:
"""token 地址 → (module_id, block_id, sector_id, offset)"""
module_id = token_idx // HierarchicalKV.MODULE_SIZE
rem = token_idx % HierarchicalKV.MODULE_SIZE
block_id = rem // HierarchicalKV.BLOCK_SIZE
rem2 = rem % HierarchicalKV.BLOCK_SIZE
sector_id = rem2 // HierarchicalKV.SECTOR_SIZE
offset = rem2 % HierarchicalKV.SECTOR_SIZE
return module_id, block_id, sector_id, offset
@staticmethod
def build_value_hist(latents: list, bins: int = 8) -> np.ndarray:
"""构建块价值直方图 (两阶段驱逐用, Pro 讨论结论) — 由 two_stage_evict 使用"""
if not latents:
return np.zeros(bins)
# 用 latent 范数作为价值代理 (可替换为注意力分数)
vals = np.array([np.linalg.norm(l) for l in latents])
hist, _ = np.histogram(vals, bins=bins, range=(0, max(vals.max(), 1e-8)))
return hist / max(hist.sum(), 1)
@staticmethod
def two_stage_evict(blocks: dict, total_keep: int = None):
"""
两阶段驱逐 (Pro 终极方案):
1. 每块分位数直方图 → 全局价值分布代理
2. 按价值密度分配各块保留配额
3. 块内按分数排序精确选幸存
4. 相邻块配额微调消除边界效应
"""
block_ids = list(blocks.keys())
if not block_ids:
return {}
# 1. 每块价值摘要 (直方图熵加权)
summaries = {}
for bid in block_ids:
h = blocks[bid].get('value_hist')
if h is None:
h = np.ones(8) / 8
# 价值密度 = 直方图高价值区间占比 (后50%区间加权)
density = float(np.sum(h[len(h)//2:]) + 0.1 * np.sum(h[:len(h)//2]))
summaries[bid] = density
# 2. 全局配额分配
total_density = sum(summaries.values()) or 1.0
if total_keep is None:
total_keep = sum(len(b.get('latents', [])) for b in blocks.values())
quotas = {bid: int(summaries[bid] / total_density * total_keep)
for bid in block_ids}
# 3. 块内排序选幸存
survivors = {}
for bid in block_ids:
latents = blocks[bid].get('latents', [])
scores = blocks[bid].get('scores', [])
q = min(quotas[bid], len(latents))
if scores:
idx = np.argsort(scores)[-q:] if q > 0 else []
survivors[bid] = {
'latents': [latents[i] for i in idx],
'scores': [scores[i] for i in idx],
}
else:
survivors[bid] = {'latents': latents[-q:], 'scores': []}
return survivors
class ReversibleQuantizer:
"""
自适应 Top-K 精度保护 + 真 INT4 打包 (colibrì quant.h 移植)
=================================================================
讨论室方案: 1bit 误差符号补偿 → 实测无效 (Top-1 翻转 34%)
实测根因: 99.9% 翻转发生在分数差<0.1 的接近竞争, 补偿无法消除噪声
真解法: 路由层已知 Top-K → 对 Top-K 块保留 16bit, 其余 4bit
→ 翻转率 0/100 (完美), 存储压缩大部分保留
核心原则: 精度预算花在"可能参与 Top-K 竞争"的块上
INT4 打包 (colibrì quant.h pack_int4):
对称量化 s = amax/7, 每个 int4 存 nibble (v+8), 两个 nibble 塞 1 字节
576 维 latent → 288B q4 + n_ch×4B scales
"""
@staticmethod
def quantize_int4(latent: np.ndarray, n_ch: int = 16) -> tuple:
"""
真 INT4 打包 (colibrì pack_int4 移植): 对称量化 + per-channel scale
返回: (q4 打包字节 np.uint8, scales np.float32, 大小字节)
"""
d = latent.shape[-1]
ch = d // n_ch
tc = latent.reshape(n_ch, ch)
# 对称量化: s = amax / 7 (int4 范围 -8..7, 对称用 7)
amax = np.abs(tc).max(axis=-1) # [n_ch]
scales = amax / 7.0
scales = np.where(scales < 1e-8, 1e-8, scales) # 防除零
q = np.round(tc / scales[:, None]).astype(np.int32)
q = np.clip(q, -8, 7)
# nibble 打包: (v0+8) | ((v1+8)<<4), 两个 int4 塞 1 字节
q_flat = q.reshape(-1)
n_pairs = (q_flat.shape[0] + 1) // 2
q4 = np.zeros(n_pairs, dtype=np.uint8)
q4[0::1] = (q_flat[0::2] + 8).astype(np.uint8)
if q_flat.shape[0] % 2 == 1:
# 奇数: 最后补一个 0
q4 = np.zeros(n_pairs, dtype=np.uint8)
q4[:len(q_flat)//2] = ((q_flat[0::2][:len(q_flat)//2] + 8)
| ((q_flat[1::2][:len(q_flat)//2] + 8) << 4)).astype(np.uint8)
q4[-1] = (q_flat[-1] + 8).astype(np.uint8)
else:
q4 = ((q_flat[0::2] + 8) | ((q_flat[1::2] + 8) << 4)).astype(np.uint8)
size = int(n_pairs) + int(n_ch * 4) # q4 字节 + scales 字节
return q4, scales.astype(np.float32), size
@staticmethod
def dequant_int4(q4: np.ndarray, scales: np.ndarray, d: int = 576) -> np.ndarray:
"""
解包: q4 nibbles → float32 (检索/路由用)
"""
vals = np.empty(d, dtype=np.float32)
n_pairs = q4.shape[0]
lo = (q4 & 0x0F).astype(np.int32) - 8
hi = ((q4 >> 4) & 0x0F).astype(np.int32) - 8
vals[0::2] = lo
if d % 2 == 1:
vals[1::2][:len(hi)] = hi
else:
vals[1::2] = hi
# per-channel scale 反缩放
n_ch = scales.shape[0]
ch = d // n_ch
vals = vals.reshape(n_ch, ch) * scales[:, None]
return vals.reshape(-1)
@staticmethod
def quantize(latent: np.ndarray, quant_bits: int, n_ch: int = 32,
reversible: bool = False):
"""
per-channel 非对称量化 (兼容旧路径)
返回: (量化后值, 保护标记, 大小字节)
"""
d = latent.shape[-1]
ch = d // n_ch
tc = latent.reshape(n_ch, ch)
if quant_bits < 16:
max_val = 2 ** quant_bits - 1
tmin = tc.min(axis=-1, keepdims=True)
tmax = tc.max(axis=-1, keepdims=True)
scale = (tmax - tmin) / max_val
q = np.clip(np.round((tc - tmin) / (scale + 1e-8)), 0, max_val)
deq = q * scale + tmin
bytes_per = quant_bits / 8
else:
deq = tc
bytes_per = 2
size = int(d * bytes_per)
return deq.reshape(-1), None, size
@staticmethod
def protect_topk(kv: CompressedKV, is_topk: bool):
"""
Top-K 保护: 路由层确认该块参与 Top-K 竞争时, 标记为高精度块
回滚/关键推理时, 这些块用原始精度 (补偿时跳过量化误差)
"""
kv.protected = is_topk
return kv
@staticmethod
def compensate(kv: CompressedKV) -> np.ndarray:
"""
回滚补偿: 对受保护的 Top-K 块, 返回"需重读原始值"标记
实际由存储层决定: protected 块走高精度路径, 其余走量化路径
"""
if kv.protected:
return kv.latent # 高精度块: 量化误差可忽略
return kv.latent
class AbsorbedMLA:
"""
吸收式 MLA: 只缓存 576 维潜在向量, 不展开 KV
270KB → 30.4KB (8.9×) → INT8 15.2KB → INT4 7.6KB (28.4×)
(28.4× 为 L40S 真实推理实测, DeepSeek-V2-Lite 15.7B; 当前仓库为 numpy 模拟版)
"""
KV_LORA_RANK = 512
K_ROPE = 64
DIM = KV_LORA_RANK + K_ROPE # 576
def __init__(self, n_layers=27, quant_bits=4, n_ch=16, reversible=False):
"""n_ch=16: 每通道 36 维, 288B q4 + 64B scales = 352B/层 → 27层 9.3KB ≈ 28.4x"""
self.n_layers = n_layers
self.quant_bits = quant_bits
self.n_ch = n_ch
self.reversible = reversible # 1bit 可逆量化开关
self.chunks = {} # chunk_id -> CompressedKV
def encode(self, hidden_states: np.ndarray) -> CompressedKV:
"""压缩: [seq, d] → 潜在向量 [576]"""
# 模拟 kv_a_proj_with_mqa: 压缩到 512 + 64
if hidden_states.ndim == 2:
h = hidden_states.mean(axis=0)
else:
h = hidden_states
# 投影到 576 维 (实际是学习矩阵, 这里用确定性映射)
latent = np.tanh(h[:self.DIM] if len(h) >= self.DIM else np.pad(h, (0, self.DIM - len(h))))
return self._quantize(latent)
def _quantize(self, latent: np.ndarray) -> CompressedKV:
"""
混合精度存储 (colibrì Top-K 保护):
- latent: 存原始 float32 (无损) → 路由摘要/检索精度不受量化影响
- q4/scales: 真 INT4 打包 (持久化压缩 29.1x 保持)
- protected 块: 16bit 高精度路径 (size_bytes = 1152B), 其余 4bit (352B)
"""
if self.quant_bits == 4:
q4, scales, size = ReversibleQuantizer.quantize_int4(latent, self.n_ch)
kv = CompressedKV(
chunk_id=len(self.chunks),
latent=latent.astype(np.float32), # 原始无损值 (检索用)
quant_bits=self.quant_bits,
size_bytes=size,
reversible=self.reversible,
q4=q4,
scales=scales,
)
else:
deq, _, size = ReversibleQuantizer.quantize(
latent, self.quant_bits, self.n_ch, self.reversible)
kv = CompressedKV(
chunk_id=len(self.chunks),
latent=deq,
quant_bits=self.quant_bits,
size_bytes=size,
reversible=self.reversible,
)
self.chunks[kv.chunk_id] = kv
return kv
def dequant(self, chunk_id: int) -> np.ndarray:
"""从 INT4 打包解包 (模拟 4bit 低精度路径, 验证/对比用)"""
kv = self.chunks.get(chunk_id)
if kv is None or kv.q4 is None:
return None
return ReversibleQuantizer.dequant_int4(kv.q4, kv.scales, self.DIM)
def protect_topk(self, chunk_id: int):
"""
路由层调用: 标记 Top-K 块为高精度保护 (colibrì #441 思想).
受保护块走 16bit 路径 (原始 latent, size 1152B), 其余走 INT4 (352B).
"""
kv = self.chunks.get(chunk_id)
if kv and not kv.protected:
kv.protected = True
kv.quant_bits = 16
kv.size_bytes = self.DIM * 2 # 16bit 高精度存储
if kv.q4 is not None:
# 保留 q4 供降级用, 但主路径走原始 latent
pass
return True
return False
def bytes_per_token(self) -> float:
"""每 token 每层字节 (27 层总): INT4 打包 = 288B q4 + n_ch*4B scales"""
if self.quant_bits == 4:
per_layer = self.DIM // 2 + self.n_ch * 4 # q4 nibbles + scales
elif self.quant_bits < 16:
per_layer = self.DIM * (self.quant_bits / 8)
else:
per_layer = self.DIM * 2
return per_layer * self.n_layers
def storage_stats(self) -> dict:
"""混合精度统计: protected (16bit) vs 普通 (4bit)"""
n_protected = sum(1 for kv in self.chunks.values() if kv.protected)
n_total = len(self.chunks)
if n_total == 0:
return {"total": 0, "protected": 0, "protected_pct": 0.0}
return {
"total": n_total,
"protected": n_protected,
"protected_pct": round(n_protected / n_total * 100, 1),
}
def memory_bytes(self, seq_len: int) -> int:
"""N 层总占用 (bytes_per_token 已含层数, 不再重复乘)"""
return self.bytes_per_token() * seq_len
# ============================================================
# L2 路由层: Landmark 摘要 + 分层软max + Q-Cal
# ============================================================
@dataclass
class ChunkSelection:
"""路由层输出"""
chunk_ids: list
scores: list
weights: list # 分层软max 权重
class LandmarkRouter:
"""
HiLS 思路: 块摘要 = Attn(q'c, Kc, Kc) 加权和 + 熵偏置
分数 = (q̂·k'c)/√d + b'c, q̂ = q + W_up W_down h (Q-Cal)
"""
def __init__(self, chunk_size=64, top_k=32, d_model=576, rank=16, seed=42):
self.chunk_size = chunk_size
self.top_k = top_k
self.d_model = d_model
rng = np.random.default_rng(seed)
# Q-Cal 低秩校准 (仅 0.6% 参数)
self.W_up = rng.normal(0, 0.02, (d_model, rank)) * 0.1
self.W_down = rng.normal(0, 0.02, (rank, d_model)) * 0.1
# landmark 可学习 query (每层共享一个, 简化)
self.landmark_q = rng.normal(0, 0.1, d_model)
def build_summary(self, chunk_latents: np.ndarray) -> tuple:
"""
块摘要: k'c = Σ p_j k_j (注意力加权和)
b'c = -Σ p_j log p_j (熵偏置)
"""
# 用 landmark query 对块内 latent 打分
scores = self.landmark_q @ chunk_latents.T / np.sqrt(self.d_model)
p = np.exp(scores - scores.max())
p /= p.sum()
k_prime = p @ chunk_latents # 加权和 → 块摘要
entropy = -(p * np.log(p + 1e-9)).sum() # 熵偏置
return k_prime, entropy
def q_calibrate(self, query: np.ndarray, hidden: np.ndarray) -> np.ndarray:
"""低秩校准: q̂ = q + W_up W_down h"""
delta = self.W_up @ (self.W_down @ hidden)
return query + delta
def route(self, query: np.ndarray, hidden: np.ndarray,
chunk_summaries: dict, boost_chunks: list = None,
boost_weight: float = 0.5) -> ChunkSelection:
"""
打分 + top-K:
ŝ_i,c = (q̂ᵢ·k'c)/√d + b'c + (认知层指令加权, 可选)
boost_chunks: 认知层 directive 认为"任务需要"的块 (标签匹配), 加分提升命中。
boost_weight: 每次命中加权量 (默认 0.5, 相当于一次强相关命中)。
"""
q_hat = self.q_calibrate(query, hidden)
boost = set(boost_chunks) if boost_chunks else set()
scores = {}
for cid, (k_prime, bias) in chunk_summaries.items():
s = q_hat @ k_prime / np.sqrt(self.d_model) + bias
if cid in boost:
s += boost_weight # 认知层指令: 任务需要的块加权
scores[cid] = s
# top-K
ranked = sorted(scores.items(), key=lambda x: -x[1])
selected = ranked[:self.top_k]
# 分层软max: 分数标准化防饱和
raw = np.array([s for _, s in selected])
# z-score 标准化 + 温度
if raw.std() > 1e-8:
norm = (raw - raw.mean()) / (raw.std() + 1e-8)
else:
norm = raw - raw.mean()
exp_s = np.exp(norm)
weights = exp_s / exp_s.sum()
return ChunkSelection(
chunk_ids=[cid for cid, _ in selected],
scores=[s for _, s in selected],
weights=weights.tolist(),
)
# ============================================================
# L4 物理层: KV 换页 + 预测性驱逐 (含遗忘曲线)
# ============================================================
class ForgettingCurve:
"""
遗忘曲线 (源自 Project-Aura): 决定 KV 块的热度衰减
公式: S(t) = I · 2^(-t / τ)
其中:
S(t) = t 时刻的记忆强度 (热度)
I = 初始重要性
τ = 半衰期 (高频访问的块半衰期延长)
访问增强: boost = log2(access_count+1) × 0.1
"""
def __init__(self, default_half_life: float = 100.0):
# KV 场景时间尺度小 (轮次而非天), 默认 100 轮
self.default_half_life = default_half_life
def compute_half_life(self, kv: CompressedKV) -> float:
"""根据访问频率动态调整半衰期"""
base = self.default_half_life
if kv.access_count > 10:
base *= 7 # 高频访问延至 7×
elif kv.access_count > 5:
base *= 3 # 中频延至 3×
base *= (0.5 + kv.importance) # 高重要性更持久
return base
def strength(self, kv: CompressedKV, now: float) -> float:
"""记忆强度: 指数衰减 + 访问增强 (替代简单线性热度)"""
elapsed = max(now - kv.last_access, 0.0)
half_life = self.compute_half_life(kv)
decay = math.pow(2, -elapsed / half_life)
boost = math.log2(kv.access_count + 1) * 0.1
return min(1.0, kv.importance * decay + boost)
def should_forget(self, kv: CompressedKV, now: float, threshold: float = 0.15) -> bool:
"""强度低于阈值 → 该换出"""
return self.strength(kv, now) < threshold
class KVPager:
"""
三级存储: 显存(热) / 内存(温) / 磁盘(冷, mmap)
预测性驱逐: 遗忘曲线强度 + 注意力衰减
"""
def __init__(self, vram_limit_mb=10240, ram_limit_mb=32768):
self.vram_limit = vram_limit_mb * 1024 * 1024
self.ram_limit = ram_limit_mb * 1024 * 1024
self.vram = {} # chunk_id -> CompressedKV
self.ram = {}
self.disk = {} # 模拟 mmap
self.vram_used = 0
self.ram_used = 0
self.access_log = [] # (chunk_id, time, attention_score)
self.curve = ForgettingCurve() # 遗忘曲线 (Aura 移植)
def place(self, kv: CompressedKV, now: float = 0.0, force_hot: bool = False):
"""放置: 按遗忘曲线强度分层 (force_hot=True 时新知识优先进 VRAM)"""
if force_hot and kv.size_bytes > 0:
# 新摄入知识: 直接进热层 (若容量允许)
if self.vram_used + kv.size_bytes <= self.vram_limit:
self.vram[kv.chunk_id] = kv
self.vram_used += kv.size_bytes
kv.heat = 1.0
if kv.max_heat < 1.0:
kv.max_heat = 1.0
kv.last_access = now
return
strength = self.curve.strength(kv, now)
kv.heat = strength
if strength >= 0.6:
if self.vram_used + kv.size_bytes <= self.vram_limit:
self.vram[kv.chunk_id] = kv
self.vram_used += kv.size_bytes
return
if strength >= 0.3:
if self.ram_used + kv.size_bytes <= self.ram_limit:
self.ram[kv.chunk_id] = kv
self.ram_used += kv.size_bytes
return
self.disk[kv.chunk_id] = kv
def access(self, chunk_id: int, attention_score: float, now: float):
"""访问: 更新访问计数 + 重要性 + 日志"""
self.access_log.append((chunk_id, now, attention_score))
kv = self.vram.get(chunk_id) or self.ram.get(chunk_id) or self.disk.get(chunk_id)
if kv:
kv.access_count += 1
kv.importance = min(1.0, kv.importance + abs(attention_score) * 0.05)
kv.last_access = now
# 重新计算强度并分层
strength = self.curve.strength(kv, now)
kv.heat = strength
# 历史峰值热度 (LFRU 滞回基准): 曾热过的块不轻易驱逐
if strength > kv.max_heat:
kv.max_heat = strength
# 磁盘 → 提升
if chunk_id in self.disk and strength >= 0.5:
self.disk.pop(chunk_id)
self.place(kv, now)
def eviction_score(self, kv: CompressedKV, now: float) -> float:
"""预测性驱逐分数 (越高越该驱逐): 遗忘曲线 + 注意力衰减"""
# 遗忘曲线: 强度越低越该走 (主因子)
forget_factor = 1.0 - self.curve.strength(kv, now)
# 注意力衰减: 最近分数越低越该走
logs = [a for cid, t, a in self.access_log if cid == kv.chunk_id]
attn_decay = 1.0 - np.mean(logs[-5:]) if logs else 0.5
# 时间衰减: 越久没访问越该走
time_decay = min((now - kv.last_access) / self.curve.compute_half_life(kv), 1.0)
return 0.5 * forget_factor + 0.3 * attn_decay + 0.2 * time_decay
def effective_eviction_score(self, kv: CompressedKV, now: float) -> float:
"""
LFRU 有效驱逐分数 (colibrì #441/#497 思想)
============================================
历史峰值热度 (max_heat) 作为信用分折减驱逐分数: 曾热过的块给"
一次机会"。信用随时间衰减 (淡忘): 久不访问的热块最终仍会被驱逐。
完全冷透 (score>0.9) 时信用无效, 直接驱逐。
"""
score = self.eviction_score(kv, now)
if score >= 0.9:
return score # 彻底冷透: 历史热度救不了它
elapsed = max(now - kv.last_access, 0.0)
half_life = self.curve.compute_half_life(kv)
# 信用衰减: 每 2 个半衰期折半 (热块淡忘速度与遗忘曲线同源)
decayed_heat = kv.max_heat * math.pow(0.5, elapsed / (half_life * 2))
return score - 0.2 * decayed_heat
def evict(self, now: float, target_layer: str = "vram"):
"""
驱逐: LFRU 滞回 (防抖)
=======================
吸收 colibrì #441/#497: 纯 LRU/分数驱逐会让"历史高频但暂时冷却"的块
在驱逐边缘反复横跳。滞回规则: 历史峰值热度 (max_heat) 折减驱逐分数,
热过的块获得保护, 但信用随时间衰减, 完全冷透后照常驱逐。
"""
pool = self.vram if target_layer == "vram" else self.ram
if not pool:
return None
# 按 LFRU 有效分数降序 (最该驱逐的在前)
victim_id = max(pool,
key=lambda cid: self.effective_eviction_score(pool[cid], now))
victim = pool.pop(victim_id)
if target_layer == "vram":
self.vram_used -= victim.size_bytes
self.place(victim, now) # 降级到 RAM/DISK (按遗忘曲线自动分)
else:
self.ram_used -= victim.size_bytes
self.disk[victim_id] = victim
return victim_id
def prefetch(self, cids: list, now: float = 0.0) -> list:
"""
预取 (colibrì couple_prefetch 落地): 把预测块从 disk 提升到 RAM 温层。
不占 VRAM 热层 (热层留给真实访问); RAM 满时驱逐最冷的 RAM 块腾位。
返回成功提升的块 id 列表。
"""
moved = []
for cid in cids:
if cid not in self.disk:
continue # 已在 RAM/VRAM, 无需提升
kv = self.disk.pop(cid)
kv.heat = 0.4 # 温层标记
kv.last_access = now
if self.ram_used + kv.size_bytes <= self.ram_limit:
self.ram[cid] = kv
self.ram_used += kv.size_bytes
moved.append(cid)
continue
# RAM 满: 驱逐最冷的 RAM 块腾位 (用 LFRU 有效分数)
victim_id = max(self.ram,
key=lambda c: self.effective_eviction_score(self.ram[c], now))
victim = self.ram.pop(victim_id)
self.ram_used -= victim.size_bytes
self.disk[victim_id] = victim
self.ram[cid] = kv
self.ram_used += kv.size_bytes
moved.append(cid)
return moved
def stats(self) -> dict:
return {
"vram": f"{len(self.vram)} 块 / {self.vram_used/1024/1024:.1f}MB",
"ram": f"{len(self.ram)} 块 / {self.ram_used/1024/1024:.1f}MB",
"disk": f"{len(self.disk)} 块",
}
# ============================================================
# L1 认知层: 元认知控制器
# ============================================================
@dataclass
class SubTask:
"""子任务"""
id: int
name: str
info_needs: list # 需要的信息类型
confidence: float = 0.0 # 完成置信度
@dataclass
class RetrievalDirective:
"""认知层 → 物理层指令"""
required_chunks: list
priority: Literal["hot", "warm", "cold"]
reason: str
confidence: float
class MetaCog:
"""
认知层: 任务分解 + 置信度追踪 + 信息缺口分析 + 记忆提升
"""
def __init__(self, confidence_threshold=0.7):
self.confidence_threshold = confidence_threshold
self.task_stack = []
self.long_term = {} # 长期记忆 (key -> 摘要)
self.episodic = [] # 情景记忆
self._task_id = 0
def decompose(self, task: str) -> list[SubTask]:
"""任务分解: 规则+关键词 (生产可换 LLM)"""
# 简单规则: 按句子/分号切分
parts = [p.strip() for p in task.replace(';', ';').replace('。', ';').split(';') if p.strip()]
subs = []
for p in parts:
self._task_id += 1
# 信息需求推断: 含"分析/对比/总结"等需要历史信息
needs = []
for kw, info in [("对比", "comparison"), ("分析", "method"),
("历史", "history"), ("数据", "data"),
("代码", "code"), ("总结", "result"),
("稀疏", "sparse")]:
if kw in p:
needs.append(info)
if not needs:
needs = ["data"] # 默认需要数据
subs.append(SubTask(id=self._task_id, name=p, info_needs=needs))
self.task_stack = subs
return subs
def track_confidence(self, response_likelihood: float) -> float:
"""置信度追踪 (0-1)"""
return response_likelihood
def build_directive(self, subtask: SubTask, chunk_map: dict) -> RetrievalDirective:
"""生成检索指令 (认知层 → 路由/物理层)"""
needed = []
for need in subtask.info_needs:
for cid, meta in chunk_map.items():
if need in meta.get("tags", []):
needed.append(cid)
return RetrievalDirective(
required_chunks=needed,
priority="hot" if len(needed) <= 3 else "warm",
reason=f"子任务 '{subtask.name}' 需要 {subtask.info_needs}",
confidence=subtask.confidence,
)
def promote(self, chunk_id: int, importance: float):
"""记忆提升: 高频+重要 → 长期"""
if importance > 0.8:
self.long_term[chunk_id] = {"importance": importance, "promoted_at": len(self.episodic)}
self.episodic.append({"chunk": chunk_id, "importance": importance})
# ============================================================
# 四层编排 (端到端)
# ============================================================
class QuadLayerAgent:
"""四层融合 Agent"""
def __init__(self, n_layers=27, quant_bits=4, top_k=32, seed=42, reversible=False,
vram_limit_mb=10240, ram_limit_mb=32768):
self.metacog = MetaCog()
self.router = LandmarkRouter(top_k=top_k, seed=seed)
self.store = AbsorbedMLA(n_layers=n_layers, quant_bits=quant_bits, reversible=reversible)
# 修复: 配置的显存/内存上限真实传入 (之前 KVPager 用默认值, 配置不生效)
self.pager = KVPager(vram_limit_mb=vram_limit_mb, ram_limit_mb=ram_limit_mb)
self.chunk_meta = {} # chunk_id -> {tags, ...}
self.summaries = {} # chunk_id -> (k_prime, bias)
self.now = 0.0
def ingest(self, text_chunks: list[dict]):
"""
摄取知识: text_chunks = [{"text": "...", "tags": ["data"]}, ...]
每块 → 压缩为 CompressedKV → 建摘要 → 放置
"""
for i, chunk in enumerate(text_chunks):
# 模拟 hidden states
h = np.random.default_rng(i).normal(0, 1, self.store.DIM)
kv = self.store.encode(h)
kv.heat = 0.9 # 新知识 = 热
self.pager.place(kv, self.now)
# 建 landmark 摘要
k_prime, bias = self.router.build_summary(kv.latent.reshape(1, -1))
self.summaries[kv.chunk_id] = (k_prime, bias)
self.chunk_meta[kv.chunk_id] = {"tags": chunk.get("tags", []), "text": chunk.get("text", "")}
def run_task(self, task: str, query: np.ndarray):
"""执行任务: 认知 → 路由 → 存储 → 输出"""
print(f"\n{'='*56}")
print(f"任务: {task}")
print(f"{'='*56}")
# L1 认知: 分解
subtasks = self.metacog.decompose(task)
print(f"[认知层] 分解为 {len(subtasks)} 个子任务")
# L1 认知: 信息缺口 → 检索指令
directive = self.metacog.build_directive(subtasks[0], self.chunk_meta)
print(f"[认知层] 检索指令: 需要 {directive.required_chunks} ({directive.reason})")
# L4 物理: 预取
for cid in directive.required_chunks:
if cid in self.pager.disk:
kv = self.pager.disk.pop(cid)
kv.heat = 0.6
self.pager.place(kv, self.now)
print(f"[物理层] 预取完成 → {self.pager.stats()}")
# L2 路由: landmark 检索
selection = self.router.route(query, query, self.summaries)
print(f"[路由层] top-{len(selection.chunk_ids)} 块: {selection.chunk_ids[:5]}...")
print(f"[路由层] 权重分布: {[f'{w:.2f}' for w in selection.weights[:5]]}...")
# L4 物理: 访问更新
for cid, score in zip(selection.chunk_ids, selection.scores):
self.pager.access(cid, abs(score), self.now)
self.metacog.promote(cid, abs(score))
# L3 存储: 汇总被选中块的压缩表示
gathered = []
for cid in selection.chunk_ids:
kv = (self.pager.vram.get(cid) or self.pager.ram.get(cid) or self.pager.disk.get(cid))
if kv:
gathered.append(kv.latent)
context = np.mean(gathered, axis=0) if gathered else np.zeros(self.store.DIM)
print(f"[存储层] 聚合 {len(gathered)} 块压缩表示 (每块 {self.store.bytes_per_token():.1f}B)")
# 置信度
conf = self.metacog.track_confidence(0.85)
print(f"[认知层] 置信度: {conf:.2f}")
# 驱逐维护
if self.pager.vram_used > self.pager.vram_limit * 0.9:
victim = self.pager.evict(self.now, "vram")
print(f"[物理层] 驱逐 {victim} → 降级")
self.now += 1
return context, selection
# ============================================================
# 验证
# ============================================================
if __name__ == "__main__":
print("=" * 56)
print("AgentFrame 四层融合 — 端到端验证")
print("=" * 56)
# 创建 Agent
agent = QuadLayerAgent(n_layers=27, quant_bits=4, top_k=8)
# 注入知识库 (12 块, 带标签)
knowledge = [
{"text": "KV 缓存 270KB/token 展开存储", "tags": ["data", "comparison"]},
{"text": "吸收式 MLA 缓存 576 维潜在向量", "tags": ["data", "method"]},
{"text": "INT8 思考链误差 0.011", "tags": ["data", "comparison"]},
{"text": "INT4 工具结果误差 0.079", "tags": ["data"]},
{"text": "per-channel 非对称量化 27 倍精度", "tags": ["method"]},
{"text": "L40S 实测 138 万 token 上下文", "tags": ["data", "result", "comparison"]},
{"text": "HiLS 分层软max 端到端块选择", "tags": ["method", "sparse"]},
{"text": "landmark token 块摘要检索", "tags": ["method", "sparse"]},
{"text": "Q-Cal 低秩校准 0.6% 参数", "tags": ["method"]},
{"text": "GQA 组内 max 聚合", "tags": ["method", "sparse"]},
{"text": "KV 换页 mmap 冷存储", "tags": ["method", "paging"]},
{"text": "预测性驱逐注意力信号", "tags": ["method", "paging"]},
]
agent.ingest(knowledge)
print(f"[存储层] 注入 {len(knowledge)} 块知识")
print(f"[存储层] 每 token 缓存: {agent.store.bytes_per_token():.1f}B (L40S 实测 28.4×)")
print(f"[物理层] 初始: {agent.pager.stats()}")
# 执行任务
rng = np.random.default_rng(0)
query = rng.normal(0, 1, 576)
agent.run_task("对比压缩方案;分析稀疏路由;总结实测数据", query)
# 容量对比
seq_len = 100_000
original = 270 * 1024 * seq_len * 27
compressed = agent.store.memory_bytes(seq_len)
print(f"\n{'='*56}")
print(f"容量对比 (100K token × 27 层):")
print(f" 原始展开: {original/1024**3:.1f} GB")
print(f" 四层后: {compressed/1024**3:.3f} GB")
print(f" 压缩比: {original/compressed:.1f}×")
print(f"{'='*56}")