""" AgentFrame 核心: 前缀感知缓存池 (Prefix-Aware Cache Pool) ======================================================= 专为 Agent 场景设计的 KV 缓存复用框架 核心思想: 1. Agent 的 System Prompt + 工具定义是稳定前缀 (几千 token) 2. 多个 Agent 会话共享同一前缀的 KV 缓存 (只存 1 份) 3. 增量部分按信息价值分层量化 (思考链 INT8, 工具结果 INT4) 4. 基于吸收式 MLA 缓存 (270KB → 7.6KB/token) 逻辑验证版 (numpy), 架构与 torch 版一致 """ import hashlib import threading from dataclasses import dataclass, field from typing import Dict, Optional, List, Tuple import numpy as np # ============================================================ # 1. 前缀缓存池: 多个会话共享 System Prompt 的 KV # ============================================================ @dataclass class PrefixCache: """一个 Agent System Prompt 的共享 KV 缓存""" prefix_text: str prefix_hash: str num_tokens: int layers: Dict[int, Dict] = field(default_factory=dict) ref_count: int = 0 lock: threading.Lock = field(default_factory=threading.Lock) class PrefixPool: """前缀缓存池: 按 System Prompt 内容去重, 只存一份 KV""" def __init__(self): self._pool: Dict[str, PrefixCache] = {} self._lock = threading.Lock() @staticmethod def _hash(text: str) -> str: return hashlib.sha256(text.encode()).hexdigest()[:16] def acquire(self, system_prompt: str, num_tokens: int) -> PrefixCache: h = self._hash(system_prompt) with self._lock: if h in self._pool: cache = self._pool[h] cache.ref_count += 1 return cache cache = PrefixCache( prefix_text=system_prompt, prefix_hash=h, num_tokens=num_tokens, ) cache.ref_count = 1 self._pool[h] = cache return cache def release(self, cache: PrefixCache): with self._lock: cache.ref_count -= 1 if cache.ref_count <= 0: self._pool.pop(cache.prefix_hash, None) print(f"🗑 前缀缓存已淘汰: {cache.prefix_hash}") def memory_usage(self) -> Dict: """当前池内存占用 (字节)""" total = 0 for h, c in self._pool.items(): for layer_data in c.layers.values(): for k, v in layer_data.items(): if isinstance(v, np.ndarray): total += v.size * v.itemsize return {"cached_prefixes": len(self._pool), "bytes": total, "MB": total / 1024**2} # ============================================================ # 2. 会话层: 每 Agent 一个, 引用共享前缀 + 维护增量 KV # ============================================================ @dataclass class AgentSession: session_id: str prefix: PrefixCache incremental_layers: Dict[int, List] = field(default_factory=dict) thinking_bits: int = 8 toolresult_bits: int = 4 class SessionManager: def __init__(self, pool: PrefixPool): self.pool = pool self.sessions: Dict[str, AgentSession] = {} self._lock = threading.Lock() def create_session(self, session_id: str, system_prompt: str, prompt_tokens: int) -> AgentSession: prefix = self.pool.acquire(system_prompt, prompt_tokens) session = AgentSession(session_id=session_id, prefix=prefix) with self._lock: self.sessions[session_id] = session return session def close_session(self, session_id: str): with self._lock: session = self.sessions.pop(session_id, None) if session: self.pool.release(session.prefix) def append_tool_result(self, session: AgentSession, layer_idx: int, tensor: np.ndarray): session.incremental_layers.setdefault(layer_idx, []).append(tensor) def session_memory(self, session: AgentSession) -> Dict: inc_bytes = 0 for layer_idx, tensors in session.incremental_layers.items(): for t in tensors: inc_bytes += t.size * t.itemsize prefix_bytes = self.pool.memory_usage()["bytes"] / max(1, session.prefix.ref_count) return { "prefix_shared_bytes": prefix_bytes, "incremental_bytes": inc_bytes, "total_bytes": prefix_bytes + inc_bytes, "prefix_refs": session.prefix.ref_count, } # ============================================================ # 3. KV 编码器: 吸收式 MLA + 分层量化 # ============================================================ class AbsorbedMLAEncoder: def __init__(self, kv_lora_rank=512, qk_rope=64, n_layers=27): self.kv_rank = kv_lora_rank self.k_rope = qk_rope self.n_layers = n_layers def _quantize(self, tensor: np.ndarray, bits: int, n_ch: int = 32) -> np.ndarray: """per-channel 非对称量化""" d = tensor.shape[-1] ch = d // n_ch tc = tensor.reshape(-1, n_ch, ch) tmin = tc.min(axis=-1, keepdims=True) tmax = tc.max(axis=-1, keepdims=True) scale = (tmax - tmin) / (2**bits - 1) q = np.round((tc - tmin) / (scale + 1e-8)) q = np.clip(q, 0, 2**bits - 1) return (q * scale + tmin).reshape(tensor.shape) def encode_thought(self, kv: np.ndarray) -> np.ndarray: """思考链区: INT8 (误差 0.011)""" return self._quantize(kv, bits=8) def encode_tool_result(self, kv: np.ndarray) -> np.ndarray: """工具结果区: INT4 (误差 0.079)""" return self._quantize(kv, bits=4) def per_token_bytes(self, bits: int) -> float: """每 token 每层 KV 大小 (字节)""" return (self.kv_rank * bits / 8) + (self.k_rope * bits / 8) # ============================================================ # 4. 容量估算 # ============================================================ def capacity_estimate(encoder: AbsorbedMLAEncoder, vram_gb: float = 10.0): tok4 = encoder.per_token_bytes(4) total_bytes = vram_gb * 1024**3 prefix_tokens = 3000 inc_tokens = 5000 print(f"\n{'='*60}") print(f"AgentFrame 容量估算 (L40S 10GB KV 空间)") print(f"{'='*60}") print(f"Agent 典型: 前缀 3000 token (共享) + 增量 5000 token/会话") print(f"每 token KV: INT4 = {tok4:.1f}B/层, 27层 = {tok4*27/1024:.1f}KB") print(f"\n📊 AgentFrame (前缀共享 + INT4):") for n_agents in [1, 5, 10, 30, 50]: prefix_cost = prefix_tokens * tok4 * encoder.n_layers inc_cost = inc_tokens * tok4 * encoder.n_layers * n_agents total = prefix_cost + inc_cost fits = total <= total_bytes print(f" {n_agents:>2} 个 Agent: {'✅' if fits else '❌'} {total/1024**2:.0f}MB / {vram_gb}GB") print(f"\n📊 普通框架 (270KB/token, 无共享):") for n_agents in [1, 2, 3, 5]: cost = (prefix_tokens + inc_tokens) * 276480 * n_agents fits = cost <= total_bytes print(f" {n_agents:>2} 个 Agent: {'✅' if fits else '❌'} {cost/1024**2:.0f}MB / {vram_gb}GB") # ============================================================ # 5. 演示 # ============================================================ if __name__ == "__main__": print("=" * 60) print("AgentFrame: 前缀感知缓存池 演示") print("=" * 60) pool = PrefixPool() sessions = SessionManager(pool) sys_prompt = """你是智能助手。你有以下工具可用: - search(query): 搜索网络 - calculator(expr): 数学计算 - code_runner(code): 执行代码 请根据用户需求选择合适的工具。""" prompt_tokens = 3000 agent_a = sessions.create_session("agent-A", sys_prompt, prompt_tokens) agent_b = sessions.create_session("agent-B", sys_prompt, prompt_tokens) print(f"\n✅ Agent A + B 共享前缀: ref_count = {agent_a.prefix.ref_count}") encoder = AbsorbedMLAEncoder() fake_kv = np.random.randn(1, 64, encoder.kv_rank) # Agent A: 思考(INT8) + 工具结果(INT4) sessions.append_tool_result(agent_a, 0, encoder.encode_thought(fake_kv)) sessions.append_tool_result(agent_a, 0, encoder.encode_tool_result(fake_kv)) # Agent B: 只思考(INT8) sessions.append_tool_result(agent_b, 0, encoder.encode_thought(fake_kv)) print(f"\n📊 前缀池状态: {pool.memory_usage()}") print(f"📊 Agent A 内存: {sessions.session_memory(agent_a)}") print(f"📊 Agent B 内存: {sessions.session_memory(agent_b)}") capacity_estimate(encoder) sessions.close_session("agent-A") sessions.close_session("agent-B") print(f"\n✅ 会话关闭后前缀池: {pool.memory_usage()}") print("\n✅ AgentFrame 核心逻辑验证完成")