> EulerStack > 튜토리얼 > 10. 논문 → YAML 포팅 사례 (DeepSeek-V3 / Jamba / DeepSeek-R1 / Titans)

10. 논문 → YAML 포팅 사례 (DeepSeek-V3 / Jamba / DeepSeek-R1 / Titans)

이 튜토리얼은 교수 (P)학생 (S) 의 대화를 통해, 최신 논문 4 편을 EulerStack YAML 로 옮기는 과정 전체를 보여줍니다. 단순한 "이렇게 쓰면 된다" 가 아니라 "왜 이렇게 쓰는지" 를 다음 여덟 단계로 차근차근 풀어갑니다.

각 사례(Case)의 구성은 아래와 같습니다.

단계 내용
§X.1 배경과 동기 이 논문이 왜 나왔나? 해결하려는 병목은 무엇이고 선행 연구와 어떻게 연결되나?
§X.2 메커니즘 심화 수식 · ASCII 다이어그램 수준에서 동작 원리. 대안과의 차이.
§X.3 YAML 유도 빈 YAML 에서 출발해 필드를 하나씩 채워가며 왜 그 값인지 설명.
§X.4 검증 & 컴파일 실제 CLI 명령어와 예상 출력. 파라미터 수 산수.
§X.5 파라미터 튜닝 latent_dim, capacity_factor 등 주요 knob 의 적정 범위.
§X.6 함정과 디버깅 초보자가 자주 밟는 지뢰 4–6 개. 에러 메시지 → 원인 매핑.
§X.7 다른 primitive 와 결합 MLA + MoE, MLA + MoD 같은 2차 조합의 직교성 / 충돌.
§X.8 심화 읽을거리 논문 · 후속 연구 · 관련 프리셋 / 튜토리얼 링크.

목차: - Case 1. DeepSeek-V3 — MLA (Multi-head Latent Attention) - Case 2. Jamba — Mamba × Attention × MoE 하이브리드 - Case 3. DeepSeek-R1 — 2-phase reasoning (execution_modes) - Case 4. Titans — 추론 시점에 학습하는 neural memory - 마무리 — 네 사례의 공통 구조


Case 1. DeepSeek-V3 — MLA (Multi-head Latent Attention)

1.1 배경과 동기

S: 교수님, DeepSeek-V3 논문(2024-12)을 보면 Multi-head Latent Attention 이 핵심이라고 강조합니다. 그냥 attention 변형 하나 같은데, 왜 이렇게까지 떠들썩한가요?

P: 맥락을 먼저 볼까. 2023~24 년에 "긴 컨텍스트 (long context)" 가 LLM 의 핵심 경쟁 축이 됐어. Llama 2 의 4K → Llama 3 의 8K → 32K → 128K 로 치솟았지. 문제는 이 경쟁이 불평등한 비용으로 왔다는 거야.

S: 불평등한 비용이라뇨?

P: attention 의 계산 비용은 FlashAttention 덕에 극적으로 떨어졌는데, 메모리 비용은 그대로 남았거든. 구체적으로 KV 캐시 크기가 병목이야.

KV 캐시 크기 = 2 × n_layers × n_heads × head_dim × seq_len × batch × dtype_bytes

Llama 3 70B 기준 128K 컨텍스트면 레이어당 KV 캐시가 대략 5GB, 80 레이어 곱하면 400GB. bfloat16 기준이야. H100 한 장에 올릴 수 없어.

S: 그래서 업계가 뭘 했나요?

P: 세 갈래로 갔어:

  1. Grouped-Query Attention (GQA) — Llama 2/3. 여러 head 가 K/V 를 공유. head 개수 대비 1/8 까지 줄임. 단, quality loss.
  2. Sliding-Window Attention — Mistral / Longformer. 전역 attention 을 포기하고 윈도우 내부만 봄. 단, long-range dependency 가 약해짐.
  3. Multi-head Latent Attention (MLA) — DeepSeek-V2/V3. K·V 를 하나의 저차원 latent 로 압축해 캐시는 그 latent 만 유지. quality loss 없이 메모리 이득.

3 번이 MLA 야. 2 번은 지역성 을 희생, 1 번은 head 다양성 을 희생, MLA 는 차원 을 희생하는데 쿼리 재구성 시 up-projection 으로 다양성을 회복해. 그래서 품질 저하가 거의 없어.

S: 그럼 MLA 가 현재 왕인가요?

P: 2025년 기준 long-context 메모리 효율에서는 사실상 표준이 됐어. DeepSeek-V2/V3, Qwen 2.5 일부 변형, Kimi 의 MoBA 계열이 모두 MLA 를 채택했어. Llama 계열은 여전히 GQA 지만, 차세대에서 MLA 로 갈 가능성이 높아.

S: DeepSeek-V2 와 V3 차이는?

P: MLA 자체는 V2 에서 도입됐고, V3 는 규모(236B → 671B)와 훈련 기법 (MTP, Multi-Token Prediction)이 커진 버전이야. MLA 구조는 동일. 우리가 YAML 로 옮길 것도 "MLA 한 줄" 이고, 모델 크기는 하이퍼파라미터로 조절해.

1.2 메커니즘 심화

S: MLA 가 정확히 어떻게 동작하는지 수식으로 봤으면 합니다.

P: 표준 MHA 부터 복습. 입력 x ∈ R^{T×d} 가 있을 때:

Q = x W_q    (d → d)
K = x W_k    (d → d)
V = x W_v    (d → d)

attention = softmax(Q K^T / √d_h) V

MHA 는 dn_heads × head_dim 으로 쪼개서 각 head 가 독립적으로 계산해. KV 캐시에 저장하는 건 K 와 V 둘 다 이고, 각각 shape 가 (B, n_heads, T, head_dim).

S: MLA 는?

P: 이렇게 바뀌어:

# Down-projection (한 번만, K/V 공유)
kv_latent = x W_kv      (d → l)                     ← 캐시할 것은 이 하나

# Up-projection (계산 시점에 재구성)
K = kv_latent W_k_up    (l → d)                     ← 쿼리마다 다시 만듦
V = kv_latent W_v_up    (l → d)

# Q 는 표준
Q = x W_q               (d → d)

S: 그러니까 "K 와 V 는 버리고 그 재료가 되는 latent 만 저장한다" 는 거군요.

P: 정확해. 그래서 KV 캐시 크기가 2 × d → 1 × l 이 돼. l = d/2 면 캐시 메모리 75% 감소. l = d/487.5% 감소. head-sharing 없이.

S: 그런데 매 step 마다 up-projection 을 다시 해야 하잖아요. 계산량이 늘지 않나요?

P: 두 가지 최적화가 있어.

  1. Absorb trick: inference 시 Q W_k_up^T 를 미리 fused 해두면 up-projection 이 실질적으로 사라져. DeepSeek 구현이 이걸 함.
  2. 병렬화: up-projection 은 행렬곱 하나라서 memory-bound 인 KV 읽기 대비 추가 latency 가 거의 없음 — 메모리에 읽는 비용이 훨씬 크기 때문.
┌─────────────────────────────────────────────────┐
│  Cache layout 비교                                │
├─────────────────────────────────────────────────┤
│  MHA:         [K: H×D]  [V: H×D]     → 2HD/token │
│  GQA (4:1):   [K: H/4×D] [V: H/4×D]  → HD/2      │
│  MLA l=d/2:   [kv_latent: L]          → L = D/2   │
│                                                    │
│  d_model=768, 32 layers, 128K ctx, bf16:          │
│    MHA:   24 GB                                   │
│    GQA:    6 GB                                   │
│    MLA:    6 GB   (quality ≈ MHA)                 │
└─────────────────────────────────────────────────┘

S: 파라미터 수도 보여주세요.

P: attention 블록당:

l = d/2 인 MLA 는 2d² + 1.5d² = 3.5d²MHA 보다 파라미터가 적으면서 캐시는 MHA 의 25%. quality 도 유지. 이래서 "free lunch" 라고 부르는 거야.

1.3 YAML 유도

S: 실제로 EulerStack YAML 로는 어떻게 씁니까?

P: 빈 YAML 에서 시작하자. 먼저 버전과 모델 메타데이터.

schema_version: 1

model:
  name: deepseek-v3-clone
  d_model: 768          # 768 은 교육용 소규모. 실제 V3 는 d_model=7168
  vocab_size: 32000
  max_seq_len: 32768    # 32K. V3 는 128K 까지 확장
  n_heads: 12           # d_model/n_heads = 64 (head_dim)
  n_kv_heads: 12        # MLA 는 GQA 와 직교 — 둘 다 써도 된다
  mlp_ratio: 4          # FFN 차원 = 4 × d_model

S: n_kv_headsn_heads 와 같게 둔 건 GQA 를 안 쓴다는 거죠?

P: 맞아. MLA 가 이미 KV 를 latent 로 압축했으니 GQA 를 겹치는 건 redundant 해. DeepSeek 원 논문도 MLA 단독이야. 만약 "MLA + GQA 4:1" 로 더 공격적으로 줄이고 싶다면 n_kv_heads: 3 으로 두면 되는데, quality 가 빠르게 떨어져서 권장하지 않아.

S: 다음은 토크나이저와 embedding 이죠.

P: 응. 실제 DeepSeek 은 자체 100K BPE 를 쓰지만, 학습 목적이면 gpt2 로 대체해서 validate 만 통과시키자.

tokenizer_contract:
  type: hf
  pretrained: gpt2
  add_bos: true
  add_eos: true

embedding:
  type: learned
  positional: rope
  rope_theta: 500000.0          # 긴 컨텍스트 용 대형 theta
  tie_word_embeddings: true

S: rope_theta: 500000.0 은 왜 이렇게 큰가요? 기본값이 10000 아닙니까?

P: 좋은 질문. RoPE 의 기본 theta=10000 은 GPT-2 시절 설계로 4K 이하 컨텍스트에 맞춰져 있어. 컨텍스트를 32K+ 로 늘리면 고주파 축이 주기 충돌 을 일으켜서 position 식별력이 떨어져. theta 를 키우면 주기가 길어져서 높은 인덱스도 구분 가능해져. Llama 3 는 500K, DeepSeek 은 10M 까지 써.

S: 이제 핵심인 layer_templates.

P: MLA 가 들어가는 라인이야.

layer_templates:
  mla_decoder:
    mixer:
      type: attention
      attention:
        latent_dim: 384        # ← 이 한 줄이 MLA 의 전부. d_model=768 의 절반
        qkv_bias: false
    ffn:
      type: gated_mlp
      activation: swiglu
    norm:
      type: rmsnorm
      position: pre
    residual:
      type: sequential
      scaling: 1.0
    state:
      kv_cache: true

S: 정말 latent_dim: 384 한 줄이 끝이에요?

P: 끝이야. EulerStack 의 CausalSelfAttentionlatent_dim 이 있으면 다섯 개 projection (Q, kv_latent, K_up, V_up, out) 을 대신 인스턴스화해. 없으면 표준 fused QKV. 스펙 레이어에서 "MLA 쓸 거다" 만 선언하면 나머지는 런타임이 알아서 해.

S: schedule 은?

P:

layer_schedule:
  - template: mla_decoder
    repeat: 12          # 교육용. V3 는 61 layers

head:
  type: causal_lm
  tie_weights: true

compatibility:
  compile_target: huggingface

완성. 이게 DeepSeek-V3 MLA 구조 그 자체 야.

1.4 검증 & 컴파일

S: 이걸 어떻게 검증하죠?

P: 저장하고 CLI 한 줄.

eulerstack --lang ko validate --preset deepseek_v3_clone.yml --report

예상 출력 (요약):

[validation] schema ... PASS
[validation] cross-field ... PASS
  - mixer.latent_dim=384 < d_model=768 ✓
  - d_model=768 % n_heads=12 == 0 ✓
  - head_dim = 64 (32–256 권장 범위 내) ✓
[params] estimated: ~85M
  - embedding (learned + tied): ~25M
  - attention × 12: ~32M   (MLA 절감 반영)
  - ffn (swiglu) × 12: ~28M
[realism] PASS
  - head_dim: 64 (권장 범위)
  - rope_theta: 500000 (long-context 정당화 정상)
  - n_kv_heads == n_heads: GQA 미사용, MLA 만으로 KV 압축 OK
OK: deepseek_v3_clone.yml is valid.

S: 파라미터 수 85M 이 맞나요? 산수해봐도 되나요?

P:

총 134M. CLI 의 estimate 는 대략적이라 ±20% 정도 차이는 있어.

S: 실제로 HF 모델로 만들어보죠.

P:

eulerstack --lang ko compile --preset deepseek_v3_clone.yml --output-dir ./v3_clone
from transformers import AutoModelForCausalLM
from eulerstack.hf.auto_register import register_eulerstack_auto_classes

register_eulerstack_auto_classes()
model = AutoModelForCausalLM.from_pretrained("./v3_clone", trust_remote_code=True)

# MLA 가 실제로 들어갔는지 확인
from eulerstack.blocks.attention import CausalSelfAttention
mla_layers = [m for m in model.modules()
              if isinstance(m, CausalSelfAttention) and m.latent_dim is not None]
print(f"MLA layers: {len(mla_layers)}, latent_dim={mla_layers[0].latent_dim}")
# → MLA layers: 12, latent_dim=384

# Projection 이름도 확인
print([n for n in mla_layers[0].state_dict() if 'proj' in n or 'latent' in n])
# → ['q_proj.weight', 'kv_latent.weight', 'k_up.weight', 'v_up.weight', 'out_proj.weight']

state_dictkv_latent / k_up / v_up 이 나타나면 진짜 MLA 가 인스턴스화된 거야.

1.5 파라미터 튜닝 가이드

S: latent_dim 을 얼마로 설정할지 기준이 있나요?

P: 아래 표가 실전 가이드:

latent_dim KV 캐시 절감 Quality 영향 용도
d_model 0% 표준 MHA 와 동일 (의미 없음 — validator 가 거부)
d_model × 0.75 ~25% ~0 보수적 — MLA 를 처음 도입할 때
d_model × 0.5 ~75% ~0 권장 출발점. DeepSeek 기본
d_model × 0.33 ~83% 미세 저하 128K+ 메모리 타이트할 때
d_model × 0.25 ~87.5% 눈에 띄는 저하 극한 상황 — A/B 검증 필수
d_model × 0.125 ~94% 심각한 저하 피하기

S: head_dim 과는 어떤 관계인가요?

P: latent_dimhead_dim정수배로 두는 게 권장돼. KV 캐시의 stride 가 깔끔하게 떨어지고 FlashAttention-like 커널이 잘 맞기 때문. 예: d_model=768, head_dim=64latent_dim ∈ {128, 192, 256, 384, 512}.

S: MLA 를 쓰면서 max_seq_len 을 얼마까지 늘릴 수 있나요?

P: MLA 이득은 컨텍스트가 길수록 커져. 짧은 컨텍스트 (2K 이하) 에선 굳이 쓸 필요 없고, 16K 부터 눈에 띄는 차이가 나고, 128K 에선 사실상 MLA 없이 A100/H100 single-node 서빙이 어려워. DeepSeek 는 128K 를 기본 운영점으로 잡음.

S: rope_thetamax_seq_len 관계는?

P: 경험 법칙:

rope_theta ≈ max(10000, 10000 × (max_seq_len / 2048)²)

1.6 함정과 디버깅

S: 제가 MLA YAML 을 쓰면서 밟을 가장 흔한 지뢰가 뭔가요?

P: 여섯 가지야.

함정 1. latent_dim >= d_model

ValidationError (R): mla_latent_dim_range — latent_dim=1024 exceeds d_model=768
Fix: set latent_dim < d_model (commonly d_model/2)

→ 원인: MLA 의 의미는 "압축" 이므로 d_model 이상은 말이 안 됨.

함정 2. latent_dim 이 홀수 혹은 head_dim 의 배수가 아님

warning: mla_latent_dim_range — latent_dim=100 is not a multiple of head_dim=64

→ validate 는 통과하지만 KV cache stride 가 깨져 미묘한 성능 저하. latent_dim 을 64 의 배수로.

함정 3. "MLA 만 쓰면 long context 는 끝" 이라는 착각 → MLA 는 메모리 를 줄일 뿐, rope_theta 가 작으면 여전히 position 식별력이 떨어져. 함께 조정.

함정 4. MLA 레이어를 kv_cache: false 로 둠

state:
  kv_cache: false        # ← MLA 를 쓸 이유가 없음

→ MLA 의 존재 이유가 KV 캐시 압축인데 캐시를 끄면 자원만 낭비. 학습 시엔 false 여도 OK 지만 inference 배포는 반드시 true.

함정 5. GQA 와 MLA 의 과도한 중복

n_kv_heads: 2         # 4:1 GQA
attention:
  latent_dim: 128     # 추가로 MLA

→ 이론적으로 가능하지만 quality 가 빠르게 저하. MLA 단독으로 충분. n_kv_heads 는 n_heads 와 같게 두는 게 권장.

함정 6. "MLA 를 쓰면 사전학습 weight 를 가져올 수 있다" 는 착각 → EulerStack 은 랜덤 초기화 모델을 만들 뿐. DeepSeek-V3 의 실제 사전학습 weight 는 별도로 다운로드해서 converter 를 써야 해. v1.2 로드맵.

1.7 다른 primitive 와 결합

S: MLA 를 다른 primitive 와 결합할 수 있나요?

P: 네 가지 조합이 실전적:

1.7.1 MLA + MoE (DeepSeek-V3 실제 구성)

layer_templates:
  mla_moe:
    mixer:
      type: attention
      attention: { latent_dim: 384 }
    ffn:
      type: moe
      moe: { experts: 32, top_k: 3, router: softmax, capacity_factor: 1.25 }

직교적 — MLA 는 attention 의 메모리, MoE 는 FFN 의 capacity. 둘이 같은 자리를 두고 싸우지 않아. DeepSeek-V3 가 이 조합을 쓰고 있음.

1.7.2 MLA + Jamba-style hybrid

layer_schedule:
  - template: mla_decoder
    repeat: 6
  - template: mamba_block
    repeat: 18

보완적 — 전체 레이어 수의 1/4 만 MLA, 나머지는 Mamba. 전역 retrieval 이 필요한 레이어에서만 MLA 가 KV 캐시를 절약. Samba / Jamba 의 1.5 업그레이드 버전으로 볼 수 있어.

1.7.3 MLA + Mixture-of-Depths

layer_schedule:
  - template: mla_decoder
    repeat: 24
    depth_gating:
      enabled: true
      capacity: 0.5
      router: top_k

시너지 — MoD 는 토큰의 50% 만 해당 레이어를 거치게 함. MLA 의 KV 캐시 는 지나가는 토큰만큼만 생성되므로, MoD 의 토큰 절감이 MLA 의 메모리 절감에 직접 곱해짐. 128K 컨텍스트에서 이 조합이 매우 강력.

1.7.4 MLA + execution_modes (R1 + V3)

execution_modes:
  - { name: think, max_tokens: 16384, kv_share: true,  loss_weight: 0.0 }
  - { name: answer, max_tokens: 2048, loss_weight: 1.0 }

이상적 — R1-style reasoning 은 think 구간이 매우 길어서 (16K+) KV 캐시가 병목. MLA 가 이를 직접 해결. 차세대 추론 모델의 표준 구성으로 예상됨.

1.8 심화 읽을거리


Case 2. Jamba — Mamba × Attention × MoE 하이브리드

2.1 배경과 동기

S: Jamba 는 Mamba 와 attention 을 섞는다는데, 단순히 번갈아 쌓는 거 아닌가요?

P: 그보다 깊은 이야기야. 2023~24 년은 SSM(state-space models) 르네상스 였어. S4 → H3 → Mamba 로 이어지는 흐름이 "attention 은 필요 없다" 는 주장을 강하게 밀었지.

S: Mamba 의 어떤 점이 그랬나요?

P: Mamba 는 선형 시간 O(N) 에 시퀀스를 처리하면서도 표현력이 attention 에 근접했어. 구체적으로:

S: 그런데 왜 "attention 이 필요없다" 가 정답이 아니었나요?

P: Mamba 의 약점이 있어. In-context learning (ICL) 이야. "문맥에서 주어진 k/v 쌍을 정확히 지목해서 복원하는" 능력이 attention 에 크게 못 미쳐.

S: 왜요? Mamba 도 과거를 기억하는 RNN 이잖아요.

P: 기억하지만 "compressed" 기억이야. attention 의 KV 캐시는 모든 과거 토큰을 손실 없이 들고 있고 softmax 로 임의의 토큰을 지목 가능. Mamba state 는 고정 크기 latent 에 모든 과거를 압축해서 들고 있어. 이 압축이 ICL 을 깎아먹어.

실험적으로 Mamba 만으로 구성된 모델은: - LAMBADA (next-word prediction): attention 과 비슷 - Needle-in-haystack 128K: attention 에 크게 뒤처짐

S: 그래서 Jamba 가 등장한 거군요.

P: 정확해. AI21 Labs 가 2024-03 에 Jamba-1 을 발표하면서 "Mamba 와 attention 을 섞자" 는 아이디어를 검증했어. 구체적 비율이 Mamba 7 : Attention 1.

S: 7:1 이 어디서 나온 거죠?

P: 실험적으로 튜닝한 값이야. Needle task 재현이 가능한 최소 비율이 1/8 (12.5%) attention 이었어. 그 이하로 내리면 ICL 이 무너지고, 더 올리면 Mamba 의 속도 이점이 줄어. sweet spot 이 7:1.

S: Jamba 가 다른 유사 연구와 어떻게 다른가요?

P:

연구 구성 특징
Striped Hyena (Together AI, 2023) Hyena + Attn Hyena 를 "attention 대체" 로
Samba (Microsoft, 2024) Mamba + SWA 1:1 슬라이딩 윈도우 로컬 + SSM 글로벌
Jamba (AI21, 2024) Mamba + Attn 7:1 + MoE FFN 을 MoE 로 대폭 확장
Jamba 1.5 (AI21, 2024-08) 같은 구조 + 훈련 스케일업 256K context

Jamba 의 특별한 점은 FFN 자리에 MoE 를 얹은 것. attention 은 7:1 로 이미 줄였으니까, FFN 에 capacity 를 몰아서 모델의 지식 저장력을 극대화.

2.2 메커니즘 심화

S: Mamba 와 attention 이 실제로 어떻게 섞이는지 블록 다이어그램으로 보고 싶습니다.

P: 32 layer Jamba 의 schedule 은 이래:

Layer 1:  Mamba    Layer 5:  Mamba     Layer 9:  Mamba     ...
Layer 2:  Mamba    Layer 6:  Mamba     Layer 10: Mamba
Layer 3:  Mamba    Layer 7:  Mamba     Layer 11: Mamba
Layer 4:  Attn     Layer 8:  Attn      Layer 12: Attn

  ↑ 이 pattern 이 8 번 반복 → 32 layers, 8 개 attention

8 번째마다 attention 이 오는 건데, 그 자리마다 FFN 을 MoE 로 대체해:

Layer 4 (Attn + MoE):
  x → norm → attention → + residual
    → norm → MoE(8 experts, top-2) → + residual

Layer 1-3 (Mamba):
  x → norm → mamba2 → + residual
    → norm → gated_mlp(SwiGLU) → + residual

S: 왜 Mamba 레이어의 FFN 은 MoE 가 아니고 일반 gated_mlp 인가요?

P: 두 가지 이유.

  1. Mamba 자체가 "mixer+FFN 결합"에 가까움. Mamba 블록은 state update 도 있고 input-dependent gating 도 있어서 attention 처럼 "순수 mixer" 가 아니야. 여기다 MoE 를 얹으면 redundancy.
  2. MoE 는 attention 과 "서로 다른 축" 의 capacity. attention 은 토큰 간 정보 교환, MoE 는 토큰별 처리 변형. 같이 두면 상호 보완.

S: in-context learning 은 왜 attention 1/8 만으로 충분한가요?

P: needle task 를 풀려면 "특정 과거 토큰을 정확히 지목하는" 능력이 모델 전체에 한 번씩만 있으면 돼. 8 개 attention 레이어가 있으니까 8 번의 "글로벌 lookup 기회" 가 주어지는 셈. 그 사이에 끼인 Mamba 는 토큰 수준의 패턴 인식과 지역성을 맡아.

2.3 YAML 유도

S: 빈 YAML 에서 출발해 Jamba 구조를 만들어 봅시다.

P: 먼저 모델 메타.

schema_version: 1

model:
  name: jamba-clone
  d_model: 768               # 교육용. Jamba-1 은 d_model=4096
  vocab_size: 32000
  max_seq_len: 32768
  n_heads: 12
  n_kv_heads: 4              # GQA 3:1 — attention 레이어 KV 추가 절감
  mlp_ratio: 4

tokenizer_contract:
  type: hf
  pretrained: gpt2
  add_bos: true
  add_eos: true

S: n_kv_heads: 4 는 GQA 3:1 이죠. Jamba 가 GQA 를 쓰나요?

P: 사용해. attention 레이어가 1/8 밖에 안 되긴 해도 그 몇 안 되는 레이어가 KV 캐시의 전부야. GQA 로 추가 압축하는 게 합리적. 실제 Jamba 는 GQA 4:1 을 씀.

S: 다음 embedding.

P:

embedding:
  type: learned
  positional: rope
  rope_theta: 500000.0
  tie_word_embeddings: true

S: 잠깐, Mamba 는 positional encoding 이 필요 없다고 들었는데요?

P: Mamba 는 recurrent 라 position 정보를 state 에 암묵적으로 담아. 하지만 attention 레이어는 필요해. Jamba 는 attention 레이어에만 RoPE 를 적용해. YAML 에서 positional: rope 를 선언하고 Mamba 는 자동으로 무시.

S: 이제 템플릿.

P: 두 개 필요해. Mamba 용 하나, Attention+MoE 용 하나.

layer_templates:
  mamba_block:
    mixer:
      type: mamba
      mamba:
        variant: mamba2       # Mamba2 (SSD 계산)
        d_state: 128          # SSM 내부 state 차원
        d_conv: 4             # local conv kernel size
        expand: 2             # internal expansion ratio
    ffn:
      type: gated_mlp
      activation: swiglu
    norm: { type: rmsnorm, position: pre }
    state: { ssm_state: true, kv_cache: false }

  attn_moe:
    mixer:
      type: attention
      attention:
        qkv_bias: false
    ffn:
      type: moe
      moe:
        experts: 8
        top_k: 2
        router: softmax
        capacity_factor: 1.25
        z_loss: 0.001
    norm: { type: rmsnorm, position: pre }
    state: { kv_cache: true }

S: d_state: 128 이 큰 건가요 작은 건가요?

P: Mamba2 기본이 64~128 이야. d_state 가 클수록 "기억 용량" 이 크지만 속도는 느려져. Jamba 는 128 로 잡았어.

S: capacity_factor: 1.25 는요?

P: MoE 의 load balancing 파라미터. 1.0 이면 각 expert 가 정확히 tokens / experts 개 토큰을 받을 수 있는 공간만 가져. 1.25 는 25% 버퍼를 주는 거야. 훈련 중에 라우터가 불균등하게 배치해도 overflow 가 덜 생겨. 추론 시엔 1.0 으로 내려도 안전.

S: schedule 이 핵심이네요.

P: 이게 Jamba 의 정체성이야.

layer_schedule:
  # 8 반복 × 4 layer (Mamba×3 + Attn+MoE×1) = 32 layers
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1

S: 이렇게 길게 반복하는 게 좀 지저분한데요.

P: let: 을 쓰면 훨씬 깔끔해져.

let:
  mamba_per_cycle: 3
  total_cycles: 8

# schedule 을 동적으로 작성할 수는 없지만 (v1 은 Level 1 산술만 지원),
# n_layers 총합 ${let.mamba_per_cycle * 8 + 8} 를 확인하는 용도로 활용 가능.

실제로 YAML 수준에선 반복을 for-loop 처럼 쓸 수 없어. 대신 schedule 의 긴 목록을 수용 가능한 형태로 받아들이는 것이 설계 언어 (ADL) 수준 설명성의 trade-off. 같은 문제가 HDL 계열에도 있었어 — Verilog 초기엔 반복 구조가 없다가 generate ... endgenerate 블록이 도입됐고, VHDL 은 generate 스테이트먼트로 해결했지. EulerStack 도 schedule: 에 반복 iterator 를 도입하는 것이 v1.x 로드맵에 있어.

S: head 와 훈련 힌트는?

P:

head:
  type: causal_lm
  tie_weights: true

training_hints:
  init: normal_0.02
  dropout: 0.0
  checkpointing: true          # 32 layers 는 메모리 절약을 위해 checkpointing ON
  seed: 1234

compatibility:
  compile_target: huggingface

2.4 검증 & 컴파일

S: validate 하면 뭐가 나오죠?

P:

eulerstack validate --preset jamba_clone.yml --report

[validation] schema ... PASS
[validation] cross-field ... PASS
  - mamba_block.state.ssm_state=true ↔ mixer=mamba ✓
  - attn_moe.state.kv_cache=true ↔ mixer=attention ✓
  - MoE: top_k=2 ≤ experts=8 ✓
[params] estimated: ~1.04B
  - embedding: ~25M
  - mamba layers (24): ~240M  (state + gating)
  - attn+moe layers (8): ~280M  (attn + 8×moe expert)
  - ffn (gated_mlp, mamba side): ~480M  (SwiGLU 가 큼)
  - norms, output: ~15M
[realism] PASS
  - positional: rope + mamba mixer: warning 없음 (attention 이 섞여 있음)
  - MoE expert ratio: 8 × 1/4 layers = 정상
OK: jamba_clone.yml is valid.

S: 왜 활성 파라미터는 적은가요?

P: MoE 덕분. 총 1.04B 지만, 매 forward 에서 활성화되는 건:

합산 활성 ~330M. "작은 모델 비용에 큰 모델 용량" 이라는 MoE 의 전형적 이득. DeepSeek-V3 가 이걸 극한까지 밀어 671B 총 / 37B 활성을 만든 거.

2.5 파라미터 튜닝 가이드

S: Mamba:Attention 비율을 왜 7:1 로 두나요?

P: 아래가 실전 가이드:

Mamba:Attn ICL 성능 (needle) 속도 용도
0:1 (순수 attn) 최고 느림 (O(N²)) 기본선. Llama 계열
1:1 (Samba 비율) 거의 최고 중간 균형. 긴 context 우선
3:1 상위 빠름 일반 권장
7:1 (Jamba 비율) 충분 빠름 Jamba 기본
15:1 저하 눈에 띔 매우 빠름 위험 — needle 실패
1:0 (순수 Mamba) 매우 낮음 최고 특수 용도만

S: MoE 의 expertstop_k 는?

P:

experts × top_k 활성 비율 대표 모델 특징
8 × 2 25% Mixtral, Jamba 표준. 안정적
16 × 2 12.5% Phi-3.5-MoE 더 sparse, 더 capacity
64 × 6 9.4% DeepSeek-V2 fine-grained
256 × 8 3.1% DeepSeek-V3 극단적 fine-grained

"fine-grained" = expert 개수 많게, top_k 도 함께 키우는 전략. expert 당 차원이 작아져서 각 expert 가 더 좁은 전문성을 가짐. DeepSeek 이 이걸 밀었고 성능 이득이 입증됨.

S: capacity_factor 는?

P:

2.6 함정과 디버깅

함정 2.1. Mamba 만 쓰고 ICL 이 안 되는 현상 → 증상: needle benchmark 점수 급락. 원인: attention 비율 0. 해법: 최소 1/8 은 attention 유지.

함정 2.2. ssm_state: false 로 설정

state:
  ssm_state: false     # ← Mamba 인데 state 가 없음

→ Mamba 는 state 가 정체성. cross-field validator 가 CompatibilityError 를 던져. ssm_state: true 필수.

함정 2.3. Mamba 레이어에 attention: { latent_dim: ... } 을 얹음

mixer:
  type: mamba
  attention:             # ← mamba 인데 attention sub-object
    latent_dim: 128

→ validator 가 "sub-object 'attention' not allowed when type='mamba'" 로 거부.

함정 2.4. MoE 의 top_k > experts

moe: { experts: 4, top_k: 8 }   # ← 불가능

ValidationError (R14): top_k=8 > experts=4.

함정 2.5. positional: none 을 Attention 이 섞인 모델에 설정 → 가능은 하지만 attention 레이어가 position 을 구분 못 해서 훈련이 불안정. Jamba 는 positional: rope 가 정답. Mamba 는 자동으로 무시.

함정 2.6. capacity_factor: 1.0 으로 훈련 시작 → 초기 라우팅이 매우 불균형 → 많은 토큰이 drop → loss 가 튐. 1.25 로 시작, 안정되면 내림.

함정 2.7. MoE 를 매 레이어에 둠

# attn_moe 를 32 번 반복
- template: attn_moe
  repeat: 32

→ 총 파라미터 8 배 폭증, 활성은 2/8 만. Jamba 철학은 "MoE 는 attention 레이어에만". Mixtral 처럼 모든 layer 에 MoE 를 둘 거면 따로 설계해야 함.

2.7 다른 primitive 와 결합

2.7.1 Jamba + MLA — 차세대 추론 최적화

layer_templates:
  attn_mla_moe:
    mixer:
      type: attention
      attention:
        latent_dim: 384       # MLA
    ffn:
      type: moe
      moe: { experts: 8, top_k: 2, router: softmax, capacity_factor: 1.25 }

강력 — Jamba 의 1/8 attention 레이어가 KV 캐시 전부인데, 그 레이어에 MLA 로 추가 압축. 이론적으로 DeepSeek-V3 × Jamba.

2.7.2 Jamba + execution_modes

execution_modes:
  - { name: think, max_tokens: 8192, kv_share: true, loss_weight: 0.0 }
  - { name: answer, max_tokens: 2048, loss_weight: 1.0 }

보완적 — 추론 단계에서 긴 think 구간을 Mamba 가 O(N) 으로 처리, 최종 answer 는 attention 레이어가 retrieval. 메모리 / 속도 최적.

2.7.3 Jamba + Titans memory

layer_templates:
  attn_with_mem:
    mixer: { type: attention, attention: {} }
    ffn: { type: moe, moe: {...} }
    memory:
      type: neural_memory
      update_at_inference: true
      params: { hidden: 1024 }
      inner_lr: 0.001

실험적 — attention 레이어에만 Titans memory 추가. 대화형 세션에서 추론 시점 학습이 가능. 연구 주제로 좋음.

2.7.4 Jamba + Mixture-of-Depths

layer_schedule:
  - template: mamba_block
    repeat: 3
  - template: attn_moe
    repeat: 1
    depth_gating:
      enabled: true
      capacity: 0.5
      router: top_k

시너지 — attention 레이어만 MoD 로 skip. 토큰의 50% 만 retrieval 을 거침. "단순한 토큰은 Mamba 로, 어려운 토큰만 attention 으로" 라는 해석.

2.8 심화 읽을거리


Case 3. DeepSeek-R1 — 2-phase reasoning (execution_modes)

3.1 배경과 동기

S: R1 은 "추론 먼저, 답변 나중" 하는 모델이라는데, 아키텍처가 바뀐 건가요?

P: 정답은 거의 안 바뀌었다 야. R1 의 아키텍처는 DeepSeek-V3 와 사실상 동일 (MLA + fine-grained MoE). 바뀐 건 훈련과 생성 전략이지 구조가 아니야.

S: 그런데 왜 이렇게 화제인가요?

P: 2024 년 말, OpenAI 가 o1 을 발표했어. 긴 "chain-of-thought" 추론을 RL 로 학습했다는 블랙박스 모델. 업계는 "어떻게 하는지?" 로 술렁였지. 2025-01 에 DeepSeek 이 R1오픈 소스 로 공개하면서 "o1 이 하는 건 이런 것" 을 증명했어. 그 방법이:

  1. Base 모델 (DeepSeek-V3) 에 <think> 태그 기반 장문 추론 을 할 수 있게 훈련
  2. 추론 단계에서 RL (GRPO 변형) 로 "정답에 도달하는 추론" 을 학습
  3. 생성 시 think 단계는 사용자에게 숨기고 answer 만 출력

S: 그럼 아키텍처는 정말 그냥 V3 인가요?

P: 정확해. 핵심 변경은 학습 레시피 + 생성 메타데이터 야. R1 이 야기한 진짜 혁신은 "RL 만으로 추론 능력을 만들 수 있다" 는 증명. 아키텍처 논문이라기보다 훈련 논문이지.

S: 그러면 EulerStack 은 R1 을 어떻게 표현하나요?

P: v1 은 이런 상황을 위해 execution_modes + transition 이라는 선언형 메타데이터를 제공해. 아키텍처는 그대로 두고 "이 모델은 2-phase reasoning 을 한다" 는 계약을 config 에 새겨두는 거야. 훈련 스크립트와 generate() 가 이 메타데이터를 읽어서 동작해.

S: 왜 이걸 아키텍처 필드가 아니라 execution_modes 로 분리했나요?

P: 세 가지 이유:

  1. 모델 구조가 실제로 변하지 않으므로, 구조 필드에 넣으면 거짓말이 돼.
  2. 여러 종류의 phase 전략이 있어 — R1 (think/answer), Quiet-STaR (per-token rationale), o3 (multi-round). 메타데이터로 두면 전략 간 교체가 YAML diff 수준.
  3. 훈련 / 서빙 파이프라인이 일관된 계약을 읽기 위해 — 훈련은 loss_weight 을, 서빙은 visible_to_user 를, eval 은 kv_share 를 본다. 단일 metadata 블록으로.

3.2 메커니즘 심화

S: R1 의 실제 훈련 과정을 더 자세히 보여주세요.

P: 4 단계로 나뉘어.

Stage 1 — Cold start

입력:  "Prove that √2 is irrational."
출력:  "<think>
          Assume √2 = a/b in lowest terms...
          Then a² = 2b², so a is even.
          Let a = 2c, then 4c² = 2b², b² = 2c²
          So b is also even, contradiction.
          </think>
        <answer>
          √2 is irrational because assuming rationality
          leads to a contradiction.
          </answer>"

규칙 기반 reward 로 supervised fine-tune.

Stage 2 — Reasoning-focused RL - <think> 구간의 길이와 "해결 정확도" 를 reward 로 RL (GRPO) - <answer> 는 고정된 template 에 맞춰 출력

Stage 3 — Rejection sampling + SFT - RL 로 생성한 궤적 중 "정답에 도달한" 것만 골라 재훈련 데이터로

Stage 4 — Full RLHF + preference - <answer> 의 helpfulness / harmlessness

S: <think> 구간이 얼마나 긴가요?

P: R1 의 think 는 평균 2000~4000 토큰 이고 어려운 문제에선 16K+ 까지 늘어나. 전체 컨텍스트가 32K 일 때 think 가 28K 를 차지하는 경우도 있어.

S: 그래서 max_seq_len 이 크게 필요하군요.

P: 응. 그리고 KV share 가 중요해져. think 를 생성하면서 쌓인 KV 를 answer 단계에서 재사용하지 않으면 메모리 낭비가 커. R1 은 share.

S: Quiet-STaR 은 R1 과 어떻게 다른가요?

P: Zelikman et al. (NeurIPS 2024). 다른 접근이야:

R1 Quiet-STaR
Phase 구조 긴 think 한 번, 그 다음 answer 매 토큰마다 작은 rationale
Rationale 길이 2K~16K 16 (짧음)
Transition <think_end> 특수 토큰 고정 길이 스텝
Training signal 최종 정답 reward 다음 토큰 예측 loss 감소

두 접근은 EulerStack 의 같은 execution_modes 스키마로 표현 가능해.

3.3 YAML 유도

S: R1 YAML 을 빈 파일부터 만들어 봅시다.

P: base 구조는 V3 와 동일 (MLA + MoE). 여기서는 MLA 만 쓸게.

schema_version: 1

model:
  name: r1-clone
  d_model: 1024              # 교육용 scale
  vocab_size: 32000
  max_seq_len: 16384         # think(8K) + answer(2K) + 여유
  n_heads: 16
  n_kv_heads: 4              # GQA
  mlp_ratio: 4

tokenizer_contract:
  type: hf
  pretrained: gpt2
  add_bos: true
  add_eos: true

embedding:
  type: learned
  positional: rope
  rope_theta: 500000.0       # long context
  tie_word_embeddings: true

S: max_seq_len 을 어떻게 잡나요?

P: sum(execution_modes.max_tokens) + buffer 로 잡아. think 8192 + answer 2048 + 버퍼 6144 = 16384. execution_modes_budget 이라는 realism rule 이 이걸 자동 체크해.

S: 다음 템플릿.

P: MLA attention 하나.

layer_templates:
  reasoner:
    mixer:
      type: attention
      attention:
        latent_dim: 512       # d_model/2 — long think 에서 KV 캐시 절감
        qkv_bias: false
    ffn:
      type: gated_mlp
      activation: swiglu
    norm: { type: rmsnorm, position: pre }
    state: { kv_cache: true }

MoE 를 추가할 수도 있지만 (실제 R1 은 MoE), 튜토리얼이니까 단순화.

layer_schedule:
  - template: reasoner
    repeat: 24

head:
  type: causal_lm
  tie_weights: true

compatibility:
  compile_target: huggingface

S: 여기까지는 그냥 V3 네요. R1 은 어디서 나타나나요?

P: 맨 밑에 execution_modes 가 추가되는 것 이 R1 의 전부야.

execution_modes:
  - name: think
    max_tokens: 8192
    kv_share: true                # answer 가 think 의 KV 를 재사용
    loss_weight: 0.0              # primary LM loss 에서 제외
    visible_to_user: false        # 서빙 시 사용자에게 숨김
    per_token_rationale: false    # R1 은 R1, Quiet-STaR 이 true

  - name: answer
    max_tokens: 2048
    kv_share: false
    loss_weight: 1.0              # 정답은 LM loss 에 포함
    visible_to_user: true
    per_token_rationale: false

transition:
  type: special_token
  token: "<think_end>"

S: kv_share: true 의 의미를 다시 짚어주세요.

P: think 단계를 생성할 때 쌓은 KV 캐시를, answer 단계가 시작될 때 그대로 이어서 사용 한다는 뜻. 만약 false 면 answer 가 빈 KV 로 시작해서 think 를 "다시 읽어야" 해 — 낭비. R1 은 당연히 true.

S: per_token_rationale 은?

P: Quiet-STaR 전용 플래그야. R1 은 false. Quiet-STaR 같으면:

execution_modes:
  - name: rationale
    max_tokens: 16
    per_token_rationale: true    # 매 토큰마다 작은 rationale
    loss_weight: 0.1
    visible_to_user: false

  - name: answer
    max_tokens: 256
    loss_weight: 1.0
    visible_to_user: true

S: transition.type 은 뭐가 있죠?

P: 두 가지:

special_token 을 기본으로 쓰고, budget_exhausted 는 fallback 으로 이해하면 돼. 많은 레시피가 "둘 중 먼저 발생" 으로 결합 해.

3.4 검증 & 컴파일

S: validate 는?

P:

eulerstack validate --preset r1_clone.yml --report

[validation] schema ... PASS
[validation] cross-field ... PASS
  - MLA latent_dim=512 < d_model=1024 ✓
  - d_model=1024 % n_heads=16 == 0 (head_dim=64) ✓
[validation] execution_modes ... PASS
  - 2 modes: [think, answer]
  - transition: special_token ("<think_end>") ✓
  - execution_modes_budget: 8192 + 2048 = 10240 ≤ max_seq_len=16384 ✓
[params] estimated: ~480M
[realism] PASS
  - kv_share=true + think_before_answer: 표준 R1 패턴
OK: r1_clone.yml is valid.

S: compile 하면 config.v1_extensions 에 뭐가 들어가나요?

P:

from transformers import AutoConfig
cfg = AutoConfig.from_pretrained("./r1_clone", trust_remote_code=True)
print(cfg.v1_extensions)
# {
#   'execution_modes': [
#     {'name': 'think', 'max_tokens': 8192, 'kv_share': True,
#      'loss_weight': 0.0, 'visible_to_user': False,
#      'per_token_rationale': False},
#     {'name': 'answer', 'max_tokens': 2048, 'kv_share': False,
#      'loss_weight': 1.0, 'visible_to_user': True,
#      'per_token_rationale': False},
#   ],
#   'transition': {'type': 'special_token', 'token': '<think_end>'},
#   'schedule_kinds': [...]
# }

이 필드가 훈련 스크립트서빙 generate 가 각자 읽는 공통 계약.

3.5 파라미터 튜닝 가이드

S: think.max_tokens 는 얼마로?

P: 문제 난이도에 달려있어. 가이드:

용도 think max_tokens answer max_tokens
간단한 QA (역사, 상식) 512 512
수학 증명 중급 2048 1024
수학/코딩 olympiad 8192 2048
Agent 다단계 작업 16384 4096
연구 수준 난제 32768 8192

R1 은 8K think 를 기본으로 권장.

S: loss_weight: 0.0 이 think 를 학습 안 하는 건가요?

P: primary LM loss 에서 제외한다는 뜻. RL stage 에서 별도 reward 로 학습. LM loss 로 think 를 학습하면 "긴 문장을 외우는" 효과가 생겨서 일반화가 안 돼.

Quiet-STaR 은 다르게 써: loss_weight: 0.1 — 적은 가중치로 LM loss 에 반영. "다음 토큰 예측이 잘 되는 rationale" 을 직접 학습하려는 전략.

S: kv_share 는 언제 false 인가요?

P: 두 경우:

  1. multi-turn reasoning — 각 turn 이 독립적인 think 를 한다면 think KV 를 세션 간 공유하지 않음. 이때 think 는 false 가 될 수 있음.
  2. think 를 재시작하는 프로토콜 — 실패한 think 를 버리고 다시 생성.

R1 기본은 think.kv_share: true, answer.kv_share: false. (think 의 KV 를 answer 가 이어쓰지만, answer 생성이 끝나면 think KV 는 버림.)

3.6 함정과 디버깅

함정 3.1. execution_modes.sum(max_tokens) > model.max_seq_len

model: { max_seq_len: 4096 }
execution_modes:
  - { name: think, max_tokens: 8192 }    # ← 예산 초과
  - { name: answer, max_tokens: 2048 }

→ realism execution_modes_budget 경고 발생. 훈련 중 position overflow.

함정 3.2. transition.token 이 tokenizer vocab 에 없음

transition:
  type: special_token
  token: "<think_end>"         # vocab 에 추가하지 않으면 unk

→ validate 는 통과하지만 런타임에 생성 중 "<"=토큰, "think_end"=다음 토큰, 등 여러 토큰으로 쪼개져 transition 감지 실패. tokenizer 에 special token 추가 필수:

tokenizer.add_special_tokens({"additional_special_tokens": ["<think_end>"]})

함정 3.3. "execution_modes 를 선언하면 자동으로 그렇게 동작한다" 는 오해 → EulerStack 은 메타데이터만 보존. 실제 phase-aware generate 와 RL 훈련은 사용자가 이 메타데이터를 읽어 구현해야 함. 샘플 generate 루프는 examples/r1_generate.py (v1.2 로드맵) 참조.

함정 3.4. per_token_rationale: true 를 R1 모드에 설정 → R1 은 "한 번의 긴 think". per_token_rationale 은 Quiet-STaR 전용. 혼동 주의.

함정 3.5. kv_share: true 인데 think 와 answer 의 position 을 따로 카운트 → answer 가 KV 를 이어쓰니까 position index 도 이어져야 함. answer 의 첫 토큰 position = think 토큰 수 + 1. 구현 시 잊기 쉬움.

함정 3.6. "loss_weight=0 → 학습 안 됨" 이라 생각하고 RL 도 안 함 → loss_weight 은 LM supervised loss 의 가중치. RL reward 는 별도. 둘 다 0 으로 두면 진짜 학습 안 됨. R1 은 LM loss = 0, RL reward = 정답 기반 reward.

3.7 다른 primitive 와 결합

3.7.1 R1 + MLA (권장 조합)

위의 YAML 예제가 이미 이 조합. think 가 길수록 KV 캐시 가 병목이니 MLA 가 자연스러운 파트너.

3.7.2 R1 + Titans memory

layer_templates:
  reasoner_with_memory:
    mixer: { type: attention, attention: { latent_dim: 512 } }
    ffn: { type: gated_mlp }
    memory:
      type: neural_memory
      update_at_inference: true
      params: { hidden: 2048 }
      inner_lr: 0.001
      persistence: session       # think 단계에서 축적된 통찰을 answer 가 활용

실험적 — think 단계에서 memory 가 "왜 이 경로로 가는지" 를 축적하고, answer 가 그 memory 를 활용. 아직 학계 정설은 없지만 연구 주제로 흥미.

3.7.3 R1 + MoE

실제 R1 은 DeepSeek-V3 기반이라 MoE 가 들어있어. 튜토리얼 단순화를 위해 위 YAML 은 뺐는데, 실전 규모로 키우려면:

ffn:
  type: moe
  moe: { experts: 64, top_k: 6, router: softmax, capacity_factor: 1.25 }

3.7.4 R1 + Neural-ODE integrator

layer_schedule:
  - integrator:
      type: ode_rk4
      steps: 4
      body: reasoner
      output: token

연구 — think 의 추론 과정을 "ODE 흐름" 으로 해석. Chain-of-thought 를 연속 동역학 시스템으로 보는 시각. 아직 실험 단계.

3.8 심화 읽을거리


Case 4. Titans — 추론 시점에 학습하는 neural memory

4.1 배경과 동기

S: "test time 에 학습한다" 는 게 말이 되나요?

P: 정말로 돼. Titans (Behrouz, Zhong, Mirrokni — Google, 2024-2025) 는 이 아이디어를 학계 수준에서 입증한 논문이야. 핵심은:

모델 전체가 아니라, 각 레이어에 붙은 작은 "memory 모듈" 만 inner gradient step 으로 추론 시점에 업데이트한다.

S: 왜 이게 필요한가요? KV 캐시가 있잖아요.

P: KV 캐시의 한계를 보자:

메커니즘 매개변수? 시퀀스 길이에 종속? 업데이트 방식
KV cache ✗ non-parametric ✓ 선형 증가 토큰 추가만 (역진 불가)
Mamba state ✗ (있지만 고정 크기) recurrent update
Titans memory ✓ parametric gradient step

Titans 의 특별한 점은 매개변수를 가진 작은 네트워크 이고 그 매개변수가 추론 중에 gradient-based 로 업데이트된다는 거야. KV 는 "뭘 봤는지" 를 저장, Titans memory 는 "보면서 뭘 학습했는지" 를 저장.

S: 선행 연구가 있나요?

P:

연구 연도 아이디어 Titans 와의 관계
Memorizing Transformers (Wu et al.) ICLR 2022 외부 KV memory + k-NN lookup 비-parametric, Titans 의 조상
RETRO (Borgeaud et al.) ICML 2022 retrieval-augmented 외부 DB, Titans 는 내부 파라미터
Infini-attention (Google, 2024) 2024-04 compressive memory with MLP Titans 에 가장 가까움
TTT layer (Sun et al., 2024) 2024-07 mixer 전체를 inner-optim 레이어 전체 vs Titans 는 보조 모듈
Titans (Behrouz et al.) 2024-12 small MLP per layer, inner SGD at inference

Titans 는 TTT 의 "inner optimizer" 아이디어를 "보조 memory 모듈" 에 한정해 적용한 실용화 버전으로 볼 수 있어.

S: Titans 가 실제로 뭘 할 수 있나요?

P: 논문의 벤치마크에서:

일반 LLM 은 컨텍스트가 넘치면 잊지만, Titans 는 "memory 에 요약해서" 기억함. 이게 "test-time learning" 의 실체야.

4.2 메커니즘 심화

S: 수식으로 보여주세요.

P: Titans memory 모듈 M 은 작은 MLP:

M_θ(x) = W_2 · GeLU(W_1 · LayerNorm(x) + b_1) + b_2
         θ = {W_1, b_1, W_2, b_2, m_state}
         m_state ∈ R^h  ← "persistent memory vector"

Forward 는 일반 residual:

y = x + g(x) ⊙ M_θ(x)        where g(x) = sigmoid(W_g x)

g(x) 는 gate — memory 의 기여도를 토큰별로 조절.

S: "추론 시점 업데이트" 는 어떻게 하나요?

P: 매 forward 뒤에 optional hook 이 호출돼:

# 1. "Surprise" loss 계산
surprise = || M_θ(x) - x ||²       ← memory 가 현재 hidden 을 얼마나
                                      잘 재구성하는가

# 2. memory 파라미터만 (모델 본체 아님!) gradient step
θ ← θ - η · ∇_θ surprise

S: 왜 surprise 가 reconstruction MSE 인가요?

P: 정보이론적 해석이야. memory 가 x 를 잘 재구성하지 못하면 "x 는 예상 밖" → 기억할 만한 사건. 크게 업데이트해서 memory 가 x 를 더 잘 재구성하도록 학습. 반대로 예상되는 hidden 에는 update 가 작아 — 이미 "알고 있으므로".

┌───────────────────────────────────────────────────────┐
│  Surprise learning loop                                │
├───────────────────────────────────────────────────────┤
│                                                         │
│   token_t ──→ layer ──→ hidden_t                        │
│                            │                            │
│                    ┌───────┴───────┐                    │
│                    │               │                    │
│                 forward         surprise                │
│                    │               │                    │
│                    ▼               ▼                    │
│               y = x + M(x)    || M(x) - x ||²          │
│                                    │                    │
│                                    ▼                    │
│                            ∇_θ surprise                 │
│                                    │                    │
│                                    ▼                    │
│                         θ ← θ - η · ∇_θ                │
│                                                         │
└───────────────────────────────────────────────────────┘

S: 모델 본체의 gradient 는 흐르지 않나요?

P: memory 파라미터에만 흐르게 격리해. 구현 상:

# pseudo-code
with torch.enable_grad():
    for p in memory.parameters():
        p.requires_grad_(True)
    for p in base_model.parameters():
        p.requires_grad_(False)

    surprise = mse(memory(x_det), x_det)   # x_det = x.detach()
    surprise.backward()

    with torch.no_grad():
        for p in memory.parameters():
            p.sub_(p.grad, alpha=inner_lr)
            p.grad = None

x 를 detach 해서 outer graph 에 grad 가 새지 않도록 하고, memory 파라미터에만 step. outer training 은 건드리지 않아.

S: persistence 가 뭔가요?

P: memory 가 얼마나 오래 유지되는지의 정책:

persistent 는 A/B 테스트나 여러 사용자가 공유하는 서빙에서 위험해 — 모든 사용자 데이터가 한 memory 에 쌓이기 때문. 서빙은 session 이 안전.

4.3 YAML 유도

S: 빈 YAML 에서.

P:

schema_version: 1

model:
  name: titans-demo
  d_model: 768
  vocab_size: 32000
  max_seq_len: 8192
  n_heads: 12
  n_kv_heads: 4
  mlp_ratio: 4

tokenizer_contract:
  type: hf
  pretrained: gpt2
  add_bos: true
  add_eos: true

embedding:
  type: learned
  positional: rope
  rope_theta: 500000.0
  tie_word_embeddings: true

S: 이제 template 에 memory 가 들어가는 거죠.

P: 응. 핵심 부분.

layer_templates:
  attn_with_memory:
    mixer:
      type: attention
      attention:
        qkv_bias: false
    ffn:
      type: gated_mlp
      activation: swiglu
    norm: { type: rmsnorm, position: pre }
    state: { kv_cache: true }

    memory:
      type: neural_memory
      update_at_inference: true         # inference 중 grad step 활성화
      params:
        hidden: 1024                    # memory 내부 MLP 크기
      inner_lr: 0.001                   # SGD step size
      persistence: session              # per_query | session | persistent

S: hidden: 1024 는 어떻게 정해지나요?

P: memory 의 표현력 을 결정하는 핵심 파라미터야. 권장 범위:

hidden 파라미터 추가 (d=768 기준) 용도
256 ~0.4M/layer 최소. memory 가 거의 비활성
512 ~0.8M/layer 가벼움. fact 암기 정도
1024 ~1.6M/layer 표준 권장. Titans 논문
2048 ~3.2M/layer 큰 표현력. 장문 요약
4096 ~6.4M/layer 과도. 오버피팅 위험

d_model 의 1~2 배가 sweet spot.

S: inner_lr: 0.001 은?

P: 훈련 시 outer LR (AdamW 3e-4) 보다 커도 괜찮아. inner step 은 memory 만 건드리고, step 하나로 끝나기 때문에:

Titans 논문은 0.001 기준.

S: 모든 레이어에 memory 를 달아야 하나요?

P: 아니. 선택적이야. 예를 들어 24 layer 중 끝 8 layer 만:

layer_templates:
  plain_attn:        # memory 없음
    mixer: { type: attention, attention: {} }
    ffn:   { type: gated_mlp }

  attn_with_memory:  # memory 있음
    mixer: { type: attention, attention: {} }
    ffn:   { type: gated_mlp }
    memory: { ... }

layer_schedule:
  - template: plain_attn
    repeat: 16                # 앞 16 개는 plain
  - template: attn_with_memory
    repeat: 8                 # 뒤 8 개가 memory 장착

"앞쪽은 패턴 인식, 뒤쪽은 기억 축적" 이라는 직관.

S: 전체 YAML.

P:

layer_schedule:
  - template: attn_with_memory
    repeat: 18

head:
  type: causal_lm
  tie_weights: true

compatibility:
  compile_target: huggingface

4.4 검증 & 컴파일

S: validate 출력은?

P:

[validation] schema ... PASS
[validation] memory ... PASS
  - type: neural_memory ∈ {neural_memory, associative, retrieval} ✓
  - persistence: session ∈ {per_query, session, persistent} ✓
  - params.hidden: 1024 > 0 ✓
[params] estimated: ~310M
  - base attention + ffn: ~280M
  - Titans memory × 18 layers: ~29M
[realism] PASS
OK: titans_demo.yml is valid.

S: 실제로 로드해서 memory 가 살아있는지 확인하려면?

P:

from transformers import AutoModelForCausalLM
from eulerstack.hf.auto_register import register_eulerstack_auto_classes
from eulerstack.components.titans_memory import TitansMemoryModule

register_eulerstack_auto_classes()
model = AutoModelForCausalLM.from_pretrained("./titans_demo",
                                              trust_remote_code=True)

mems = [m for m in model.modules() if isinstance(m, TitansMemoryModule)]
print(f"Titans memory modules: {len(mems)}")
# → Titans memory modules: 18
print(f"params per memory: {sum(p.numel() for p in mems[0].parameters()):,}")
# → params per memory: ~1.6M

S: inference-time update 는 어떻게 호출하나요?

P: 모델에 표준 hook 이 달려있어.

model.eval()
ids = tokenizer("Paris is the capital of France.",
                return_tensors="pt").input_ids

# 1. forward
out = model(ids, output_hidden_states=True)

# 2. 표준 hook — 이 한 줄이 "test-time learning" 그 자체
surprise = model.step_memory_at_inference(out.hidden_states[-1])

print(surprise)
# {
#   'eulerstack.layers.0.titans_memory': 0.4231,
#   'eulerstack.layers.1.titans_memory': 0.3894,
#   ...
#   'eulerstack.layers.17.titans_memory': 0.2013,
# }

반환값은 각 레이어 memory 의 surprise loss. 시간이 지나면서 이 값이 감소 하는 경향 을 보여야 해 — memory 가 점점 더 잘 "기억" 하고 있다는 뜻.

4.5 파라미터 튜닝 가이드

S: memory 를 모델의 어느 깊이에 둘까요?

P: 두 가지 접근:

전략 A — "모든 레이어" (Titans 논문 기본)

# 24 layer 모두 memory
layer_schedule:
  - template: attn_with_memory
    repeat: 24

총 파라미터 증가 대략 10~15%. 모든 레이어가 기여.

전략 B — "후반부만" (효율 중심)

layer_schedule:
  - template: plain_attn
    repeat: 18
  - template: attn_with_memory
    repeat: 6       # 뒤 25% 만

파라미터 증가 3%. 경험적으로 memory 의 효과 75% 이상 유지. 권장.

전략 C — "중간 레이어만" (hypothesis)

layer_schedule:
  - template: plain_attn
    repeat: 8
  - template: attn_with_memory
    repeat: 8       # 중간 1/3
  - template: plain_attn
    repeat: 8

연구 중. 표현 학습의 "중간층" 에만 memory 를 두는 가설.

S: update_at_inference=false 를 쓰는 경우는?

P: 훈련 전용 모드 야. outer optimizer 가 memory 도 함께 학습하지만, inference 에서는 memory 가 "frozen" 상태. hook 은 no-op.

언제 쓰나?

  1. Serving 에서 memory drift 를 원하지 않을 때 — A/B 테스트 중.
  2. 훈련 중에만 학습해서 "평균 기억" 을 만들고 배포는 고정 — 모든 사용자 에게 동일한 memory.
  3. 디버깅 — inference update 가 의심될 때 비교용.

production 에서는 session persistence + update_at_inference: true 가 기본.

4.6 함정과 디버깅

함정 4.1. persistence: persistent 를 multi-user 서빙에 사용 → 보안/프라이버시 사고 직행. 모든 사용자 데이터가 같은 memory 에 축적. A 사용자의 memory 를 B 사용자가 읽는 꼴. 서빙은 반드시 session, 각 세션마다 모델 인스턴스 격리.

함정 4.2. update_at_inference=true + 매우 큰 inner_lr

memory:
  update_at_inference: true
  inner_lr: 0.5              # ← 너무 큼

→ 첫 몇 토큰 뒤 memory 가 수렴을 잃어 drift. 서빙 중 답변 품질이 "떨어지는" 현상. 0.001 이하 권장.

함정 4.3. memory 를 모든 mixer 타입에 무분별하게 적용

layer_templates:
  mamba_with_memory:
    mixer: { type: mamba, ... }
    memory: { type: neural_memory, ... }

→ 가능하지만 Mamba 는 이미 state 로 "기억" 을 하고 있어. memory 와 역할 중복으로 훈련 불안정. attention 레이어에만 두는 게 안전.

함정 4.4. params.hidden < d_model/2

memory:
  params: { hidden: 128 }   # d_model=768 기준 너무 작음

→ memory 의 표현력이 부족해 "아무 것도 기억 안 함". hidden ≥ d_model/2 를 최소 기준으로.

함정 4.5. surprise 가 처음부터 0

surprise = model.step_memory_at_inference(hidden)
print(surprise)
# {'...': 0.0001, '...': 0.0001, ...}

→ 초기화가 잘못됐을 가능성. TitansMemoryModule 은 out_proj.weight 를 0 으로 초기화하도록 돼 있어 (module 이 처음엔 identity 에 가까움). 그러면 surprise 가 작아. 몇 스텝 훈련 후 재확인.

함정 4.6. inference update 후 surprise 가 오히려 증가 → 증상: 시간이 갈수록 surprise 가 커짐. 원인 두 가지: 1. inner_lr 이 커서 overshoot — 줄여라. 2. input distribution 이 훈련과 크게 다름 — 예: 완전히 다른 언어. memory 가 적응 중이라 일시적일 수 있음. 10~20 토큰 뒤 안정되는지 관찰.

4.7 다른 primitive 와 결합

4.7.1 Titans + MLA

layer_templates:
  mla_with_memory:
    mixer:
      type: attention
      attention: { latent_dim: 384 }      # MLA
    ffn: { type: gated_mlp }
    memory:
      type: neural_memory
      update_at_inference: true
      params: { hidden: 1024 }
      inner_lr: 0.001

강력 — MLA 가 KV 캐시를 줄이고 memory 가 "장기 기억" 을 맡음. long context (128K+) 에서 특히 시너지.

4.7.2 Titans + R1 (execution_modes)

layer_templates:
  reasoner_with_memory:
    mixer: { type: attention, attention: { latent_dim: 512 } }
    ffn: { type: gated_mlp }
    memory:
      type: neural_memory
      update_at_inference: true
      params: { hidden: 2048 }           # 큰 memory
      inner_lr: 0.001
      persistence: session

execution_modes:
  - { name: think, max_tokens: 8192, kv_share: true, loss_weight: 0.0 }
  - { name: answer, max_tokens: 2048, loss_weight: 1.0 }

연구 가치 큼 — think 구간의 논리를 memory 에 축적, answer 생성이 참조. 멀티턴에서 "이전 turn 의 추론" 을 이어가는 효과. 2025-26 에 이 조합 이 많이 연구될 것으로 예상.

4.7.3 Titans + Jamba

layer_templates:
  attn_moe_with_memory:
    mixer: { type: attention, attention: {} }
    ffn:
      type: moe
      moe: { experts: 8, top_k: 2, router: softmax, capacity_factor: 1.25 }
    memory:
      type: neural_memory
      update_at_inference: true
      params: { hidden: 1024 }

attention 레이어 (1/8) 에만 memory. Mamba 의 O(N) + attention 의 retrieval + Titans 의 long-term.

4.7.4 Titans + TTT (double inner-optim)

실험적 — TTT 레이어 자체가 inner gradient 를 씀. 같은 모델에 Titans memory 를 추가로 두면 inner-optim 이 두 곳. 이론적으로 안정성 확보가 어렵고 실전 보고는 아직 없음. 연구 주제.

4.8 심화 읽을거리


마무리 — 네 사례의 공통 구조

사례 바뀐 primitive EulerStack 필드 런타임 (v1.1) 핵심 아이디어
DeepSeek-V3 attention KV 압축 attention.latent_dim ✅ Core 차원 희생 → 메모리 이득, quality 유지
Jamba mamba/attn 하이브리드 + MoE layer_schedule + ffn.type: moe ✅ Core 7:1 비율로 속도와 ICL 둘 다
DeepSeek-R1 추론 단계 분리 execution_modes: + transition: ✅ Core (metadata) 구조 불변, 훈련 · 생성 레시피만
Titans test-time parametric memory template.memory: + step_memory_at_inference ✅ Core (v1.1) inference 중 memory 만 grad step

네 사례의 공통 학습

  1. "한 YAML 파일이 한 논문" — 어떤 사례든 아키텍처 변경이 modeling_xxx.py 200 줄이 아니라 YAML 5~20 줄로 표현됐어.
  2. 직교성 (orthogonality) 설계 — MLA 는 FFN 을 건드리지 않고, MoE 는 attention 을 건드리지 않고, execution_modes 는 구조를 건드리지 않고, Titans memory 는 forward path 를 건드리지 않아. 그래서 조합이 쉬움.
  3. "메타데이터 먼저, 런타임은 점진적으로" — Schema-first. R1 의 execution_modes 는 아직 generate 가 실험 단계지만 메타데이터는 이미 왕복. Titans 는 v1.0 에 schema, v1.1 에 runtime.
  4. 레고 블록은 진짜 레고 — 결합이 산수 — MLA × Jamba × R1 × Titans = 4 가지 논문을 한 YAML 에 얹을 수 있어. §1.7 / §2.7 / §3.7 / §4.7 의 조합 예제를 모으면 arch_expert_kitchen_sink.yml 같은 capstone 이 나옴.

다음 단계