콘텐츠로 이동

track-f Decoder Capacity Swap — Design Spec

User 결정 pending (5건) — 본 design spec 은 pending 결정 반영 후 부분 재작성 필요: 1. W1 decoder 구조 선택 (옵션 α=FC-MLP / β=Transformer / γ=병렬) — 본문 γ 전제 2. Aggregation 복원 범위 (decoder 단독 / v6 cos_similarity+personalized 포함) — 본문 decoder-only 전제 3. rounds 확장 (smoke 수렴 관찰 후 조건부) — 본문 rounds=10 smoke 유지 + 조건부 rounds=30 전제 4. Gate 수치 (track-f.0 파일럿 후 확정) — 본문 placeholder [파일럿 후 확정] 5. Gray-zone 진입 정책 (PAPE 45~50)

본문은 옵션 γ + decoder-only + rounds 조건부 + gate placeholder 전제. 다른 선택 시 해당 섹션 재작성.

§1 목표 (pre-registered, revised 2026-04-20 — critic F9 반영)

v7 ProposedModel의 decoder/encoder capacity 를 v6 R1b 수준으로 복원하여 decoder capacity 단독 효과 를 측정한다. FL aggregation 은 v7 FedAvg 로 고정 (v6 R1b 의 cos_similarity + personalized memory 는 비복원 교란 요인으로 disclosure).

중요 제한 (critic F9 반영): v6 R1b PAPE 38.40 은 "decoder capacity + 개인화 aggregation + VQ 조합" 의 공동 효과. track-f W1/W2 는 decoder 구조만 복원하므로 PAPE 38.40 재현은 empirical 검증 사항이며 보장되지 않음.

Success gate (track-f.0 파일럿, 신규 — critic F6 반영)

  • 목적: gate 수치 empirical 기준 확보
  • Scope: W1 (+ W2, 옵션 γ 선택 시) × seed {42, 123, 456} × Apt6,Apt88 × rounds=10
  • Runs: 6 (2 variants × 3 seeds), ~30분
  • 산출: track-f.1 threshold = 파일럿 min ± 2pp, track-f.2 std gate = 파일럿 std × 1.5

Success gate (track-f.1 1-seed smoke, 병렬 W1+W2)

  • PASS: W1 또는 W2 PAPE ≤ [파일럿 후 확정] (A1 47.1 돌파 기준, 기준선 45)
  • Stretch: PAPE ≤ 40 (v6 R1b 38.40 근접; v6 aggregation 비복원으로 보장 없음)
  • Inconclusive: [파일럿 후 확정] < PAPE ≤ 50 → 5가구 확장 smoke 1회 (user 승인 전제)
  • FAIL: 양쪽 variant PAPE > 50 → 파라미터 튜닝 금지, (a) 포팅 검증, (b) aggregation 복원 확장, (c) Option d rollback

Success gate (track-f.2 5-seed 통계)

  • 선정 variant PAPE 평균 ≤ [파일럿 후 확정] (기준선 43)
  • std < [파일럿 std × 1.5]
  • rel_decrease > 0 on ≥ 4/5 seeds
  • vs B0(52.3) 및 A1(47.1) 모두 개선

§2 v7/v8 계승 사항

영역 내용 유지/변경
Stage 0 pre-registration Golden tensor G1~G5, definition_hash 8be2bd2f691deed0, Apt_max_load=Apt88 유지
v7_runner.py CLI --mode, --phase, --cells 체계 유지 + W1/W2 추가
CELL_REGISTRY 기존 12 cell B0, B1, B2, B3, B4, A1, A2, A3, A4, V1, V2, V3, V4, V5 행동 불변
공용 모듈 src/peak_analysis/v7/metrics.py 유지
Smoke infra v7_stage05_smoke_analysis.py, 6 Gates, rel_decrease 유지
테스트 스위트 155+ tests 유지 + W1/W2 tests 추가
Peak-aware loss peak_weighted_smooth_l1(alpha=2.0, beta=0.1) 유지
Seed set {42, 123, 456, 789, 2024} 유지
Fail-fast threshold val ratio > 1.5, train > 3.0 유지
FL aggregation 대상 VQ codebook only (16K floats) 유지
MLflow 로깅 필수 best checkpoint, y_true/y_pred .npy, train_loss per round 유지

§3 신규 cell spec

프레이밍 변경 (critic F4 반영): 이전 "W1 primary / W2 fallback" 순차 프레이밍을 폐기하고 W1 + W2 병렬 실행 → track-f.0 파일럿 및 track-f.1 smoke 에서 양자 동시 수행 → PAPE 낮은 variant 를 track-f.2 primary 로 선정. user 지시 "Transformer 교체" 원문 존중과 v6 FC-MLP precedent 양쪽을 모두 empirically 검증.

3.1 W1 (α 병렬 실행, v6 FC-MLP 복원)

필드
cell_id W1
name v6-FC-decoder + CNN-residual encoder + VQ + DLinear + peak-loss
method proposed
peak_loss True (alpha=2.0)
use_vq True
use_dlinear True
vq_alignment False (v6 R1b와 정렬 — alignment 없었음)
vq_reset False
decoder_type (신규) v6_fc
extra_hparams {}

3.2 W2 (β 병렬 실행, 축소 Transformer)

필드
cell_id W2
name Transformer-2L decoder + v7 encoder + VQ + DLinear + peak-loss
method proposed
peak_loss True
use_vq True
use_dlinear True
vq_alignment False
vq_reset False
decoder_type tf2_128

§4 Decoder / Encoder 구체 architecture

4.1 decoder_type = "v7_conv" (default, legacy)

기존 v7 ProposedModel 행동 그대로. 이 분기는 B0~V5 cell에서 사용되며 bit-exact backward-compat 필수: - Encoder: nn.Sequential(Conv1d(64,64,k=3,p=1), ReLU, Conv1d(64,64,k=3,p=1), ReLU) - Decoder: 동일 구조 Conv1d×2 - pred_head: Linear(64, 1)

4.2 decoder_type = "v6_fc" (W1, α 병렬)

Encoder + Decoder 둘 다 교체. 추가로 v6 R1b forward pass 보강 3건 (critic F3 반영):

  1. RevIN affine=False, unbiased=False — v6 R1b 원본은 RevIN(num_features=1, affine=False) 사용 (lib/models/revin.py 참조). PyTorch default .std()unbiased=True (N-1 분모) 이므로 v6 동작과 불일치. inline 구현 시 반드시 torch.var(x, dim=1, unbiased=False, keepdim=True).sqrt() + eps 로 biased std 를 재현해야 한다. Engineer contract §R4 와 test 에 bit-exact assertion 필요.
  2. loss_decode auxiliary supervision — v6 R1b 는 decoder 중간 출력과 normalized target 사이 smooth_l1 loss 를 training loss 에 추가 (v6_0415_fedpm_original.py:408: loss = loss_decode + loss_all + vq_loss). track-f 에서도 이 보조항 복원 필요. Decoder forward 가 (y_hat, y_decode_intermediate) tuple 을 반환하도록 시그니처 확장.
  3. DLinear differential LR — v6 R1b 는 DLinear 파라미터 group 을 base_lr × 0.1 로 학습 (v6_0415_fedpm_original.py:308-313). CellSpec 에 dlinear_lr_scale: float = 1.0 필드 추가 (default 1.0 backward-compat; W1/W2 에서 0.1 설정).

Encoder (v6 원본 import):

from lib.models.Encoder_and_Retrieval import Encoder

self.encoder = Encoder(
    in_channels=1,                 # 단변량
    num_hiddens=128,               # v6 block_hidden_size
    num_residual_layers=2,
    num_residual_hiddens=64,       # v6 res_hidden_size
    embedding_dim=64,              # = VQ_EMBEDDING_DIM
    compression_factor=4,          # 96 → 24
    # encoder_type='cnn' 은 Encoder 내부 default
)

VQ layer (기존 v7 VectorQuantizer 유지, 교체 없음)

Decoder (v6 원본 import):

from lib.models.decoder import XcodeYtimeDecoder

self.decoder = XcodeYtimeDecoder(
    d_in=64,                       # = embedding_dim
    d_model=64,
    nhead=4,                       # fc 경로에선 미사용이지만 v6 config 일치
    d_hid=256,
    nlayers=4,
    seq_in_len=24,                 # SEQ_LEN // compression_factor = 96//4
    seq_out_len=24,                # = PRED_LEN
    dropout=0.0,
    decoder_type='fc',
    compression_factor=4,
    num_residual_layers=2,
    num_residual_hiddens=64,
)

Pred head: None (XcodeYtimeDecoder이 linear_out: Linear(seq_in_len*d_model, seq_out_len) 을 내장)

Forward pass (W1, revised — critic F3 3 보강 포함):

# x: [B, 96, 1]

# --- RevIN (affine=False, unbiased=False) — v6 R1b 원본 일치 ---
x_sq = x.squeeze(-1)                                             # [B, 96]
mean = x_sq.mean(dim=1, keepdim=True)
var = x_sq.var(dim=1, keepdim=True, unbiased=False)              # biased std
std = var.sqrt() + 1e-8
x_norm = (x_sq - mean) / std                                     # [B, 96]

# --- v6 Encoder: 채널 1 입력 ---
x_nchw = x_norm.unsqueeze(1)          # [B, 1, 96]
z = self.encoder(x_nchw)              # [B, 64, 24] (compression_factor=4)

# VQ: 기존 ProposedModel과 동일 shape 기대 [B, 24, 64]
z = z.transpose(1, 2)                 # [B, 24, 64]
z_hat, vq_loss, codebook_util = self.vq(z)

# Decoder: XcodeYtimeDecoder는 [seq, B, d_in] 기대
z_hat_tdb = z_hat.transpose(0, 1)     # [24, B, 64]
y_vq_norm, y_decode_intermediate = self.decoder(z_hat_tdb)       # tuple (F3 보강 2)
# y_vq_norm: [B, 24]   normalized pred
# y_decode_intermediate: [B, 24]   보조 supervision 대상

# De-normalize (RevIN inverse)
y_vq = y_vq_norm * std + mean                                    # [B, 24]

# DLinear residual (local, differential LR — optimizer 단계에서 base_lr × 0.1 적용)
y_dlinear = self.dlinear(x).squeeze(-1)                          # [B, 24]
y_hat = y_vq + y_dlinear

return y_hat.unsqueeze(-1), vq_loss, codebook_util, {
    "y_decode_intermediate": y_decode_intermediate,              # loss_decode 용
    "x_norm": x_norm,                                            # target normalize 용
}

Training loss (W1, revised):

# v6 R1b line 408 pattern 복원:
#   loss = loss_decode + loss_all + vq_loss
loss_decode = F.smooth_l1_loss(aux["y_decode_intermediate"], target_norm)
loss_all = peak_weighted_smooth_l1(y_hat, y_true, alpha=2.0, beta=0.1)
loss = loss_decode + loss_all + vq_loss

Optimizer (W1, revised):

# v6 R1b line 308-313 pattern: DLinear group differential LR
dlinear_params = list(model.dlinear.parameters())
other_params = [p for n, p in model.named_parameters() if not n.startswith("dlinear.")]
optimizer = torch.optim.AdamW([
    {"params": other_params, "lr": base_lr},
    {"params": dlinear_params, "lr": base_lr * spec.dlinear_lr_scale},  # 0.1 for W1/W2
])

파라미터 수 (예상): - Encoder: 156,288 - VQ codebook: 16,384 - Decoder (XcodeYtimeDecoder fc): 956,696 - DLinear (local, per-HH): 4,656 - Total per client: ~1,133,424 (shared VQ 16K + local ~1.12M)

4.3 decoder_type = "tf2_128" (W2, β fallback)

Encoder 는 v7 기존 유지 (Conv1d×2). Decoder 만 교체:

class TransformerDecoderW2(nn.Module):
    def __init__(self, d_model=64, nhead=4, d_hid=128, nlayers=2,
                 seq_len=24, pred_len=24):
        super().__init__()
        enc_layer = nn.TransformerEncoderLayer(
            d_model=d_model, nhead=nhead,
            dim_feedforward=d_hid,
            batch_first=True, dropout=0.0,
            norm_first=False,
        )
        self.tf = nn.TransformerEncoder(enc_layer, num_layers=nlayers)
        self.pos_embed = nn.Parameter(torch.zeros(1, seq_len, d_model))
        self.out_proj = nn.Linear(d_model * seq_len, pred_len)

    def forward(self, x):
        # x: [B, seq_len=24, d_model=64]
        x = x + self.pos_embed
        x = self.tf(x)                         # [B, 24, 64]
        x = x.flatten(1)                       # [B, 1536]
        return self.out_proj(x)                # [B, 24]

W2 forward pass: 기존 v7과 동일하되 decoder만 교체 + pred_head 제거.

파라미터 수: decoder 105,368. Encoder + VQ + DLinear = v7 기존.

§5 FL aggregation 고려사항

5.1 track-f에서 공유 대상

변경 없음: VQ codebook (16,384 floats/round) 만 FedAvg weighted mean.

  • 근거: v7_runner.py:1615-1622에서 local_vq_states → weighted mean → global_vq_state.
  • Decoder 와 DLinear 는 per-client local. Encoder도 per-client local.
  • 통신량 증가 = 0.

5.2 LayerNorm / Positional Encoding FL 주의점 (track-f에서는 해당 없음)

만약 미래에 decoder 를 shared 로 전환한다면 (track-f scope 아님): - LayerNorm scale/shift: FedAvg weighted mean OK. - Positional encoding: learnable은 drift 위험, deterministic sinusoidal 권장.

5.3 DLinear residual 결합 방식

v6 R1b와 동일한 덧셈 결합 유지: y_hat = y_vq + y_dlinear. concat/gated 결합은 track-f scope 아님.

§6 실험 단계 (revised — critic F4, F5, F6 반영)

진입 순서: track-f.0 파일럿 → track-f.1 병렬 smoke → track-f.2 5-seed → track-f.3 Stage 1

6.0 track-f.0 — 파일럿 (신규, gate 수치 확정용)

  • Cells: W1, W2 (옵션 γ 선택 시 둘 다; 옵션 α/β 선택 시 해당만)
  • Seeds: {42, 123, 456}
  • Households: Apt6 + Apt88
  • Rounds: 10
  • Expected wall-clock: ~30분 (6 runs × ~5분)
  • MLflow tags: phase=track-f, stage=f0, cell=W1|W2, decoder_type=v6_fc|tf2_128
  • 산출 (gate 확정값):
  • track-f.1 PAPE threshold = min(파일럿 PAPE) ± 2pp
  • track-f.2 std gate = std(파일럿 PAPE) × 1.5

6.1 track-f.1 — 1-seed smoke (병렬 W1+W2)

  • Cells: W1 + W2 병렬 (옵션 γ 전제, 다른 옵션 선택 시 해당 cell 만)
  • Seed: 42
  • Households: Apt6 + Apt88 (v7 smoke 관행 유지)
  • Rounds: 10 (critic F5 반영 — rounds=30 으로 확장하지 않음, 튜닝 경계 보호)
  • Expected wall-clock: ~10분 (W1) + ~10분 (W2) = ~20분 병렬 실행 시 ~10~15분
  • MLflow tags: phase=track-f, stage=f1, cell=W1|W2, decoder_type=v6_fc|tf2_128
  • Gate: PAPE ≤ [파일럿 후 확정] → pass, > 50 → fail protocol
  • rounds 확장 조건부 승인 로직 (critic F5):
  • smoke 완료 후 loss curve 분석: epoch 8~10 감소율 > epoch 1~4 감소율의 50% → 미수렴, rounds=30 확장 승인 (user 결정 필요)
  • 그렇지 않으면 rounds=10 track-f.2 유지

6.2 track-f.2 — 5-seed 통계

  • Cell: track-f.1 에서 PAPE 낮은 variant (W1 또는 W2)
  • Seeds: {42, 123, 456, 789, 2024}
  • Households: 5가구 (Apt6, Apt15, Apt30, Apt51, Apt88) — v6 R1b 와 정렬
  • Rounds: [10 | 30] — track-f.1 smoke 수렴 판정 결과에 따라 조건부
  • Expected wall-clock: rounds=10 ~40분 / rounds=30 ~2h
  • Gate: 평균 ≤ [파일럿 후 확정, 기준선 43], std < [파일럿 std × 1.5, 기준선 4.0], rel_decrease > 0 on ≥ 4/5 seeds

6.3 track-f.3 — Stage 1 재진입

  • Cells: B0, B1, B2, B3, B4, A1, A2, A3, A4, V1, V2, V3, 선정 W variant × 5 seed × 5 가구
  • Rounds: track-f.2 와 동일
  • Expected wall-clock: ~3.5h
  • Gate: Gate 1~6 전체 통과 + 선정 W variant 가 모든 cell 중 PAPE 1위

§7 실패 프로토콜 (revised — critic F9 반영)

7.1 track-f.1 FAIL (양쪽 variant PAPE > 50)

  • 파라미터 튜닝 금지 (lr, rounds, peak_alpha, EMA gamma 등)
  • 가능 옵션 (exp-expert 분석 + user 승인):
  • (a) 포팅 검증: XcodeYtimeDecoder, Encoder import bit-exact test 추가, RevIN affine/unbiased 설정 재검증, v6 원본 MLflow y_pred 와 shape/값 대조
  • (b) v6 aggregation 복원 확장 (신규, critic F9): decoder 단독 교체로 PAPE 회복 실패 시, cos_similarity + personalized memory + threshold=0.7 + gamma=0.95 까지 v6 1:1 복원 (workload +2~4h). 이는 "구조적 방법론 확장"이며 하이퍼파라미터 튜닝 아님.
  • (c) Option d rollback: track-f 중단 + shared linear adapter 복귀 (ADR-010 필수)

7.2 track-f.1 INCONCLUSIVE ([파일럿 후 확정] < PAPE ≤ 50)

  • 5가구 확장 smoke 1회 (rounds=10 유지, 데이터셋 조건만 확장)
  • 이는 "파라미터 튜닝"이 아닌 "smoke 조건 보강" → user 승인 전제로 허용
  • 결과가 그래도 PAPE > [파일럿 후 확정] 이면 §7.1 프로토콜 적용

7.3 track-f.2 FAIL

  • 4/5 이상 seed 에서 A1 수준 미달
  • user 결정권 호출:
  • (α) 다른 variant (W1 → W2 또는 역순)
  • (β) v6 aggregation 복원 확장 (§7.1 b 옵션)
  • (γ) FL narrative 포기 + KD paper 축 유지 (ADR-010)

7.4 track-f.3 FAIL (Stage 1)

  • Gate 1~6 중 하나라도 실패
  • 기존 v7 stage 0.5 프로토콜 따름 (exp-critic 적대적 검토 → exp-expert 수정판)

§8 MLflow 로깅 요구사항

필수 기록 (mandatory)

  • params: decoder_type, decoder_param_count, encoder_type, peak_alpha, rounds, seed
  • metrics (per round): train_loss, val_loss, val_mae, val_mse, codebook_util, vq_loss
  • metrics (final): test_pape_avg, test_hr_avg, test_mae_avg, test_mse_avg
  • artifacts:
  • best_checkpoint.pt (재추론 가능)
  • y_true.npy, y_pred.npy (per-household, per-split)
  • codebook_final.npy
  • train_loss_curve.png (시각화 참고용, 의무 아님)

태그

  • phase: track-f
  • cell: W1|W2
  • seed: 42|123|456|789|2024
  • households: Apt6+Apt88|Apt6+Apt15+Apt30+Apt51+Apt88

§9 검증 순서

  1. engineer가 W1/W2 구현 + 5개 신규 unit test 작성 후 uv run python -m pytest tests/ -v --ignore=tests/integration_distilts.py 통과
  2. 기존 155+ 테스트 regression 없음 확인 (backward-compat 검증)
  3. track-f.1 실행 (W1, seed 42, Apt6+Apt88, rounds 10)
  4. Gate 판정:
  5. PASS → track-f.2 진행
  6. INCONCLUSIVE → 5가구 smoke 1회 (user 승인 후)
  7. FAIL → 실패 프로토콜
  8. track-f.2 실행 (W1, 5 seed × 5 가구, rounds 30)
  9. 통계 Gate 판정 → track-f.3 진행
  10. track-f.3 Stage 1 재진입 → exp-critic 검토 → reporter 요약

§10 범위 외 (explicit non-goals)

  • 하이퍼파라미터 탐색 (peak_alpha, commitment_beta 등)
  • VQ 알고리즘 변경 (EMA gamma, alignment, RESET 등 — V1~V5는 기존 그대로)
  • FL aggregation 대상 확장 (decoder/encoder 공유)
  • 새 baseline 추가
  • Chronos teacher 관련 KD 작업
  • Paper draft 수정

§11 관련 문서

  • ADR: docs/decisions/ADR-009_v8_to_track_f_decoder_swap.md
  • 심층 분석: report/version8/exp-expert/track_f_decoder_analysis.md
  • TODO: todos/track-f_decoder_swap.md
  • Engineer contract: report/version8/exp-expert/track_f_engineer_contract.md
  • Previous (superseded): docs/reference/project_state/track_v8_vq_rescue.md