Files
frankenstallm/source/PLAN_hybrid_3b_fixes.md
ModelHub XC d4abdb70fa 初始化项目,由ModelHub XC社区提供模型
Model: pathcosmos/frankenstallm
Source: Original Platform
2026-07-14 04:21:16 +08:00

499 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FRANKENSTALLM-H 3B Hybrid Model — 점검 결과 및 수정 실행 가이드
> **작성일**: 2026-03-05
> **목적**: Phase 2 검증 전, 발견된 이슈 6건을 수정하고 바로 실행 가능한 상태로 만든다.
> **다음 세션에서 이 문서를 참조하여 바로 실행할 것.**
---
## 이슈 요약 (6건)
| # | 심각도 | 이슈 | 파일 | 영향 |
|---|--------|------|------|------|
| 1 | **CRITICAL** | Mamba 블록에 FFN(channel mixer) 없음 | `model/mamba_block.py` | 37/40 레이어 capacity 부족 |
| 2 | **HIGH** | `n_groups=1` (Nemotron 표준은 8) | `configs/hybrid_3b.yaml` | B/C projection 표현력 저하 |
| 3 | **HIGH** | Hybrid 아키텍처 startup 로그 없음 | `train/pretrain.py` | 디버깅·모니터링 곤란 |
| 4 | **MEDIUM** | 체크포인트 resume 시 아키텍처 검증 없음 | `train/utils.py` | 잘못된 가중치 로드 가능 |
| 5 | **MEDIUM** | selective_scan에 NaN/Inf 감지 없음 | `model/mamba_block.py` | 수치 불안정 진단 불가 |
| 6 | **LOW** | selective_scan 입력 shape 검증 없음 | `model/mamba_block.py` | 모호한 에러 메시지 |
---
## 구현 순서 및 의존성
```
Step 1 (FFN 추가) ← 가장 먼저, 아키텍처 변경
├── 1a. model/config.py: mamba_d_ffn 필드 추가
├── 1b. model/mamba_block.py: FFN sublayer 추가
├── 1c. model/transformer.py: 생성자 인자 전달 + _init_weights 수정
└── 1d. configs/hybrid_3b.yaml: mamba_d_ffn=4608 추가
Step 2 (n_groups) ← Step 1과 독립, 병렬 가능
└── configs/hybrid_3b.yaml: n_groups=8
Step 3 (로그) ← Step 1 완료 후 (파라미터 수 정확해야)
└── train/pretrain.py: startup 배너에 hybrid 정보 추가
Step 4 (체크포인트 검증) ← 독립
└── train/utils.py: load_checkpoint에 config 비교 로직
Step 5-6 (NaN 감지 + shape 검증) ← 독립
└── model/mamba_block.py: selective_scan 함수
```
**병렬 가능**: Step 1 + Step 2는 YAML만 겹침 (마지막에 합치면 됨).
Step 4, Step 5-6도 독립적으로 병렬 실행 가능.
---
## Step 1: Mamba2Block에 FFN 추가 (CRITICAL)
### 배경
- Mamba2Block은 SSM(sequence mixer)만 있고 FFN(channel mixer)이 없음
- Nemotron-H에서는 모든 Mamba 레이어 뒤에 MLP가 따라옴
- 현재 37/40 레이어에 FFN이 없어 feature mixing이 불가능
- **확정**: `mamba_d_ffn = 4608` (d_model × 1.5), 총 파라미터 ~4.5B, VRAM ~80GB/GPU
### 1a. `model/config.py` 수정
**위치**: LMConfig dataclass 내부 (line 61 이후)
**추가할 필드** (기존 `mamba_chunk_size` 뒤에):
```python
mamba_d_ffn: Optional[int] = None # FFN dim for Mamba blocks (None → d_ffn)
```
**`__post_init__` 추가** (line 86, hybrid validation 블록 뒤에):
```python
# Mamba FFN dimension: default to d_ffn if not specified
if self.mamba_d_ffn is None:
self.mamba_d_ffn = self.d_ffn
```
**`to_dict()` 추가** (기존 mamba_chunk_size 뒤에):
```python
"mamba_d_ffn": self.mamba_d_ffn,
```
### 1b. `model/mamba_block.py` 수정
**Import 변경** (line 19):
```python
# 변경 전:
from .layers import RMSNorm
# 변경 후:
from .layers import RMSNorm, SwiGLU
```
**`Mamba2Block.__init__` 시그니처 변경** (line 128-137):
```python
# 변경 전:
def __init__(
self,
d_model: int,
d_state: int = 128,
head_dim: int = 64,
expand: int = 2,
conv_kernel: int = 4,
n_groups: int = 1,
chunk_size: int = 256,
) -> None:
# 변경 후:
def __init__(
self,
d_model: int,
d_state: int = 128,
head_dim: int = 64,
expand: int = 2,
conv_kernel: int = 4,
n_groups: int = 1,
chunk_size: int = 256,
d_ffn: int = 0,
bias: bool = False,
) -> None:
```
**FFN 서브레이어 추가** (line 192, `self.out_proj` 뒤에):
```python
# --- FFN sublayer (channel mixer) ---
if d_ffn > 0:
self.ffn_norm = RMSNorm(d_model)
self.ffn = SwiGLU(d_model, d_ffn, bias=bias)
else:
self.ffn_norm = None
self.ffn = None
```
**`forward()` 수정** (line 280):
```python
# 변경 전:
return residual + self.out_proj(y)
# 변경 후:
x = residual + self.out_proj(y)
# FFN sublayer (channel mixer)
if self.ffn is not None:
x = x + self.ffn(self.ffn_norm(x))
return x
```
### 1c. `model/transformer.py` 수정
**Mamba2Block 생성자 호출 변경** (line 124-132):
```python
# 변경 전:
layers.append(Mamba2Block(
d_model=config.d_model,
d_state=config.mamba_d_state,
head_dim=config.mamba_head_dim,
expand=config.mamba_expand,
conv_kernel=config.mamba_conv_kernel,
n_groups=config.mamba_n_groups,
chunk_size=config.mamba_chunk_size,
))
# 변경 후:
layers.append(Mamba2Block(
d_model=config.d_model,
d_state=config.mamba_d_state,
head_dim=config.mamba_head_dim,
expand=config.mamba_expand,
conv_kernel=config.mamba_conv_kernel,
n_groups=config.mamba_n_groups,
chunk_size=config.mamba_chunk_size,
d_ffn=config.mamba_d_ffn,
bias=config.bias,
))
```
**`_init_weights` 수정** (line 180-182):
```python
# 변경 전:
# Mamba2Block handles its own parameter init (A_log, D, dt_bias, etc.)
if isinstance(module, Mamba2Block):
return
# 변경 후 (이 3줄을 삭제):
# 삭제 이유: FFN 추가 후 내부 SwiGLU의 nn.Linear가 init 필요.
# A_log, D, dt_bias는 nn.Parameter이므로 isinstance(nn.Linear) 체크에 걸리지 않아
# 자동으로 스킵됨 (Mamba2Block.__init__에서 직접 초기화됨).
```
### 1d. `configs/hybrid_3b.yaml` 수정
```yaml
# mamba_chunk_size: 256 뒤에 추가:
mamba_d_ffn: 4608
```
### Step 1 검증
```bash
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
CUDA_VISIBLE_DEVICES=0 python -c "
import torch, sys
sys.path.insert(0, '.')
from model import LLM, LMConfig
config = LMConfig.from_yaml('configs/hybrid_3b.yaml')
print(f'mamba_d_ffn = {config.mamba_d_ffn}')
model = LLM(config)
total = sum(p.numel() for p in model.parameters())
print(f'Total params: {total:,} ({total/1e9:.2f}B)')
# Forward test
x = torch.randint(0, 64000, (1, 128))
logits, loss = model(x, targets=x)
print(f'Forward OK: logits shape={logits.shape}, loss={loss.item():.4f}')
# Backward test
loss.backward()
grads_ok = all(p.grad is not None for p in model.parameters() if p.requires_grad)
print(f'Backward OK: all grads exist = {grads_ok}')
"
# 예상 출력: Total params ~4.5B, Forward/Backward OK
```
---
## Step 2: n_groups 수정
### `configs/hybrid_3b.yaml`
```yaml
# 변경 전:
mamba_n_groups: 1
# 변경 후:
mamba_n_groups: 8
```
### 검증
n_heads(= d_inner / head_dim = 6144 / 64 = 96) % 8 == 0 ✓
Step 1 검증 스크립트에서 함께 확인됨 (assertion이 `__init__`에 있음).
---
## Step 3: 하이브리드 아키텍처 startup 로그 추가
### `train/pretrain.py` 수정
**위치**: line 296-297 (모델 파라미터 출력 부분) 뒤에 추가
```python
if is_main_process():
total_params = sum(p.numel() for p in model.parameters())
print(f"Model parameters: {total_params:,}")
print(f"LMConfig: {lm_config}")
# --- 여기부터 추가 ---
if lm_config.use_hybrid:
pattern = lm_config.hybrid_pattern.split()
m_count = sum(1 for p in pattern if p == 'M')
a_count = sum(1 for p in pattern if p == 'A')
mamba_params = sum(
p.numel() for n, p in model.named_parameters()
if 'layers.' in n and pattern[int(n.split('.')[1])] == 'M'
)
attn_params = sum(
p.numel() for n, p in model.named_parameters()
if 'layers.' in n and pattern[int(n.split('.')[1])] == 'A'
)
other_params = total_params - mamba_params - attn_params
print(
f" arch : Hybrid Mamba-Transformer\n"
f" layers : {m_count} Mamba + {a_count} Attention = {len(pattern)} total\n"
f" params : Mamba {mamba_params/1e6:.0f}M + "
f"Attn {attn_params/1e6:.0f}M + Other {other_params/1e6:.0f}M\n"
f" mamba cfg: d_state={lm_config.mamba_d_state}, "
f"head_dim={lm_config.mamba_head_dim}, "
f"expand={lm_config.mamba_expand}, "
f"n_groups={lm_config.mamba_n_groups}, "
f"d_ffn={lm_config.mamba_d_ffn}"
)
# --- 추가 끝 ---
```
### 검증
Step 1 검증 실행 시 로그에 hybrid 정보가 출력되는지 확인.
---
## Step 4: 체크포인트 resume 아키텍처 검증
### `train/utils.py` — `load_checkpoint()` 수정
**위치**: line 179 (`raw_model.load_state_dict(...)`) 직전에 추가
```python
# --- Architecture validation ---
config_path = ckpt_dir / "config.yaml"
if config_path.exists() and hasattr(raw_model, "config"):
with open(config_path, "r", encoding="utf-8") as f:
saved_cfg = yaml.safe_load(f)
current_cfg = raw_model.config.to_dict()
critical_keys = [
"d_model", "n_layers", "n_heads", "n_kv_heads", "vocab_size",
"use_hybrid", "hybrid_pattern",
]
mismatches = []
for key in critical_keys:
saved_val = saved_cfg.get(key)
current_val = current_cfg.get(key)
if saved_val is not None and saved_val != current_val:
mismatches.append(
f" {key}: checkpoint={saved_val} vs current={current_val}"
)
if mismatches:
raise ValueError(
f"Checkpoint architecture mismatch!\n"
f"Checkpoint dir: {ckpt_dir}\n"
+ "\n".join(mismatches)
+ "\nUse --config matching the checkpoint, or start fresh."
)
# --- End architecture validation ---
```
**참고**: `yaml`은 이미 `train/utils.py` line 23에서 import 되어 있음.
### 검증
```bash
# 의도적으로 다른 config로 resume 시도
CUDA_VISIBLE_DEVICES=0 python train/pretrain.py \
--config configs/small.yaml \
--train_data data/3b_train.bin \
--resume checkpoints/hybrid_3b_run1/checkpoint-0001000
# 예상: ValueError "Checkpoint architecture mismatch!" 출력
```
---
## Step 5: selective_scan NaN/Inf 감지
### `model/mamba_block.py` — `selective_scan()` 수정
**위치**: line 94 (`y[:, t, :, :] = y_t.to(x.dtype)`) 뒤에 추가
```python
# Periodic NaN/Inf check (every 512 steps, < 1% overhead)
if t % 512 == 511:
if not torch.isfinite(h).all():
raise RuntimeError(
f"NaN/Inf in Mamba SSM state at timestep {t}/{seq_len}. "
f"h stats: min={h.min().item():.4e}, max={h.max().item():.4e}, "
f"A_log range=[{A_log.min().item():.4f}, {A_log.max().item():.4f}]"
)
```
### 검증
```bash
CUDA_VISIBLE_DEVICES=0 python -c "
import torch, sys
sys.path.insert(0, '.')
from model.mamba_block import Mamba2Block
block = Mamba2Block(d_model=256, d_state=64, head_dim=32, d_ffn=384)
x = torch.randn(1, 1024, 256)
# 정상 케이스
y = block(x)
print(f'Normal: output shape={y.shape}, finite={torch.isfinite(y).all()}')
# NaN 주입 테스트
block.A_log.data.fill_(100.0) # 매우 큰 값 → exp(100) overflow
try:
y = block(x)
print('WARNING: NaN not detected!')
except RuntimeError as e:
print(f'NaN correctly detected: {e}')
"
```
---
## Step 6: selective_scan 입력 shape 검증
### `model/mamba_block.py` — `selective_scan()` 수정
**위치**: line 49 (`batch, seq_len, n_heads, head_dim = x.shape`) 직전에 추가
```python
# Input shape validation
assert x.ndim == 4, f"x expected 4D (B,L,n_heads,head_dim), got {x.shape}"
assert dt.ndim == 3, f"dt expected 3D (B,L,n_heads), got {dt.shape}"
assert B.ndim == 4, f"B expected 4D (B,L,n_groups,d_state), got {B.shape}"
assert C.ndim == 4, f"C expected 4D (B,L,n_groups,d_state), got {C.shape}"
```
---
## 최종 검증 절차 (모든 Step 완료 후)
### 1. 모델 생성 + Forward/Backward (단일 GPU)
```bash
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
CUDA_VISIBLE_DEVICES=0 python -c "
import torch, sys
sys.path.insert(0, '.')
from model import LLM, LMConfig
config = LMConfig.from_yaml('configs/hybrid_3b.yaml')
model = LLM(config).cuda()
total = sum(p.numel() for p in model.parameters())
print(f'Total params: {total:,} ({total/1e9:.2f}B)')
assert 4.0e9 < total < 5.0e9, f'Expected ~4.5B params, got {total/1e9:.2f}B'
# Forward
x = torch.randint(0, 64000, (2, 512)).cuda()
logits, loss = model(x, targets=x)
print(f'Forward: logits={logits.shape}, loss={loss.item():.4f}')
# Backward
loss.backward()
no_grad = [n for n, p in model.named_parameters() if p.requires_grad and p.grad is None]
assert len(no_grad) == 0, f'Missing gradients: {no_grad}'
print(f'Backward: all {sum(1 for p in model.parameters() if p.requires_grad)} params have grad')
# VRAM
print(f'VRAM: {torch.cuda.memory_allocated()/1e9:.1f}GB allocated')
"
```
### 2. DDP 8-GPU 테스트 (10 steps)
```bash
cd /PROJECT/0325120031_A/ghong/taketimes/llm-bang
torchrun --nproc_per_node=8 --master_port=29501 train/pretrain.py \
--config configs/hybrid_3b.yaml \
--train_data data/3b_train.bin \
--batch_size 2 \
--lr 1e-4 \
--warmup_steps 5 \
--grad_accum 1 \
--max_steps 10 \
--checkpoint_dir /tmp/hybrid_test_ckpt \
--use_fp8
# 예상: 10 steps 완료, 체크포인트 저장, startup 배너에 hybrid 정보 출력
```
### 3. 체크포인트 Resume 테스트
```bash
# Step 2 체크포인트에서 resume
torchrun --nproc_per_node=8 --master_port=29501 train/pretrain.py \
--config configs/hybrid_3b.yaml \
--train_data data/3b_train.bin \
--batch_size 2 \
--lr 1e-4 \
--warmup_steps 5 \
--grad_accum 1 \
--max_steps 20 \
--checkpoint_dir /tmp/hybrid_test_ckpt \
--resume /tmp/hybrid_test_ckpt/checkpoint-0000010 \
--use_fp8
# 예상: step 10에서 이어서 step 20까지 학습
```
---
## 수정하지 않는 것들 (의도적 제외)
- **sequential scan 성능**: Python for-loop는 느리지만 구조 변경이 큼. 별도 태스크로 chunked SSD 구현
- **FP8 + Mamba 혼합**: 현재 설계(Mamba=bf16, Attention=FP8)가 올바름. te.fp8_autocast는 te 모듈만 영향
- **DDP 설정**: find_unused_parameters=False, gradient_as_bucket_view=True 모두 정상
- **pure Transformer 모드**: use_hybrid=False면 기존 동작 유지 (하위 호환)
---
## 수정 대상 파일 요약
| 파일 | Step | 변경 내용 |
|------|------|----------|
| `model/config.py` | 1a | `mamba_d_ffn` 필드 + `__post_init__` + `to_dict()` |
| `model/mamba_block.py` | 1b, 5, 6 | SwiGLU import, FFN sublayer, NaN 감지, shape 검증 |
| `model/transformer.py` | 1c | Mamba2Block 생성자에 d_ffn/bias 전달, `_init_weights` 수정 |
| `configs/hybrid_3b.yaml` | 1d, 2 | `mamba_d_ffn: 4608`, `mamba_n_groups: 8` |
| `train/pretrain.py` | 3 | Hybrid startup 로그 |
| `train/utils.py` | 4 | `load_checkpoint()` 아키텍처 검증 |
---
## 실행 지시 (다음 세션용)
이 문서를 참조하여 다음 명령을 내리면 됩니다:
> "이 문서(hashed-drifting-harp.md)의 Step 1~6을 순서대로 실행해 줘.
> Step 1+2는 병렬로, Step 3~6은 독립적으로 진행.
> 각 Step 완료 후 해당 검증을 실행하고,
> 전체 완료 후 최종 검증 절차 3단계를 모두 실행해 줘."