# JAX PPO Model-Capacity Diagnostic - 2026-07-12 "MLP를 더 키우면 아직 얻을 게 남아 있나?"에 대한 진단이다. 신규 학습 없이 기존 체크포인트, `league_metrics.jsonl`, 소스만 읽어 작성했다. 새로 실행한 학습·평가·벤치마크는 없다. Subject: final candidate `/mnt/2tbhdd/coolrl-lost-cities-artifacts/final-cycles/2026-07-05/final_candidate` Metrics: `.../final-cycles/2026-07-05/league/2026-07-05_191933_jax-ppo-final-cycle-c01/league_metrics.jsonl` (500 updates) ## 요약 용량이 **현재의 병목일 가능성은 낮다.** 다만 PPO 스택에서 모델 크기는 **한 번도 검증된 적이 없으므로**, 이 판단은 아직 측정으로 뒷받침되지 않았다. 단발 A/B로 결론을 확정할 것을 권한다 — [docs/plans/jax-ppo-model-size-ab.md](../plans/jax-ppo-model-size-ab.md). ## 1. 현재 크기는 근거 없는 상속값 `configs/jax_ppo/` 의 실 학습 config는 **전부** `hidden_size: 512, num_layers: 3` 이다 (`smoke.yaml`만 64×2). final candidate도 동일하다 (`main_ppo_config.json` → `network: {hidden_size: 512, num_layers: 3}`). `hidden_size`를 다룬 기존 문서는 전부 은퇴한 Deep CFR/PyTorch 스택 것이다 (`input_dim=365`, `legacy/deep-cfr/configs/` 기준). 특히 [docs/plans/model_size_experiment.md](../plans/archive/model_size_experiment.md)는 **현 JAX PPO 스택과 무관하다.** → 512×3은 실험으로 고른 값이 아니라 구 스택에서 복사돼 온 값이다. ## 2. 데이터 대비 모델이 매우 작다 (스케일링에 유리한 신호) | 항목 | 값 | | --- | ---: | | `OBS_DIM` (`types.py:27`) | 454 | | `N_ACTIONS` | 96 | | 512×3 파라미터 수 | **808,033** | | 1024×4 파라미터 수 | 3,714,145 | | 2048×4 파라미터 수 | 13,719,649 | | 최종 사이클 소비 transition | **1,638,400,000** | | 샘플 : 파라미터 | **약 2,028 : 1** | transition 수는 `rollout_steps=400 × batch_games=8192 × updates=500`. 셀프플레이라 데이터는 사실상 무한하다. 샘플:파라미터 2,000:1은 **데이터 제약이 아니라 용량/최적화 제약** 구간의 전형이며, 보통 이런 영역에서 스케일링이 먹힌다. 0.8M 파라미터는 절대적으로도 작다. 여기까지만 보면 "키우면 이득"이다. 그러나 3절이 반대 방향을 가리킨다. ## 3. 학습 로그에 용량 부족의 지문이 없다 최종 사이클 500 업데이트 구간 평균: | update | value_loss | entropy_mean | approx_kl | return_mean | return_std | | --- | ---: | ---: | ---: | ---: | ---: | | 0–50 | 0.0468 | 1.1159 | 0.0433 | 0.2696 | 0.6299 | | 50–100 | 0.0462 | 1.1167 | 0.0438 | 0.2799 | 0.6275 | | 100–200 | 0.0461 | 1.1241 | 0.0441 | 0.2822 | 0.6316 | | 200–300 | 0.0459 | 1.1143 | 0.0437 | 0.2857 | 0.6306 | | 300–400 | 0.0460 | 1.1239 | 0.0424 | 0.2900 | 0.6323 | | 400–500 | 0.0463 | 1.1163 | 0.0423 | 0.2916 | 0.6340 | 세 가지를 읽을 수 있다. **(a) 크리틱은 이미 잘 맞춘다.** `value_loss`는 순수 MSE다 (`ppo.py:624`: `masked_mean((mb_returns - value) ** 2, active_weight)`). `return_std ≈ 0.634` → 에피소드 리턴 분산 ≈ 0.402. MSE 0.0463과 비교하면 **설명분산 ≈ 0.88**. > 주의 — 이 0.88은 **상한 추정치**다. `return_std`는 에피소드 리턴의 표준편차이고 > (`ppo.py:1072`: `episode_return = jnp.sum(transitions.reward, axis=0)`), > `value_loss`의 타깃은 GAE 리턴(`mb_returns`)이다. final candidate는 shaping이 > 0이고 `gamma=1.0`이라 두 분포가 가깝지만, `gae_lambda=0.95`의 부트스트랩이 > 타깃 분산을 축소시키므로 실제 설명분산은 이보다 낮을 수 있다. 용량이 모자란 네트워크는 loss가 **높은 지점에서** 정체한다. 여기는 **낮은 지점에서** 정체한다. 잔여 오차 상당 부분은 불완전정보 + 덱 셔플에서 오는 **환원 불가능한 분산**으로 보인다. **(b) 엔트로피는 계수가 잡아둔 평형이다.** `entropy_mean`이 1.114–1.124에서 500 업데이트 내내 미동도 없다. `entropy_coef=0.01`과 정책 그래디언트가 이룬 평형이지 용량 한계가 아니다. **파라미터를 늘려도 이 값은 안 변한다.** (불완전정보에서 혼합 전략은 정상이므로 이 자체가 결함은 아니다.) **(c) 죽은 게 아니라 느린 것이다.** `return_mean`은 0.2696 → 0.2916으로 여전히 오르고 있다. `approx_kl`도 0.042–0.044로 유지되어 정책이 계속 움직인다. 수렴해서 멈춘 상태가 아니다. ## 4. 실제 천장은 exploitability이고, 이건 용량 문제가 아니다 [diminishing-returns-2026-07-05.md](diminishing-returns-2026-07-05.md) 기준, final 계열을 상대로 새로 학습시킨 exploiter가 **여전히 승률 0.539–0.549로 이긴다** (`repair_c01_update_500`, 3개 프로토콜). 불완전정보 게임에서 이 잔여 착취가능성은 **게임이론적 문제**다. PPO 셀프플레이는 내쉬로 수렴하지 않고 전략공간을 순환한다. MLP를 키우면 *같은 순환 역학 안에서 더 나은 응수*를 찾을 뿐, 이 0.54 바닥 자체를 무너뜨리지는 못한다. ## 판정 - 스케일링이 **소폭 이득**을 줄 가능성은 있다 (2절: 샘플:파라미터 2,000:1). - 그러나 **용량 부족의 직접 증거는 없다** (3절: 낮은 지점에서 평평한 value_loss, 계수가 고정한 엔트로피). - 그리고 **모델을 실제로 가두고 있는 것은 exploitability**이며, 이건 스케일로 풀리지 않는다 (4절). 따라서 우선순위는 낮다. 다만 **한 번도 측정한 적이 없다**는 사실 때문에 위 판단은 전부 정황증거다. 결론을 확정하려면 단발 A/B가 필요하다 → [docs/plans/jax-ppo-model-size-ab.md](../plans/jax-ppo-model-size-ab.md). ## Notes - 이 작업에서 신규 학습, config 변경, 체크포인트 수정은 없었다. - 파라미터 수는 `ActorCritic(hidden, layers).init()` 후 리프 크기 합으로 계산했다 (`ppo.py:104`). - GUI 영향 참고: 현재 CPU 추론 약 50ms/수. 1024×4로 키워도 150–200ms 수준이라 `lost-cities-play` 대국에는 지장이 없다.