Index
2026-08-12 — Research

WAM 계열 모델 CFG 처리 방식 코드 전수 조사

lingbot-va · cosmos-policy · Cosmos3-Nano-Policy-DROID · dreamzero · ImageWAM · FastWAM (+X-WAM 검증)

TL;DR

7
조사 레포
2
action CFG ON
2
video만 CFG
3
CFG 완전 OFF

1 배경 / 목적

WAM(World-Action Model) 계열 모델들이 inference에서 CFG를 실제로 쓰는지, 쓴다면 video branch에만인지 action branch에도인지, guidance scale 기본값이 얼마인지가 레포마다 제각각이라 모델 간 비교 실험 설계가 불가능한 상태였다.

사전에 확인된 것은 두 가지뿐이었다 — X-WAM은 RoboCasa eval에서 cfg=0(OFF), RoboLab eval에서만 cfg=2.0. 나머지 6개는 미확인.

기존 한계: README/논문 서술만으로는 판단 불가. "CFG 지원"이라고 적혀 있어도 (a) 실제 config 기본값이 neutral이거나 (b) 파라미터가 dead code이거나 (c) video에만 걸리는 경우가 섞여 있다. 코드 단에서 실행 경로를 직접 확인해야 한다.

2 조사 방법

각 레포에서 네 가지를 소스 직독으로 확정했다. 함수 시그니처에 파라미터가 존재한다는 것만으로는 "CFG 지원"으로 인정하지 않고, body에서 실제로 참조되는지까지 확인했다 — ImageWAM/FastWAM에서 이 구분이 결론을 뒤집었다.

① CFG 수식이 실제로 실행되는 코드 지점 (dead branch 여부 포함) ② unconditional branch를 무엇으로 만드는가 (빈 텍스트 / negative prompt / 조건 자체 제거) ③ 설정 파일·서버 argparse의 실제 기본값 (함수 default가 아니라 배포 경로의 값) ④ training 시 conditioning dropout 확률 (CFG를 쓸 수 있게 학습되었는가)

조사 경로

lingbot-va wan_va/wan_va_server.py, wan_va/configs/*.py, wan_va/dataset/lerobot_latent_dataset.py cosmos-policy cosmos_policy/models/policy_{video2world,text2world}_model.py, conditioner.py, config/experiment/cosmos_policy_experiment_configs.py, + resolve된 outputs/.../cosmos_predict2_2b_480p_robocasa_50_demos_per_task/config.yaml Cosmos3-DROID cosmos/packages/cosmos3/cosmos_framework/model/vfm/omni_mot_model.py, scripts/action_policy_server_{robolab,libero}.py, configs/base/experiment/action/posttrain_config/action_policy_droid_nano.py dreamzero groot/vla/model/dreamzero/action_head/wan_flow_matching_action_tf.py ImageWAM src/imagewam/models/backbones/{imagewam,wan22,imagewam_*_idm}.py, configs/sim_*.yaml FastWAM src/fastwam/models/wan22/{fastwam,wan22,fastwam_idm,fastwam_joint}.py, configs/sim_*.yaml

3 결과 — 요약표

모델CFG 구현실제 기본 scalevideoactionuncond branch
Cosmos3-Nano-Policy-DROID통합 CFG (modality 구분 없음)RoboLab 3.0 / LIBERO 1.0ONON빈 caption ""
X-WAM 참고video+action+proprio 전부RoboCasa 0.0 / RoboLab 2.0조건부조건부context [2B] 뒤쪽 B
lingbot-vavideo/action 분리 2-scaleguidance=5, action_guidance=1ON (5.0)OFF빈 문자열 T5 emb
dreamzerovideo 전용cfg_scale=5.0 하드코딩ON (5.0)OFFWan 기본 negative prompt
cosmos-policy구조적으로 제거됨uncondition=NoneOFFOFF없음 (생성 스킵)
ImageWAM파라미터만 존재 (dead code)text_cfg_scale=1.0OFFOFF없음
FastWAM파라미터만 존재 (dead code)text_cfg_scale=1.0OFFOFF없음
패턴: action CFG가 켜져 있는 두 경우 모두 RoboLab(실기 DROID) 서빙 경로다. 같은 코드베이스라도 시뮬(LIBERO / RoboCasa) 경로는 예외 없이 CFG를 끈다.

4 레포별 상세

4.1 lingbot-va — video/action 분리 CFG, action은 기본 OFF

wan_va/wan_va_server.py 379: self.use_cfg = (guidance_scale > 1) or (action_guidance_scale > 1) 513: if guidance_scale > 1: 514: video_noise_pred = video_noise_pred[1:] + guidance_scale * (video_noise_pred[:1] - video_noise_pred[1:]) 552: if action_guidance_scale > 1: 553: action_noise_pred = action_noise_pred[1:] + action_guidance_scale * (action_noise_pred[:1] - action_noise_pred[1:])

[:1]=cond, [1:]=uncond. 수식은 uncond + s·(cond−uncond)s=1이 neutral. 그리고 va_demo_cfg / va_libero_cfg / va_robotwin_cfg / va_franka_cfg 4개 config 전부 guidance_scale=5, action_guidance_scale=1이다.

uncond는 _repeat_input_for_cfg()(254–263행)에서 text embedding만 negative(빈 문자열)로 교체한다. 이미지/관측 conditioning은 drop하지 않으므로 순수 text-CFG다. Training 쪽은 cfg_prob=0.1로 text embedding을 empty_emb으로 치환(lerobot_latent_dataset.py:247-248) — 즉 CFG를 쓸 수 있게 학습된 모델이다.

비효율: action_guidance_scale=1인데도 use_cfg=True라서 action branch까지 batch 2배로 돌린 뒤 [:1]만 취해 버린다(553행 else). 액션 denoise 연산이 그대로 2배 낭비된다.
불일치: evaluation/robotwin/eval_polict_client_openpi.py가 요청에 video_guidance_scale/action_guidance_scale를 실어 보내고 launch_client.sh는 5/1을 넘기지만, OSS VA_Server.infer()(607–624행)는 이 키를 읽지 않는다. 실제 scale은 전적으로 job_config에서 온다.

4.2 cosmos-policy — CFG를 코드 레벨에서 삭제

policy_video2world_model.py:604에 의도가 명시되어 있다:

NOTE (user): Modified to remove the negative prompt and "uncondition" since we are doing always-conditional generation instead of CFG.
cosmos_policy/conditioner.py:144-157 for emb_name, embedder in self.embedders.items(): cond_dropout_rates[emb_name] = 0.0 dropout_rates[emb_name] = 1.0 if embedder.dropout_rate > 1e-4 else 0.0 if cond_dropout_rates == dropout_rates: condition = self(data_batch, override_dropout_rate=cond_dropout_rates) un_condition = None # ← uncond 자체를 안 만듦

실제로 모든 dropout이 0이 되는지 resolve된 config(outputs/cosmos_policy/cosmos_v2_finetune/cosmos_predict2_2b_480p_robocasa_50_demos_per_task/config.yaml)로 확인했다 — fps / padding_mask / text / use_video_condition 전부 dropout_rate: '0.0'.

경로: base 실험(stage3_2B_ablation.py:156-161)이 use_video_condition→0.0, text→0.3을 설정하고, policy 실험(cosmos_policy_experiment_configs.py:110-114)이 text를 0.0으로 덮어쓴다. 주석: "IMPORTANT: We don't want any text dropout; otherwise, the model may fail to follow language".

cond_x0 = self.denoise(noise_x, sigma, condition).x0 if uncondition is not None: uncond_x0 = self.denoise(noise_x, sigma, uncondition).x0 raw_x0 = cond_x0 + guidance * (cond_x0 - uncond_x0) else: raw_x0 = cond_x0 # ← 항상 이쪽
dead parameter: generate_samples_from_batch(guidance=1.5)의 1.5는 전혀 쓰이지 않는다. 실제 호출부(cosmos_utils.py:1113, run_robocasa_eval.py:587, aloha/deploy.py:506)도 guidance=를 넘기지 않고 is_negative_prompt=False # Negative prompt is for CFG로 명시한다.
잠재 버그: use_flowunipc_scheduler=True 경로는 denoise_with_velocity(..., uncondition)를 None 체크 없이 호출한다 → uncondition=None이면 터진다. 기본 off라 노출되지 않을 뿐.

4.3 Cosmos3-Nano-Policy-DROID — 유일하게 action에 CFG가 걸리는 policy

cosmos_framework/model/vfm/omni_mot_model.py:2492, 2510 v_pred = [u_i + guidance * (c_i - u_i) for c_i, u_i in zip(cond_v, uncond_v)]

uncond는 빈 caption(uncond_captions = [""] * len(cond_tokens), 1567행) — text-CFG다. MoT 구조라 한 sample의 latent 벡터에 vision 토큰과 action 토큰이 연속으로 packing되어 있고(2620–2656행의 offset 기반 분해), CFG는 그 벡터 전체에 적용된다. 즉 guidance>1이면 action 토큰에도 그대로 CFG가 걸린다. modality별 분리 scale은 없다.

RoboLab / DROID server
guidance = 3.0
LIBERO server
guidance = 1.0
train cfg_dropout_rate
0.1
denoising steps (RoboLab)
4

action_policy_server_robolab.py:330guidance: float = 3.0. 문서 docs/action_policy_robolab_server.md:22,53"guidance 3.0, 4 denoising steps, shift 5.0"로 배포 기본값을 못박는다. 반면 action_policy_server_libero.py:447guidance: float = 1.0 → 시뮬 경로는 CFG OFF.

학습 쪽은 action_policy_droid_nano.py:207cfg_dropout_rate=0.1 — caption을 10% 확률로 빈 문자열로 치환. 부가 기능으로 guidance_interval(timestep 구간 한정), normalize_cfg(norm clamp), control_guidance(transfer 전용), CFG parallelism(cfgp, 2 rank 분산), FSDP 정합용 dp_shard MAX all-reduce가 있다.

4.4 dreamzero — CFG 5.0 하드코딩, 단 video에만

groot/vla/model/dreamzero/action_head/wan_flow_matching_action_tf.py 182: self.cfg_scale = 5.0 # __init__ 하드코딩, config/CLI 노출 없음 1275: flow_pred_uncond, flow_pred_uncond_action = predictions[1] 1277: flow_pred = flow_pred_uncond + self.cfg_scale * (flow_pred_cond - flow_pred_uncond) 1300: sample_scheduler_action.step(model_output=flow_pred_cond_action, ...) # ← cond만
핵심: uncond action velocity(flow_pred_uncond_action)를 계산해 놓고 그대로 버린다. action scheduler에는 conditional 예측만 들어간다 → action CFG 없음.

negative prompt는 Wan 표준 미학 negative prompt를 하드코딩(transform/dreamzero_cotrain.py:538: "Vibrant colors, overexposed, static, blurry details, ..."). ip_size>1이면 CFG를 2개 rank에 분산하며 824/840행의 assert self.cfg_scale != 1.0 때문에 그 모드에서는 CFG를 끌 수 없다. Dreamzero_robomm 변종도 동일(cfg_scale=5.0, 177행 / 수식 1212행).

4.5 ImageWAM / FastWAM — CFG는 시그니처에만 존재하는 dead parameter

정책 클래스(ImageWAM, FastWAM)의 infer / infer_joint / infer_actionnegative_prompt, text_cfg_scale을 인자로 받지만 body에서 한 번도 참조하지 않는다.

FastWAM.infer(text_cfg_scale=5.0, action_cfg_scale=1.0) # fastwam.py:1062-1063 └→ infer_joint(text_cfg_scale=...) # 1080 — 전달만 └→ body에서 text_cfg_scale 미사용, action_cfg_scale은 전달조차 안 됨

fastwam.py에서 nega 문자열은 총 4회 등장하며 전부 시그니처/전달 라인이다(ImageWAM도 4개). IDM 변종은 아예 명시적으로 폐기한다:

imagewam_noise_idm.py:558 del negative_prompt, text_cfg_scale imagewam_cache_idm.py:648 del action, negative_prompt, text_cfg_scale, test_action_with_infer_action fastwam_idm.py:288 del negative_prompt, text_cfg_scale, test_action_with_infer_action

CFG를 실제로 구현한 유일한 클래스는 순수 video 백본 Wan22Core(wan22.py:274-385, 두 레포 동일):

341: if text_cfg_scale != 1.0: 342: context_nega, _ = self.encode_prompt("" if negative_prompt is None else negative_prompt) 343: action_nega = torch.zeros_like(action) if (action is not None and action_cfg_scale != 1.0) else None 371: noise_pred = noise_pred + (text_cfg_scale - 1.0) * (noise_pred_posi - noise_pred_text_nega) 381: noise_pred = noise_pred + (action_cfg_scale - 1.0) * (noise_pred_posi - noise_pred_action_nega)

여기엔 action을 0 벡터로 대체하는 action-CFG까지 있지만 이건 video 생성 품질용이지 policy용이 아니다. 실제 설정값도 전부 neutral — sim_libero.yaml/sim_robotwin.yaml/sim_libero_omnigen2.yaml 모두 text_cfg_scale: 1.0, negative_prompt: "", trainer.py의 validation sampling도 text_cfg_scale=1.0, action_cfg_scale=1.0.

이름 함정: task 이름의 libero_uncond_2cam224_1e-4 / robocasa_uncond_7d_1e-4에서 "uncond"는 CFG와 무관하다. fastwam / fastwam_joint / fastwam_idm 3개 모델 변종 중 하나를 가리키는 이름이다(configs/model/*.yaml 대응).

4.6 X-WAM (기존 인식 검증)

modules/wan_model.py:536-563 — video / action / proprio 세 modality 모두 CFG 적용(out = uncond + cfg·(cond−uncond)), depth만 제외(cond only). cfg > 0.0이 게이트라 cfg=0은 단일 forward.

scripts/eval_robocasa.sh:45 CFG=${CFG:-0.0} 주석: "every released RoboCasa number was produced at cfg=0" evaluation/robolab_server.py:167 p.add_argument("--cfg", type=float, default=2.0) scripts/run_robolab_eval.sh:49 CFG="${CFG:-2.0}"
검증 결과: 기존 인식(RoboCasa cfg=0 / RoboLab cfg=2.0)이 코드와 정확히 일치한다.

5 Takeaway

의미

1. action CFG는 실기 도메인 전용 관행이다. action에 CFG를 거는 두 케이스(Cosmos3-DROID 3.0, X-WAM 2.0)가 모두 RoboLab 서빙 경로이고, 같은 코드베이스의 시뮬 경로는 예외 없이 끈다. 실기에서 언어 추종을 강제할 필요가 있다는 경험칙이 코드에 반영된 것으로 보인다.

2. video CFG와 action CFG는 완전히 별개 축이다. lingbot-va와 dreamzero는 video만 5.0을 걸고 action은 conditional만 쓴다. 따라서 "이 모델은 CFG를 쓴다"는 서술은 액션 품질에 대해 아무것도 말해주지 않는다. 비교표에서는 반드시 두 축을 분리해야 한다.

3. cosmos-policy의 CFG 제거는 의도적이며 근거가 코드에 남아 있다 — text dropout이 language following을 망친다는 것. CFG의 전제인 conditioning dropout 학습 자체를 안 했으므로, 이 모델에 CFG를 다시 붙이려면 재학습이 필요하다.

4. 시그니처만 보고 판단하면 3개 레포에서 틀린다. cosmos-policy(guidance=1.5), ImageWAM/FastWAM(text_cfg_scale=5.0) 모두 함수 default가 CFG-on처럼 보이지만 실행 경로에서는 전부 무력화된다.

5. convention 함정. cosmos-policy만 cond + g·(cond−uncond)g=0이 neutral(validation callback도 guidance=[0]), 나머지는 uncond + g·(cond−uncond)g=1이 neutral. 숫자를 그대로 옮겨 적으면 안 된다.

6 Next Steps

lingbot-va: 낭비된 action forward 제거

action_guidance_scale=1인데 use_cfg=True면 action branch가 batch 2로 돌고 절반을 버린다(wan_va_server.py:543-555). use_cfg를 branch별로 분리하면 action denoise 연산이 절반으로 줄어든다. RoboCasa post-training 후 eval 속도에 직접 영향.

lingbot-va: action CFG 스윕

학습 시 cfg_prob=0.1로 text drop을 했으므로 action CFG를 켤 수 있는 모델이다. action_guidance_scale ∈ {1.0, 1.5, 2.0, 3.0} 스윕이 유효한지 확인 필요. 가설: Cosmos3-DROID가 3.0을 쓰는 것을 보면 1보다 큰 값에 여지가 있다.

cosmos3: modality별 CFG 분리

vision과 action이 같은 scale을 공유하므로, action만 따로 튜닝하려면 omni_mot_model.pyv_pred 계산에서 offset 기반 분할이 필요하다.

미조사 레포

lingbot-vla-v2, FlowWAM, MetaView-action, Geometric-Action-Model — 동일 프로토콜로 추가 조사 가능.