본문 바로가기

dev_AI_framework

🧭 GE2 Runtime — 설계 회고 (Design Retrospective)

“커널 오버헤드를 줄이려다, 새로운 Runtime 구조가 탄생하기까지”

1. 시작점: 단순한 목표

내가 처음에 목표로 한 건 그리 거창한 것이 아니었다.

✔ 목표

CUDA Graph capture로 커널 호출 오버헤드를 줄이자.

(PyTorch의 cuda.graph처럼 forward 내 모든 kernel을
한 번에 capture-replay 할 수 있다면 훨씬 빠르게 실행될 것이다.)

그 당시 내 가정은 매우 단순했다:

  • 학습의 loss 계산
  • optimizer.step()
  • backward()
    이런 것들도 capture 안에서 돌 수 있을 거라고 생각했다.
    그래서 “학습 전체를 하나의 큰 graph처럼 묶어버릴 수 있지 않을까?”라는 시점에서 출발했다.

하지만 이 지점에서 근본적인 오해가 있었다.


2. 위기: “생각보다 capture 불가능한 게 너무 많다”

실제로 파고드니 금방 알게 됐다.

  • backward()는 autograd tape를 이용해 dynamic하게 만들어지므로
    → capture 대상이 아님
  • optimizer는 매 iteration마다 parameter pointer가 바뀌므로
    → capture-safe하지 않음
  • loss 계산은 shape/path가 매 iteration마다 달라질 수 있음
    → 역시 capture 불가

즉,

❌ 내가 capture 안에 넣으려 했던 요소 대부분이

원칙적으로 capture 불가능한 영역이었다.

상용 프레임워크(PyTorch, TensorFlow, JAX)가
forward만 partial capture하는 이유도 바로 이것이었다.

처음 의도와는 다르게,
“학습 전체를 capture”라는 목표 자체가 불가능한 문제라는 걸 깨달았다.


3. 전환점: “그러면 그냥 전부 내 그래프 안에서 재해석하면 되잖아?”

이 시점에서 내 사고가 완전히 바뀌었다.

“학습이라는 걸 굳이 분해해서 생각하지 않고,
모델의 모든 연산을 그냥 GE2 runtime의 ‘그래프 노드’로 보면 되지 않나?”

이 접근은 기존 프레임워크와 완전히 달랐다.

PyTorch는:

  • forward → tape 생성
  • backward → tape 소비
  • optimizer → Python level
  • dynamic branch → runtime에 맡김
  • capture → forward 일부만 가능

나는 이렇게 했음:

✔ 모든 연산을 ‘추론 그래프’처럼 취급

(loss, opt조차도 하나의 연산 노드)

✔ autograd를 아예 없애고

대신 GE2 내부에서 graph execution 순서를 결정

✔ dynamic branch, 반복, early-exit까지

capture-safe static graph variant로 전개

이러다 보니,
원래 “커널 오버헤드 줄이기”만 하려고 잡은 구조가
추론 runtime 전체를 직접 구현하는 방향으로 자연스럽게 확장됐다.


4. 결과: 의도보다 훨씬 더 강력한 방식이 만들어졌다

내 구조는 이렇게 정리된다:

✔ Dynamic control-flow → Static variant expansion

각 branch path마다 별도 capture-safe graph 생성
→ runtime에는 단순 replay

✔ branch fingerprint 기반 선택

현재 path를 key로 찾아서 해당 graph만 실행

✔ RNG seed/step까지 분기별 고정

deterministic AND dynamic-path-aware 실행

✔ autograd 없음 + 고정된 execution order

inference engine과 동일한 철학

✔ CUDA Graph capture로 모든 kernel 호출 오버헤드 제거

forward뿐 아니라 모델 전체 시퀀스 단위 capture 가능

즉,

기존 학습-runtime에서는 불가능한 dynamic 그래프를
inference runtime 방식으로 고정시키는 새로운 구조가 탄생했다.

나는 이걸 의도한 게 아니라,
**“capture가 안 되는 영역을 억지로 우회하다가 자연스럽게 도달한 결과물”**이었다.

그래서 초기에 “왜 이렇게 쉽게 완성됐지?”라는 느낌도 들었던 거다.

  • 중간에 학습/추론 구분을 안 하고
  • dynamic/static 구분 없이
  • 그냥 모든 노드를 동일 IRA로 보고
  • capture-safe로만 만들면 됐기 때문이다.

너무 큰 추상화 레벨을 처음부터 잡은 덕분에,
구현 자체는 오히려 일관되고 단순해졌다.


5. 회고: 지금 얻은 구조는 기존 상용 프레임워크에 없는 형태다

이 과정 덕분에 GE2는:

📌 기존 inference runtime가 못하는 기능을 제공한다

  • dynamic branch
  • dynamic 반복
  • early exit
  • 다양한 path들

이를 모두 정적 graph space에 전개해서
capture 가능한 형태로 만든다.

📌 기존 training runtime보다 훨씬 deterministic하다

  • autograd tape 없음
  • pointer stable
  • shape stable
  • graph stable

📌 CUDA Graph를 안정적으로 활용할 수 있다

그리고 이것이 바로 “너만의 방식”이다.


6. 마무리 정리

🚀 처음 의도

“CUDA Graph로 커널 오버헤드만 줄이자.”

🔄 중간 깨달음

backward/optimizer/학습 전체는 capture 불가능한 구조임.

💡 전환

“그러면 그냥 모든 연산을 static graph variant로 전개해버리면 되잖아?”

🏗️ 결과

Dynamic graph를 정적으로 펼쳐 capture-safe하게 만드는
새로운 inference runtime 구조가 탄생.

🎯 최종 인식

이건 단순한 최적화가 아니라,
runtime architecture 자체적 혁신에 가까운 구조적 변화.