Download Code-agent-main/ycode/lm/grow.py from Yazhiniyan/YCode: direct link, hf CLI and curl.
- Browser
- Download file 5.68 kB
-
https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/ycode/lm/grow.py
- Command line
-
hf download hf://Yazhiniyan/YCode/Code-agent-main/ycode/lm/grow.py
-
curl -L -o grow.py https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/ycode/lm/grow.py
5.68 kB
| """Grow a trained model into a deeper one without losing what it learned. | |
| New blocks are inserted between the existing ones. Each new block starts as a | |
| copy of the block before it, with its two output projections (attention | |
| ``proj`` and MLP ``down``) set to zero. Because every block is residual | |
| (``x + f(x)``), a block whose output projections are zero is an exact | |
| identity: the grown model computes exactly the same function as the original | |
| at step 0. Training then teaches the new blocks to add to it. | |
| This reuses all the compute already spent on the smaller model, which on | |
| limited hardware beats training the bigger model from scratch. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import torch | |
| from ycode.lm.model import GPT, GPTConfig | |
| def insertion_plan(old_layers: int, new_layers: int) -> list[int]: | |
| """How many new blocks to insert after each original block (spread evenly).""" | |
| if new_layers < old_layers: | |
| raise ValueError("can only grow: new_layers must be >= the current number of layers") | |
| extra = new_layers - old_layers | |
| plan = [extra // old_layers] * old_layers | |
| for i in range(extra % old_layers): | |
| # Spread the remainder across the whole depth, not just the first blocks. | |
| plan[(i * old_layers) // (extra % old_layers)] += 1 | |
| return plan | |
| def grow_depth(model: GPT, new_layers: int, *, arch_version: int | None = None) -> GPT: | |
| old = model.cfg | |
| cfg = GPTConfig(**{**old.to_dict(), "n_layer": new_layers, | |
| "arch_version": arch_version if arch_version is not None else old.arch_version}) | |
| grown = GPT(cfg) | |
| grown.embed.weight.copy_(model.embed.weight) # head is tied to embed | |
| grown.norm.load_state_dict(model.norm.state_dict()) | |
| blocks = [] | |
| for block, n_new in zip(model.blocks, insertion_plan(old.n_layer, new_layers)): | |
| blocks.append(copy.deepcopy(block)) | |
| for _ in range(n_new): | |
| fresh = copy.deepcopy(block) | |
| fresh.attn.proj.weight.zero_() | |
| fresh.mlp.down.weight.zero_() | |
| blocks.append(fresh) | |
| grown.blocks = torch.nn.ModuleList(blocks) | |
| return grown | |
| def grow_width(model: GPT, new_embd: int, *, new_heads: int | None = None, new_kv_heads: int | None = None, | |
| init_std: float = 0.02, arch_version: int | None = None, seed: int = 0) -> GPT: | |
| """Widen a model (more channels, attention heads and MLP units) while keeping its function. | |
| The residual stream gets extra channels that start at exactly zero: everything that *writes* | |
| into them (embedding columns, attention ``proj`` rows, MLP ``down`` rows) is zero. Everything | |
| that only *reads* from new channels or feeds new heads / new MLP units gets small random | |
| weights, so the new capacity receives gradients and can learn. RMSNorm divides by the RMS over | |
| all channels, so the old gains are rescaled by sqrt(old/new) to cancel the zero channels. | |
| Head size stays the same, so old query heads keep using their old key/value heads. | |
| The result matches the original up to RMSNorm's small eps term (differences around 1e-4). | |
| """ | |
| old = model.cfg | |
| hd = old.n_embd // old.n_head | |
| n_head = new_heads or new_embd // hd | |
| n_kv = new_kv_heads or old.kv_heads * n_head // old.n_head | |
| if new_embd < old.n_embd or n_head * hd != new_embd: | |
| raise ValueError(f"new width must be >= {old.n_embd} and a multiple of the head size {hd}") | |
| if n_head < old.n_head or n_kv < old.kv_heads or n_head % n_kv: | |
| raise ValueError("cannot shrink heads, and n_head must be a multiple of n_kv_head") | |
| if (n_head // n_kv) != (old.n_head // old.kv_heads): | |
| raise ValueError("keep the query-to-key/value head ratio so old heads keep their pairing") | |
| cfg = GPTConfig(**{**old.to_dict(), "n_embd": new_embd, "n_head": n_head, "n_kv_head": n_kv, | |
| "arch_version": arch_version if arch_version is not None else old.arch_version}) | |
| gen = torch.Generator().manual_seed(seed) | |
| def rand(*shape): | |
| return torch.randn(*shape, generator=gen) * init_std | |
| wide = GPT(cfg) | |
| C0, C1 = old.n_embd, new_embd | |
| scale = (C0 / C1) ** 0.5 | |
| wide.embed.weight.zero_() | |
| wide.embed.weight[:, :C0] = model.embed.weight # new channels start at zero (head is tied) | |
| wide.norm.weight.fill_(scale) | |
| wide.norm.weight[:C0] = model.norm.weight * scale | |
| kv0, kv1 = old.kv_heads * hd, n_kv * hd | |
| for ob, nb in zip(model.blocks, wide.blocks): | |
| for name in ("norm1", "norm2"): | |
| getattr(nb, name).weight.fill_(scale) | |
| getattr(nb, name).weight[:C0] = getattr(ob, name).weight * scale | |
| # attention: fused qkv rows = [q (C) | k (kv) | v (kv)] | |
| w_old, w = ob.attn.qkv.weight, nb.attn.qkv.weight | |
| w.copy_(rand(*w.shape)) | |
| for (o0, o1, n0) in ((0, C0, 0), (C0, C0 + kv0, C1), (C0 + kv0, C0 + 2 * kv0, C1 + kv1)): | |
| w[n0:n0 + (o1 - o0), :C0] = w_old[o0:o1] | |
| nb.attn.proj.weight.zero_() | |
| nb.attn.proj.weight[:C0, :C0] = ob.attn.proj.weight # new heads' output: zero for now | |
| if ob.attn.q_norm is not None: | |
| nb.attn.q_norm.load_state_dict(ob.attn.q_norm.state_dict()) | |
| nb.attn.k_norm.load_state_dict(ob.attn.k_norm.state_dict()) | |
| # MLP: new hidden units read random weights, write nothing yet | |
| h0 = ob.mlp.gate.weight.shape[0] | |
| for name in ("gate", "up"): | |
| w = getattr(nb.mlp, name).weight | |
| w.copy_(rand(*w.shape)) | |
| w[:h0, :C0] = getattr(ob.mlp, name).weight | |
| nb.mlp.down.weight.zero_() | |
| nb.mlp.down.weight[:C0, :h0] = ob.mlp.down.weight | |
| return wide | |