← 文章 / AI技术
东东4050 5小时前 · 2026-09-04 03:38:40 · 2 阅读

探索AI模型结构的自主生长机制

我们找到了病灶,但发现手术刀不够长

引子:一次关于AI"内部手术"的实验

过去一段时间,我们一直在尝试一件听起来有点疯狂的事情:给AI模型做"内部手术"。

我们想知道:当一个AI学会了一个任务后,如果让它学习一个新任务,我们能不能提前"看到"它的结构哪里不够用,然后精准地在那个位置"生长"出新结构来帮助它?

这听起来像是科幻小说——给AI做手术,让它"长出"新能力。但我们已经把这件事变成了实验室里的真实实验。

实验规模很小(模型只有约54万参数,比主流大模型小几个数量级),但它揭示了一些极其清晰的信号。最近的一轮实验,更是给出了迄今为止最精确的答案。

一、我们的"手术方案"

1.1 两个任务:先学A,再测B

我们让模型学习两个数学任务:

· 任务A:给一串数字,计算它们的累积和(比如 1,2,3 → 1,3,6)。

· 任务B:给一串数字,计算它们的加权累积和(比如 1,2,3 → 1, 1×1+2×2, 1×1+2×2+3×3 = 1,5,14)。

这两个任务看起来很像——都是"累加"——但任务B多了一个关键要求:每个数字要乘以它的位置序号。这需要模型学会"位置加权"的概念。

1.2 三步走:诊断 → 定位 → 生长

第一步:学会任务A。 我们把模型训练到任务A的准确率超过90%,让它形成一个稳定的"计算骨架"。

第二步:用"X光"扫描模型。 我们用了三个工具来"看"模型内部:

· 有效秩:测量模型实际用到了多少个"独立方向"。如果这个数字下降,说明模型正在把知识压缩到更少的维度上。

· 外部压力:让模型看一眼任务B的数据,计算它的"学习方向"中有多少是现有结构无法覆盖的。

· 消融测试:删除模型尾部的一些"次要方向",看任务B的表现会变好还是变差。

第三步:尝试"结构生长"。 我们在诊断出的"最需要生长"的位置添加一个小型新支路(仅占模型总参数量的1%左右),然后让它在任务B上训练。

我们比较了四种情况:

· 不生长:原模型直接学任务B。

· 随机生长:新支路的方向是随机选的。

· SVD引导生长:新支路的方向基于当前权重的几何结构。

· 梯度引导生长:新支路的方向基于任务B的"学习信号"指向。

· Oracle("理想"生长):新支路加上让基座也进行适应性调整——这是我们的"上限对照"。

如果"引导生长"能比"随机生长"学到更多东西,那就说明我们的诊断是有用的。

二、我们看到了什么?

2.1 诊断:教科书级别的成功

为了确保结果不是偶然,我们跑了三个不同的随机种子。诊断部分的结果出奇地一致:

种子 外部压力 有效秩

42 44.5% 36.47

123 63.0% 27.93

2026 71.5% 24.64

更重要的是,三个种子都精确地指向了同一层——模型的最后一层——作为"最需要生长"的位置。

诊断部分的结论非常清晰:

· 模型在学习任务A的过程中,确实把知识压缩到了更少的方向上(有效秩下降)。

· 当面对任务B时,有44%~71%的"学习方向"指向了现有结构之外(外部压力很强)。

· 诊断工具能够精确定位到"病灶"的位置。

2.2 三种引导生长:精确到0.0000的"完全无效"

然后我们满怀信心地进行了"生长手术"。结果让人震惊:

生长方式 种子42 种子123 种子2026

随机生长 0.0000 0.0000 0.0000

SVD引导生长 0.0000 0.0000 0.0000

梯度引导生长 0.0000 0.0000 0.0000

三种引导生长,在三个独立种子上,增益全部精确为0。任务B的表现没有任何改善。

这不是"效果不好"——这是完全没有效果。三种不同的引导方向,结果完全一样,就像新添加的支路根本不存在一样。

2.3 Oracle:意外中的意外

但Oracle的结果给了我们一个巨大的反差:

种子 Oracle增益 B损失变化

42 11.86 15.85 → 3.99

123 15.65 19.91 → 4.25

2026 12.64 16.68 → 4.05

Oracle成功了!而且非常成功——损失的下降幅度巨大(从15~20降到3.9~4.3)。

但是,有一个关键的细节:准确率几乎没变(仍保持在6.7%左右,接近随机)。

这意味着什么?Oracle能够消除任务A带来的干扰(损失大幅下降),但无法真正学会任务B(准确率不动)。

三、这是怎么回事?

3.1 我们的"手术刀"根本没碰到病灶

经过深入分析,我们发现了问题的根源:

当模型学会任务A后,它的最后一层FFN(前馈网络)的中间层激活(GELU层输出)高度专化于任务A。当面对任务B时,这个中间层几乎不输出任何信号——它被"门控"关掉了。

而我们的growth branch(新支路)正好接在这个中间层之后。如果中间层的输出≈0,那么无论growth branch的权重设成什么,它的输出都是0×权重=0。

这就像:我们给病人开刀,找到了病灶的位置,但发现手术器械根本伸不进去——因为通往病灶的通道被锁死了。

三种不同的引导方向(随机、SVD、梯度)在"输入为零"的情况下毫无差别——方向再好,乘以0还是0。

3.2 Oracle的"半成功"揭示了一个更深层的真相

Oracle之所以能成功(损失下降),是因为它解冻了基座的最后一层,让模型能够"遗忘"任务A的部分专有表示,从而降低损失。

但Oracle之所以不能完全成功(准确率不变),是因为任务B的真正难点不在于最后一层FFN的容量,而在于它需要跨层的位置加权计算——这需要在新计算路径中让多个层协同工作。

这就像一个乐队:换掉一个乐手解决不了问题,需要重新编排整个乐谱。

四、这次实验的真正价值

如果目标是"证明结构生长有效",那这次实验无疑是失败的。但如果我们是在探索"AI结构演化的边界",那这次实验是一次巨大的成功。

4.1 我们确认了四件事

1. 诊断工具是有效的。三个独立种子都精确定位到同一层,外部压力信号稳定且强烈。

2. "局部低秩生长"有明确的前置条件:基座冻结时,无论方向如何优化,都无法产生任何效果。

3. "灾难性干扰"是真实存在的——任务A的专有表示会主动"锁死"通往新任务的通道。

4. 真正的瓶颈是"跨层计算路径",而非"单层容量不足"。

4.2 这给出了明确的下一步方向

v6的核心改动应该是:把growth branch插入到输入非零的位置。

具体方案:

· 方案A:插入到注意力层输出之后(而非FFN中间层之后),此时输入包含Token之间的上下文交互。

· 方案B:直接在残差连接上添加并行路径,绕过FFN的"门控"机制。

· 方案C:在多个层同时插入growth branch,构建跨层的新计算通道。

五、结语:科学就是不断缩小未知的范围

如果这次实验跑出了"引导生长显著优于随机生长"的结果,我们会很高兴。但现实给了我们一个更珍贵的礼物:一个精确到小数点后四位的"零结果",以及一个清晰的机制解释。

我们的发现:

· 诊断工具能定位"病灶"(✅ 三种子全部一致)。

· 但"病灶"不是单层容量不足,而是跨层计算路径缺失(✅ Oracle的"Loss降但Acc不动"证明了这一点)。

· 基座冻结会阻断新任务的信号流入(✅ 三冻结臂gain=0.0000证明了这一点)。

这就像医生用CT找到了肿瘤,然后发现微创手术器械伸不进去——不是诊断错了,而是手术方案需要升级。

每一次这样的实验,都在把"未知"的范围缩小一点点。我们知道病灶在哪里,知道它为什么没能被切除,也知道下一步该怎么做。而这,正是科学最本来的样子。

# ============================================================

# Smart Density / Structural Growth - v5

# ============================================================

# 基于v4的关键升级:

# 1) 多层诊断:计算所有层的外界压力信号,选择结构缺口最大的层。

# 2) 对该层实施四种生长干预:

# - Random(随机正交矩阵)

# - SVD(基于权重几何结构)

# - Gradient(基于投影梯度方向)

# - Oracle(全容量:解冻整层 + 大分支)

# 3) 增大生长规模并部分解冻基座,以克服"自信错误"的干扰。

# 4) 使用参数和步骤匹配的严格因果对比。

# ============================================================

import os, json, math, time, copy, random

from dataclasses import dataclass, asdict

import numpy as np

import torch

import torch.nn as nn

import torch.nn.functional as F

from torch.utils.data import Dataset, DataLoader

BASE_SEED = 42

SEEDS = [42, 123, 2026]

def seed_everything(s):

random.seed(s)

np.random.seed(s)

torch.manual_seed(s)

```python if torch.cuda.is_available(): torch.cuda.manual_seed_all(s) seed_everything(BASE_SEED) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device.type == 'cuda': torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.benchmark = True print('PyTorch:', torch.__version__) print('Device:', device) if device.type == 'cuda': print('GPU:', torch.cuda.get_device_name(0)) # --------------------------- # 配置 # --------------------------- @dataclass class CFG: vocab_size: int = 32 seq_len: int = 32 d_model: int = 128 n_heads: int = 4 n_layers: int = 4 d_ff: int = 256 dropout: float = 0.0 batch_size: int = 64 phase_a_max_steps: int = 8000 phase_a_target_acc: float = 0.90 growth_steps: int = 800 eval_batches: int = 16 lr: float = 1e-3 growth_lr: float = 1e-3 # 为生长阶段调大 weight_decay: float = 0.01 svd_rank: int = 16 growth_rank: int = 16 # oracle 使用更大的秩 train_size: int = 12000 eval_size: int = 2000 eps: float = 1e-12 # 生长尺度:初始输出幅度 growth_scale: float = 0.3 # 比 v4 的 0.01 大得多 cfg = CFG() # --------------------------- # 数据工具函数(与 v4 相同) # --------------------------- PAD = 0 BOS = 1 TOKEN_OFFSET = 4 DATA_V = cfg.vocab_size - TOKEN_OFFSET def make_example(task): x = torch.randint(TOKEN_OFFSET, cfg.vocab_size, (cfg.seq_len,)) z = x - TOKEN_OFFSET if task == 'A': y = torch.cumsum(z, 0) % DATA_V elif task == 'B': y = torch.cumsum(z * torch.arange(1, cfg.seq_len + 1), 0) % DATA_V else: raise ValueError(task) return x, (y + TOKEN_OFFSET).long() class ToyTaskDataset(Dataset): def __init__(self, task, n): data = [make_example(task) for _ in range(n)] self.x = torch.stack([d[0] for d in data]) self.y = torch.stack([d[1] for d in data]) def __len__(self): return self.x.size(0) def __getitem__(self, i): return self.x[i], self.y[i] def loaders(): ``` ```python train_A = ToyTaskDataset('A', cfg.train_size) train_B = ToyTaskDataset('B', cfg.train_size) eval_A = ToyTaskDataset('A', cfg.eval_size) eval_B = ToyTaskDataset('B', cfg.eval_size) pin = device.type == 'cuda' return ( DataLoader(train_A, batch_size=cfg.batch_size, shuffle=True, drop_last=True, pin_memory=pin), DataLoader(train_B, batch_size=cfg.batch_size, shuffle=True, drop_last=True, pin_memory=pin), DataLoader(eval_A, batch_size=cfg.batch_size, shuffle=False, drop_last=True, pin_memory=pin), DataLoader(eval_B, batch_size=cfg.batch_size, shuffle=False, drop_last=True, pin_memory=pin) ) # --------------------------- # 模型(与 v4 相同) # --------------------------- class TinyTransformer(nn.Module): def __init__(self): super().__init__() self.tok = nn.Embedding(cfg.vocab_size, cfg.d_model) self.pos = nn.Parameter(torch.randn(1, cfg.seq_len, cfg.d_model) * 0.02) layer = nn.TransformerEncoderLayer( cfg.d_model, cfg.n_heads, cfg.d_ff, cfg.dropout, 'gelu', batch_first=True, norm_first=True ) self.blocks = nn.TransformerEncoder(layer, cfg.n_layers) self.norm = nn.LayerNorm(cfg.d_model) self.head = nn.Linear(cfg.d_model, cfg.vocab_size) mask = torch.full((cfg.seq_len, cfg.seq_len), float('-inf')) mask = torch.triu(mask, 1) self.register_buffer('causal_mask', mask, persistent=False) def forward(self, x, return_hidden=False): h = self.tok(x) + self.pos[:, :x.size(1)] h = self.blocks(h, mask=self.causal_mask[:x.size(1), :x.size(1)]) h = self.norm(h) z = self.head(h) return (z, h) if return_hidden else z # --------------------------- # 辅助函数(与 v4 相同) # --------------------------- def count_params(m, trainable=False): return sum(p.numel() for p in m.parameters() if (p.requires_grad or not trainable)) def batch_to_device(b): return b[0].to(device, non_blocking=True), b[1].to(device, non_blocking=True) def loss_batch(m, b): x, y = batch_to_device(b) logits = m(x) return F.cross_entropy(logits.reshape(-1, cfg.vocab_size), y.reshape(-1)) @torch.no_grad() ``` ```

def evaluate(m, loader, n=None):

m.eval()

total_loss = 0.0

count = 0

for i, b in enumerate(loader):

if n is not None and i >= n:

break

total_loss += float(loss_batch(m, b))

count += 1

return total_loss / max(count, 1)

@torch.no_grad()

def accuracy(m, loader, n=None):

m.eval()

correct = total = 0

for i, b in enumerate(loader):

if n is not None and i >= n:

break

x, y = batch_to_device(b)

pred = m(x).argmax(-1)

correct += int((pred == y).sum())

total += y.numel()

return correct / max(total, 1)

# ---------------------------

# SVD 工具函数(扩展支持多层)

# ---------------------------

def effrank(S):

q = S.float().pow(2)

p = q / (q.sum() + cfg.eps)

return float(torch.exp(-(p * (p + cfg.eps).log()).sum()))

def get_linear_layers(m):

"""返回所有 FFN 输出投影层的 (layer_idx, linear_module) 列表。"""

layers = []

for i, block in enumerate(m.blocks.layers):

# 每个 block 包含 linear2(即 FFN 输出投影)

layers.append((i, block.linear2))

return layers

def svd_diag(linear_layer, rank=16):

W = linear_layer.weight.detach().float().cpu()

U, S, Vh = torch.linalg.svd(W, full_matrices=False)

k = min(rank, U.shape[1])

return {

'U': U[:, :k].to(device),

'S': S,

'effective_rank': effrank(S),

'topk_energy': float((S[:k]**2).sum() / (S.pow(2).sum() + cfg.eps))

}

def grad_geometry(linear_layer, U, batch):

"""计算给定层和 U 的外侧压力。"""

linear_layer.zero_grad(set_to_none=True)

L = loss_batch_for_layer(linear_layer, batch) # 需要通过整个模型计算损失

L.backward()

G = linear_layer.weight.grad.detach().float()

GGt = G @ G.T

total = torch.trace(GGt).clamp_min(cfg.eps)

P = U @ U.T

inside = torch.trace(P @ GGt).clamp_min(0)

outside = (total - inside).clamp_min(0)

linear_layer.zero_grad(set_to_none=True)

return {

'grad_inside': float(inside / total),

``` ```python 'grad_outside': float(outside / total), 'G': G, 'loss': float(L) } # 需要一个包装函数来为给定线性层的修改计算损失。 # 我们暂时克隆模型来实现?为了效率,我们对整个模型计算 # 梯度,然后提取目标层的梯度。 # 这样更简单且更准确。 def compute_layer_gradients(m, batch, target_layers): """为指定的线性层计算梯度并返回外部压力。""" m.zero_grad(set_to_none=True) L = loss_batch(m, batch) L.backward() results = {} for idx, lin in target_layers: if lin.weight.grad is None: results[idx] = None continue G = lin.weight.grad.detach().float() # 对权重进行 SVD 以获取 U W = lin.weight.detach().float().cpu() U_full, S_full, _ = torch.linalg.svd(W, full_matrices=False) k = min(cfg.svd_rank, U_full.shape[1]) U = U_full[:, :k].to(device) GGt = G @ G.T total = torch.trace(GGt).clamp_min(cfg.eps) P = U @ U.T inside = torch.trace(P @ GGt).clamp_min(0) outside = (total - inside).clamp_min(0) results[idx] = { 'outside_pressure': float(outside / total), 'G': G, 'U': U, 'effective_rank': effrank(S_full), } m.zero_grad(set_to_none=True) return results # --------------------------- # 生长模块(增强版) # --------------------------- class LowRankGrowth(nn.Module): def __init__(self, in_dim, out_dim, rank, U_init=None, scale=0.3): super().__init__() self.down = nn.Linear(in_dim, rank, bias=False) self.up = nn.Linear(rank, out_dim, bias=False) nn.init.normal_(self.down.weight, std=0.02) with torch.no_grad(): if U_init is None: Q = torch.linalg.qr(torch.randn(out_dim, rank, device=device))[0] self.up.weight.data = Q * scale else: Q, _ = torch.linalg.qr(U_init, mode='reduced') self.up.weight.data = Q * scale def forward(self, x): ``` ```python return self.up(self.down(x)) class GrowingLinear(nn.Module): def __init__(self, base, growth): super().__init__() self.base = base self.growth = growth @property def weight(self): return self.base.weight @property def bias(self): return self.base.bias def forward(self, x): return self.base(x) + self.growth(x) def make_growth_at_layer(m, layer_idx, growth_type, U_init=None, rank=16, scale=0.3): """ 将指定层的 linear2 替换为 GrowingLinear。 growth_type: 'random'、'svd'、'gradient'、'oracle' 对于 'oracle' 模式,我们还会解冻基础层(允许完整微调)。 """ block = m.blocks.layers[layer_idx] old = block.linear2 if growth_type == 'oracle': # Oracle:之后我们会解冻整个基础层,但当前先创建一个大型增长分支 growth = LowRankGrowth(cfg.d_ff, cfg.d_model, rank=rank, U_init=None, scale=scale) else: growth = LowRankGrowth(cfg.d_ff, cfg.d_model, rank=rank, U_init=U_init, scale=scale) block.linear2 = GrowingLinear(old, growth).to(device) return m def freeze_all_except_growth(m, layer_idx, growth_type): """冻结所有参数,仅保留增长分支(oracle 模式下可选保留基础层)。""" for p in m.parameters(): p.requires_grad = False # 始终解冻增长分支 growth = m.blocks.layers[layer_idx].linear2.growth growth.down.weight.requires_grad = True growth.up.weight.requires_grad = True if growth_type == 'oracle': # 同时解冻基础线性层 base = m.blocks.layers[layer_idx].linear2.base base.weight.requires_grad = True if base.bias is not None: base.bias.requires_grad = True # 是否也要解冻 layer norm 等其他组件?这里先保持简单。 # --------------------------- # 训练流程 # --------------------------- def train_until_convergence(m, loader, eval_loader, max_steps, target_acc, lr, weight_decay=0.0): m.train() opt = torch.optim.AdamW([p for p in m.parameters() if p.requires_grad], lr=lr, weight_decay=weight_decay) ``` ```python it = iter(loader) step = 0 while step < max_steps: try: b = next(it) except StopIteration: it = iter(loader) b = next(it) opt.zero_grad(set_to_none=True) L = loss_batch(m, b) L.backward() torch.nn.utils.clip_grad_norm_([p for p in m.parameters() if p.requires_grad and p.grad is not None], 1.0) opt.step() step += 1 if step % 200 == 0 or step == max_steps: acc = accuracy(m, eval_loader, cfg.eval_batches) if acc >= target_acc: print(f"Converged at step {step}, acc={acc:.4f}") return step print(f"Reached max steps {max_steps}, acc={accuracy(m, eval_loader, cfg.eval_batches):.4f}") return step def train_growth(m, loader, steps, lr): m.train() params = [p for p in m.parameters() if p.requires_grad] if not params: print("Warning: No trainable parameters!") return m opt = torch.optim.AdamW(params, lr=lr, weight_decay=0.0) it = iter(loader) for _ in range(steps): try: b = next(it) except StopIteration: it = iter(loader) b = next(it) opt.zero_grad(set_to_none=True) L = loss_batch(m, b) L.backward() torch.nn.utils.clip_grad_norm_(params, 1.0) opt.step() return m # --------------------------- # 单次种子实验 # --------------------------- def run(seed): print(f"\n{'='*70}\nSEED {seed}\n{'='*70}") seed_everything(seed) A, B, eA, eB = loaders() m = TinyTransformer().to(device) # Phase A 训练 steps_taken = train_until_convergence( m, A, eA, max_steps=cfg.phase_a_max_steps, target_acc=cfg.phase_a_target_acc, lr=cfg.lr, weight_decay=cfg.weight_decay ) state = copy.deepcopy(m.state_dict()) # 基线评估 base = { 'A_loss': evaluate(m, eA, cfg.eval_batches), 'A_acc': accuracy(m, eA, cfg.eval_batches), 'B_loss': evaluate(m, eB, cfg.eval_batches), ``` ```python 'B_acc': accuracy(m, eB, cfg.eval_batches) } print("Phase A 收敛:", base) # 多层诊断 batch_B = next(iter(B)) layer_info = [(i, block.linear2) for i, block in enumerate(m.blocks.layers)] grad_info = compute_layer_gradients(m, batch_B, layer_info) # 找出外部压力最高的层 best_layer = max(grad_info.items(), key=lambda x: x[1]['outside_pressure'] if x[1] is not None else -1) layer_idx = best_layer[0] outside_pressure = best_layer[1]['outside_pressure'] G = best_layer[1]['G'] U_old = best_layer[1]['U'] eff_rank = best_layer[1]['effective_rank'] print(f"最佳层: {layer_idx}, outside_pressure: {outside_pressure:.4f}, eff_rank: {eff_rank:.2f}") # 准备生长方向 # SVD 引导:直接使用 U_old(前k个左奇异向量) # 梯度引导:使用投影后的梯度方向 P_perp = torch.eye(U_old.shape[0], device=device) - U_old @ U_old.T G_proj = P_perp @ G U_grad, S_grad, _ = torch.linalg.svd(G_proj, full_matrices=False) rank_g = min(cfg.growth_rank, U_grad.shape[1]) U_grad_init = U_grad[:, :rank_g] # 构建模型 # 无生长 ng = TinyTransformer().to(device) ng.load_state_dict(state) # 随机生长 rg = TinyTransformer().to(device) rg.load_state_dict(state) rg = make_growth_at_layer(rg, layer_idx, 'random', U_init=None, rank=cfg.growth_rank, scale=cfg.growth_scale) freeze_all_except_growth(rg, layer_idx, 'random') # SVD 引导生长 sg = TinyTransformer().to(device) sg.load_state_dict(state) sg = make_growth_at_layer(sg, layer_idx, 'svd', U_init=U_old[:, :cfg.growth_rank], rank=cfg.growth_rank, scale=cfg.growth_scale) freeze_all_except_growth(sg, layer_idx, 'svd') # 梯度引导生长 gg = TinyTransformer().to(device) gg.load_state_dict(state) gg = make_growth_at_layer(gg, layer_idx, 'gradient', U_init=U_grad_init, rank=cfg.growth_rank, scale=cfg.growth_scale) freeze_all_except_growth(gg, layer_idx, 'gradient') # Oracle 生长:解冻基础层 + 大成长 og = TinyTransformer().to(device) og.load_state_dict(state) ``` ```python og = make_growth_at_layer(og, layer_idx, 'oracle', U_init=None, rank=cfg.growth_rank*2, scale=cfg.growth_scale) freeze_all_except_growth(og, layer_idx, 'oracle') added_params = count_params(rg) - count_params(ng) print(f"Added parameters per growth: {added_params}") # 在 Task B 上训练所有 growth 模型 print("Training no growth (baseline)...") # no growth 已在 A 上训练,不在 B 上进一步训练 print("Training random growth...") rg = train_growth(rg, B, cfg.growth_steps, cfg.growth_lr) print("Training SVD-guided growth...") sg = train_growth(sg, B, cfg.growth_steps, cfg.growth_lr) print("Training gradient-guided growth...") gg = train_growth(gg, B, cfg.growth_steps, cfg.growth_lr) print("Training oracle growth...") og = train_growth(og, B, cfg.growth_steps, cfg.growth_lr) # 最终评估 results = {} models = [('no_growth', ng), ('random_growth', rg), ('svd_growth', sg), ('gradient_growth', gg), ('oracle_growth', og)] for name, model in models: results[name] = { 'A_loss': evaluate(model, eA, cfg.eval_batches), 'A_acc': accuracy(model, eA, cfg.eval_batches), 'B_loss': evaluate(model, eB, cfg.eval_batches), 'B_acc': accuracy(model, eB, cfg.eval_batches), } print("Results:") for name, res in results.items(): print(f" {name}: B_loss={res['B_loss']:.4f}, B_acc={res['B_acc']:.4f}") baseB = results['no_growth']['B_loss'] gains = { 'random_gain': baseB - results['random_growth']['B_loss'], 'svd_gain': baseB - results['svd_growth']['B_loss'], 'gradient_gain': baseB - results['gradient_growth']['B_loss'], 'oracle_gain': baseB - results['oracle_growth']['B_loss'], } print(f"Gains: {gains}") return { 'seed': seed, 'phase_A_steps': steps_taken, 'phase_A': base, 'best_layer': layer_idx, 'outside_pressure': outside_pressure, 'effective_rank': eff_rank, 'results': results, 'gains': gains, 'added_params': added_params, } ``` ```python # ============================================================ # 主程序 # ============================================================ runs = [run(s) for s in SEEDS] print("\n" + "="*70) print("多种子汇总结果") print("="*70) # 提取指标 rg = [r['gains']['random_gain'] for r in runs] sg = [r['gains']['svd_gain'] for r in runs] gg = [r['gains']['gradient_gain'] for r in runs] og = [r['gains']['oracle_gain'] for r in runs] op = [r['outside_pressure'] for r in runs] for name, arr in [('随机增益', rg), ('SVD增益', sg), ('梯度增益', gg), ('理想增益', og), ('外部压力', op)]: mean = np.mean(arr) std = np.std(arr, ddof=1) print(f"{name:20s} 均值={mean:.6f} 标准差={std:.6f}") print(f"理想增益 > 梯度增益: {sum(o > g for o, g in zip(og, gg))} / {len(runs)}") print(f"梯度增益 > 随机增益: {sum(g > r for g, r in zip(gg, rg))} / {len(runs)}") if len(runs) > 1: corr = np.corrcoef(op, gg)[0, 1] print(f"corr(外部压力, 梯度增益): {corr:.4f}") # 保存结果 out_dir = './smart_density_v5' os.makedirs(out_dir, exist_ok=True) summary = { 'config': asdict(cfg), 'seeds': SEEDS, 'device': str(device), 'runs': runs, } with open(os.path.join(out_dir, 'results.json'), 'w') as f: json.dump(summary, f, indent=2) print(f"已保存至 {os.path.abspath(out_dir)}/results.json") try: import matplotlib.pyplot as plt x = np.arange(len(SEEDS)) w = 0.15 plt.figure(figsize=(10,5)) plt.bar(x - 1.5*w, rg, w, label='Random') plt.bar(x - 0.5*w, sg, w, label='SVD') plt.bar(x + 0.5*w, gg, w, label='Gradient') plt.bar(x + 1.5*w, og, w, label='Oracle') plt.xticks(x, [str(s) for s in SEEDS]) plt.ylabel('任务B损失降幅') plt.title('结构增长对比 (v5)') plt.legend() plt.tight_layout() plt.show() except Exception as e: print('绘图已跳过:', repr(e)) ``` /tmp/ipykernel_1842/2151775399.py:132: UserWarning: enable_nested_tensor is True, but self.use_nested_tensor is False because encoder_layer.norm_first was True

self.blocks = nn.TransformerEncoder(layer, cfg.n_layers)

Converged at step 3400, acc=0.9017

Phase A converged: {'A_loss': 0.3280856553465128, 'A_acc': 0.9017333984375, 'B_loss': 15.854797780513763, 'B_acc': 0.0667724609375}

Best layer: 3, outside_pressure: 0.4445, eff_rank: 36.47

/tmp/ipykernel_1842/2151775399.py:132: UserWarning: enable_nested_tensor is True, but self.use_nested_tensor is False because encoder_layer.norm_first was True

self.blocks = nn.TransformerEncoder(layer, cfg.n_layers)

Added parameters per growth: 6144

Training no growth (baseline)...

Training random growth...

Training SVD-guided growth...

Training gradient-guided growth...

Training oracle growth...

Results:

no_growth: B_loss=15.8548, B_acc=0.0668

random_growth: B_loss=15.8548, B_acc=0.0668

svd_growth: B_loss=15.8548, B_acc=0.0668

gradient_growth: B_loss=15.8548, B_acc=0.0668

oracle_growth: B_loss=3.9925, B_acc=0.0672

Gains: {'random_gain': 0.0, 'svd_gain': 0.0, 'gradient_gain': 0.0, 'oracle_gain': 11.862322941422462}

======================================================================

SEED 123

======================================================================

Converged at step 6800, acc=0.9725

Phase A converged: {'A_loss': 0.10075912298634648, 'A_acc': 0.97247314453125, 'B_loss': 19.90610122680664, 'B_acc': 0.066925048828125}

Best layer: 3, outside_pressure: 0.6300, eff_rank: 27.93

Added parameters per growth: 6144

Training no growth (baseline)...

Training random growth...

Training SVD-guided growth...

Training gradient-guided growth...

Training oracle growth...

Results:

no_growth: B_loss=19.9061, B_acc=0.0669

random_growth: B_loss=19.9061, B_acc=0.0669

svd_growth: B_loss=19.9061, B_acc=0.0669

gradient_growth: B_loss=19.9061, B_acc=0.0669

oracle_growth: B_loss=4.2520, B_acc=0.0675

Gains: {'random_gain': 0.0, 'svd_gain': 0.0, 'gradient_gain': 0.0, 'oracle_gain': 15.654108047485352}

======================================================================

SEED 2026

======================================================================

第6400步收敛,acc=0.9207

A阶段收敛:{'A_loss': 0.20898992102593184, 'A_acc': 0.920654296875, 'B_loss': 16.68497395515442, 'B_acc': 0.06683349609375}

最优层:3,outside_pressure: 0.7145,eff_rank: 24.64

每次生长新增参数量:6144

训练无生长(基线)...

训练随机生长...

训练SVD引导生长...

训练梯度引导生长...

训练oracle生长...

结果:

无生长: B_loss=16.6850, B_acc=0.0668

随机生长: B_loss=16.6850, B_acc=0.0668

SVD生长: B_loss=16.6850, B_acc=0.0668

梯度生长: B_loss=16.6850, B_acc=0.0668

oracle生长: B_loss=4.0462, B_acc=0.0674

增益:{'random_gain': 0.0, 'svd_gain': 0.0, 'gradient_gain': 0.0, 'oracle_gain': 12.638744369149208}

======================================================================

多种子汇总

======================================================================

随机增益 均值=0.000000 标准差=0.000000

SVD增益 均值=0.000000 标准差=0.000000

梯度增益 均值=0.000000 标准差=0.000000

Oracle增益 均值=13.385058 标准差=2.003034

外部压力 均值=0.596325 标准差=0.138112

Oracle > 梯度:3 / 3

梯度 > 随机:0 / 3

corr(外部压力, 梯度增益): nan

已保存至 /content/smart_density_v5/results.json

/usr/local/lib/python3.13/dist-packages/numpy/lib/_function_base_impl.py:2999: RuntimeWarning: invalid value encountered in divide

c /= stddev[:, None]

/usr/local/lib/python3.13/dist-packages/numpy/lib/_function_base_impl.py:3000: RuntimeWarning: invalid value encountered in divide

c /= stddev[None, :]

原始来源: 东东4050

评论 (0)