cortexnet 3.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,360 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ 分层记忆系统 (Hierarchical Memory System)
5
+
6
+ 受人类认知架构启发的三层记忆系统:
7
+
8
+ ┌─────────────────────────────────────────────────────────────┐
9
+ │ 分层记忆架构 │
10
+ ├─────────────────────────────────────────────────────────────┤
11
+ │ │
12
+ │ ┌─── 工作记忆 (Working Memory) ──────────────────────┐ │
13
+ │ │ • 快速权重,逐 token 更新 │ │
14
+ │ │ • 高分辨率,小容量 │ │
15
+ │ │ • 衰减快,捕获即时上下文 │ │
16
+ │ │ • 类似人脑前额叶的工作记忆 │ │
17
+ │ └────────────────────────────────────────────────────┘ │
18
+ │ │
19
+ │ ┌─── 情景记忆 (Episodic Memory) ─────────────────────┐ │
20
+ │ │ • 可学习的记忆槽位,跨序列积累 │ │
21
+ │ │ • 通过交叉注意力读写 │ │
22
+ │ │ • 中等容量,存储压缩的上下文快照 │ │
23
+ │ │ • 类似海马体的情景记忆 │ │
24
+ │ └────────────────────────────────────────────────────┘ │
25
+ │ │
26
+ │ ┌─── 语义记忆 (Semantic Memory) ─────────────────────┐ │
27
+ │ │ • 全局可学习知识库,所有层共享 │ │
28
+ │ │ • 通过训练缓慢更新 │ │
29
+ │ │ • 大容量,存储抽象的通用知识 │ │
30
+ │ │ • 类似大脑皮层的长期语义记忆 │ │
31
+ │ └────────────────────────────────────────────────────┘ │
32
+ │ │
33
+ │ ┌─── 记忆控制器 (Memory Controller) ─────────────────┐ │
34
+ │ │ • 动态决定从哪层记忆读取/写入 │ │
35
+ │ │ • 学习不同情境下的最优记忆策略 │ │
36
+ │ │ • 门控融合三层记忆输出 │ │
37
+ │ └────────────────────────────────────────────────────┘ │
38
+ └─────────────────────────────────────────────────────────────┘
39
+
40
+ 创新点:
41
+ 1. 三层记忆各有不同的时间尺度和容量
42
+ 2. 记忆控制器动态平衡读写策略
43
+ 3. 选择性遗忘机制防止记忆过载
44
+ 4. 工作记忆提供即时上下文,语义记忆提供长期知识
45
+ """
46
+
47
+ from typing import Optional, Tuple
48
+
49
+ import torch
50
+ import torch.nn as nn
51
+ import torch.nn.functional as F
52
+
53
+ class WorkingMemory(nn.Module):
54
+ """工作记忆:快速权重系统,逐 token 更新。
55
+
56
+ 通过快速权重矩阵累积键值关联,每个 token 先读后写。
57
+ 加入选择性遗忘门控,防止记忆过载。
58
+
59
+ 数学原理:
60
+ 遗忘门: f_t = σ(W_f · x_t)
61
+ 读取: o_t = φ(q_t) · M_{t-1} / norm
62
+ 写入: M_t = f_t · M_{t-1} + φ(k_t)^T · v_t
63
+ """
64
+
65
+ def __init__(self, d_model: int, memory_dim: int = 64):
66
+ super().__init__()
67
+ self.memory_dim = memory_dim
68
+ self.scale = memory_dim ** -0.5
69
+
70
+ self.q_proj = nn.Linear(d_model, memory_dim, bias=False)
71
+ self.k_proj = nn.Linear(d_model, memory_dim, bias=False)
72
+ self.v_proj = nn.Linear(d_model, d_model, bias=False)
73
+
74
+ # 选择性遗忘门控(每个记忆维度独立衰减)
75
+ self.forget_gate = nn.Linear(d_model, memory_dim)
76
+
77
+ self.out_proj = nn.Linear(d_model, d_model, bias=False)
78
+ self.norm = nn.LayerNorm(d_model)
79
+
80
+ def forward(
81
+ self,
82
+ x: torch.Tensor,
83
+ past_memory: Optional[torch.Tensor] = None,
84
+ past_z: Optional[torch.Tensor] = None,
85
+ use_cache: bool = False,
86
+ ) -> torch.Tensor | Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
87
+ B, L, D = x.shape
88
+ if use_cache or past_memory is not None:
89
+ return self._sequential_forward(x, past_memory, past_z, use_cache)
90
+ return self._parallel_forward(x)
91
+
92
+ def _parallel_forward(self, x: torch.Tensor) -> torch.Tensor:
93
+ """并行线性注意力(带 per-position 遗忘门),无 Python 循环。
94
+
95
+ log-space cumsum trick 处理 position-dependent decay:
96
+ log_cum[t] = Σ_{i=0}^{t} log(forget[i])
97
+ decay(t→s) = exp(log_cum[t] - log_cum[s])
98
+ """
99
+ B, L, D = x.shape
100
+ q = F.elu(self.q_proj(x), alpha=1.0) + 1 # (B, L, mem)
101
+ k = F.elu(self.k_proj(x), alpha=1.0) + 1
102
+ v = self.v_proj(x) # (B, L, D)
103
+ q = q * self.scale
104
+ forget = torch.sigmoid(self.forget_gate(x)) # (B, L, mem)
105
+
106
+ log_f = torch.log(forget.clamp(min=1e-6)) # (B, L, mem)
107
+ log_f_cum = torch.cumsum(log_f, dim=1).clamp(-20, 20) # clamp 防溢出
108
+
109
+ exp_neg = torch.exp(-log_f_cum) # (B, L, mem) — 归一化因子
110
+ exp_pos = torch.exp(log_f_cum) # (B, L, mem) — 还原因子
111
+
112
+ # M_t = Σ_{s≤t} decay(t,s) * k_s ⊗ v_s (并行)
113
+ kv = k.unsqueeze(-1) * v.unsqueeze(-2) # (B, L, mem, D)
114
+ weighted_kv = exp_neg.unsqueeze(-1) * kv
115
+ cum_kv = torch.cumsum(weighted_kv, dim=1)
116
+ M = exp_pos.unsqueeze(-1) * cum_kv # (B, L, mem, D)
117
+
118
+ # z_t = Σ_{s≤t} decay(t,s) * k_s
119
+ weighted_k = exp_neg * k
120
+ cum_k = torch.cumsum(weighted_k, dim=1)
121
+ z = exp_pos * cum_k # (B, L, mem)
122
+
123
+ numerator = torch.einsum('blm,blmd->bld', q, M)
124
+ denominator = torch.einsum('blm,blm->bl', q, z).unsqueeze(-1) + 1e-6
125
+
126
+ return self.out_proj(self.norm(numerator / denominator))
127
+
128
+ def _sequential_forward(self, x, past_memory, past_z, use_cache):
129
+ """顺序实现(用于增量缓存)。"""
130
+ B, L, D = x.shape
131
+ q = F.elu(self.q_proj(x), alpha=1.0) + 1
132
+ k = F.elu(self.k_proj(x), alpha=1.0) + 1
133
+ v = self.v_proj(x)
134
+ q = q * self.scale
135
+ forget = torch.sigmoid(self.forget_gate(x))
136
+ memory = (
137
+ past_memory if past_memory is not None
138
+ else torch.zeros(B, self.memory_dim, D, device=x.device, dtype=x.dtype)
139
+ )
140
+ z = (
141
+ past_z if past_z is not None
142
+ else torch.zeros(B, self.memory_dim, 1, device=x.device, dtype=x.dtype)
143
+ )
144
+ outputs = []
145
+ for t in range(L):
146
+ q_t, k_t, v_t = q[:, t], k[:, t], v[:, t]
147
+ f_t = forget[:, t].unsqueeze(-1)
148
+ num = torch.bmm(q_t.unsqueeze(1), memory).squeeze(1)
149
+ den = torch.bmm(q_t.unsqueeze(1), z).squeeze(1)
150
+ outputs.append(num / (den + 1e-6))
151
+ memory = f_t * memory + torch.bmm(k_t.unsqueeze(2), v_t.unsqueeze(1))
152
+ z = f_t * z + k_t.unsqueeze(2)
153
+ out = self.out_proj(self.norm(torch.stack(outputs, dim=1)))
154
+ if use_cache:
155
+ return out, memory, z
156
+ return out
157
+
158
+
159
+ class EpisodicMemory(nn.Module):
160
+ """情景记忆:可学习的记忆槽位,通过交叉注意力交互。
161
+
162
+ 维护固定数量的记忆槽位,通过训练学习存储有用的模式。
163
+ 使用交叉注意力机制让输入 token 检索相关记忆。
164
+
165
+ 架构:
166
+ 读取: CrossAttention(input → memory_slots)
167
+ 更新: 通过梯度缓慢调整槽位内容
168
+
169
+ Args:
170
+ d_model: 模型维度
171
+ num_slots: 记忆槽位数量
172
+ num_heads: 交叉注意力头数
173
+ """
174
+
175
+ def __init__(self, d_model: int, num_slots: int = 32, num_heads: int = 4):
176
+ super().__init__()
177
+ self.num_slots = num_slots
178
+ self.num_heads = num_heads
179
+ self.head_dim = d_model // num_heads
180
+
181
+ # 可学习的记忆槽位
182
+ self.memory_slots = nn.Parameter(
183
+ torch.randn(1, num_slots, d_model) * 0.02
184
+ )
185
+
186
+ # 交叉注意力投影
187
+ self.q_proj = nn.Linear(d_model, d_model, bias=False)
188
+ self.k_proj = nn.Linear(d_model, d_model, bias=False)
189
+ self.v_proj = nn.Linear(d_model, d_model, bias=False)
190
+ self.out_proj = nn.Linear(d_model, d_model, bias=False)
191
+
192
+ # 记忆更新门控
193
+ self.update_gate = nn.Sequential(
194
+ nn.Linear(d_model * 2, d_model),
195
+ nn.Sigmoid(),
196
+ )
197
+
198
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
199
+ B, L, D = x.shape
200
+
201
+ memory = self.memory_slots.expand(B, -1, -1) # (B, slots, D)
202
+
203
+ # 交叉注意力:input queries → memory keys/values
204
+ q = self.q_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
205
+ k = self.k_proj(memory).view(B, self.num_slots, self.num_heads, self.head_dim).transpose(1, 2)
206
+ v = self.v_proj(memory).view(B, self.num_slots, self.num_heads, self.head_dim).transpose(1, 2)
207
+
208
+ # SDPA: 自动选择 FlashAttention / Memory-Efficient 后端
209
+ out = F.scaled_dot_product_attention(q, k, v, dropout_p=0.0, is_causal=False)
210
+ out = out.transpose(1, 2).contiguous().view(B, L, D)
211
+
212
+ return self.out_proj(out)
213
+
214
+
215
+ class SemanticMemory(nn.Module):
216
+ """语义记忆:全局长期知识库。
217
+
218
+ 维护一组全局知识向量,通过双向交叉注意力与输入交互:
219
+ 1. 输入从语义记忆中检索相关知识
220
+ 2. 语义记忆根据输入生成上下文相关的知识表示
221
+
222
+ 与情景记忆的区别:
223
+ - 语义记忆存储抽象的、跨样本的通用知识
224
+ - 情景记忆存储具体的、样本内的上下文信息
225
+ - 语义记忆的容量更大,更新更慢
226
+
227
+ Args:
228
+ d_model: 模型维度
229
+ num_slots: 知识向量数量
230
+ """
231
+
232
+ def __init__(self, d_model: int, num_slots: int = 64):
233
+ super().__init__()
234
+ self.num_slots = num_slots
235
+
236
+ # 全局知识向量
237
+ self.knowledge = nn.Parameter(
238
+ torch.randn(1, num_slots, d_model) * 0.02
239
+ )
240
+
241
+ # 双向注意力
242
+ self.input_to_memory = nn.Linear(d_model, d_model, bias=False)
243
+ self.memory_to_input = nn.Linear(d_model, d_model, bias=False)
244
+ self.gate = nn.Linear(d_model, d_model)
245
+ self.out_proj = nn.Linear(d_model, d_model, bias=False)
246
+
247
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
248
+ B, L, D = x.shape
249
+
250
+ knowledge = self.knowledge.expand(B, -1, -1) # (B, slots, D)
251
+
252
+ # 输入 → 知识检索 (SDPA 加速)
253
+ query = self.input_to_memory(x).unsqueeze(1) # (B, 1, L, D) 作为单头
254
+ k = knowledge.unsqueeze(1) # (B, 1, slots, D)
255
+ v = knowledge.unsqueeze(1) # (B, 1, slots, D)
256
+ retrieved = F.scaled_dot_product_attention(
257
+ query, k, v, dropout_p=0.0, is_causal=False,
258
+ ).squeeze(1) # (B, L, D)
259
+
260
+ # 门控融合
261
+ gate = torch.sigmoid(self.gate(x))
262
+ output = gate * retrieved + (1 - gate) * x
263
+
264
+ return self.out_proj(output)
265
+
266
+
267
+ class MemoryController(nn.Module):
268
+ """记忆控制器:动态协调三层记忆的读写。
269
+
270
+ 根据输入内容决定从哪层记忆中读取以及各层的权重:
271
+ - 需要即时上下文 → 偏重工作记忆
272
+ - 需要历史模式 → 偏重情景记忆
273
+ - 需要通用知识 → 偏重语义记忆
274
+ """
275
+
276
+ def __init__(self, d_model: int):
277
+ super().__init__()
278
+ bottleneck = max(d_model // 4, 32)
279
+ self.controller = nn.Sequential(
280
+ nn.Linear(d_model, bottleneck),
281
+ nn.SiLU(),
282
+ nn.Linear(bottleneck, 3), # 三层记忆的权重
283
+ )
284
+
285
+ def forward(
286
+ self,
287
+ working_out: torch.Tensor,
288
+ episodic_out: torch.Tensor,
289
+ semantic_out: torch.Tensor,
290
+ x: torch.Tensor,
291
+ ) -> torch.Tensor:
292
+ # 基于输入计算记忆权重
293
+ weights = F.softmax(self.controller(x), dim=-1) # (B, L, 3)
294
+
295
+ stacked = torch.stack(
296
+ [working_out, episodic_out, semantic_out], dim=-1
297
+ ) # (B, L, D, 3)
298
+ fused = (stacked * weights.unsqueeze(-2)).sum(dim=-1)
299
+
300
+ return fused
301
+
302
+
303
+ class HierarchicalMemorySystem(nn.Module):
304
+ """分层记忆系统:统一接口。
305
+
306
+ 整合三层记忆和记忆控制器,提供统一的前向接口。
307
+
308
+ Args:
309
+ d_model: 模型维度
310
+ working_dim: 工作记忆维度
311
+ episodic_slots: 情景记忆槽位数
312
+ semantic_slots: 语义记忆槽位数
313
+ num_heads: 情景记忆注意力头数
314
+ """
315
+
316
+ def __init__(
317
+ self,
318
+ d_model: int,
319
+ working_dim: int = 64,
320
+ episodic_slots: int = 32,
321
+ semantic_slots: int = 64,
322
+ num_heads: int = 4,
323
+ ):
324
+ super().__init__()
325
+
326
+ self.working = WorkingMemory(d_model, working_dim)
327
+ self.episodic = EpisodicMemory(d_model, episodic_slots, num_heads)
328
+ self.semantic = SemanticMemory(d_model, semantic_slots)
329
+ self.controller = MemoryController(d_model)
330
+
331
+ def forward(
332
+ self,
333
+ x: torch.Tensor,
334
+ past_working_memory: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
335
+ use_cache: bool = False,
336
+ ) -> torch.Tensor | Tuple[torch.Tensor, Optional[Tuple[torch.Tensor, torch.Tensor]]]:
337
+ """
338
+ Args:
339
+ x: (batch, seq_len, d_model)
340
+ past_working_memory: (past_memory, past_z) 仅 WorkingMemory 有状态
341
+ use_cache: 若 True 返回 (output, (new_memory, new_z))
342
+ """
343
+ if past_working_memory is not None:
344
+ past_mem, past_z = past_working_memory
345
+ else:
346
+ past_mem = past_z = None
347
+ if use_cache:
348
+ w_out, new_mem, new_z = self.working(
349
+ x, past_memory=past_mem, past_z=past_z, use_cache=True
350
+ )
351
+ else:
352
+ w_out = self.working(
353
+ x, past_memory=past_mem, past_z=past_z, use_cache=False
354
+ )
355
+ e_out = self.episodic(x)
356
+ s_out = self.semantic(x)
357
+ out = self.controller(w_out, e_out, s_out, x)
358
+ if use_cache:
359
+ return out, (new_mem, new_z)
360
+ return out
@@ -0,0 +1,196 @@
1
+ """
2
+ 可解释性与监控系统 (Interpretability & Monitoring System)
3
+
4
+ 核心创新:
5
+ 实时追踪模型内部的"思维流",让模型的决策过程透明可解释。
6
+
7
+ ┌─────────────────────────────────────────────────────────┐
8
+ │ CortexNet 思维流监控 │
9
+ ├─────────────────────────────────────────────────────────┤
10
+ │ │
11
+ │ ┌─── 路径利用率分析 ──────────────────────────────┐ │
12
+ │ │ 追踪 SSM / Attention / Memory / GNN 的使用比例 │ │
13
+ │ │ 了解模型在不同层、不同 token 上偏好哪条路径 │ │
14
+ │ └──────────────────────────────────────────────────┘ │
15
+ │ │
16
+ │ ┌─── 专家路由可视化 ──────────────────────────────┐ │
17
+ │ │ 追踪哪些 token 被路由到哪些专家 │ │
18
+ │ │ 检测专家负载均衡和专业化程度 │ │
19
+ │ └──────────────────────────────────────────────────┘ │
20
+ │ │
21
+ │ ┌─── 注意力重要性分析 ────────────────────────────┐ │
22
+ │ │ 追踪哪些 token 被选为"重要 token" │ │
23
+ │ │ 可视化稀疏注意力的选择模式 │ │
24
+ │ └──────────────────────────────────────────────────┘ │
25
+ │ │
26
+ │ ┌─── 记忆系统状态 ───────────────────────────────┐ │
27
+ │ │ 监控工作记忆/情景记忆/语义记忆的活跃度 │ │
28
+ │ │ 追踪记忆控制器的分配策略 │ │
29
+ │ └──────────────────────────────────────────────────┘ │
30
+ └─────────────────────────────────────────────────────────┘
31
+ """
32
+
33
+ import torch
34
+ import torch.nn as nn
35
+ import torch.nn.functional as F
36
+ from typing import Dict, List, Any
37
+ from collections import defaultdict
38
+
39
+
40
+ class ThoughtFlowMonitor:
41
+ """思维流监控器:追踪模型内部的信息处理路径。
42
+
43
+ 通过 forward hook 机制捕获模型的内部状态,
44
+ 无需修改模型代码即可实现完整的可解释性。
45
+
46
+ 使用方法:
47
+ monitor = ThoughtFlowMonitor(model)
48
+ monitor.start_monitoring()
49
+
50
+ output = model(input_ids)
51
+
52
+ report = monitor.get_report()
53
+ monitor.stop_monitoring()
54
+ """
55
+
56
+ def __init__(self, model: nn.Module):
57
+ self.model = model
58
+ self.hooks: List[Any] = []
59
+ self.data: Dict[str, Any] = defaultdict(dict)
60
+ self._monitoring = False
61
+
62
+ def start_monitoring(self):
63
+ """开始监控(注册 forward hooks)。"""
64
+ self.stop_monitoring() # 清除旧 hooks
65
+ self.data = defaultdict(dict)
66
+ self._monitoring = True
67
+ self._register_hooks()
68
+
69
+ def stop_monitoring(self):
70
+ """停止监控(移除所有 hooks)。"""
71
+ for hook in self.hooks:
72
+ hook.remove()
73
+ self.hooks = []
74
+ self._monitoring = False
75
+
76
+ def _register_hooks(self):
77
+ """注册 forward hooks 到关键组件。"""
78
+ # 检查模型是否有 blocks 属性
79
+ blocks = getattr(self.model, "blocks", [])
80
+
81
+ for layer_idx, block in enumerate(blocks):
82
+ # 捕获融合门控权重
83
+ fusion = getattr(block, "fusion", None)
84
+ if fusion is not None:
85
+ gate_module = getattr(fusion, "gate", None)
86
+ if gate_module is not None:
87
+ self.hooks.append(
88
+ gate_module.register_forward_hook(
89
+ self._make_fusion_hook(layer_idx)
90
+ )
91
+ )
92
+
93
+ # 捕获 MoE 路由决策
94
+ moe = getattr(block, "moe", None)
95
+ if moe is not None:
96
+ router = getattr(moe, "router", None)
97
+ if router is not None:
98
+ self.hooks.append(
99
+ router.register_forward_hook(
100
+ self._make_routing_hook(layer_idx)
101
+ )
102
+ )
103
+
104
+ def _make_fusion_hook(self, layer_idx: int):
105
+ def hook(module, input, output):
106
+ # output 是 gate 的输出: (B, L, num_paths)
107
+ if isinstance(output, torch.Tensor):
108
+ weights = F.softmax(output.detach(), dim=-1)
109
+ self.data["fusion_weights"][layer_idx] = (
110
+ weights.cpu()
111
+ )
112
+ return hook
113
+
114
+ def _make_routing_hook(self, layer_idx: int):
115
+ def hook(module, input, output):
116
+ # output 是 router logits: (B*L, num_experts)
117
+ if isinstance(output, torch.Tensor):
118
+ probs = F.softmax(output.detach(), dim=-1)
119
+ self.data["routing_probs"][layer_idx] = probs.cpu()
120
+ return hook
121
+
122
+ def get_report(self) -> Dict[str, Any]:
123
+ """生成可解释性报告。"""
124
+ report = {}
125
+
126
+ # 路径利用率
127
+ if "fusion_weights" in self.data:
128
+ report["pathway_utilization"] = (
129
+ self._analyze_pathway_utilization()
130
+ )
131
+
132
+ # 专家负载
133
+ if "routing_probs" in self.data:
134
+ report["expert_load"] = self._analyze_expert_load()
135
+
136
+ return report
137
+
138
+ def _analyze_pathway_utilization(self) -> Dict[str, List[float]]:
139
+ """分析各层的路径利用率。"""
140
+ path_names = ["SSM", "Attention", "Memory", "GraphReasoning"]
141
+ utilization = {name: [] for name in path_names}
142
+
143
+ for layer_idx in sorted(self.data["fusion_weights"].keys()):
144
+ weights = self.data["fusion_weights"][layer_idx]
145
+ mean_weights = weights.mean(dim=(0, 1)) # (num_paths,)
146
+
147
+ for i, name in enumerate(path_names):
148
+ if i < len(mean_weights):
149
+ utilization[name].append(mean_weights[i].item())
150
+
151
+ return utilization
152
+
153
+ def _analyze_expert_load(self) -> Dict[str, Any]:
154
+ """分析专家负载分布。"""
155
+ expert_loads = {}
156
+
157
+ for layer_idx in sorted(self.data["routing_probs"].keys()):
158
+ probs = self.data["routing_probs"][layer_idx]
159
+ mean_probs = probs.mean(dim=0) # (num_experts,)
160
+ expert_loads[f"layer_{layer_idx}"] = {
161
+ "mean_prob": mean_probs.tolist(),
162
+ "max_prob": mean_probs.max().item(),
163
+ "min_prob": mean_probs.min().item(),
164
+ "balance_ratio": (
165
+ mean_probs.min() / mean_probs.max()
166
+ ).item()
167
+ if mean_probs.max() > 0
168
+ else 0,
169
+ }
170
+
171
+ return expert_loads
172
+
173
+ def print_summary(self):
174
+ """打印监控摘要。"""
175
+ report = self.get_report()
176
+
177
+ print("\n ╔═══ CortexNet 思维流报告 ═══╗\n")
178
+
179
+ if "pathway_utilization" in report:
180
+ print(" ◆ 路径利用率(各层平均):")
181
+ for path, values in report["pathway_utilization"].items():
182
+ if values:
183
+ avg = sum(values) / len(values)
184
+ bar = "█" * int(avg * 40)
185
+ print(f" {path:>14s}: {avg:.1%} {bar}")
186
+
187
+ if "expert_load" in report:
188
+ print("\n ◆ 专家负载均衡:")
189
+ for layer, stats in report["expert_load"].items():
190
+ balance = stats.get("balance_ratio", 0)
191
+ status = "✓ 均衡" if balance > 0.5 else "⚠ 不均衡"
192
+ print(
193
+ f" {layer}: balance={balance:.2f} {status}"
194
+ )
195
+
196
+ print("\n ╚═══════════════════════════╝")