Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """ | |
| 联邦学习模块 — 虫群节点间权重聚合 | |
| 三档模型(lite/standard/pro) + FedAvg跨档位聚合 | |
| 支持: 权重同步、增量diff、弹性加入/退出 | |
| """ | |
| import numpy as np | |
| import base64 | |
| import io | |
| import time | |
| import copy | |
| from typing import Dict, List, Optional, Tuple | |
| from pathlib import Path | |
| # ============================================================ | |
| # 三档模型配置 | |
| # ============================================================ | |
| TIER_CONFIG = { | |
| 'lite': {'embed_dim': 128, 'hidden': 512, 'layers': 2, 'max_vocab': 10000}, | |
| 'standard': {'embed_dim': 256, 'hidden': 1024, 'layers': 2, 'max_vocab': 10000}, | |
| 'pro': {'embed_dim': 256, 'hidden': 2048, 'layers': 3, 'max_vocab': 20000}, | |
| } | |
| AREA_NAMES = { | |
| 'sensory': '感觉区', 'memory': '记忆区', 'association': '联想区', | |
| 'motor': '运动区', 'prefrontal': '前额叶', 'thalamus': '丘脑', | |
| } | |
| class WeightCodec: | |
| """权重编解码器 — numpy ↔ base64字符串""" | |
| def encode(weights: Dict[str, np.ndarray]) -> str: | |
| """权重字典 → base64字符串""" | |
| buf = io.BytesIO() | |
| np.savez_compressed(buf, **weights) | |
| return base64.b64encode(buf.getvalue()).decode('ascii') | |
| def decode(data: str) -> Dict[str, np.ndarray]: | |
| """base64字符串 → 权重字典""" | |
| raw = base64.b64decode(data) | |
| buf = io.BytesIO(raw) | |
| return dict(np.load(buf)) | |
| class FedAvgAggregator: | |
| """FedAvg聚合器 — 多节点权重平均""" | |
| def __init__(self): | |
| self._weight_buffer: Dict[str, List[Dict[str, np.ndarray]]] = {} | |
| self._node_versions: Dict[str, int] = {} | |
| def submit(self, node_id: str, area: str, | |
| weights: Dict[str, np.ndarray], version: int): | |
| """提交节点权重""" | |
| key = f"{node_id}:{area}" | |
| if area not in self._weight_buffer: | |
| self._weight_buffer[area] = [] | |
| self._weight_buffer[area].append(weights) | |
| self._node_versions[key] = version | |
| def aggregate(self, area: str) -> Optional[Dict[str, np.ndarray]]: | |
| """FedAvg聚合: W_avg = ΣW_i / N""" | |
| if area not in self._weight_buffer or not self._weight_buffer[area]: | |
| return None | |
| weight_list = self._weight_buffer[area] | |
| n = len(weight_list) | |
| # 取第一个作为模板 | |
| result = {} | |
| for key in weight_list[0]: | |
| tensors = [w[key] for w in weight_list if key in w] | |
| if tensors: | |
| # 零填充升维(不同档位) | |
| max_shape = max(t.shape for t in tensors) | |
| padded = [] | |
| for t in tensors: | |
| if t.shape == max_shape: | |
| padded.append(t) | |
| else: | |
| p = np.zeros(max_shape, dtype=np.float32) | |
| slices = tuple(slice(0, s) for s in t.shape) | |
| p[slices] = t | |
| padded.append(p) | |
| result[key] = np.mean(padded, axis=0) | |
| # 清空缓冲 | |
| self._weight_buffer[area] = [] | |
| return result | |
| def get_version(self, node_id: str, area: str) -> int: | |
| return self._node_versions.get(f"{node_id}:{area}", 0) | |
| class DiffSync: | |
| """增量同步 — 只传权重变化量""" | |
| def compute_diff(old: Dict[str, np.ndarray], | |
| new: Dict[str, np.ndarray], | |
| threshold: float = 1e-6) -> Dict[str, np.ndarray]: | |
| """计算权重差异""" | |
| diff = {} | |
| for key in new: | |
| if key in old: | |
| d = new[key] - old[key] | |
| if np.max(np.abs(d)) > threshold: | |
| diff[key] = d | |
| else: | |
| diff[key] = new[key] | |
| return diff | |
| def apply_diff(base: Dict[str, np.ndarray], | |
| diff: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: | |
| """应用差异到基础权重""" | |
| result = {k: v.copy() for k, v in base.items()} | |
| for key, delta in diff.items(): | |
| if key in result: | |
| result[key] = result[key] + delta | |
| else: | |
| result[key] = delta | |
| return result | |
| class FederationNode: | |
| """联邦学习节点 — 管理本地训练和远程同步""" | |
| def __init__(self, node_id: str, tier: str = 'lite', | |
| areas: Optional[List[str]] = None): | |
| self.node_id = node_id | |
| self.tier = tier | |
| self.areas = areas or list(AREA_NAMES.keys()) | |
| # 本地权重版本 | |
| self._versions: Dict[str, int] = {a: 0 for a in self.areas} | |
| # 上次同步的权重快照(用于diff计算) | |
| self._snapshots: Dict[str, Dict[str, np.ndarray]] = {} | |
| # 统计 | |
| self.stats = { | |
| 'local_updates': 0, | |
| 'sync_sent': 0, | |
| 'sync_received': 0, | |
| 'last_sync': None, | |
| } | |
| def after_train(self, area: str, weights: Dict[str, np.ndarray]): | |
| """本地训练后更新版本""" | |
| self._versions[area] += 1 | |
| self.stats['local_updates'] += 1 | |
| def get_sync_payload(self, area: str, | |
| weights: Dict[str, np.ndarray], | |
| use_diff: bool = True) -> Dict: | |
| """准备同步数据""" | |
| version = self._versions[area] | |
| if use_diff and area in self._snapshots: | |
| diff = DiffSync.compute_diff(self._snapshots[area], weights) | |
| payload_weights = diff | |
| mode = 'diff' | |
| else: | |
| payload_weights = weights | |
| mode = 'full' | |
| # 保存快照 | |
| self._snapshots[area] = {k: v.copy() for k, v in weights.items()} | |
| self.stats['sync_sent'] += 1 | |
| self.stats['last_sync'] = time.time() | |
| return { | |
| 'node_id': self.node_id, | |
| 'area': area, | |
| 'version': version, | |
| 'tier': self.tier, | |
| 'mode': mode, | |
| 'weights': WeightCodec.encode(payload_weights), | |
| } | |
| def apply_received(self, area: str, | |
| weights: Dict[str, np.ndarray], | |
| mode: str = 'full', | |
| base_weights: Optional[Dict[str, np.ndarray]] = None): | |
| """应用接收到的聚合权重""" | |
| if mode == 'diff' and base_weights: | |
| result = DiffSync.apply_diff(base_weights, weights) | |
| else: | |
| result = weights | |
| self.stats['sync_received'] += 1 | |
| return result | |