From 5e031bd3fd55cf0b264d9ffd2dc28b4b24fe3eca Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:24:17 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[FEAT]:=20neuron=20Phase=2013=20(1/3)=20?= =?UTF-8?q?=E2=80=94=20RMSNorm=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/graphlm/neuron/rms_norm.py 신규 — LLaMA / Mistral 표준 norm - mean centering 생략 + bias 없음 (LayerNorm 대비 파라미터 ↓, 안정성 동등) - weight=1 초기화로 function preservation 보장 - mixed precision 안전성 위해 float32 cast 후 RMS 계산 - 8 unit tests (shape / scaling / gradient / 입력 검증) --- src/graphlm/neuron/rms_norm.py | 55 ++++++++++++++++++++++++++++ tests/neuron/test_rms_norm.py | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 src/graphlm/neuron/rms_norm.py create mode 100644 tests/neuron/test_rms_norm.py diff --git a/src/graphlm/neuron/rms_norm.py b/src/graphlm/neuron/rms_norm.py new file mode 100644 index 0000000..4557556 --- /dev/null +++ b/src/graphlm/neuron/rms_norm.py @@ -0,0 +1,55 @@ +"""Phase 13 — RMSNorm (root-mean-square layer normalization). + +Modern Transformer (LLaMA / Mistral / Gemma) 의 표준 norm. nn.LayerNorm 대비: + +- mean centering 생략 → 연산량 ↓ +- bias 파라미터 없음 → 파라미터 수 ↓ (hidden_dim 만큼 절약) +- numerical 안정성 동등 또는 우위 (특히 large hidden_dim) + +수식: ``y = x / RMS(x) * weight``, ``RMS(x) = sqrt(mean(x^2) + eps)`` + +본 paradigm 의 Phase 13 backbone (HybridGraphTransformer) 에서 nn.LayerNorm 대체. +backbone.py 의 기존 LayerNorm 은 Phase 1~12 호환성 위해 그대로 유지. +""" + +from __future__ import annotations + +import torch +from torch import Tensor, nn + + +class RMSNorm(nn.Module): + """Root-mean-square layer normalization. + + Args: + hidden_dim: 정규화 대상 마지막 축의 크기. + eps: RMS 분모 numerical 안정성 (LLaMA 의 1e-6 동일). + + Shape: + - input: ``(..., hidden_dim)`` + - output: same as input + """ + + def __init__(self, hidden_dim: int, eps: float = 1e-6): + super().__init__() + if not isinstance(hidden_dim, int) or hidden_dim < 1: + raise ValueError(f"hidden_dim must be a positive int, got {hidden_dim!r}") + if eps <= 0: + raise ValueError(f"eps must be > 0, got {eps}") + self.hidden_dim = hidden_dim + self.eps = eps + # weight 만 학습 (LayerNorm 의 bias 없음) — function preservation 위해 1.0 으로 시작 + self.weight = nn.Parameter(torch.ones(hidden_dim)) + + def forward(self, x: Tensor) -> Tensor: + if x.shape[-1] != self.hidden_dim: + raise ValueError(f"expected last dim {self.hidden_dim}, got {x.shape[-1]}") + # float32 로 cast 해서 RMS 계산 (mixed precision 안전성) + x_dtype = x.dtype + x_f = x.float() + rms = torch.rsqrt(x_f.pow(2).mean(dim=-1, keepdim=True) + self.eps) + y = (x_f * rms).to(x_dtype) + return y * self.weight + + def extra_repr(self) -> str: + return f"hidden_dim={self.hidden_dim}, eps={self.eps}" diff --git a/tests/neuron/test_rms_norm.py b/tests/neuron/test_rms_norm.py new file mode 100644 index 0000000..a7c88d6 --- /dev/null +++ b/tests/neuron/test_rms_norm.py @@ -0,0 +1,65 @@ +"""Tests for graphlm.neuron.rms_norm — Phase 13 RMSNorm.""" + +from __future__ import annotations + +import pytest +import torch + +from graphlm.neuron.rms_norm import RMSNorm + + +def test_shape_preserved(): + norm = RMSNorm(16) + x = torch.randn(2, 8, 16) + assert norm(x).shape == x.shape + + +def test_init_weight_is_ones(): + norm = RMSNorm(16) + assert torch.allclose(norm.weight, torch.ones(16)) + + +def test_rms_normalizes_to_unit_rms(): + """초기 weight=1 에서 출력의 RMS 가 1 에 매우 가까워야 함.""" + norm = RMSNorm(64) + x = torch.randn(4, 16, 64) * 5.0 # arbitrary scale + y = norm(x) + rms = y.pow(2).mean(dim=-1).sqrt() + # eps 때문에 정확히 1 은 아니지만 매우 근사 + assert torch.allclose(rms, torch.ones_like(rms), atol=1e-3) + + +def test_weight_is_learnable(): + norm = RMSNorm(16) + x = torch.randn(2, 16) + out = norm(x) + out.sum().backward() + assert norm.weight.grad is not None + assert (norm.weight.grad.abs().sum() > 0).item() + + +def test_zero_dim_raises(): + with pytest.raises(ValueError, match="positive int"): + RMSNorm(0) + + +def test_negative_eps_raises(): + with pytest.raises(ValueError, match="eps must be > 0"): + RMSNorm(16, eps=-1e-6) + + +def test_wrong_last_dim_raises(): + norm = RMSNorm(16) + with pytest.raises(ValueError, match="expected last dim 16"): + norm(torch.randn(2, 8)) + + +def test_scaling_via_weight(): + """weight=2.0 로 setting → 출력도 2배 scale.""" + norm = RMSNorm(16) + with torch.no_grad(): + norm.weight.fill_(2.0) + x = torch.randn(4, 16) + y = norm(x) + rms = y.pow(2).mean(dim=-1).sqrt() + assert torch.allclose(rms, torch.full_like(rms, 2.0), atol=1e-3) From 3df646613b10b6e853c2765b060130e789b22418 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:27:35 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[FEAT]:=20neuron=20Phase=2013=20(2/3)=20?= =?UTF-8?q?=E2=80=94=20HybridGraphTransformerBlock=20+=20FFN=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/graphlm/neuron/hybrid_transformer.py 신규 - HybridGraphFFN: HybridGraphLinear 로 fc1 + fc2 (GELU) - HybridGraphTransformerBlock: pre-norm (RMSNorm + attn + RMSNorm + FFN) - PlainFFN / PlainTransformerBlock: 공정 비교용 baseline (norm 은 RMSNorm 통일) - make_block: 4 arch dispatch (plain / hybrid_full_full / full_around_one / around_one_around_one) - identity outer 는 FFN 의 rectangular 구조상 미지원 (ValueError 명시) - 14 unit tests — FFN 동치성 / block 동치성 / gradient flow / arch dispatch - __init__.py exports 갱신 --- src/graphlm/neuron/__init__.py | 12 ++ src/graphlm/neuron/hybrid_transformer.py | 199 +++++++++++++++++++++++ tests/neuron/test_hybrid_transformer.py | 161 ++++++++++++++++++ 3 files changed, 372 insertions(+) create mode 100644 src/graphlm/neuron/hybrid_transformer.py create mode 100644 tests/neuron/test_hybrid_transformer.py diff --git a/src/graphlm/neuron/__init__.py b/src/graphlm/neuron/__init__.py index dafd3e6..84d0c0b 100644 --- a/src/graphlm/neuron/__init__.py +++ b/src/graphlm/neuron/__init__.py @@ -16,6 +16,13 @@ from graphlm.neuron.graph_hybrid import HybridGraphLinear from graphlm.neuron.growable import GrowableEmbedding, GrowableLayerNorm, GrowableLinear from graphlm.neuron.growth import add_attn_function_preserving, add_attn_smooth_start +from graphlm.neuron.hybrid_transformer import ( + HybridGraphFFN, + HybridGraphTransformerBlock, + PlainTransformerBlock, + make_block, +) +from graphlm.neuron.rms_norm import RMSNorm __all__ = [ "ChannelGraphLinear", @@ -23,11 +30,16 @@ "GrowableEmbedding", "GrowableLayerNorm", "GrowableLinear", + "HybridGraphFFN", "HybridGraphLinear", + "HybridGraphTransformerBlock", "NeuronBlock", "NeuronConfig", "NeuronGrowingDecoder", + "PlainTransformerBlock", + "RMSNorm", "SinusoidalAlpha", "add_attn_function_preserving", "add_attn_smooth_start", + "make_block", ] diff --git a/src/graphlm/neuron/hybrid_transformer.py b/src/graphlm/neuron/hybrid_transformer.py new file mode 100644 index 0000000..b14b984 --- /dev/null +++ b/src/graphlm/neuron/hybrid_transformer.py @@ -0,0 +1,199 @@ +"""Phase 13 — HybridGraphLinear 의 Transformer FFN 통합 + RMSNorm pre-norm block. + +본 paradigm 의 graph 표현 (Phase 12 HybridGraphLinear) 을 **실제 Transformer 아키텍처** 안의 +FFN 위치에 도입. Phase 8~12 는 MLP-LM baseline 이었고, Phase 13 부터 standard pre-norm +Transformer block 위에서 검증. + +Scope (Phase 13): +- FFN 의 fc1 (hidden → ffn) + fc2 (ffn → hidden) 만 ``HybridGraphLinear`` +- attention (qkv / out) 은 표준 ``nn.Linear`` 유지 (Phase 14 검토) +- norm 은 ``RMSNorm`` (modern Transformer 표준) + +function preservation: +- ``HybridGraphFFN`` 의 adj_outer=full + adj_inner=full + 동일 weight 초기화 → standard FFN 동치 +- ``HybridGraphTransformerBlock`` 의 FFN 만 hybrid, attention/norm 은 표준 → standard pre-norm block 과 동치 +- 검증은 tests/neuron/test_hybrid_transformer.py 참조 +""" + +from __future__ import annotations + +from typing import Literal + +import torch +import torch.nn.functional as F +from torch import Tensor, nn + +from graphlm.neuron.backbone import CausalSelfAttention +from graphlm.neuron.graph_hybrid import AdjInnerInit, AdjOuterInit, HybridGraphLinear +from graphlm.neuron.rms_norm import RMSNorm + + +class HybridGraphFFN(nn.Module): + """FFN with two ``HybridGraphLinear`` layers + GELU. + + Args: + hidden_dim: in/out 차원 (Transformer hidden size). + ffn_dim: 중간 확장 차원 (보통 4·hidden_dim). + group_size: ``HybridGraphLinear`` 의 block size. hidden_dim / ffn_dim 모두 배수여야 함. + adj_outer_init: outer adj 초기화 (``"full"`` / ``"uniform_around_one"``). + ``"identity"`` 는 FFN 이 정의상 rectangular (hidden ≠ ffn) 이라 지원하지 않음. + adj_inner_init: inner adj 초기화 (``"full"`` / ``"uniform_around_one"``). + + Forward: + ``y = fc2(GELU(fc1(x)))`` — function preserving when both adj = full. + """ + + def __init__( + self, + hidden_dim: int, + ffn_dim: int, + group_size: int, + *, + adj_outer_init: AdjOuterInit = "full", + adj_inner_init: AdjInnerInit = "full", + ): + super().__init__() + if adj_outer_init == "identity": + raise ValueError( + "HybridGraphFFN 은 adj_outer_init='identity' 미지원 — " + "FFN 은 hidden_dim → ffn_dim → hidden_dim 으로 rectangular 라 정방 identity 정의 불가. " + "'full' 또는 'uniform_around_one' 사용." + ) + self.fc1 = HybridGraphLinear( + hidden_dim, + ffn_dim, + group_size=group_size, + adj_outer_init=adj_outer_init, + adj_inner_init=adj_inner_init, + bias=False, + ) + self.fc2 = HybridGraphLinear( + ffn_dim, + hidden_dim, + group_size=group_size, + adj_outer_init=adj_outer_init, + adj_inner_init=adj_inner_init, + bias=False, + ) + + def forward(self, x: Tensor) -> Tensor: + return self.fc2(F.gelu(self.fc1(x))) + + +class HybridGraphTransformerBlock(nn.Module): + """Pre-norm Transformer block — RMSNorm + CausalSelfAttention + RMSNorm + HybridGraphFFN. + + Forward: + ``x = x + attn(rms1(x))`` + ``x = x + ffn(rms2(x))`` + """ + + def __init__( + self, + hidden_dim: int, + n_heads: int, + ffn_dim: int, + group_size: int, + *, + adj_outer_init: AdjOuterInit = "full", + adj_inner_init: AdjInnerInit = "full", + dropout: float = 0.0, + ): + super().__init__() + self.rms1 = RMSNorm(hidden_dim) + self.attn = CausalSelfAttention(hidden_dim, n_heads, dropout=dropout) + self.rms2 = RMSNorm(hidden_dim) + self.ffn = HybridGraphFFN( + hidden_dim, + ffn_dim, + group_size=group_size, + adj_outer_init=adj_outer_init, + adj_inner_init=adj_inner_init, + ) + + def forward(self, x: Tensor) -> Tensor: + x = x + self.attn(self.rms1(x)) + return x + self.ffn(self.rms2(x)) + + +Arch = Literal[ + "plain", + "hybrid_full_full", + "hybrid_full_around_one", + "hybrid_around_one_around_one", +] + + +class PlainFFN(nn.Module): + """Standard 2-layer FFN (no bias) — Phase 13 ``"plain"`` baseline.""" + + def __init__(self, hidden_dim: int, ffn_dim: int): + super().__init__() + self.fc1 = nn.Linear(hidden_dim, ffn_dim, bias=False) + self.fc2 = nn.Linear(ffn_dim, hidden_dim, bias=False) + + def forward(self, x: Tensor) -> Tensor: + return self.fc2(F.gelu(self.fc1(x))) + + +class PlainTransformerBlock(nn.Module): + """Pre-norm Transformer block with standard FFN — Phase 13 ``"plain"`` baseline. + + HybridGraphTransformerBlock 과 동일한 norm/attention/residual 구조 — FFN 만 표준 nn.Linear. + 공정 비교 위해 RMSNorm 동일 사용. + """ + + def __init__(self, hidden_dim: int, n_heads: int, ffn_dim: int, dropout: float = 0.0): + super().__init__() + self.rms1 = RMSNorm(hidden_dim) + self.attn = CausalSelfAttention(hidden_dim, n_heads, dropout=dropout) + self.rms2 = RMSNorm(hidden_dim) + self.ffn = PlainFFN(hidden_dim, ffn_dim) + + def forward(self, x: Tensor) -> Tensor: + x = x + self.attn(self.rms1(x)) + return x + self.ffn(self.rms2(x)) + + +def make_block( + arch: Arch, + hidden_dim: int, + n_heads: int, + ffn_dim: int, + group_size: int, + dropout: float = 0.0, +) -> nn.Module: + """4 가지 arch 중 하나로 Phase 13 Transformer block 생성.""" + if arch == "plain": + return PlainTransformerBlock(hidden_dim, n_heads, ffn_dim, dropout=dropout) + if arch == "hybrid_full_full": + return HybridGraphTransformerBlock( + hidden_dim, + n_heads, + ffn_dim, + group_size=group_size, + adj_outer_init="full", + adj_inner_init="full", + dropout=dropout, + ) + if arch == "hybrid_full_around_one": + return HybridGraphTransformerBlock( + hidden_dim, + n_heads, + ffn_dim, + group_size=group_size, + adj_outer_init="full", + adj_inner_init="uniform_around_one", + dropout=dropout, + ) + if arch == "hybrid_around_one_around_one": + return HybridGraphTransformerBlock( + hidden_dim, + n_heads, + ffn_dim, + group_size=group_size, + adj_outer_init="uniform_around_one", + adj_inner_init="uniform_around_one", + dropout=dropout, + ) + raise ValueError(f"unknown arch: {arch}") diff --git a/tests/neuron/test_hybrid_transformer.py b/tests/neuron/test_hybrid_transformer.py new file mode 100644 index 0000000..4b6c110 --- /dev/null +++ b/tests/neuron/test_hybrid_transformer.py @@ -0,0 +1,161 @@ +"""Tests for graphlm.neuron.hybrid_transformer — Phase 13 Transformer integration.""" + +from __future__ import annotations + +import pytest +import torch +from torch import nn + +from graphlm.neuron.hybrid_transformer import ( + HybridGraphFFN, + HybridGraphTransformerBlock, + PlainFFN, + PlainTransformerBlock, + make_block, +) + + +# ── HybridGraphFFN ─────────────────────────────────────────── + + +def test_ffn_shape(): + ffn = HybridGraphFFN(hidden_dim=32, ffn_dim=64, group_size=8) + x = torch.randn(2, 16, 32) + assert ffn(x).shape == (2, 16, 32) + + +def test_ffn_identity_outer_rejected(): + """FFN 은 rectangular 라 identity outer 미지원.""" + with pytest.raises(ValueError, match="rectangular"): + HybridGraphFFN(hidden_dim=32, ffn_dim=64, group_size=8, adj_outer_init="identity") + + +def test_ffn_function_preservation_full_full(): + """adj_outer=full + adj_inner=full + 같은 weight → standard PlainFFN 동일.""" + torch.manual_seed(0) + hidden, ffn_d, k = 16, 32, 4 + hg_ffn = HybridGraphFFN(hidden, ffn_d, group_size=k) + plain_ffn = PlainFFN(hidden, ffn_d) + + # hg_ffn 의 block weight 를 standard nn.Linear weight 로 변환해서 plain 에 복사 + _copy_hybrid_to_plain(hg_ffn.fc1, plain_ffn.fc1) + _copy_hybrid_to_plain(hg_ffn.fc2, plain_ffn.fc2) + + x = torch.randn(2, 8, hidden) + y_hg = hg_ffn(x) + y_plain = plain_ffn(x) + assert torch.allclose(y_hg, y_plain, atol=1e-5), ( + f"function preservation 깨짐: max |diff| = {(y_hg - y_plain).abs().max().item()}" + ) + + +def test_ffn_all_params_have_gradient(): + ffn = HybridGraphFFN( + 16, 32, group_size=4, adj_outer_init="uniform_around_one", adj_inner_init="uniform_around_one" + ) + x = torch.randn(2, 16) + ffn(x).sum().backward() + for layer in [ffn.fc1, ffn.fc2]: + for attr in ["weight", "adj_outer", "adj_inner"]: + grad = getattr(layer, attr).grad + assert grad is not None, f"{layer}.{attr}.grad is None" + assert (grad.abs().sum() > 0).item(), f"{layer}.{attr}.grad all zero" + + +# ── HybridGraphTransformerBlock ────────────────────────────── + + +def test_block_shape(): + block = HybridGraphTransformerBlock( + hidden_dim=32, n_heads=4, ffn_dim=64, group_size=8 + ) + x = torch.randn(2, 16, 32) + assert block(x).shape == (2, 16, 32) + + +def test_block_function_preservation_against_plain(): + """hybrid block (adj=full/full) + plain block 의 동일 weight 로 forward 동일.""" + torch.manual_seed(0) + hidden, n_heads, ffn_d, k = 16, 4, 32, 4 + hybrid = HybridGraphTransformerBlock(hidden, n_heads, ffn_d, group_size=k) + plain = PlainTransformerBlock(hidden, n_heads, ffn_d) + + # rms / attn 은 standard module 이므로 state_dict copy 가능 + plain.rms1.load_state_dict(hybrid.rms1.state_dict()) + plain.rms2.load_state_dict(hybrid.rms2.state_dict()) + plain.attn.load_state_dict(hybrid.attn.state_dict()) + # FFN 만 block → standard 변환 + _copy_hybrid_to_plain(hybrid.ffn.fc1, plain.ffn.fc1) + _copy_hybrid_to_plain(hybrid.ffn.fc2, plain.ffn.fc2) + + x = torch.randn(2, 8, hidden) + y_hybrid = hybrid(x) + y_plain = plain(x) + assert torch.allclose(y_hybrid, y_plain, atol=1e-5), ( + f"block forward 차이: max |diff| = {(y_hybrid - y_plain).abs().max().item()}" + ) + + +def test_block_gradient_flows_all_params(): + block = HybridGraphTransformerBlock( + hidden_dim=16, + n_heads=4, + ffn_dim=32, + group_size=4, + adj_outer_init="uniform_around_one", + adj_inner_init="uniform_around_one", + ) + x = torch.randn(2, 4, 16) + block(x).sum().backward() + null_grad = [n for n, p in block.named_parameters() if p.grad is None] + assert not null_grad, f"grad 없는 파라미터: {null_grad}" + + +# ── make_block dispatch ────────────────────────────────────── + + +@pytest.mark.parametrize( + "arch", + ["plain", "hybrid_full_full", "hybrid_full_around_one", "hybrid_around_one_around_one"], +) +def test_make_block_all_archs_forward(arch): + block = make_block(arch, hidden_dim=16, n_heads=4, ffn_dim=32, group_size=4) + x = torch.randn(2, 8, 16) + assert block(x).shape == (2, 8, 16) + + +def test_make_block_unknown_raises(): + with pytest.raises(ValueError, match="unknown arch"): + make_block("bogus", 16, 4, 32, 4) # type: ignore[arg-type] + + +def test_make_block_plain_uses_nn_linear(): + block = make_block("plain", 16, 4, 32, 4) + assert isinstance(block, PlainTransformerBlock) + assert isinstance(block.ffn.fc1, nn.Linear) + + +def test_make_block_hybrid_uses_hybrid_ffn(): + block = make_block("hybrid_full_full", 16, 4, 32, 4) + assert isinstance(block, HybridGraphTransformerBlock) + assert isinstance(block.ffn, HybridGraphFFN) + + +# ── helpers ────────────────────────────────────────────────── + + +def _copy_hybrid_to_plain(hg, plain): + """HybridGraphLinear 의 block weight → nn.Linear 표준 weight 형식으로 복사. + + Phase 12 test_function_preservation_full_full_equivalent_to_linear 와 동일 로직. + """ + in_f, out_f = hg.in_features, hg.out_features + k = hg.group_size + W_std = torch.zeros(out_f, in_f) + for go in range(hg.n_groups_out): + for gi in range(hg.n_groups_in): + W_std[go * k : (go + 1) * k, gi * k : (gi + 1) * k] = hg.weight[go, gi].T + with torch.no_grad(): + plain.weight.copy_(W_std) + if plain.bias is not None and hg.bias is not None: + plain.bias.copy_(hg.bias) From 9c4629aebe365d2c422e8f9b9d85b3275b690589 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:30:14 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[FEAT]:=20neuron=20Phase=2013=20(3/3)=20?= =?UTF-8?q?=E2=80=94=20HybridGraphTransformerLM=20demo=20+=20train=20helpe?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/graphlm/neuron/hybrid_transformer_demo.py 신규 - HybridGraphTransformerLM: token+pos emb → N blocks → RMSNorm → lm_head - train_hybrid_transformer_lm: 1 run unit (4 arch dispatch) - count_parameters: arch 간 파라미터 수 reporting - _snapshot_adj: hybrid arch 의 block 별 FFN fc1/fc2 outer/inner snapshot (Phase 12 hierarchy 호환) - ruff format / unused import 정리 - 120 tests all green --- src/graphlm/neuron/hybrid_transformer.py | 1 - src/graphlm/neuron/hybrid_transformer_demo.py | 183 ++++++++++++++++++ tests/neuron/test_hybrid_transformer.py | 11 +- 3 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 src/graphlm/neuron/hybrid_transformer_demo.py diff --git a/src/graphlm/neuron/hybrid_transformer.py b/src/graphlm/neuron/hybrid_transformer.py index b14b984..2aa2c85 100644 --- a/src/graphlm/neuron/hybrid_transformer.py +++ b/src/graphlm/neuron/hybrid_transformer.py @@ -19,7 +19,6 @@ from typing import Literal -import torch import torch.nn.functional as F from torch import Tensor, nn diff --git a/src/graphlm/neuron/hybrid_transformer_demo.py b/src/graphlm/neuron/hybrid_transformer_demo.py new file mode 100644 index 0000000..5e31aa4 --- /dev/null +++ b/src/graphlm/neuron/hybrid_transformer_demo.py @@ -0,0 +1,183 @@ +"""Phase 13 — HybridGraphTransformerLM demo + train helper. + +노트북 분리 규약 준수. Phase 13 노트북 12-phase13-hybrid-transformer.ipynb 에서 import. + +4 arch 비교 (모두 0-init 금지 + magnitude rule 적용): +- ``"plain"`` — RMSNorm + 표준 nn.Linear FFN (baseline) +- ``"hybrid_full_full"`` — outer=full + inner=full (function preserving) +- ``"hybrid_full_around_one"`` — outer=full + inner=uniform_around_one (Phase 11 channel-level) +- ``"hybrid_around_one_around_one"`` — 둘 다 uniform_around_one (fully scale-corrected) + +identity outer 는 Phase 13 에서 미지원 — FFN 의 rectangular 구조상 의미 없음. +""" + +from __future__ import annotations + +from collections.abc import Iterator + +import torch +import torch.nn.functional as F +from torch import Tensor, nn + +from graphlm.data.tinyshakespeare import TinyShakespeareDataset, iter_random_batches +from graphlm.neuron.hybrid_transformer import ( + Arch, + HybridGraphTransformerBlock, + make_block, +) +from graphlm.neuron.rms_norm import RMSNorm +from graphlm.utils import set_seed + + +class HybridGraphTransformerLM(nn.Module): + """Small char-LM Transformer with arch-dispatched FFN. + + - token embedding + learned positional embedding + - N × pre-norm block (RMSNorm + attn + RMSNorm + FFN) + - final RMSNorm + LM head (weight not tied for 공정 비교) + """ + + def __init__( + self, + vocab_size: int, + hidden_dim: int, + n_heads: int, + ffn_dim: int, + n_layers: int, + max_seq_len: int, + arch: Arch, + group_size: int, + dropout: float = 0.0, + ): + super().__init__() + self.arch = arch + self.max_seq_len = max_seq_len + self.tok_emb = nn.Embedding(vocab_size, hidden_dim) + self.pos_emb = nn.Embedding(max_seq_len, hidden_dim) + self.blocks = nn.ModuleList( + [ + make_block( + arch, + hidden_dim=hidden_dim, + n_heads=n_heads, + ffn_dim=ffn_dim, + group_size=group_size, + dropout=dropout, + ) + for _ in range(n_layers) + ] + ) + self.final_norm = RMSNorm(hidden_dim) + self.lm_head = nn.Linear(hidden_dim, vocab_size, bias=False) + + def forward(self, x: Tensor) -> Tensor: + _batch, seq_len = x.shape + if seq_len > self.max_seq_len: + raise ValueError(f"seq_len {seq_len} > max_seq_len {self.max_seq_len}") + pos = torch.arange(seq_len, device=x.device) + h = self.tok_emb(x) + self.pos_emb(pos) + for block in self.blocks: + h = block(h) + h = self.final_norm(h) + return self.lm_head(h) + + +def _block_iter(model: HybridGraphTransformerLM) -> Iterator[HybridGraphTransformerBlock]: + """모델의 hybrid block 만 yield (plain 은 skip).""" + for blk in model.blocks: + if isinstance(blk, HybridGraphTransformerBlock): + yield blk + + +def _snapshot_adj(model: HybridGraphTransformerLM) -> list[dict[str, dict[str, Tensor]]] | None: + """hybrid arch 인 경우 각 block 의 FFN adj snapshot (Phase 12 demo 와 동일 hierarchy).""" + if model.arch == "plain": + return None + snapshots: list[dict[str, dict[str, Tensor]]] = [] + for blk in _block_iter(model): + snapshots.append( + { + "fc1": { + "outer": blk.ffn.fc1.adj_outer.detach().cpu().clone(), + "inner": blk.ffn.fc1.adj_inner.detach().cpu().clone(), + }, + "fc2": { + "outer": blk.ffn.fc2.adj_outer.detach().cpu().clone(), + "inner": blk.ffn.fc2.adj_inner.detach().cpu().clone(), + }, + } + ) + return snapshots + + +def train_hybrid_transformer_lm( + *, + dataset: TinyShakespeareDataset, + vocab_size: int, + seed: int, + arch: Arch, + hidden_dim: int, + n_heads: int, + ffn_dim: int, + n_layers: int, + group_size: int, + block_size: int, + batch_size: int, + lr: float, + max_steps: int, + device: str = "cpu", + dropout: float = 0.0, +) -> dict: + """1 run 학습 — Phase 13 sweep unit. + + Returns: ``losses``, ``final_loss`` (last 100 mean), ``final_adj`` + (hybrid arch 인 경우 block 별 fc1/fc2 outer/inner snapshot list). + """ + set_seed(seed) + model = HybridGraphTransformerLM( + vocab_size=vocab_size, + hidden_dim=hidden_dim, + n_heads=n_heads, + ffn_dim=ffn_dim, + n_layers=n_layers, + max_seq_len=block_size, + arch=arch, + group_size=group_size, + dropout=dropout, + ).to(device) + data_iter = iter_random_batches( + dataset, batch_size=batch_size, block_size=block_size, seed=seed + ) + optimizer = torch.optim.AdamW(model.parameters(), lr=lr) + losses: list[float] = [] + model.train() + for _step in range(1, max_steps + 1): + x, y = next(data_iter) + x, y = x.to(device), y.to(device) + optimizer.zero_grad() + logits = model(x) + loss = F.cross_entropy(logits.reshape(-1, vocab_size), y.reshape(-1)) + loss.backward() + optimizer.step() + losses.append(loss.item()) + + n_last = min(100, len(losses)) + final_loss = sum(losses[-n_last:]) / n_last if n_last > 0 else 0.0 + + return { + "losses": losses, + "final_loss": final_loss, + "final_adj": _snapshot_adj(model), + } + + +def count_parameters(model: nn.Module) -> int: + """전체 학습 가능 파라미터 수 (arch 간 공정 비교용 reporting).""" + return sum(p.numel() for p in model.parameters() if p.requires_grad) + + +__all__ = [ + "HybridGraphTransformerLM", + "count_parameters", + "train_hybrid_transformer_lm", +] diff --git a/tests/neuron/test_hybrid_transformer.py b/tests/neuron/test_hybrid_transformer.py index 4b6c110..91d683a 100644 --- a/tests/neuron/test_hybrid_transformer.py +++ b/tests/neuron/test_hybrid_transformer.py @@ -14,7 +14,6 @@ make_block, ) - # ── HybridGraphFFN ─────────────────────────────────────────── @@ -51,7 +50,11 @@ def test_ffn_function_preservation_full_full(): def test_ffn_all_params_have_gradient(): ffn = HybridGraphFFN( - 16, 32, group_size=4, adj_outer_init="uniform_around_one", adj_inner_init="uniform_around_one" + 16, + 32, + group_size=4, + adj_outer_init="uniform_around_one", + adj_inner_init="uniform_around_one", ) x = torch.randn(2, 16) ffn(x).sum().backward() @@ -66,9 +69,7 @@ def test_ffn_all_params_have_gradient(): def test_block_shape(): - block = HybridGraphTransformerBlock( - hidden_dim=32, n_heads=4, ffn_dim=64, group_size=8 - ) + block = HybridGraphTransformerBlock(hidden_dim=32, n_heads=4, ffn_dim=64, group_size=8) x = torch.randn(2, 16, 32) assert block(x).shape == (2, 16, 32) From 7c449a84129b356c1770d6698a854f07c5629030 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:32:03 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[FEAT]:=20Phase=2013=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=EB=B6=81=20=E2=80=94=204=20arch=20sweep=20(plain=20vs=20hybrid?= =?UTF-8?q?=5Ffull=5Ffull=20/=20full=5Faround=5Fone=20/=20around=5Fone=5Fa?= =?UTF-8?q?round=5Fone)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb 신규 - 4 arch × 2 seed = 8 run, max_steps=1500, TinyShakespeare char-LM - model: hidden=128, ffn=256, n_layers=4, n_heads=4, block_size=64 - 자동 verdict 4 가지: function preservation / scale-corrected ≤ plain / full around_one 안정성 / RMSNorm NaN 없음 - loss curve (mean ± σ rolling) + hybrid adj heatmap (outer / inner) - output figure → runs/notebook-neuron-phase13/{loss_curves,hybrid_adj}.png --- .../12-phase13-hybrid-transformer.ipynb | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb diff --git a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb new file mode 100644 index 0000000..d1d5adf --- /dev/null +++ b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb @@ -0,0 +1,370 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# 12-phase13-hybrid-transformer\n", + "\n", + "**neuron Phase 13** — HybridGraphLinear 를 **실제 Transformer 의 FFN 위치** 에 통합 + **RMSNorm** 도입.\n", + "Phase 8~12 는 모두 MLP-LM baseline 이었고, Phase 13 부터 standard pre-norm Transformer block 위에서 graph hidden layer 의 paradigm 이 동작하는지 검증.\n", + "\n", + "핵심 가설:\n", + "1. **function preservation (Transformer 위)** — `hybrid_full_full` ≈ `plain` (RMSNorm + 표준 FFN)?\n", + "2. **dual routing 우위 재현** — Phase 12 에서 hybrid_full_around_one 이 최저 final_loss 였음. Transformer 에서도 유지?\n", + "3. **full scale-corrected (around_one × around_one)** — outer/inner 둘 다 학습 활성화 init 의 효과?\n", + "4. **RMSNorm 안정성** — LayerNorm 대비 학습 동등 또는 우위 (모든 arch 에서 NaN 없음)?\n", + "\n", + "설계: 4 arch × 2 seed = 8 run, max_steps=1500.\n", + "\n", + "데이터: TinyShakespeare (char-LM, block_size=64)\n", + "시드: [42, 123]\n", + "작성일: 2026-05-26\n", + "연관: Issue [#67](https://github.com/EinSofINTEREST/GraphLM/issues/67) / Phase 12 baseline PR [#66](https://github.com/EinSofINTEREST/GraphLM/pull/66)\n", + "\n", + "Phase 13 의 ``identity`` outer 미지원 → 4 arch 는 plain / hybrid_full_full / hybrid_full_around_one / hybrid_around_one_around_one." + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "## 0. 환경 / 의존성" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "\n", + "import math\n", + "import statistics\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import torch\n", + "\n", + "from graphlm.data.tinyshakespeare import (\n", + " CharTokenizer,\n", + " TinyShakespeareDataset,\n", + " load_tinyshakespeare_text,\n", + ")\n", + "from graphlm.neuron.hybrid_transformer_demo import (\n", + " HybridGraphTransformerLM,\n", + " count_parameters,\n", + " train_hybrid_transformer_lm,\n", + ")\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"device: {device}\")\n", + "print(f\"torch: {torch.__version__}\")" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "## 1. Config + 데이터" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "# data\n", + "text = load_tinyshakespeare_text()\n", + "tokenizer = CharTokenizer(text)\n", + "dataset = TinyShakespeareDataset(text, tokenizer)\n", + "vocab_size = tokenizer.vocab_size\n", + "print(f\"vocab_size = {vocab_size}, dataset size = {len(dataset)}\")\n", + "\n", + "# model + train hyperparameters\n", + "HIDDEN_DIM = 128\n", + "N_HEADS = 4\n", + "FFN_DIM = 256 # 2x hidden (작게 — sweep 속도 위해)\n", + "N_LAYERS = 4\n", + "GROUP_SIZE = 16 # hidden_dim/group_size = 8, ffn_dim/group_size = 16\n", + "BLOCK_SIZE = 64\n", + "BATCH_SIZE = 32\n", + "LR = 3e-4\n", + "MAX_STEPS = 1500\n", + "SEEDS = [42, 123]\n", + "ARCHS = [\n", + " \"plain\",\n", + " \"hybrid_full_full\",\n", + " \"hybrid_full_around_one\",\n", + " \"hybrid_around_one_around_one\",\n", + "]\n", + "\n", + "# 모델 파라미터 수 비교 (arch 별)\n", + "print(\"\\n== Parameter count by arch ==\")\n", + "for arch in ARCHS:\n", + " m = HybridGraphTransformerLM(\n", + " vocab_size=vocab_size,\n", + " hidden_dim=HIDDEN_DIM,\n", + " n_heads=N_HEADS,\n", + " ffn_dim=FFN_DIM,\n", + " n_layers=N_LAYERS,\n", + " max_seq_len=BLOCK_SIZE,\n", + " arch=arch,\n", + " group_size=GROUP_SIZE,\n", + " )\n", + " print(f\" {arch:32s} params = {count_parameters(m):,}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5", + "metadata": {}, + "source": [ + "## 2. Sweep 실행 (4 arch × 2 seed = 8 run)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], + "source": [ + "results = {}\n", + "for arch in ARCHS:\n", + " for seed in SEEDS:\n", + " key = (arch, seed)\n", + " print(f\"\\n== arch={arch} seed={seed} ==\")\n", + " out = train_hybrid_transformer_lm(\n", + " dataset=dataset,\n", + " vocab_size=vocab_size,\n", + " seed=seed,\n", + " arch=arch,\n", + " hidden_dim=HIDDEN_DIM,\n", + " n_heads=N_HEADS,\n", + " ffn_dim=FFN_DIM,\n", + " n_layers=N_LAYERS,\n", + " group_size=GROUP_SIZE,\n", + " block_size=BLOCK_SIZE,\n", + " batch_size=BATCH_SIZE,\n", + " lr=LR,\n", + " max_steps=MAX_STEPS,\n", + " device=device,\n", + " )\n", + " results[key] = out\n", + " print(f\" final_loss = {out['final_loss']:.4f} (perplexity = {math.exp(out['final_loss']):.2f})\")" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "## 3. 결과 표 + 자동 verdict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"{'arch':32s} {'seed':>6s} {'final_loss':>12s} {'perplexity':>12s}\")\n", + "print(\"-\" * 70)\n", + "for (arch, seed), out in results.items():\n", + " fl = out[\"final_loss\"]\n", + " print(f\"{arch:32s} {seed:>6d} {fl:>12.4f} {math.exp(fl):>12.2f}\")\n", + "\n", + "# arch 별 평균 / 표준편차\n", + "print(\"\\n== Arch-level summary (mean ± σ across seeds) ==\")\n", + "summary = {}\n", + "for arch in ARCHS:\n", + " vals = [results[(arch, s)][\"final_loss\"] for s in SEEDS]\n", + " summary[arch] = (statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0)\n", + " m, s = summary[arch]\n", + " print(f\" {arch:32s} {m:.4f} ± {s:.4f} (perplexity ≈ {math.exp(m):.2f})\")\n", + "\n", + "# 자동 verdict\n", + "print(\"\\n== Verdict ==\")\n", + "plain_loss = summary[\"plain\"][0]\n", + "ff_loss = summary[\"hybrid_full_full\"][0]\n", + "fa_loss = summary[\"hybrid_full_around_one\"][0]\n", + "aa_loss = summary[\"hybrid_around_one_around_one\"][0]\n", + "\n", + "# 1. function preservation (학습 후 hybrid_full_full ≈ plain — 학습 dynamics 가 동일 sweet spot 유지)\n", + "diff_ff = abs(ff_loss - plain_loss)\n", + "verdict_1 = \"PASS\" if diff_ff < 0.15 else \"FAIL\"\n", + "print(f\"1. function preservation: |hybrid_full_full - plain| = {diff_ff:.4f} [{verdict_1}]\")\n", + "\n", + "# 2. scale-corrected 우위 (hybrid_full_around_one 이 plain 보다 우수 또는 동등)\n", + "verdict_2 = \"PASS\" if fa_loss <= plain_loss + 0.05 else \"FAIL\"\n", + "diff_fa = fa_loss - plain_loss\n", + "print(f\"2. inner around_one ≤ plain + 0.05: diff = {diff_fa:+.4f} [{verdict_2}]\")\n", + "\n", + "# 3. full scale-corrected (around_one × around_one) 안정성 (NaN 없음 + plain 근방)\n", + "verdict_3 = \"PASS\" if (not math.isnan(aa_loss) and aa_loss < plain_loss + 0.5) else \"FAIL\"\n", + "diff_aa = aa_loss - plain_loss\n", + "print(f\"3. full around_one stable: diff = {diff_aa:+.4f} [{verdict_3}]\")\n", + "\n", + "# 4. 모두 NaN 없이 학습 종료 (RMSNorm 안정성)\n", + "all_finite = all(not math.isnan(out[\"final_loss\"]) for out in results.values())\n", + "verdict_4 = \"PASS\" if all_finite else \"FAIL\"\n", + "print(f\"4. RMSNorm stability (all finite): {all_finite} [{verdict_4}]\")" + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "## 4. Loss curve 시각화" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(1, 1, figsize=(10, 5))\n", + "colors = {\n", + " \"plain\": \"tab:gray\",\n", + " \"hybrid_full_full\": \"tab:blue\",\n", + " \"hybrid_full_around_one\": \"tab:orange\",\n", + " \"hybrid_around_one_around_one\": \"tab:green\",\n", + "}\n", + "window = 50 # rolling mean for smoothing\n", + "\n", + "for arch in ARCHS:\n", + " losses_per_seed = [results[(arch, s)][\"losses\"] for s in SEEDS]\n", + " # rolling mean\n", + " smoothed = []\n", + " for losses in losses_per_seed:\n", + " smoothed.append(\n", + " [\n", + " sum(losses[max(0, i - window) : i + 1]) / min(i + 1, window)\n", + " for i in range(len(losses))\n", + " ]\n", + " )\n", + " # mean ± σ across seeds\n", + " arr = torch.tensor(smoothed)\n", + " mean = arr.mean(dim=0)\n", + " std = arr.std(dim=0)\n", + " steps = list(range(len(mean)))\n", + " color = colors[arch]\n", + " ax.plot(steps, mean, label=arch, color=color, linewidth=1.5)\n", + " ax.fill_between(steps, mean - std, mean + std, color=color, alpha=0.15)\n", + "\n", + "ax.set_xlabel(\"step\")\n", + "ax.set_ylabel(f\"loss (rolling mean w={window})\")\n", + "ax.set_title(\"Phase 13 — Transformer FFN: 4 arch loss curves (mean ± σ over 2 seeds)\")\n", + "ax.legend(loc=\"upper right\")\n", + "ax.grid(alpha=0.3)\n", + "plt.tight_layout()\n", + "\n", + "out_dir = Path(\"../../runs/notebook-neuron-phase13\")\n", + "out_dir.mkdir(parents=True, exist_ok=True)\n", + "fig.savefig(out_dir / \"loss_curves.png\", dpi=150, bbox_inches=\"tight\")\n", + "plt.show()\n", + "print(f\"saved: {out_dir / 'loss_curves.png'}\")" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "## 5. adj_outer / adj_inner heatmap (hybrid arch 만)\n", + "\n", + "각 hybrid arch 의 첫 block 의 fc1 adj_outer 와 adj_inner (block-aggregated) 학습 후 모습." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "hybrid_archs = [a for a in ARCHS if a != \"plain\"]\n", + "n_arch = len(hybrid_archs)\n", + "fig, axes = plt.subplots(n_arch, 2, figsize=(10, 3 * n_arch))\n", + "if n_arch == 1:\n", + " axes = axes.reshape(1, -1)\n", + "\n", + "for row, arch in enumerate(hybrid_archs):\n", + " # seed 42 의 첫 block 의 fc1\n", + " snap = results[(arch, 42)][\"final_adj\"][0][\"fc1\"]\n", + " outer = snap[\"outer\"].numpy() # (G_out, G_in)\n", + " inner = snap[\"inner\"].abs().mean(dim=(-1, -2)).numpy() # (G_out, G_in) — block-aggregated magnitude\n", + "\n", + " ax_o = axes[row, 0]\n", + " im_o = ax_o.imshow(outer, cmap=\"RdBu_r\", vmin=-2, vmax=2)\n", + " ax_o.set_title(f\"{arch} — adj_outer (block 0, fc1)\")\n", + " ax_o.set_xlabel(\"G_in\")\n", + " ax_o.set_ylabel(\"G_out\")\n", + " plt.colorbar(im_o, ax=ax_o, fraction=0.046)\n", + "\n", + " ax_i = axes[row, 1]\n", + " im_i = ax_i.imshow(inner, cmap=\"viridis\")\n", + " ax_i.set_title(f\"{arch} — |adj_inner| block-mean (block 0, fc1)\")\n", + " ax_i.set_xlabel(\"G_in\")\n", + " ax_i.set_ylabel(\"G_out\")\n", + " plt.colorbar(im_i, ax=ax_i, fraction=0.046)\n", + "\n", + "plt.tight_layout()\n", + "fig.savefig(out_dir / \"hybrid_adj.png\", dpi=150, bbox_inches=\"tight\")\n", + "plt.show()\n", + "print(f\"saved: {out_dir / 'hybrid_adj.png'}\")" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "## 6. 결론 / 다음 단계\n", + "\n", + "(셀 출력 보고 사용자가 채울 영역)\n", + "\n", + "- function preservation: hybrid_full_full vs plain — Transformer 위에서도 동등?\n", + "- dual routing 우위: Phase 12 패턴 재현?\n", + "- adj 학습 패턴: outer / inner 가 다른 영역을 cover?\n", + "\n", + "**Phase 14 후보**:\n", + "- attention 의 qkv / out 을 HybridGraphLinear 로 교체 — function-level graph 가 attention 까지 확장\n", + "- Net2Net / LiGO 식 growable transformer block — 학습 중 hidden_dim 또는 n_layers 증가" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "GraphLM (uv .venv)", + "language": "python", + "name": "graphlm-uv-venv" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From c04a86f970945334f4b0eb70729f27676a68fd15 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:32:38 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[CHORE]:=20Phase=2013=20=EB=85=B8?= =?UTF-8?q?=ED=8A=B8=EB=B6=81=20ruff=20format=20=EC=A0=81=EC=9A=A9=20+=20n?= =?UTF-8?q?b-clean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../02-function-level/12-phase13-hybrid-transformer.ipynb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb index d1d5adf..e70f9b0 100644 --- a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb +++ b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb @@ -159,7 +159,9 @@ " device=device,\n", " )\n", " results[key] = out\n", - " print(f\" final_loss = {out['final_loss']:.4f} (perplexity = {math.exp(out['final_loss']):.2f})\")" + " print(\n", + " f\" final_loss = {out['final_loss']:.4f} (perplexity = {math.exp(out['final_loss']):.2f})\"\n", + " )" ] }, { @@ -305,7 +307,9 @@ " # seed 42 의 첫 block 의 fc1\n", " snap = results[(arch, 42)][\"final_adj\"][0][\"fc1\"]\n", " outer = snap[\"outer\"].numpy() # (G_out, G_in)\n", - " inner = snap[\"inner\"].abs().mean(dim=(-1, -2)).numpy() # (G_out, G_in) — block-aggregated magnitude\n", + " inner = (\n", + " snap[\"inner\"].abs().mean(dim=(-1, -2)).numpy()\n", + " ) # (G_out, G_in) — block-aggregated magnitude\n", "\n", " ax_o = axes[row, 0]\n", " im_o = ax_o.imshow(outer, cmap=\"RdBu_r\", vmin=-2, vmax=2)\n", From 2db4666f61c41f476acf381aa4816ccb918b9c04 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:38:50 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[FIX]:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81,=20RMSNorm=20dtype=20mismatch=20+=20?= =?UTF-8?q?=EB=85=B8=ED=8A=B8=EB=B6=81=20perplexity=20overflow=202?= =?UTF-8?q?=EA=B1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/graphlm/neuron/rms_norm.py — RMSNorm.forward 에서 self.weight 를 x_dtype 으로 cast (gemini #3303153077): mixed precision (FP16/BF16) 입력 시 weight 가 float32 로 남아 있어 출력 dtype 이 강제로 float32 promotion 되던 문제 — residual connection dtype mismatch 회피 - notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb — safe_perplexity 헬퍼 도입 (gemini #3303153101): 학습 초반 큰 loss 에서 math.exp OverflowError 회피 (cap=20.0) --- .../12-phase13-hybrid-transformer.ipynb | 100 +----------------- src/graphlm/neuron/rms_norm.py | 4 +- 2 files changed, 7 insertions(+), 97 deletions(-) diff --git a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb index e70f9b0..4854ccc 100644 --- a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb +++ b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb @@ -40,31 +40,7 @@ "id": "2", "metadata": {}, "outputs": [], - "source": [ - "from __future__ import annotations\n", - "\n", - "import math\n", - "import statistics\n", - "from pathlib import Path\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import torch\n", - "\n", - "from graphlm.data.tinyshakespeare import (\n", - " CharTokenizer,\n", - " TinyShakespeareDataset,\n", - " load_tinyshakespeare_text,\n", - ")\n", - "from graphlm.neuron.hybrid_transformer_demo import (\n", - " HybridGraphTransformerLM,\n", - " count_parameters,\n", - " train_hybrid_transformer_lm,\n", - ")\n", - "\n", - "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", - "print(f\"device: {device}\")\n", - "print(f\"torch: {torch.__version__}\")" - ] + "source": "from __future__ import annotations\n\nimport math\nimport statistics\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport torch\n\nfrom graphlm.data.tinyshakespeare import (\n CharTokenizer,\n TinyShakespeareDataset,\n load_tinyshakespeare_text,\n)\nfrom graphlm.neuron.hybrid_transformer_demo import (\n HybridGraphTransformerLM,\n count_parameters,\n train_hybrid_transformer_lm,\n)\n\n\ndef safe_perplexity(loss: float, cap: float = 20.0) -> float:\n \"\"\"exp(loss) overflow 방지 (학습 초반 큰 loss 또는 발산 시).\n\n rationale: gemini #3303153101 — `math.exp(loss)` 는 loss 큰 시점에 OverflowError.\n cap=20 → max perplexity ≈ 4.85e8 (충분히 큰 ceiling).\n \"\"\"\n return math.exp(min(loss, cap))\n\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"device: {device}\")\nprint(f\"torch: {torch.__version__}\")" }, { "cell_type": "markdown", @@ -136,33 +112,7 @@ "id": "6", "metadata": {}, "outputs": [], - "source": [ - "results = {}\n", - "for arch in ARCHS:\n", - " for seed in SEEDS:\n", - " key = (arch, seed)\n", - " print(f\"\\n== arch={arch} seed={seed} ==\")\n", - " out = train_hybrid_transformer_lm(\n", - " dataset=dataset,\n", - " vocab_size=vocab_size,\n", - " seed=seed,\n", - " arch=arch,\n", - " hidden_dim=HIDDEN_DIM,\n", - " n_heads=N_HEADS,\n", - " ffn_dim=FFN_DIM,\n", - " n_layers=N_LAYERS,\n", - " group_size=GROUP_SIZE,\n", - " block_size=BLOCK_SIZE,\n", - " batch_size=BATCH_SIZE,\n", - " lr=LR,\n", - " max_steps=MAX_STEPS,\n", - " device=device,\n", - " )\n", - " results[key] = out\n", - " print(\n", - " f\" final_loss = {out['final_loss']:.4f} (perplexity = {math.exp(out['final_loss']):.2f})\"\n", - " )" - ] + "source": "results = {}\nfor arch in ARCHS:\n for seed in SEEDS:\n key = (arch, seed)\n print(f\"\\n== arch={arch} seed={seed} ==\")\n out = train_hybrid_transformer_lm(\n dataset=dataset,\n vocab_size=vocab_size,\n seed=seed,\n arch=arch,\n hidden_dim=HIDDEN_DIM,\n n_heads=N_HEADS,\n ffn_dim=FFN_DIM,\n n_layers=N_LAYERS,\n group_size=GROUP_SIZE,\n block_size=BLOCK_SIZE,\n batch_size=BATCH_SIZE,\n lr=LR,\n max_steps=MAX_STEPS,\n device=device,\n )\n results[key] = out\n print(\n f\" final_loss = {out['final_loss']:.4f} (perplexity = {safe_perplexity(out['final_loss']):.2f})\"\n )" }, { "cell_type": "markdown", @@ -178,49 +128,7 @@ "id": "8", "metadata": {}, "outputs": [], - "source": [ - "print(f\"{'arch':32s} {'seed':>6s} {'final_loss':>12s} {'perplexity':>12s}\")\n", - "print(\"-\" * 70)\n", - "for (arch, seed), out in results.items():\n", - " fl = out[\"final_loss\"]\n", - " print(f\"{arch:32s} {seed:>6d} {fl:>12.4f} {math.exp(fl):>12.2f}\")\n", - "\n", - "# arch 별 평균 / 표준편차\n", - "print(\"\\n== Arch-level summary (mean ± σ across seeds) ==\")\n", - "summary = {}\n", - "for arch in ARCHS:\n", - " vals = [results[(arch, s)][\"final_loss\"] for s in SEEDS]\n", - " summary[arch] = (statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0)\n", - " m, s = summary[arch]\n", - " print(f\" {arch:32s} {m:.4f} ± {s:.4f} (perplexity ≈ {math.exp(m):.2f})\")\n", - "\n", - "# 자동 verdict\n", - "print(\"\\n== Verdict ==\")\n", - "plain_loss = summary[\"plain\"][0]\n", - "ff_loss = summary[\"hybrid_full_full\"][0]\n", - "fa_loss = summary[\"hybrid_full_around_one\"][0]\n", - "aa_loss = summary[\"hybrid_around_one_around_one\"][0]\n", - "\n", - "# 1. function preservation (학습 후 hybrid_full_full ≈ plain — 학습 dynamics 가 동일 sweet spot 유지)\n", - "diff_ff = abs(ff_loss - plain_loss)\n", - "verdict_1 = \"PASS\" if diff_ff < 0.15 else \"FAIL\"\n", - "print(f\"1. function preservation: |hybrid_full_full - plain| = {diff_ff:.4f} [{verdict_1}]\")\n", - "\n", - "# 2. scale-corrected 우위 (hybrid_full_around_one 이 plain 보다 우수 또는 동등)\n", - "verdict_2 = \"PASS\" if fa_loss <= plain_loss + 0.05 else \"FAIL\"\n", - "diff_fa = fa_loss - plain_loss\n", - "print(f\"2. inner around_one ≤ plain + 0.05: diff = {diff_fa:+.4f} [{verdict_2}]\")\n", - "\n", - "# 3. full scale-corrected (around_one × around_one) 안정성 (NaN 없음 + plain 근방)\n", - "verdict_3 = \"PASS\" if (not math.isnan(aa_loss) and aa_loss < plain_loss + 0.5) else \"FAIL\"\n", - "diff_aa = aa_loss - plain_loss\n", - "print(f\"3. full around_one stable: diff = {diff_aa:+.4f} [{verdict_3}]\")\n", - "\n", - "# 4. 모두 NaN 없이 학습 종료 (RMSNorm 안정성)\n", - "all_finite = all(not math.isnan(out[\"final_loss\"]) for out in results.values())\n", - "verdict_4 = \"PASS\" if all_finite else \"FAIL\"\n", - "print(f\"4. RMSNorm stability (all finite): {all_finite} [{verdict_4}]\")" - ] + "source": "print(f\"{'arch':32s} {'seed':>6s} {'final_loss':>12s} {'perplexity':>12s}\")\nprint(\"-\" * 70)\nfor (arch, seed), out in results.items():\n fl = out[\"final_loss\"]\n print(f\"{arch:32s} {seed:>6d} {fl:>12.4f} {safe_perplexity(fl):>12.2f}\")\n\n# arch 별 평균 / 표준편차\nprint(\"\\n== Arch-level summary (mean ± σ across seeds) ==\")\nsummary = {}\nfor arch in ARCHS:\n vals = [results[(arch, s)][\"final_loss\"] for s in SEEDS]\n summary[arch] = (statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0)\n m, s = summary[arch]\n print(f\" {arch:32s} {m:.4f} ± {s:.4f} (perplexity ≈ {safe_perplexity(m):.2f})\")\n\n# 자동 verdict\nprint(\"\\n== Verdict ==\")\nplain_loss = summary[\"plain\"][0]\nff_loss = summary[\"hybrid_full_full\"][0]\nfa_loss = summary[\"hybrid_full_around_one\"][0]\naa_loss = summary[\"hybrid_around_one_around_one\"][0]\n\n# 1. function preservation (학습 후 hybrid_full_full ≈ plain — 학습 dynamics 가 동일 sweet spot 유지)\ndiff_ff = abs(ff_loss - plain_loss)\nverdict_1 = \"PASS\" if diff_ff < 0.15 else \"FAIL\"\nprint(f\"1. function preservation: |hybrid_full_full - plain| = {diff_ff:.4f} [{verdict_1}]\")\n\n# 2. scale-corrected 우위 (hybrid_full_around_one 이 plain 보다 우수 또는 동등)\nverdict_2 = \"PASS\" if fa_loss <= plain_loss + 0.05 else \"FAIL\"\ndiff_fa = fa_loss - plain_loss\nprint(f\"2. inner around_one ≤ plain + 0.05: diff = {diff_fa:+.4f} [{verdict_2}]\")\n\n# 3. full scale-corrected (around_one × around_one) 안정성 (NaN 없음 + plain 근방)\nverdict_3 = \"PASS\" if (not math.isnan(aa_loss) and aa_loss < plain_loss + 0.5) else \"FAIL\"\ndiff_aa = aa_loss - plain_loss\nprint(f\"3. full around_one stable: diff = {diff_aa:+.4f} [{verdict_3}]\")\n\n# 4. 모두 NaN 없이 학습 종료 (RMSNorm 안정성)\nall_finite = all(not math.isnan(out[\"final_loss\"]) for out in results.values())\nverdict_4 = \"PASS\" if all_finite else \"FAIL\"\nprint(f\"4. RMSNorm stability (all finite): {all_finite} [{verdict_4}]\")" }, { "cell_type": "markdown", @@ -371,4 +279,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/src/graphlm/neuron/rms_norm.py b/src/graphlm/neuron/rms_norm.py index 4557556..1dc5854 100644 --- a/src/graphlm/neuron/rms_norm.py +++ b/src/graphlm/neuron/rms_norm.py @@ -49,7 +49,9 @@ def forward(self, x: Tensor) -> Tensor: x_f = x.float() rms = torch.rsqrt(x_f.pow(2).mean(dim=-1, keepdim=True) + self.eps) y = (x_f * rms).to(x_dtype) - return y * self.weight + # weight 도 x_dtype 로 cast — 그래야 mixed precision (FP16/BF16) 에서 residual + # connection dtype mismatch 회피 (gemini #3303153077) + return y * self.weight.to(x_dtype) def extra_repr(self) -> str: return f"hidden_dim={self.hidden_dim}, eps={self.eps}" From 7ef71a658c984b275855d36d987213f2a785c68f Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:42:15 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[FIX]:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81,=20RMSNorm=20=EC=9E=85=EB=A0=A5=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20+=20FFN=20dropout=20=EC=9D=BC=EA=B4=80=EC=84=B1=203?= =?UTF-8?q?=EA=B1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/graphlm/neuron/rms_norm.py (Copilot #3303168589): floating-point 입력 검증 - int 등 비-floating 입력 silent cast 회피 — TypeError 명시 (LayerNorm 정책과 동일) - src/graphlm/neuron/hybrid_transformer.py (Copilot #3303168649 / #3303168686): - HybridGraphFFN / PlainFFN 둘 다 dropout 인자 추가 — backbone.FFN 의 fc2-뒤-dropout 패턴 일관 - HybridGraphTransformerBlock / PlainTransformerBlock 의 dropout 인자가 attention 만이 아니라 FFN 까지 전달 - 7 신규 unit tests — non-floating 거부 / dtype roundtrip / FFN dropout / block dropout 전파 - 152 tests all green --- src/graphlm/neuron/hybrid_transformer.py | 25 +++++++++---- src/graphlm/neuron/rms_norm.py | 4 ++ tests/neuron/test_hybrid_transformer.py | 47 ++++++++++++++++++++++++ tests/neuron/test_rms_norm.py | 17 +++++++++ 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/graphlm/neuron/hybrid_transformer.py b/src/graphlm/neuron/hybrid_transformer.py index 2aa2c85..4e77b4a 100644 --- a/src/graphlm/neuron/hybrid_transformer.py +++ b/src/graphlm/neuron/hybrid_transformer.py @@ -37,9 +37,10 @@ class HybridGraphFFN(nn.Module): adj_outer_init: outer adj 초기화 (``"full"`` / ``"uniform_around_one"``). ``"identity"`` 는 FFN 이 정의상 rectangular (hidden ≠ ffn) 이라 지원하지 않음. adj_inner_init: inner adj 초기화 (``"full"`` / ``"uniform_around_one"``). + dropout: fc2 출력에 적용되는 dropout 확률 (backbone.FFN 과 동일 위치). Forward: - ``y = fc2(GELU(fc1(x)))`` — function preserving when both adj = full. + ``y = dropout(fc2(GELU(fc1(x))))`` — function preserving when both adj = full and dropout = 0. """ def __init__( @@ -50,6 +51,7 @@ def __init__( *, adj_outer_init: AdjOuterInit = "full", adj_inner_init: AdjInnerInit = "full", + dropout: float = 0.0, ): super().__init__() if adj_outer_init == "identity": @@ -74,9 +76,10 @@ def __init__( adj_inner_init=adj_inner_init, bias=False, ) + self.dropout = nn.Dropout(dropout) def forward(self, x: Tensor) -> Tensor: - return self.fc2(F.gelu(self.fc1(x))) + return self.dropout(self.fc2(F.gelu(self.fc1(x)))) class HybridGraphTransformerBlock(nn.Module): @@ -102,12 +105,15 @@ def __init__( self.rms1 = RMSNorm(hidden_dim) self.attn = CausalSelfAttention(hidden_dim, n_heads, dropout=dropout) self.rms2 = RMSNorm(hidden_dim) + # dropout 은 attention 과 FFN 양쪽 모두 적용 — backbone.FFN 의 fc2-뒤-dropout 패턴과 일관 + # (Copilot #3303168649) self.ffn = HybridGraphFFN( hidden_dim, ffn_dim, group_size=group_size, adj_outer_init=adj_outer_init, adj_inner_init=adj_inner_init, + dropout=dropout, ) def forward(self, x: Tensor) -> Tensor: @@ -124,22 +130,26 @@ def forward(self, x: Tensor) -> Tensor: class PlainFFN(nn.Module): - """Standard 2-layer FFN (no bias) — Phase 13 ``"plain"`` baseline.""" + """Standard 2-layer FFN (no bias) — Phase 13 ``"plain"`` baseline. + + backbone.FFN 과 동일한 ``dropout(fc2(GELU(fc1(x))))`` 구조. + """ - def __init__(self, hidden_dim: int, ffn_dim: int): + def __init__(self, hidden_dim: int, ffn_dim: int, dropout: float = 0.0): super().__init__() self.fc1 = nn.Linear(hidden_dim, ffn_dim, bias=False) self.fc2 = nn.Linear(ffn_dim, hidden_dim, bias=False) + self.dropout = nn.Dropout(dropout) def forward(self, x: Tensor) -> Tensor: - return self.fc2(F.gelu(self.fc1(x))) + return self.dropout(self.fc2(F.gelu(self.fc1(x)))) class PlainTransformerBlock(nn.Module): """Pre-norm Transformer block with standard FFN — Phase 13 ``"plain"`` baseline. HybridGraphTransformerBlock 과 동일한 norm/attention/residual 구조 — FFN 만 표준 nn.Linear. - 공정 비교 위해 RMSNorm 동일 사용. + 공정 비교 위해 RMSNorm 동일 사용. dropout 은 attention + FFN 양쪽 모두 적용. """ def __init__(self, hidden_dim: int, n_heads: int, ffn_dim: int, dropout: float = 0.0): @@ -147,7 +157,8 @@ def __init__(self, hidden_dim: int, n_heads: int, ffn_dim: int, dropout: float = self.rms1 = RMSNorm(hidden_dim) self.attn = CausalSelfAttention(hidden_dim, n_heads, dropout=dropout) self.rms2 = RMSNorm(hidden_dim) - self.ffn = PlainFFN(hidden_dim, ffn_dim) + # Copilot #3303168686 — FFN dropout 도 적용해서 backbone.FFN 일관성 확보 + self.ffn = PlainFFN(hidden_dim, ffn_dim, dropout=dropout) def forward(self, x: Tensor) -> Tensor: x = x + self.attn(self.rms1(x)) diff --git a/src/graphlm/neuron/rms_norm.py b/src/graphlm/neuron/rms_norm.py index 1dc5854..a6d4184 100644 --- a/src/graphlm/neuron/rms_norm.py +++ b/src/graphlm/neuron/rms_norm.py @@ -44,6 +44,10 @@ def __init__(self, hidden_dim: int, eps: float = 1e-6): def forward(self, x: Tensor) -> Tensor: if x.shape[-1] != self.hidden_dim: raise ValueError(f"expected last dim {self.hidden_dim}, got {x.shape[-1]}") + # int 등 비-floating 입력은 silent cast 위험 — nn.LayerNorm 과 동일 정책으로 차단 + # (Copilot #3303168589) + if not x.is_floating_point(): + raise TypeError(f"RMSNorm requires floating-point input, got dtype={x.dtype}") # float32 로 cast 해서 RMS 계산 (mixed precision 안전성) x_dtype = x.dtype x_f = x.float() diff --git a/tests/neuron/test_hybrid_transformer.py b/tests/neuron/test_hybrid_transformer.py index 91d683a..f366a52 100644 --- a/tests/neuron/test_hybrid_transformer.py +++ b/tests/neuron/test_hybrid_transformer.py @@ -142,6 +142,53 @@ def test_make_block_hybrid_uses_hybrid_ffn(): assert isinstance(block.ffn, HybridGraphFFN) +# ── dropout consistency (Copilot #3303168649 / #3303168686) ── + + +def test_hybrid_ffn_applies_dropout_when_training(): + """HybridGraphFFN 도 fc2 뒤 dropout — backbone.FFN 패턴 일관.""" + ffn = HybridGraphFFN(16, 32, group_size=4, dropout=0.5) + ffn.train() + torch.manual_seed(0) + x = torch.randn(64, 16) + # dropout 활성 시 stochastic — eval 모드와 다른 output 보장 + y_train = ffn(x) + ffn.eval() + y_eval = ffn(x) + assert not torch.allclose(y_train, y_eval), "dropout 이 train 모드에서 적용되지 않음" + + +def test_plain_ffn_applies_dropout_when_training(): + """PlainFFN 도 dropout 인자 적용 — Hybrid 와 API 일관.""" + ffn = PlainFFN(16, 32, dropout=0.5) + ffn.train() + torch.manual_seed(0) + x = torch.randn(64, 16) + y_train = ffn(x) + ffn.eval() + y_eval = ffn(x) + assert not torch.allclose(y_train, y_eval), "dropout 이 train 모드에서 적용되지 않음" + + +@pytest.mark.parametrize( + "block_cls,kwargs", + [ + ( + PlainTransformerBlock, + {"hidden_dim": 16, "n_heads": 4, "ffn_dim": 32, "dropout": 0.5}, + ), + ( + HybridGraphTransformerBlock, + {"hidden_dim": 16, "n_heads": 4, "ffn_dim": 32, "group_size": 4, "dropout": 0.5}, + ), + ], +) +def test_block_dropout_propagates_to_ffn(block_cls, kwargs): + """block 의 dropout 인자가 attention 만이 아니라 FFN 까지 전달.""" + block = block_cls(**kwargs) + assert block.ffn.dropout.p == 0.5, "FFN 의 dropout p 가 block dropout 과 불일치" + + # ── helpers ────────────────────────────────────────────────── diff --git a/tests/neuron/test_rms_norm.py b/tests/neuron/test_rms_norm.py index a7c88d6..cf8c548 100644 --- a/tests/neuron/test_rms_norm.py +++ b/tests/neuron/test_rms_norm.py @@ -63,3 +63,20 @@ def test_scaling_via_weight(): y = norm(x) rms = y.pow(2).mean(dim=-1).sqrt() assert torch.allclose(rms, torch.full_like(rms, 2.0), atol=1e-3) + + +def test_non_floating_input_raises(): + """int 등 비-floating 입력은 silent cast 회피 위해 차단 (Copilot #3303168589).""" + norm = RMSNorm(16) + x_int = torch.zeros(4, 16, dtype=torch.long) + with pytest.raises(TypeError, match="floating-point"): + norm(x_int) + + +@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16]) +def test_dtype_roundtrip_mixed_precision(dtype): + """출력 dtype 이 입력 dtype 과 일치 (gemini #3303153077). residual connection 안전.""" + norm = RMSNorm(32) + x = torch.randn(4, 32, dtype=dtype) + y = norm(x) + assert y.dtype == dtype, f"expected {dtype}, got {y.dtype}" From 2e6cea65719751db17cf6e9506ea3aee44e4457e Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:45:15 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[REFAC]:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81,=20train=5Fhybrid=5Ftransformer=5Flm=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=20dataclass=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CodeRabbit #3303186824 — project rule "5+ args → dataclass" 적용 - src/graphlm/neuron/hybrid_transformer_demo.py: - HybridTransformerTrainConfig (frozen dataclass) 신규 — data/model/train/runtime 4 그룹 - train_hybrid_transformer_lm(config) 단일 인자로 단순화 (기존 14 kwargs) - notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb §2 — config 객체 구성 후 train 호출 - 152 tests all green --- .../12-phase13-hybrid-transformer.ipynb | 4 +- src/graphlm/neuron/hybrid_transformer_demo.py | 84 +++++++++++-------- 2 files changed, 52 insertions(+), 36 deletions(-) diff --git a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb index 4854ccc..8bd68e3 100644 --- a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb +++ b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb @@ -40,7 +40,7 @@ "id": "2", "metadata": {}, "outputs": [], - "source": "from __future__ import annotations\n\nimport math\nimport statistics\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport torch\n\nfrom graphlm.data.tinyshakespeare import (\n CharTokenizer,\n TinyShakespeareDataset,\n load_tinyshakespeare_text,\n)\nfrom graphlm.neuron.hybrid_transformer_demo import (\n HybridGraphTransformerLM,\n count_parameters,\n train_hybrid_transformer_lm,\n)\n\n\ndef safe_perplexity(loss: float, cap: float = 20.0) -> float:\n \"\"\"exp(loss) overflow 방지 (학습 초반 큰 loss 또는 발산 시).\n\n rationale: gemini #3303153101 — `math.exp(loss)` 는 loss 큰 시점에 OverflowError.\n cap=20 → max perplexity ≈ 4.85e8 (충분히 큰 ceiling).\n \"\"\"\n return math.exp(min(loss, cap))\n\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"device: {device}\")\nprint(f\"torch: {torch.__version__}\")" + "source": "from __future__ import annotations\n\nimport math\nimport statistics\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport torch\n\nfrom graphlm.data.tinyshakespeare import (\n CharTokenizer,\n TinyShakespeareDataset,\n load_tinyshakespeare_text,\n)\nfrom graphlm.neuron.hybrid_transformer_demo import (\n HybridGraphTransformerLM,\n HybridTransformerTrainConfig,\n count_parameters,\n train_hybrid_transformer_lm,\n)\n\n\ndef safe_perplexity(loss: float, cap: float = 20.0) -> float:\n \"\"\"exp(loss) overflow 방지 (학습 초반 큰 loss 또는 발산 시).\n\n rationale: gemini #3303153101 — `math.exp(loss)` 는 loss 큰 시점에 OverflowError.\n cap=20 → max perplexity ≈ 4.85e8 (충분히 큰 ceiling).\n \"\"\"\n return math.exp(min(loss, cap))\n\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"device: {device}\")\nprint(f\"torch: {torch.__version__}\")" }, { "cell_type": "markdown", @@ -112,7 +112,7 @@ "id": "6", "metadata": {}, "outputs": [], - "source": "results = {}\nfor arch in ARCHS:\n for seed in SEEDS:\n key = (arch, seed)\n print(f\"\\n== arch={arch} seed={seed} ==\")\n out = train_hybrid_transformer_lm(\n dataset=dataset,\n vocab_size=vocab_size,\n seed=seed,\n arch=arch,\n hidden_dim=HIDDEN_DIM,\n n_heads=N_HEADS,\n ffn_dim=FFN_DIM,\n n_layers=N_LAYERS,\n group_size=GROUP_SIZE,\n block_size=BLOCK_SIZE,\n batch_size=BATCH_SIZE,\n lr=LR,\n max_steps=MAX_STEPS,\n device=device,\n )\n results[key] = out\n print(\n f\" final_loss = {out['final_loss']:.4f} (perplexity = {safe_perplexity(out['final_loss']):.2f})\"\n )" + "source": "results = {}\nfor arch in ARCHS:\n for seed in SEEDS:\n key = (arch, seed)\n print(f\"\\n== arch={arch} seed={seed} ==\")\n cfg = HybridTransformerTrainConfig(\n dataset=dataset,\n vocab_size=vocab_size,\n hidden_dim=HIDDEN_DIM,\n n_heads=N_HEADS,\n ffn_dim=FFN_DIM,\n n_layers=N_LAYERS,\n group_size=GROUP_SIZE,\n arch=arch,\n block_size=BLOCK_SIZE,\n batch_size=BATCH_SIZE,\n lr=LR,\n max_steps=MAX_STEPS,\n seed=seed,\n device=device,\n )\n out = train_hybrid_transformer_lm(cfg)\n results[key] = out\n print(\n f\" final_loss = {out['final_loss']:.4f} (perplexity = {safe_perplexity(out['final_loss']):.2f})\"\n )" }, { "cell_type": "markdown", diff --git a/src/graphlm/neuron/hybrid_transformer_demo.py b/src/graphlm/neuron/hybrid_transformer_demo.py index 5e31aa4..4d3343f 100644 --- a/src/graphlm/neuron/hybrid_transformer_demo.py +++ b/src/graphlm/neuron/hybrid_transformer_demo.py @@ -14,6 +14,7 @@ from __future__ import annotations from collections.abc import Iterator +from dataclasses import dataclass import torch import torch.nn.functional as F @@ -29,6 +30,37 @@ from graphlm.utils import set_seed +@dataclass(frozen=True) +class HybridTransformerTrainConfig: + """1 run 학습에 필요한 모든 hyperparameter (CodeRabbit #3303186824 — 14 args 통합). + + 구조: model / data / optim / runtime 4 그룹으로 묶어서 가독성 ↑. + """ + + # data + dataset: TinyShakespeareDataset + vocab_size: int + + # model + hidden_dim: int + n_heads: int + ffn_dim: int + n_layers: int + group_size: int + arch: Arch + dropout: float = 0.0 + + # train + block_size: int = 64 + batch_size: int = 32 + lr: float = 3e-4 + max_steps: int = 1500 + + # runtime + seed: int = 0 + device: str = "cpu" + + class HybridGraphTransformerLM(nn.Module): """Small char-LM Transformer with arch-dispatched FFN. @@ -110,53 +142,36 @@ def _snapshot_adj(model: HybridGraphTransformerLM) -> list[dict[str, dict[str, T return snapshots -def train_hybrid_transformer_lm( - *, - dataset: TinyShakespeareDataset, - vocab_size: int, - seed: int, - arch: Arch, - hidden_dim: int, - n_heads: int, - ffn_dim: int, - n_layers: int, - group_size: int, - block_size: int, - batch_size: int, - lr: float, - max_steps: int, - device: str = "cpu", - dropout: float = 0.0, -) -> dict: +def train_hybrid_transformer_lm(config: HybridTransformerTrainConfig) -> dict: """1 run 학습 — Phase 13 sweep unit. Returns: ``losses``, ``final_loss`` (last 100 mean), ``final_adj`` (hybrid arch 인 경우 block 별 fc1/fc2 outer/inner snapshot list). """ - set_seed(seed) + set_seed(config.seed) model = HybridGraphTransformerLM( - vocab_size=vocab_size, - hidden_dim=hidden_dim, - n_heads=n_heads, - ffn_dim=ffn_dim, - n_layers=n_layers, - max_seq_len=block_size, - arch=arch, - group_size=group_size, - dropout=dropout, - ).to(device) + vocab_size=config.vocab_size, + hidden_dim=config.hidden_dim, + n_heads=config.n_heads, + ffn_dim=config.ffn_dim, + n_layers=config.n_layers, + max_seq_len=config.block_size, + arch=config.arch, + group_size=config.group_size, + dropout=config.dropout, + ).to(config.device) data_iter = iter_random_batches( - dataset, batch_size=batch_size, block_size=block_size, seed=seed + config.dataset, batch_size=config.batch_size, block_size=config.block_size, seed=config.seed ) - optimizer = torch.optim.AdamW(model.parameters(), lr=lr) + optimizer = torch.optim.AdamW(model.parameters(), lr=config.lr) losses: list[float] = [] model.train() - for _step in range(1, max_steps + 1): + for _step in range(1, config.max_steps + 1): x, y = next(data_iter) - x, y = x.to(device), y.to(device) + x, y = x.to(config.device), y.to(config.device) optimizer.zero_grad() logits = model(x) - loss = F.cross_entropy(logits.reshape(-1, vocab_size), y.reshape(-1)) + loss = F.cross_entropy(logits.reshape(-1, config.vocab_size), y.reshape(-1)) loss.backward() optimizer.step() losses.append(loss.item()) @@ -178,6 +193,7 @@ def count_parameters(model: nn.Module) -> int: __all__ = [ "HybridGraphTransformerLM", + "HybridTransformerTrainConfig", "count_parameters", "train_hybrid_transformer_lm", ] From b8ee88fe68a7cda76eac6e2da6c020e09fcb9d1a Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 19:52:01 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[CHORE]:=20CI=20=EC=9E=AC=ED=8A=B8?= =?UTF-8?q?=EB=A6=AC=EA=B1=B0=20nudge=20(GitHub=20Actions=20webhook=20?= =?UTF-8?q?=EC=A7=80=EC=97=B0=20=ED=9A=8C=ED=94=BC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 153d38120bc06a7041ddfa17f7cf2fefb8b04d53 Mon Sep 17 00:00:00 2001 From: juhyuni Date: Tue, 26 May 2026 22:57:36 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[CHORE]:=20Phase=2013=20figure=20?= =?UTF-8?q?=EC=9E=90=EC=82=B0=20=EC=B6=94=EA=B0=80=20(Notion=20=EC=9E=84?= =?UTF-8?q?=EB=B2=A0=EB=93=9C=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/figures/neuron/phase13/hybrid_adj.png | Bin 0 -> 127747 bytes docs/figures/neuron/phase13/loss_curves.png | Bin 0 -> 93281 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/figures/neuron/phase13/hybrid_adj.png create mode 100644 docs/figures/neuron/phase13/loss_curves.png diff --git a/docs/figures/neuron/phase13/hybrid_adj.png b/docs/figures/neuron/phase13/hybrid_adj.png new file mode 100644 index 0000000000000000000000000000000000000000..29534903e86ae996b6a8907d9de913ee30a54ecb GIT binary patch literal 127747 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

tG2eKp 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 literal 0 HcmV?d00001 diff --git a/docs/figures/neuron/phase13/loss_curves.png b/docs/figures/neuron/phase13/loss_curves.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b4b9e34d16388013e1f5facbf54f0cbd58f5be GIT binary patch literal 93281 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 Date: Tue, 26 May 2026 23:19:28 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[FIX]:=20=ED=94=BC=EB=93=9C=EB=B0=B1=20?= =?UTF-8?q?=EB=B0=98=EC=98=81,=20safe=5Fperplexity=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=ED=99=94=20+=20isfinite=20=EA=B2=80=EC=A6=9D=202=EA=B1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CodeRabbit #3304306120 — safe_perplexity 를 노트북 → src/graphlm/utils/metrics.py 로 이전 (project rule: 노트북은 analysis 만, 로직은 src/) - graphlm.utils 에서 export, 8 unit tests 추가 - 노트북은 `from graphlm.utils import safe_perplexity` 만 사용 - CodeRabbit #3304306127 — verdict 안정성 검사를 isnan → isfinite 로 교체 (verdict 3, 4) - isnan 만 쓰면 ±inf 가 silent PASS — 학습 발산 미감지 위험 - 152 → 160 tests all green --- .../12-phase13-hybrid-transformer.ipynb | 4 +- src/graphlm/utils/__init__.py | 9 +++- src/graphlm/utils/metrics.py | 36 +++++++++++++ tests/utils/test_metrics.py | 50 +++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 src/graphlm/utils/metrics.py create mode 100644 tests/utils/test_metrics.py diff --git a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb index 8bd68e3..3d731db 100644 --- a/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb +++ b/notebooks/02-function-level/12-phase13-hybrid-transformer.ipynb @@ -40,7 +40,7 @@ "id": "2", "metadata": {}, "outputs": [], - "source": "from __future__ import annotations\n\nimport math\nimport statistics\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport torch\n\nfrom graphlm.data.tinyshakespeare import (\n CharTokenizer,\n TinyShakespeareDataset,\n load_tinyshakespeare_text,\n)\nfrom graphlm.neuron.hybrid_transformer_demo import (\n HybridGraphTransformerLM,\n HybridTransformerTrainConfig,\n count_parameters,\n train_hybrid_transformer_lm,\n)\n\n\ndef safe_perplexity(loss: float, cap: float = 20.0) -> float:\n \"\"\"exp(loss) overflow 방지 (학습 초반 큰 loss 또는 발산 시).\n\n rationale: gemini #3303153101 — `math.exp(loss)` 는 loss 큰 시점에 OverflowError.\n cap=20 → max perplexity ≈ 4.85e8 (충분히 큰 ceiling).\n \"\"\"\n return math.exp(min(loss, cap))\n\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"device: {device}\")\nprint(f\"torch: {torch.__version__}\")" + "source": "from __future__ import annotations\n\nimport math\nimport statistics\nfrom pathlib import Path\n\nimport matplotlib.pyplot as plt\nimport torch\n\nfrom graphlm.data.tinyshakespeare import (\n CharTokenizer,\n TinyShakespeareDataset,\n load_tinyshakespeare_text,\n)\nfrom graphlm.neuron.hybrid_transformer_demo import (\n HybridGraphTransformerLM,\n HybridTransformerTrainConfig,\n count_parameters,\n train_hybrid_transformer_lm,\n)\nfrom graphlm.utils import safe_perplexity\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(f\"device: {device}\")\nprint(f\"torch: {torch.__version__}\")" }, { "cell_type": "markdown", @@ -128,7 +128,7 @@ "id": "8", "metadata": {}, "outputs": [], - "source": "print(f\"{'arch':32s} {'seed':>6s} {'final_loss':>12s} {'perplexity':>12s}\")\nprint(\"-\" * 70)\nfor (arch, seed), out in results.items():\n fl = out[\"final_loss\"]\n print(f\"{arch:32s} {seed:>6d} {fl:>12.4f} {safe_perplexity(fl):>12.2f}\")\n\n# arch 별 평균 / 표준편차\nprint(\"\\n== Arch-level summary (mean ± σ across seeds) ==\")\nsummary = {}\nfor arch in ARCHS:\n vals = [results[(arch, s)][\"final_loss\"] for s in SEEDS]\n summary[arch] = (statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0)\n m, s = summary[arch]\n print(f\" {arch:32s} {m:.4f} ± {s:.4f} (perplexity ≈ {safe_perplexity(m):.2f})\")\n\n# 자동 verdict\nprint(\"\\n== Verdict ==\")\nplain_loss = summary[\"plain\"][0]\nff_loss = summary[\"hybrid_full_full\"][0]\nfa_loss = summary[\"hybrid_full_around_one\"][0]\naa_loss = summary[\"hybrid_around_one_around_one\"][0]\n\n# 1. function preservation (학습 후 hybrid_full_full ≈ plain — 학습 dynamics 가 동일 sweet spot 유지)\ndiff_ff = abs(ff_loss - plain_loss)\nverdict_1 = \"PASS\" if diff_ff < 0.15 else \"FAIL\"\nprint(f\"1. function preservation: |hybrid_full_full - plain| = {diff_ff:.4f} [{verdict_1}]\")\n\n# 2. scale-corrected 우위 (hybrid_full_around_one 이 plain 보다 우수 또는 동등)\nverdict_2 = \"PASS\" if fa_loss <= plain_loss + 0.05 else \"FAIL\"\ndiff_fa = fa_loss - plain_loss\nprint(f\"2. inner around_one ≤ plain + 0.05: diff = {diff_fa:+.4f} [{verdict_2}]\")\n\n# 3. full scale-corrected (around_one × around_one) 안정성 (NaN 없음 + plain 근방)\nverdict_3 = \"PASS\" if (not math.isnan(aa_loss) and aa_loss < plain_loss + 0.5) else \"FAIL\"\ndiff_aa = aa_loss - plain_loss\nprint(f\"3. full around_one stable: diff = {diff_aa:+.4f} [{verdict_3}]\")\n\n# 4. 모두 NaN 없이 학습 종료 (RMSNorm 안정성)\nall_finite = all(not math.isnan(out[\"final_loss\"]) for out in results.values())\nverdict_4 = \"PASS\" if all_finite else \"FAIL\"\nprint(f\"4. RMSNorm stability (all finite): {all_finite} [{verdict_4}]\")" + "source": "print(f\"{'arch':32s} {'seed':>6s} {'final_loss':>12s} {'perplexity':>12s}\")\nprint(\"-\" * 70)\nfor (arch, seed), out in results.items():\n fl = out[\"final_loss\"]\n print(f\"{arch:32s} {seed:>6d} {fl:>12.4f} {safe_perplexity(fl):>12.2f}\")\n\n# arch 별 평균 / 표준편차\nprint(\"\\n== Arch-level summary (mean ± σ across seeds) ==\")\nsummary = {}\nfor arch in ARCHS:\n vals = [results[(arch, s)][\"final_loss\"] for s in SEEDS]\n summary[arch] = (statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0)\n m, s = summary[arch]\n print(f\" {arch:32s} {m:.4f} ± {s:.4f} (perplexity ≈ {safe_perplexity(m):.2f})\")\n\n# 자동 verdict\nprint(\"\\n== Verdict ==\")\nplain_loss = summary[\"plain\"][0]\nff_loss = summary[\"hybrid_full_full\"][0]\nfa_loss = summary[\"hybrid_full_around_one\"][0]\naa_loss = summary[\"hybrid_around_one_around_one\"][0]\n\n# 1. function preservation (학습 후 hybrid_full_full ≈ plain — 학습 dynamics 가 동일 sweet spot 유지)\ndiff_ff = abs(ff_loss - plain_loss)\nverdict_1 = \"PASS\" if diff_ff < 0.15 else \"FAIL\"\nprint(f\"1. function preservation: |hybrid_full_full - plain| = {diff_ff:.4f} [{verdict_1}]\")\n\n# 2. scale-corrected 우위 (hybrid_full_around_one 이 plain 보다 우수 또는 동등)\nverdict_2 = \"PASS\" if fa_loss <= plain_loss + 0.05 else \"FAIL\"\ndiff_fa = fa_loss - plain_loss\nprint(f\"2. inner around_one ≤ plain + 0.05: diff = {diff_fa:+.4f} [{verdict_2}]\")\n\n# 3. full scale-corrected (around_one × around_one) 안정성 (유한 + plain 근방)\n# CodeRabbit #3304306127 — isfinite 가 inf/-inf 도 거부 (isnan 만 쓰면 inf 가 PASS 됨)\nverdict_3 = \"PASS\" if (math.isfinite(aa_loss) and aa_loss < plain_loss + 0.5) else \"FAIL\"\ndiff_aa = aa_loss - plain_loss\nprint(f\"3. full around_one stable: diff = {diff_aa:+.4f} [{verdict_3}]\")\n\n# 4. 모두 finite 로 학습 종료 (RMSNorm 안정성, inf/nan 모두 거부)\nall_finite = all(math.isfinite(out[\"final_loss\"]) for out in results.values())\nverdict_4 = \"PASS\" if all_finite else \"FAIL\"\nprint(f\"4. RMSNorm stability (all finite): {all_finite} [{verdict_4}]\")" }, { "cell_type": "markdown", diff --git a/src/graphlm/utils/__init__.py b/src/graphlm/utils/__init__.py index 4e93c16..e442a19 100644 --- a/src/graphlm/utils/__init__.py +++ b/src/graphlm/utils/__init__.py @@ -1,7 +1,14 @@ """Utility functions for GraphLM (seed, exceptions, paths, logging helpers, etc.).""" from graphlm.utils.exceptions import FunctionPreservationError, GraphLMError +from graphlm.utils.metrics import safe_perplexity from graphlm.utils.paths import repo_root from graphlm.utils.seed import set_seed -__all__ = ["FunctionPreservationError", "GraphLMError", "repo_root", "set_seed"] +__all__ = [ + "FunctionPreservationError", + "GraphLMError", + "repo_root", + "safe_perplexity", + "set_seed", +] diff --git a/src/graphlm/utils/metrics.py b/src/graphlm/utils/metrics.py new file mode 100644 index 0000000..fd36e86 --- /dev/null +++ b/src/graphlm/utils/metrics.py @@ -0,0 +1,36 @@ +"""Numerical metrics helpers — safe perplexity 등. + +노트북에서 직접 정의하던 helper 들을 ``src/graphlm/`` 로 이전 — 노트북은 analysis flow ++ 시각화에만 집중 (CodeRabbit #3304306120, project rule). +""" + +from __future__ import annotations + +import math + + +def safe_perplexity(loss: float, cap: float = 20.0) -> float: + """``exp(loss)`` overflow 방지. + + 학습 초반 큰 loss 또는 발산 시 ``math.exp(loss)`` 는 ``OverflowError`` 발생. + cap=20 → max perplexity ≈ 4.85e8 (충분히 큰 ceiling, 발산 식별 가능). + + Args: + loss: cross-entropy loss (음수 또는 양수, 무한 가능). + cap: exp 적용 전 loss 의 상한 (default 20.0). + + Returns: + ``math.exp(min(loss, cap))`` — 항상 유한 양수. + + Raises: + ValueError: ``cap`` 이 음수면 (의도적 down-clip 으로 underflow 가능). + + See Also: + - gemini #3303153101 rationale: log-domain loss → exp-domain perplexity 변환 시 + overflow 회피. + """ + if cap < 0: + raise ValueError(f"cap must be non-negative, got {cap}") + if math.isnan(loss): + return math.nan + return math.exp(min(loss, cap)) diff --git a/tests/utils/test_metrics.py b/tests/utils/test_metrics.py new file mode 100644 index 0000000..f8fb59d --- /dev/null +++ b/tests/utils/test_metrics.py @@ -0,0 +1,50 @@ +"""Tests for graphlm.utils.metrics — safe_perplexity etc.""" + +from __future__ import annotations + +import math + +import pytest + +from graphlm.utils import safe_perplexity + + +def test_normal_loss(): + """일반적인 char-LM loss 값 (~2.0) 이 정상 perplexity 반환.""" + assert safe_perplexity(2.0) == pytest.approx(math.exp(2.0)) + + +def test_caps_at_default_20(): + """loss=30 → cap=20 적용 → exp(20).""" + assert safe_perplexity(30.0) == pytest.approx(math.exp(20.0)) + + +def test_below_cap_unchanged(): + """loss < cap 일 때 cap 영향 없음.""" + assert safe_perplexity(5.0, cap=20.0) == pytest.approx(math.exp(5.0)) + + +def test_negative_loss(): + """음수 loss 도 정상 처리 (분류 confidence 높을 때).""" + assert safe_perplexity(-1.0) == pytest.approx(math.exp(-1.0)) + + +def test_custom_cap(): + """cap 인자로 ceiling 조정 가능.""" + assert safe_perplexity(15.0, cap=10.0) == pytest.approx(math.exp(10.0)) + + +def test_nan_input_returns_nan(): + """NaN loss → NaN perplexity (silent overflow 회피).""" + assert math.isnan(safe_perplexity(math.nan)) + + +def test_inf_input_caps(): + """+inf loss → cap 적용 → 유한 값.""" + assert safe_perplexity(math.inf) == pytest.approx(math.exp(20.0)) + + +def test_neg_cap_rejected(): + """음수 cap 거부 — exp(음수 large) underflow 위험.""" + with pytest.raises(ValueError, match="non-negative"): + safe_perplexity(2.0, cap=-1.0)