34편에서 DES를 RL 환경으로 포장하는 인터페이스를, 35편에서 그 수학적 정식화를 갖췄다. 그런데 인터페이스와 정식화가 맞아도 학습은 얼마든지 실패할 수 있다. 시뮬레이터가 너무 느려서, 관측이 학습하기 나쁜 모양이어서, 보상이 엉뚱한 행동을 부추겨서, 혹은 시뮬레이터의 버그를 정책이 파고들어서. 이 글은 그 실전의 문제들 — 학습이 잘 되는 시뮬레이터와 문제를 어떻게 설계하는가 — 를 다룬다. 34편이 미뤄 둔 보상 설계가 이 글의 무게중심이다.
평가용과 학습용은 요구가 다르다
같은 fab 모델이라도, 그것을 평가에 쓰느냐 학습에 쓰느냐에 따라 요구 사항이 완전히 달라진다.
31편의 평가는 시나리오 × replication으로 수백~수천 번의 실행을 요구했다. 그 정도면 정밀한 모델을 하룻밤 돌려서 감당할 수 있다. 반면 RL 학습은 수백만 에피소드 규모의 상호작용을 요구한다. 원인은 RL의 sample complexity — 좋은 정책에 도달하기까지 필요한 환경 상호작용(sample)의 개수 — 가 크다는 데 있다. 시행착오로 배우는 방법의 숙명이다: 에이전트는 좋은 행동을 우연히 해 보고 그 결과를 여러 번 확인해야 배우는데, fab처럼 상태 공간이 크고 보상이 늦게 오는 문제일수록 필요한 시도 횟수가 폭발한다.
이 프레임이 시뮬레이터 성능 요구를 결정한다. 20편 서두에서 “RL 연계는 수백만 에피소드를 요구한다”고 깔아 둔 복선이 여기서 회수된다. 에피소드 하나가 10초 걸리는 시뮬레이터로 100만 에피소드를 돌리면 약 116일이다 — 실험 한 번에 넉 달이면 연구도 개발도 불가능하다. 평가용 시뮬레이터에서 속도는 편의지만, 학습용 시뮬레이터에서 속도는 1차 제약이다.
속도를 어디서 버는가
속도 확보의 일반론 — FEL 자료구조, 통계 수집 최소화, 이벤트 굵기 조정, 실행 환경 교체 — 은 20편에, 병렬화의 원리는 21편에 맡긴다. 여기서는 학습 특화 내용만 세 가지 짚는다.
Fidelity 절감: 학습용과 평가용의 2단 구성
20편에서 “질문에 답하는 데 필요 이상으로 세밀한 모델이 최악의 성능 버그”라 했다. 학습에서는 이 원칙이 한층 공격적으로 적용된다 — 학습에는 조금 거친 모델을, 최종 평가에는 정밀한 모델을 쓰는 2단 구성이다. 예컨대 학습용 모델에서는 AMHS 반송을 상수 지연으로 뭉개고 operator를 생략하되, 학습된 정책의 성적표는 반송·operator까지 갖춘 정밀 모델(또는 31편의 평가 절차)에서 받는 식이다. 물론 거친 모델에서 배운 정책이 정밀한 현실에서도 통하는가라는 새 질문이 생기는데, 이 fidelity 트레이드오프는 37편의 주제다.
Replication 병렬 = 병렬 rollout
21편의 결론을 기억하자 — 거대한 실행 하나의 병렬화(PDES)는 어렵지만, 독립 replication의 병렬은 embarrassingly parallel이다. RL 학습은 정확히 후자의 구조를 가진다. rollout(정책을 환경에서 실행해 경험 궤적을 모으는 것)은 에피소드 단위로 서로 독립이므로, 시뮬레이터 instance를 코어마다 하나씩 띄워 각자 rollout을 생산하게 하면 된다.
실제 분산 RL의 표준 구조가 이것이다. actor(정책의 사본을 들고 자기 시뮬레이터와 상호작용하며 경험을 생산하는 프로세스) 수십~수백 개가 CPU에서 병렬로 돌고, learner(학습기)는 모여든 경험을 GPU에서 배치로 묶어 정책의 parameter를 갱신한 뒤 새 정책을 actor들에게 배포한다. 21편에서 “GPU는 이벤트 구동 DES와 궁합이 나쁘다”고 했는데, 이 구조는 그 결론과 정합적이다 — DES는 CPU에서 여러 개로, 신경망 연산은 GPU에서 배치로, 각자 잘하는 곳에 배치하는 분업이다.
벡터화 환경, 그리고 고속 재작성이라는 선택
RL 프레임워크들이 제공하는 또 하나의 가속 수단이 벡터화 환경(vectorized environment) — 환경 개를 한 프로세스 안에서 배열 연산으로 동시에 진행시키는 접근 — 이다. 시간 스텝 기반 환경(물리 시뮬레이션·게임)에서는 강력하다. 개 환경이 모두 같은 갱신 규칙을 한 스텝씩 밟으므로, 상태를 행짜리 배열로 쌓아 GPU에서 한 번에 갱신할 수 있다.
DES에는 이 접근이 잘 맞지 않는다. 21편의 GPU 논의 그대로다 — 환경마다 다음 이벤트의 종류도 시각도 다르고(이벤트 비동기성), 34편에서 봤듯 decision point 사이의 간격 조차 환경마다 다르다. 개의 DES를 한 배열 연산으로 묶을 “공통의 스텝”이 없는 것이다.
그래서 fab RL 연구는 다른 길을 택하곤 한다 — 시뮬레이터를 아예 학습용으로 고속 재작성하는 것이다. 33편의 PySCFabSim이 그 사례다. SimPy의 프로세스 기반 편의(와 그 문맥 전환 비용)를 버리고 순수 Python event-based로 다시 지어 초당 수십만 job 처리를 목표로 했다 — 모델 기술의 편의보다 학습 실험의 가능성을 산 선택이다.
관측 설계: 정책이 배울 수 있는 입력
34편에서 관측을 “국소 lot feature + 전역 요약”으로 요약한다고 했다. 여기서는 그 요약을 학습이 잘 되게 만드는 원칙 네 가지를 정리한다.
(a) 결정에 필요한 정보가 다 있는가. 관측에서 빠진 정보가 결정에 실제로 중요하다면 — 예컨대 “이 장비가 곧 PM에 들어간다”는 사실이 관측에 없다면 — 에이전트는 겉보기에 같은 상태에서 다른 결과를 겪는다. 이런 상황이 부분 관측이고, 그 정식화가 POMDP(Partially Observable MDP; 에이전트가 상태 전체가 아니라 그 일부 관측만 받는 MDP)다. 원리적 해법은 이력을 요약해 관측을 보강하거나(최근 개 관측을 붙이는 등), 이력을 내부 상태로 압축하는 recurrent 정책(RNN처럼 과거 입력의 요약을 유지하는 신경망 정책)을 쓰는 것이다. 그러나 실무의 우선순위는 그보다 앞에 있다 — 시뮬레이터는 어차피 전체 상태를 알고 있으므로, 결정에 필요한 정보를 관측에 넣어 주는 것이 먼저다. 관측 설계로 피할 수 있는 POMDP를 굳이 만들 이유가 없다.
(b) 정규화와 스케일 통일. 34편에서 이미 강조했다 — 큐 길이는 0~50, 납기 여유는 수천 분처럼 스케일이 뒤섞인 입력은 신경망 학습을 불안정하게 만든다. 각 feature를 대표 규모(용량, 대표 처리 시간)로 나눠 비슷한 범위로 맞춘다.
(c) 크기 가변성. 큐의 lot 수는 결정 시점마다 다른데, 표준 신경망 입력은 고정 크기다. 두 가지 처리가 있다. 하나는 고정 크기 요약 — lot들의 feature를 평균·최소·최대 같은 통계량으로 접거나, 최대 개 슬롯을 고정하고 빈 슬롯은 masking하는 방식이다. 다른 하나는 set 입력 — lot 집합을 순서·개수에 무관하게 처리하는 구조(attention 기반 등)를 쓰는 방식으로, 표현력이 좋은 대신 구현이 무겁다.
(d) 상대적·무차원 feature. 절대 WIP 300개라는 값은 그 fab·그 로딩 수준에서만 의미가 있다. 목표 대비 WIP 비율, 31편의 CR(납기 여유/잔여 처리 시간)이나 x-factor처럼 무차원화된 비율 feature는 조건이 달라져도 의미가 유지되므로, 정책이 특정 시나리오의 절대 수치에 과적합하지 않고 다른 조건으로 일반화하는 것을 돕는다. 31편의 fab 지표들이 비율로 정의된 데에는 이유가 있었던 셈이다.
보상 설계: 측정되는 것이 최적화된다
이 글의 무게중심이다. 34편의 문제 제기를 다시 가져오면 — fab에서 진짜 목적(cycle time, tardiness 같은 31편의 지표)은 lot이 완료될 때에야 실현된다. 보상의 관점에서 이는 두 가지 나쁜 성질을 뜻한다. 희소하고(sparse reward; 대부분의 step에서 보상이 0이고 드물게만 신호가 오는 것) 지연된다(delayed reward; 행동과 그 결과 보상 사이의 시간 간격이 긴 것). 수 주짜리 lot의 완료 보상을 그 사이 수백 번의 결정에 배분해야 하는 credit assignment 문제(받은 보상을 과거의 어느 행동 덕분/탓으로 돌릴 것인가)가 극단적으로 어려워지고, 학습은 느려진다.
Proxy 보상의 유혹과 위험
자연스러운 대응은 매 step 계산 가능한 proxy 보상(진짜 목적 대신 쓰는 대리 신호)이다. 예컨대 “큐 길이를 줄이면 +보상”을 주면 신호가 촘촘해져 학습이 빨라질 것 같다.
문제는 에이전트가 proxy 그 자체를 최적화한다는 것이다. 구체적으로 보자. batch 장비 앞에서 “큐를 비우면 보상”을 받는 에이전트는, 배치가 반쯤 찼을 때 기다렸다가 꽉 채워 보내는 대신 비효율적인 반쪽짜리 배치를 조기 출발시키는 법을 배운다 — 큐는 즉시 비워지므로 proxy 보상은 최대화되지만, 장비 capacity를 낭비해 진짜 목적(throughput·cycle time)은 나빠진다. 이렇게 proxy를 최적화하면서 진짜 목적을 해치는 왜곡을 reward hacking이라 부른다. 원칙은 이것이다 — 측정되는 것이 최적화된다. 보상 함수는 “이 정도면 대충 좋은 방향이겠지”가 아니라, “이것을 문자 그대로 극대화하는 정책이 나온다면 그 정책을 받아들일 수 있는가”를 기준으로 설계해야 한다.
Potential-based shaping: 안전한 중간 신호
그렇다면 중간 신호를 주는 것 자체가 금지인가? 그렇지는 않다. 최적 정책을 바꾸지 않으면서 중간 신호를 추가하는 안전한 형태가 이론적으로 존재한다. 상태에 대한 potential 함수 — “이 상태가 얼마나 유망한가”의 점수 — 를 하나 정하고, 원래 보상에 다음 항을 더한다.
이 식이 말하는 것: 추가 보상이 상태 점수의 (할인된) 증가분의 형태를 가지면, 어떤 궤적을 따라가든 추가분의 합이 시작과 끝의 potential 차이로만 정리되어(중간 항들이 연쇄적으로 상쇄된다), 정책 간 우열이 바뀌지 않는다 — 즉 원래 문제의 최적 정책이 그대로 최적으로 남는다. 이런 기법을 reward shaping(보상 조형; 학습을 돕기 위해 보상 신호를 변형하는 것)이라 하고, 이 형태를 potential-based shaping이라 한다. 위의 큐 길이 예가 위험했던 이유도 이 렌즈로 설명된다 — “큐를 비우면 +보상”은 potential의 차이 형태가 아니라서 정책의 우열 자체를 바꿔 버린 것이다. fab이라면 를 “납기 대비 진도”류의 상태 점수로 잡는 식의 응용을 상상할 수 있다. 상세 이론은 여기서는 생략하고, 형태만 기억해 두자 — 중간 신호를 주고 싶으면 상태 점수의 차이 형태로 주라.
다목적 스칼라화: 가중치가 정책을 결정한다
마지막 문제는 31편에서 본 대로 fab의 목적이 여럿이라는 것이다 — cycle time, tardiness, throughput, 그 트레이드오프. 표준 RL은 스칼라 보상 하나를 요구하므로, 실무는 보통 가중합으로 접는다.
이 식이 말하는 것: 가중치 벡터 가 사실상 정책을 결정한다. 를 키우면 납기 절박한 lot을 우대하는 정책이, 를 키우면 같은 레시피를 몰아 setup을 아끼는 정책이 나온다 — 학습 알고리즘은 주어진 스칼라를 충실히 최적화할 뿐이고, “무엇이 좋은 운영인가”라는 판단은 알고리즘이 아니라 에 들어 있다. 그래서 는 한 번 정하고 잊는 값이 아니라 민감도 실험의 대상이다 — 를 몇 조합 바꿔 학습한 정책들을 31편의 다중 지표로 평가해, 가중치 선택이 지표 트레이드오프를 어디로 밀었는지 확인해야 한다. 31편에서 룰 평가에 요구했던 “지표 축” 규율이, 학습에서는 보상 설계 단계로 앞당겨져 들어오는 셈이다.
학습 정책은 시뮬레이터의 허점을 착취한다
보상 다음으로 뼈아픈 실전 교훈이다. 학습 정책은 시뮬레이터의 버그와 허점을 찾아내 착취한다.
평가용으로 쓸 때 시뮬레이터의 사소한 오류는 대개 무해하다 — FIFO나 CR 같은 룰은 그 오류를 이용할 줄 모르고, 지표에 작은 편향을 남길 뿐이다. 학습은 다르다. RL은 보상을 올리는 방향이라면 무엇이든 찾아내는 최적화 과정이므로, 모델에 빠진 제약이 있으면 정책이 그 구멍을 파고든다. 예컨대 실제 fab에는 있는 queue time 제약(선행 스텝 후 일정 시간 안에 후속 스텝을 처리해야 하는 제약)이 모델에 빠져 있다면, 학습 정책은 lot을 그 구간에 무한정 세워 두는 — 시뮬레이터 안에서는 이득이지만 현실에서는 웨이퍼를 폐기하게 만드는 — “치트키”를 배울 수 있다. 평가라면 눈에 띄었을 오류가, 학습에서는 정책의 핵심 전략이 되어 버리는 것이다.
여기서 두 가지가 따라 나온다.
- V&V는 학습의 전제 조건이다. 12편의 verification(코드가 모델대로 도는가)과 validation(모델이 현실대로인가)을 통과하지 않은 시뮬레이터로 학습하면, 정책은 현실이 아니라 시뮬레이터의 결함에 최적화된다. 역방향도 성립한다 — 학습된 정책의 이상 행동은 시뮬레이터 결함의 탐지기다. 정책이 이해할 수 없이 좋은 성적을 내거나 기괴한 행동 패턴을 보이면, 정책을 의심하기 전에 시뮬레이터의 구멍을 의심하라. 12편의 face validity 점검을 정책의 행동 궤적에 적용하는 것이다.
- 재현 가능한 실험이 디버깅의 기본기다. 3편의 난수 스트림 관리가 여기서 다시 등장한다 — 도착·고장·처리시간 스트림을 분리하고 시드를 고정하면, “이상 행동이 나온 그 에피소드”를 정확히 다시 돌려 이벤트 단위로 추적할 수 있다. 시드 관리 없는 학습 실험은 재현할 수 없는 버그 리포트만 쌓는다.
Curriculum과 시나리오 다양화
마지막 설계 축은 무엇을 겪게 하며 가르칠 것인가이다.
Curriculum learning(쉬운 과제부터 시작해 점차 어려운 과제로 옮겨 가는 학습 전략)은 fab에 자연스럽게 적용된다. 처음부터 로딩 95%의 포화 fab에서 학습을 시작하면, 초기의 무작위에 가까운 정책은 큐 폭발과 나쁜 보상만 겪어 학습 신호를 얻기 어렵다. 낮은 로딩 — 31편에서 봤듯 어느 정책이든 그럭저럭 굴러가는 조건 — 에서 기본기를 배우게 한 뒤, 로딩을 단계적으로 올려 룰 간 격차가 벌어지는 어려운 영역으로 데려가는 것이다.
동시에 학습 시나리오를 다양화해야 한다. 단일 시나리오(고정된 제품 믹스, 고정된 다운타임 패턴)에서만 학습한 정책은 그 시나리오에 과적합한다 — 31편에서 “한 시나리오의 승자가 다른 시나리오에서 진다”고 했던 문제가, 학습에서는 정책 안에 새겨져 버린다. 대응은 에피소드마다 조건을 무작위로 흔드는 것이다: 제품 믹스 비율, 다운타임 강도·시점, 투입 패턴을 분포에서 뽑아 매번 다른 fab을 겪게 하면, 정책은 특정 조건의 요령이 아니라 조건을 관측에서 읽고 대응하는 법을 배운다.
이 “시뮬레이터 내부 조건의 무작위화”를 한 걸음 밀고 나가면 다음 질문에 닿는다 — 시뮬레이터와 현실의 차이조차 무작위화로 덮을 수 있는가? 그것이 domain randomization이고, 시뮬레이터에서 배운 정책이 현실에서 통하는가라는 sim-to-real 문제와 함께 37편에서 다룬다.
정리
- 평가는 수백~수천 실행, 학습은 수백만 에피소드 — RL의 sample complexity가 시뮬레이터 성능 요구를 결정하고, 학습용 시뮬레이터에서 속도는 1차 제약이다 (20편 복선 회수).
- 속도는 fidelity 절감(학습용 거친 모델 + 평가용 정밀 모델의 2단 구성), replication 병렬 = 병렬 rollout(actor 여러 개 + GPU learner의 분업), 그리고 벡터화가 어려운 DES의 특성상 고속 재작성(PySCFabSim의 선택)에서 나온다.
- 관측 설계 원칙: 필요 정보의 완비(피할 수 있는 POMDP를 만들지 마라), 정규화, 크기 가변성 처리(고정 요약 vs set 입력), 상대적·무차원 feature(일반화를 돕는다).
- 보상 설계: 진짜 목적은 희소·지연 보상이고, proxy 보상은 reward hacking(“측정되는 것이 최적화된다”)의 위험을 진다. 안전한 중간 신호의 형태는 potential-based shaping . 다목적 가중합에서는 가중치가 사실상 정책을 결정하므로 민감도 실험이 필수다.
- 학습 정책은 시뮬레이터의 허점을 착취한다 — V&V(12편)는 학습의 전제 조건이고, 정책의 이상 행동은 역으로 시뮬레이터 결함의 탐지기다. 난수 스트림 분리와 시드 고정(3편)이 디버깅의 기본기다.
- Curriculum(쉬운 로딩 → 어려운 로딩)과 시나리오 무작위화(믹스·다운타임)로 과적합을 막는다 — 이 아이디어의 연장이 다음 글의 domain randomization이다.
시뮬레이터 안에서 잘 배우는 법까지 왔다. 남은 가장 무거운 질문은 바깥에 있다 — 시뮬레이터에서 배운 정책이 현실에서도 통하는가. 37편에서 sim-to-real gap을 다룬다.