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 반영):
- 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 필요. loss_decodeauxiliary 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 을 반환하도록 시그니처 확장.- 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,Encoderimport 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.npytrain_loss_curve.png(시각화 참고용, 의무 아님)
태그¶
phase: track-fcell: W1|W2seed: 42|123|456|789|2024households: Apt6+Apt88|Apt6+Apt15+Apt30+Apt51+Apt88
§9 검증 순서¶
- engineer가 W1/W2 구현 + 5개 신규 unit test 작성 후
uv run python -m pytest tests/ -v --ignore=tests/integration_distilts.py통과 - 기존 155+ 테스트 regression 없음 확인 (backward-compat 검증)
- track-f.1 실행 (W1, seed 42, Apt6+Apt88, rounds 10)
- Gate 판정:
- PASS → track-f.2 진행
- INCONCLUSIVE → 5가구 smoke 1회 (user 승인 후)
- FAIL → 실패 프로토콜
- track-f.2 실행 (W1, 5 seed × 5 가구, rounds 30)
- 통계 Gate 판정 → track-f.3 진행
- 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