Download agentframe_core.py from ljsysfurry/AgentFrame: direct link, hf CLI and curl.
- Browser
- Download file 8.87 kB
-
https://huggingface.co/ljsysfurry/AgentFrame/resolve/main/agentframe_core.py
- Command line
-
hf download hf://ljsysfurry/AgentFrame/agentframe_core.py
-
curl -L -o agentframe_core.py https://huggingface.co/ljsysfurry/AgentFrame/resolve/main/agentframe_core.py
8.87 kB
| """ | |
| 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 | |
| # ============================================================ | |
| 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() | |
| 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 | |
| # ============================================================ | |
| 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 核心逻辑验证完成") | |