PPO 하이퍼파라미터는 무엇부터 바꿔야 하나: G1에서 clip·gamma·lambda·batch를 고정한 이유

현재 G1 기본 설정은 한 번의 PPO iteration에서 98,304개의 transition을 모은 뒤 20번의 optimizer step을 수행합니다. 경로가 갑자기 무너지는 현상을 진단할 때는 transition 수를 그대로 두고 optimizer step을 4번으로 줄였습니다. 같은 로봇, 같은 부모 checkpoint, 같은 reward profile을 사용했지만 정책이 변하는 폭은 크게 달라졌습니다.

작게 업데이트한 정책은 기존 경로를 보존했습니다. 그렇다고 좋은 보행 정책이 된 것은 아니었습니다. 허리와 고관절 형태, 느린 속도의 이동거리 같은 기존 실패가 그대로 남았습니다. 이 대조를 겪으면서 PPO 하이퍼파라미터를 “성능을 올리는 숫자 목록”으로 보지 않게 됐습니다. 각 설정은 서로 다른 예산을 정합니다. 얼마나 긴 경험을 모을지, 어느 시점의 결과를 현재 행동에 연결할지, 같은 데이터를 몇 번 재사용할지, 이전 정책에서 얼마나 멀리 움직일지를 나눠서 봐야 합니다.

이 글은 rollout·GAE·clipping으로 PPO 한 번을 설명한 글의 후속편입니다. 수식을 다시 유도하기보다 현재 G1 설정의 숫자를 실제 시간과 표본 수로 환산하고, 어떤 순서로 변경해야 원인을 잃지 않는지 정리합니다. TensorBoard의 각 scalar를 읽는 방법과 v391·v393의 상세 수치는 PPO 학습 로그 읽는 법에 따로 남겼습니다.

G1 PPO 한 iteration의 clock·sample·credit·update budget과 기본 설정 및 guarded probe의 optimizer step 차이, 하이퍼파라미터 변경 순서를 정리한 다이어그램
PPO 설정은 독립된 손잡이 모음이 아닙니다. 시간축과 표본을 먼저 정한 뒤 credit horizon, optimizer budget, trust region, exploration 순서로 읽어야 앞 단계의 오류를 뒤 단계의 튜닝으로 덮지 않습니다.

먼저 기본값과 현재 실험값을 구분했습니다

sim/envs/g1_walk/agents/rsl_rl_ppo_cfg.yaml에 적힌 flat G1 기본값은 다음과 같습니다. 이 설정은 일반 학습을 시작할 수 있는 runner 계약입니다. 최근의 모든 warm-start 진단을 반드시 이 값으로 수행한다는 뜻은 아닙니다.

구분 flat G1 기본값 직접 바꾸는 대상
rollout 4,096 env × 24 step 새 on-policy 표본 수와 연속 시간
discount / GAE gamma 0.99 / lambda 0.95 return과 advantage의 시간 가중치
policy ratio clip 0.2 surrogate objective에서 추가 이득을 잘라 내는 범위
optimizer 5 epoch × 4 mini-batch 한 rollout을 재사용하는 횟수와 optimizer step 수
learning rate 1e-3, adaptive optimizer step 하나의 파라미터 이동 규모
KL controller desired KL 0.01 adaptive schedule이 learning rate를 올리거나 내리는 기준
exploration entropy coefficient 0.002, initial std 1.0 행동 분포가 너무 빨리 좁아지는 것을 억제하는 압력
gradient max norm 1.0 한 optimizer step의 수치적 폭주 제한

반면 파괴적 update drift를 확인한 뒤 만든 guarded probe는 run 단위로 learning rate, schedule, desired KL, learning epoch, mini-batch 수만 덮어쓸 수 있게 했습니다. gamma, lambda, clip, entropy는 CLI sweep 대상으로 열지 않았습니다. 문제를 진단하는 동안 return 정의와 exploration까지 한꺼번에 움직이면 어느 변경이 경로를 보존했는지 알 수 없기 때문입니다.

이 구분은 운영상 중요합니다. YAML 기본값은 설계된 configuration이고, 명령행 override는 특정 실행의 계약이며, TensorBoard와 run manifest에 남은 값이 실제 실행 증거입니다. 세 값이 다르면 명령을 입력한 의도보다 runtime 기록을 우선합니다.

1단계: rollout length를 초와 transition 수로 바꿔 읽습니다

G1 물리 simulation은 5ms마다 진행됩니다. 하나의 action을 네 번의 physics step 동안 유지하므로 policy step은 20ms, 즉 50Hz입니다. num_steps_per_env=24는 24초가 아니라 환경 하나당 0.48초의 연속 경험을 뜻합니다. 자세한 clock 경계는 physics step·decimation·vector rollout 글에서 분리해 설명했습니다.

physics dt       = 1 / 200 s = 0.005 s
policy dt        = physics dt × decimation 4 = 0.020 s
rollout duration = 24 policy steps × 0.020 s = 0.48 s
batch transitions = 4,096 envs × 24 steps = 98,304

num_steps_per_env를 늘리면 두 가지가 동시에 바뀝니다. 환경별로 더 긴 연속 구간을 보므로 touchdown이나 낙상 직전처럼 시간에 걸쳐 이어지는 현상이 한 rollout에 더 많이 들어옵니다. 동시에 전체 batch도 커집니다. 반면 env 수만 늘리면 같은 0.48초 구간을 더 많은 초기 상태에서 모으지만, 환경 하나의 연속 길이는 그대로입니다.

그래서 rollout length를 GPU 메모리나 FPS만 보고 바꾸지 않습니다. 먼저 문제가 시간적 문맥 부족인지, 상태 분포 부족인지 구분합니다. 한 발 지지 구간이 rollout보다 길어 잘리는 문제라면 step 수가 관련될 수 있습니다. 특정 발 phase의 표본이 드문 문제라면 환경 수나 reset 분포가 더 직접적인 손잡이입니다. 4,096를 8,192로 바꾼다고 한 로봇이 더 긴 미래를 경험하는 것은 아닙니다.

2단계: gamma와 lambda를 실제 20ms 시간축에 놓습니다

gamma=0.99는 “미래를 99% 본다”는 추상적인 비율이 아닙니다. 현재 구현에서는 20ms policy step마다 다음 reward의 비중에 0.99가 곱해집니다. 24 step 뒤의 reward 가중치는 0.99^24 ≈ 0.786입니다. 할인 가중치가 절반이 되는 시점은 약 69 step, 현재 clock으로 약 1.38초입니다.

rollout은 0.48초인데 gamma의 반감기는 더 깁니다. 이것이 모순은 아닙니다. rollout 끝에서 critic의 value estimate로 bootstrap하기 때문에 return이 24 step에서 무조건 0으로 잘리지 않습니다. 다만 0.48초 바깥의 결과는 실제 reward를 직접 본 값보다 critic 예측에 더 의존합니다. gamma를 높이는 일은 단순히 “장기적으로 생각하게 만들기”가 아니라 critic이 책임지는 범위를 늘리는 일이기도 합니다.

lambda=0.95는 GAE에서 여러 시점의 TD error를 얼마나 길게 섞을지 정합니다. 실제 advantage decay는 gamma × lambda = 0.9405입니다. 이 가중치의 반감기는 약 11.3 step, 0.226초이고 24 step 뒤에는 약 0.229가 남습니다. 이 숫자는 policy memory 길이가 아닙니다. observation history나 LSTM이 복원하는 숨은 상태와, GAE가 학습 신호를 시간축에 배분하는 방식은 서로 다른 문제입니다.

discount weight after k steps:        gamma^k
GAE TD-error weight after k steps:    (gamma × lambda)^k

G1 at 50 Hz:
gamma^24              = 0.7857
(gamma × lambda)^24   = 0.2294

gamma나 lambda를 바꾸면 critic target과 advantage 분포가 함께 바뀝니다. 기존 checkpoint의 actor weight만 이어 붙이고 나머지 통계를 동일하다고 보는 것도 위험합니다. 저는 delayed consequence가 실제 병목이라는 증거 없이 이 값을 reward coefficient처럼 sweep하지 않습니다. GAE가 왜 bias와 variance를 절충하는지는 Generalized Advantage Estimation 논문을 기준으로 삼았습니다.

3단계: mini-batch와 epoch를 곱해 optimizer step 수를 계산합니다

기본 batch 98,304개를 4개 mini-batch로 나누면 한 mini-batch에는 24,576개의 transition이 들어갑니다. 5 epoch 동안 같은 rollout을 다시 섞어 사용하므로 한 iteration에는 4 × 5 = 20번의 optimizer step이 발생합니다. 각 transition은 평균적으로 다섯 번 학습에 사용됩니다.

여기서 mini-batch 수를 늘리는 것을 데이터 증가로 오해하기 쉽습니다. 4개를 8개로 바꿔도 rollout은 여전히 98,304개입니다. mini-batch 하나가 12,288개로 작아지고, epoch마다 optimizer step이 4번에서 8번으로 늘어납니다. gradient 표본은 작아져 잡음이 커질 수 있고, 같은 epoch에서도 파라미터를 더 자주 갱신합니다.

설정 mini-batch 크기 optimizer step / iteration transition 재사용
기본 5 epoch × 4 batch 24,576 20 5회
guarded 1 epoch × 4 batch 24,576 4 1회
진단용 1 epoch × 8 batch 12,288 8 1회

따라서 num_mini_batches=8만 보고 update가 작아졌다고 말할 수 없습니다. epoch를 함께 봐야 합니다. 현재 일부 one-iteration probe가 1 epoch·8 mini-batch를 사용한 이유는 큰 dataset을 얻기 위해서가 아니라, 한 번만 재사용하면서 더 작은 optimizer 단위로 parameter movement를 관찰하기 위해서였습니다. 효과가 좋다는 일반 결론이 아니라 진단 surface의 선택입니다.

4단계: learning rate는 epoch와 분리해서 해석하지 않습니다

learning rate는 optimizer step 한 번의 크기에 관여합니다. 하지만 정책이 iteration 하나에서 얼마나 이동하는지는 learning rate 하나로 결정되지 않습니다. 같은 1e-5라도 20번 갱신하는 설정과 4번 갱신하는 설정은 다릅니다. mini-batch gradient 방향, Adam의 moment, advantage normalization, gradient clipping도 실제 이동량을 바꿉니다.

v391은 기존 checkpoint에 새 reward profile을 붙여 10 iteration을 실행한 warm-start probe였습니다. 첫 저장 checkpoint부터 route·heading·foot separation이 무너졌습니다. adaptive schedule 아래 learning rate는 1e-5에서 1.139e-4까지 올랐습니다. 이후 v393에서는 같은 부모와 reward profile을 두고 schedule을 fixed, learning rate를 1e-5, epoch를 1, mini-batch를 4, 전체 iteration을 2로 제한했습니다.

대조 v391 adaptive probe v393 guarded probe
새 rollout / iteration 98,304 transition 98,304 transition
learning rate 1e-5 → 1.139e-4 1e-5 fixed
epoch × mini-batch 5 × 4 1 × 4
실행 길이 10 iteration 2 iteration
route-valid row 첫 checkpoint 98 1,371
foot crossing row 151 0
판정 즉시 destructive drift 경로 보존, morphology는 실패

이 대조는 update budget bundle이 경로 붕괴와 관련 있음을 보여 줍니다. learning rate 하나의 인과 효과를 분리한 실험은 아닙니다. schedule, epoch, iteration 수가 함께 줄었기 때문입니다. 그래서 “1e-5가 G1의 정답”이라고 결론 내리지 않았습니다. 확인한 것은 최소한의 update surface에서는 부모 정책의 경로를 보존할 수 있다는 사실이며, 형태 문제를 고치려면 별도의 objective와 actionability 근거가 필요하다는 점입니다.

5단계: adaptive KL은 요청한 learning rate를 그대로 두지 않습니다

현재 custom PPO는 schedule이 adaptive이고 desired_kl이 설정돼 있으면 mini-batch마다 이전 분포와 현재 분포의 KL divergence를 계산합니다. 평균 KL이 목표의 두 배보다 크면 learning rate를 1.5로 나누고, 목표의 절반보다 작으면 1.5를 곱합니다. 허용 범위는 1e-5에서 1e-2입니다.

if mean_kl > 2.0 × desired_kl:
    learning_rate = max(1e-5, learning_rate / 1.5)
elif 0 < mean_kl < 0.5 × desired_kl:
    learning_rate = min(1e-2, learning_rate × 1.5)

이 때문에 v488에서 command에 learning_rate=1e-6을 넣었지만 실제 optimizer와 TensorBoard에는 1e-5가 기록됐습니다. 코드의 adaptive floor가 요청값보다 컸기 때문입니다. 입력한 숫자만 보고 "1e-6 실험"이라고 기록했다면 존재하지 않은 조건을 비교할 뻔했습니다.

adaptive schedule은 나쁜 기능이 아닙니다. 정책 분포 변화가 작을 때 update를 키우고 너무 클 때 줄이는 feedback controller입니다. 다만 원인 분리가 목적이라면 controller가 learning rate를 중간에 바꾸는 것이 방해가 될 수 있습니다. 그래서 guarded probe에서는 fixed schedule을 사용하고 실제 runtime learning rate를 manifest와 TensorBoard에서 다시 확인했습니다.

Isaac Lab의 공식 RSL-RL configuration APInum_steps_per_env, epoch, mini-batch, learning rate, schedule, gamma, lambda, entropy, desired KL을 별도 항목으로 정의합니다. 항목이 따로 있다는 사실보다 어느 항목이 runtime에서 다른 항목을 다시 쓰는지 확인하는 것이 더 중요합니다.

6단계: clip 0.2를 정책 변화의 hard bound로 읽지 않습니다

clip_param=0.2이면 sampled action의 probability ratio에서 surrogate objective가 주로 보는 구간은 0.8~1.2가 됩니다. 하지만 신경망 parameter나 모든 상태의 action probability를 이 범위 안에 강제로 가두는 것은 아닙니다. 같은 batch를 여러 epoch 반복하고 다른 sample의 gradient가 함께 작용하면 실제 분포는 더 멀리 움직일 수 있습니다.

PPO 원 논문이 clipped objective와 여러 epoch의 mini-batch update를 함께 제안한 이유도 sample 재사용과 과도한 policy change 사이의 절충에 있습니다. clip만 0.2에서 0.1로 낮춘다고 update budget 전체가 절반이 되지는 않습니다. clipping된 sample 비율과 KL, learning rate, epoch를 같이 보지 않으면 실제로 어느 경로가 제한됐는지 알 수 없습니다.

현재 G1 기본 logger는 clip fraction을 직접 남기지 않습니다. KL도 adaptive controller 안에서 계산되지만 기본 event scalar로 보존되지 않았던 구간이 있습니다. 관측할 수 없는 controller를 정교하게 tuning하는 것은 순서가 뒤집힌 작업입니다. 먼저 clip fraction과 KL logging을 추가하거나, fixed schedule의 bounded probe로 parameter movement를 확인해야 합니다.

7단계: entropy는 나쁜 자세를 고치는 벌점이 아닙니다

flat G1 기본 entropy coefficient는 0.002이고 초기 Gaussian action std는 1.0입니다. rough-terrain 설정 파일은 entropy 0.005와 rollout 32 step, learning rate 5e-4를 정의합니다. 이것은 rough terrain 정책이 이미 더 좋다는 실행 결과가 아니라, 더 다양한 지형 반응을 탐색하도록 설계된 별도 configuration입니다.

entropy coefficient를 높이면 actor mean 주변의 분포가 너무 빨리 좁아지는 것을 억제합니다. 탐색 부족이 병목이라면 도움이 될 수 있지만, reward가 잘못된 방향을 가리키거나 action interface가 접촉을 충분히 바꾸지 못하는 문제를 해결하지는 않습니다. 오히려 팔·허리까지 같은 방식으로 noise가 커지면 raw action saturation이나 불필요한 상체 운동을 늘릴 수 있습니다.

Policy/mean_std 하나도 29개 관절의 평균입니다. 다리의 탐색이 줄고 팔의 std가 커져도 평균은 비슷할 수 있습니다. entropy를 바꾸기 전에는 관절 그룹별 std와 action saturation, contact·route guard를 같이 남겨야 합니다. contact actionability 진단처럼 현재 action이 목표 물리량을 바꿀 수 있는지부터 확인하지 않고 noise를 키우는 것은 탐색이 아니라 불확실성 확대에 가깝습니다.

제가 PPO 하이퍼파라미터를 바꾸는 실제 순서

첫 단계는 하이퍼파라미터가 아닙니다. objective가 실제 trainer에 연결됐는지, reward와 observation의 단위가 맞는지, 부모 checkpoint와 normalizer가 의도한 상태인지 확인합니다. 그리고 route·contact·morphology 가운데 무엇을 보존해야 하는지 acceptance gate를 먼저 봉인합니다. 배선이 끊겼거나 목표가 틀린 상태에서 optimizer를 안정화하면 잘못된 목적을 더 안정적으로 최적화할 뿐입니다.

  1. clock과 표본 계약을 고정합니다. policy dt, env 수, rollout step, reset 분포를 기록하고 batch transition 수를 계산합니다.
  2. credit horizon을 검토합니다. delayed consequence가 실제 병목일 때만 gamma·lambda와 critic bootstrap을 의심합니다.
  3. update budget을 줄여 대조합니다. warm-start drift라면 fixed learning rate와 1 epoch부터 시작하고, mini-batch 크기와 optimizer step 수를 함께 적습니다.
  4. trust-region evidence를 남깁니다. clip, desired KL을 바꾸기 전에 KL·clip fraction·parameter delta를 관측 가능하게 만듭니다.
  5. exploration을 마지막에 조정합니다. entropy와 std는 관절 그룹별 행동 분포와 saturation을 함께 평가합니다.
  6. 한 번에 한 conceptual axis만 바꿉니다. runtime manifest, TensorBoard, paired checkpoint 평가가 같은 실행을 가리키는지 확인합니다.

여기서 "한 번에 하나"는 파일의 숫자 하나만 바꾸라는 뜻이 아닙니다. v393처럼 learning rate·schedule·epoch·iteration 수를 함께 묶어 update budget을 제한한다는 하나의 개념적 변경은 가능합니다. 대신 결과가 좋아졌을 때 어느 개별 숫자가 원인이었는지까지 주장하지 않습니다. 다음 실험에서 bundle을 분해해야 합니다.

현재 G1에서 고정한 것과 아직 답하지 못한 것

현재 flat G1 기본 configuration은 clip 0.2, gamma 0.99, lambda 0.95, entropy 0.002를 유지합니다. recent guarded PPO probe에서 run-scoped로 바꾸는 항목은 learning rate, fixed/adaptive schedule, desired KL, learning epoch, mini-batch 수입니다. 이 제한은 최적의 하이퍼파라미터를 찾았다는 뜻이 아니라, reward·representation·action 문제와 optimizer 문제를 섞지 않기 위한 실험 경계입니다.

v391과 v393의 대조로 update budget이 기존 경로 보존에 영향을 준다는 것은 관찰했습니다. 낮은 learning rate와 적은 epoch가 morphology를 고친다는 증거는 없습니다. clip 0.2가 최적이라는 비교도 하지 않았습니다. gamma·lambda의 대안이 접촉 timing을 개선한다는 결과도 없습니다. rough-terrain configuration은 존재하지만 이 글의 flat G1 판정을 대신하지 않습니다.

제가 하이퍼파라미터를 먼저 튜닝하지 않은 이유는 숫자가 중요하지 않아서가 아닙니다. 숫자 하나가 여러 시간축과 통계량에 걸쳐 작동하기 때문입니다. 먼저 실패가 reward, 관측, actionability, update drift 중 어디에 속하는지 좁혀야 합니다. 그 다음에야 learning rate나 epoch의 변화가 질문에 답하는 실험이 됩니다.

다음 실험에서 필요한 것은 더 넓은 sweep이 아닙니다. fixed update surface에서 실제 KL, clipping fraction, actor parameter delta를 같은 iteration에 기록하고, 부모와 후보를 동일 scenario·seed로 비교하는 것입니다. 그 증거가 있어야 clip과 desired KL을 조정한 결과를 "안정해 보였다"가 아니라 어느 policy-distance controller가 작동했는지 설명할 수 있습니다.


기술 기준: origin/main@ee4d96e의 G1 flat·rough·recurrent RSL-RL YAML, 200Hz physics/50Hz policy 환경 설정, training/rl/train.py의 run-scoped override, custom PPO adaptive schedule, v391~v393 실행 보고서를 대조했습니다. 이 글은 configuration과 실행 결과를 구분하며, 새 PPO 실행이나 checkpoint 승인을 주장하지 않습니다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤