[인턴] 논문만 주면 코드가 나올까 — PaperCoder(Paper2Code) 읽기 (2) 세 단계 파이프라인이 실제로 도는 방식

들어가며

지난 글에서는 PaperCoder(Paper2Code, ICLR 2026)의 Abstract와 Introduction만 붙잡고 “왜 이 논문이 필요했고, 무엇을 하겠다고 선언했는가”를 정리했다. 요약하면 이렇다. 논문은 많은데 코드는 평균 20%만 공개된다는 현실, 그 위에서 오직 논문만으로 충실한 코드 레포를 만들어 내겠다는 선언, 그리고 그것을 planning·analysis·coding 세 단계로 나눠서 하겠다는 큰 그림.

이번 글은 그 뼈대의 속을 채운다. 논문 3장(Method)으로 들어가, 논문 한 편이 실제로 어떤 순서를 거쳐 코드 레포가 되는지를 단계별로 따라간다. 각 단계가 무엇을 입력으로 받아 무엇을 만들고, 그 결과가 다음 단계로 어떻게 넘어가는지에 초점을 둔다.


1. 문제를 어떻게 정의했나 — 그리고 왜 단발 생성을 버렸나

먼저 저자들이 과제를 어떻게 형식화했는지부터 보자. 논문 $R$ 을 코드 레포 $C$ 로 옮기는 함수(또는 모델) $M$ 을 상정한다.

\[M(R) = C, \quad C = \{c_1, c_2, \dots, c_n\}\]

$C$ 는 여러 파일 $c_i$ 로 이뤄지고, 각 파일은 논문 속 방법·실험의 서로 다른 부분을 구현하되 전체가 하나의 일관된 파이프라인을 이뤄야 한다.

가장 순진한 방법은 그냥 LLM에게 논문을 통째로 넣고 레포 전체를 한 번에 뽑으라고 시키는 것이다.

\[M(R) := \text{LLM}(\mathcal{T}(R))\]

여기서 $\mathcal{T}$ 는 프롬프트 템플릿이다. 하지만 이 단발(single-pass) 생성은 안 된다는 게 논문의 판단이다. 이유는 세 가지다 — 과학 논문 자체가 복잡하고, 현재 모델은 롱컨텍스트에 한계가 있으며, 파일 간 의존성과 전역 구조를 일관되게 유지하기가 어렵다. 그래서 과제를 잘게 쪼개, 각 부분을 전문화된 에이전트에 맡기는 쪽으로 간다.

전체 과정은 세 단계로 형식화된다.

\[\text{Planning: } P = M_{\text{plan}}(R)\] \[\text{Analysis: } A = M_{\text{analysis}}(R, P)\] \[\text{Coding: } C = M_{\text{code}}(R, P, A)\]

여기서 눈여겨볼 점은 뒷 단계가 앞 단계의 출력을 그대로 입력으로 받는다는 것이다. analysis는 논문 $R$ 뿐 아니라 planning 결과 $P$ 를 보고, coding은 $R$·$P$·$A$ 를 다 본다. 정보가 단계마다 쌓이면서 내려간다.


2. Planning — 계획을 네 조각으로 나누다

논문은 왜 planning이 필요한지를 흥미로운 관점으로 설명한다. 논문은 소프트웨어 명세서가 아니다. 사람에게 아이디어와 발견을 설득하려고 쓴 글이라, 고수준 동기·서사·부가 설명이 뒤섞여 있다. 사람이 이해하기엔 좋지만, 소프트웨어 공학 관점에서는 노이즈가 많고 느슨하며 모호하다. 그래서 planning은 이 비정형 텍스트를 구현 수준의 추상으로 옮기는 단계다.

planning은 다시 네 하위 단계로 나뉜다. 에이전트 하나에 몰아주지 않고 순차로 쪼갠 건, 각 단계에서 LLM이 감당할 인지 부하를 줄이기 위해서다.

\[M_{\text{plan}}(R) \to P = \{o, d, l, g\}\]
  • $o$ : overall plan (전체 계획)
  • $d$ : architecture design (아키텍처 설계)
  • $l$ : logic design (로직 설계)
  • $g$ : configuration file (설정 파일)

핵심은 각 단계가 앞 단계의 출력을 맥락으로 받는다는 점이다. 하나씩 보자.

2.1 Overall Plan — 무엇을 구현할지 먼저 훑는다

첫 단계는 논문 전체에 흩어진 핵심 컴포넌트와 기능을 고수준으로 요약해, 어떤 방법과 실험을 구현해야 하는지 식별하는 것이다. 모델 컴포넌트, 학습 목표, 데이터 처리 절차, 평가 프로토콜 등이 여기 들어간다. 이게 이후 모든 단계의 토대가 된다.

\[M_{\text{plan}}^{(1)}(R) := \text{LLM}(\mathcal{T}_{\text{plan}}^{(1)}(R)) \to o\]

2.2 Architecture Design — 무엇을 만들지 구조로 그린다

overall plan과 논문을 함께 놓고, 이번엔 레포 수준의 아키텍처를 정의한다. 파일을 식별하고, 모듈로 묶고, 관계를 정한다. 구체적으로 에이전트는 세 가지를 만든다.

  • 파일 리스트(file list) — 레포의 전체 파일 구조
  • 클래스 다이어그램(class diagram) — 핵심 클래스와 속성 같은 정적 표현
  • 시퀀스 다이어그램(sequence diagram) — 동적인 상호작용
\[M_{\text{plan}}^{(2)}(R, o) := \text{LLM}(\mathcal{T}_{\text{plan}}^{(2)}(R, o)) \to d\]

2.3 Logic Design — 어떤 순서로 만들지 정한다

앞의 architecture design이 “무엇을 만들지”에 답했다면, logic design은 “이것들을 실제로 어떤 순서로 만들고 실행할지”에 답한다. 이 단계가 왜 중요한지 논문은 구체적인 예로 짚는다. 파일 B가 파일 A의 모듈을 import하는데, B를 A보다 먼저 생성해 버리면 코드 생성이 실패하거나 어긋난다. 개별 모듈은 공용 유틸리티·설정·데이터 로더에 의존하기 마련인데, 실행 순서가 명시되지 않으면 이런 의존이 깨진다.

그래서 logic design은 두 가지를 내놓는다 — 파일을 구현·실행할 순서가 매겨진 파일 리스트, 그리고 각 파일 내부 로직에 대한 더 세분화된 명세.

\[M_{\text{plan}}^{(3)}(R, o, d) := \text{LLM}(\mathcal{T}_{\text{plan}}^{(3)}(R, o, d)) \to l\]

2.4 Configuration Generation — 실행 설정을 뽑는다

planning의 마지막은 config.yaml 파일을 합성하는 것이다. 핵심 하이퍼파라미터, 모델 설정, 그 밖의 런타임 옵션이 여기 담긴다. 이게 두 가지 역할을 한다 — 코드 생성 과정을 명시적 설정으로 접지(grounding) 하고, 나아가 연구자가 소스 코드를 건드리지 않고도 실험 설정을 검토·조정할 수 있게 한다.

\[M_{\text{plan}}^{(4)}(R, o, d, l) := \text{LLM}(\mathcal{T}_{\text{plan}}^{(4)}(R, o, d, l)) \to g\]

여기서 특히 눈여겨볼 프롬프트 지침이 하나 있다. config를 뽑을 때 논문은 “세부를 지어내지 말고(DO NOT FABRICATE DETAILS), 논문이 제공하는 것만 쓰라” 고 명시적으로 못 박는다. 이 “지어내지 마라”는 원칙은 뒤의 analysis·coding 단계에서도 반복해서 등장한다.


3. Analysis — 파일 하나하나를 구현 직전까지 해석한다

planning이 레포의 전체 구조와 실행 흐름을 잡았다면, analysis는 그 안으로 들어가 각 파일의 구현 수준 디테일을 해석한다. planning이 “무엇을 만들고 어떻게 관계 맺는가”에 답했다면, analysis는 “각 컴포넌트가 파일 수준에서 구체적으로 어떻게 구현돼야 하는가“에 답한다. 함수의 목표, 입출력 동작, 파일 내·파일 간 의존성, 논문에서 끌어낸 알고리즘 명세 등이 여기 들어간다.

방식은 파일 단위 반복이다. planning에서 식별된 파일 집합 $F$ 의 각 파일 $f_i$ 에 대해, 분석 에이전트가 개별 분석 $a_i$ 를 만든다.

\[\{ M_{\text{analysis}}(R, P, f_i) \}_{i=1}^{n=|F|}, \quad M_{\text{analysis}}(R, P, f_i) := \text{LLM}(\mathcal{T}_{\text{analysis}}(R, P, f_i)) \to a_i\]

이 단계의 프롬프트에는 강한 제약이 걸려 있다. “Data structures and interfaces”를 반드시 따르고 설계를 절대 바꾸지 말 것, 그리고 설정값은 언제나 config.yaml에서 참조하되 없는 값을 지어내거나 가정하지 말 것. planning이 정한 인터페이스와 config를 analysis가 마음대로 흔들지 못하도록 묶어 두는 것이다.


4. Coding — 실행 순서대로, 앞 파일을 보면서 순차 생성한다

마지막은 실제 코드를 만드는 단계다. 각 파일은 지금까지 쌓인 모든 맥락 — overall plan, architecture design, logic design, config, 그리고 그 파일의 analysis, 여기에 원문 논문까지 — 을 근거로 생성된다.

특히 중요한 건 생성 순서다. coding은 logic design에서 정한 실행 순서(ordered file list) 를 그대로 따라 파일을 하나씩 만든다. 그리고 $i$ 번째 파일을 만들 때는 이미 만들어 둔 앞선 파일들 ${c_1, \dots, c_{i-1}}$ 을 함께 입력으로 넣는다.

\[M_{\text{code}}(R, P, f_i, a_i, \{c_1, \dots, c_{i-1}\}) := \text{LLM}(\mathcal{T}_{\text{code}}(R, P, f_i, a_i, \{c_1, \dots, c_{i-1}\})) \to c_i\] \[C = \{c_i\}_{i=1}^{n=|F|}\]

이 반복 구조 덕에 $i$ 번째 코드는 자신의 의존성과 지금까지 만들어진 레포의 상태를 온전히 인지한 채 생성된다. 앞서 logic design이 왜 “순서가 매겨진 파일 리스트”를 굳이 만들었는지가 여기서 결실을 맺는다. B가 A를 import하려면 A가 먼저 존재해야 하고, coding은 실제로 A를 먼저 만들어 B의 입력으로 넣는다.

coding 프롬프트의 지침도 꽤 구체적이다 — 한 번에 파일 하나만 완결적으로 구현할 것, 설정은 항상 기본값을 주고 강타입·명시적 변수를 쓰며 순환 import를 피할 것, “Data structures and interfaces”를 절대 바꾸지 말 것, TODO를 남기지 말고 모든 코드를 다 쓸 것, 그리고 역시 config 값을 지어내지 말 것. planning부터 coding까지 “설계 고정 + 설정 접지 + 지어내기 금지”라는 원칙이 일관되게 흐른다.


5. 왜 굳이 이렇게 나눴나 — 순서가 만든 차이

전체 흐름을 한 장으로 요약하면 이렇다.

논문 → (Planning) overall plan $o$ → architecture design $d$ → logic design $l$ → config $g$ → (Analysis) 파일별 분석 $a_i$ → (Coding) 실행 순서대로 $c_i$ 순차 생성 → 레포 $C$

정보가 단계마다 쌓이면서 내려가고, 각 단계는 앞 단계가 고정해 둔 것(인터페이스·순서·설정)을 존중하도록 프롬프트로 강하게 묶여 있다.

여기서 지난 글에 예고했던 대목을 짚어 둔다. 논문의 ablation을 보면, architecture design만 추가했을 때 오히려 성능이 떨어진다(reference-based 3.28 → 3.13). 언뜻 의아하지만 저자들은 이게 예상된 일이라고 본다. architecture design은 파일과 관계를 그려 주지만 실행·구현 순서까지 정하지는 않기 때문에, 코드 생성 단계에서 혼란이 생긴다. 실제 사례로, 의존성 순서 없이 생성하면 main.pydataset_loader.py의 함수를 중복으로 만들어 버린다.

그런데 그 다음 logic design을 붙이자 성능이 회복되고 오히려 더 올라간다(3.60). logic design이 파일 의존 순서를 명시적으로 잡고 생성 순서를 정리해 준 덕이다. “무엇을 만들지($d$)”와 “어떤 순서로 만들지($l$)”를 굳이 분리한 이유가 바로 이 지점에 있다. 구조만으로는 부족하고, 순서가 있어야 실행 가능한 코드가 나온다.

단계 누적 Ref-based Ref-free
Paper (논문만) 3.28 4.30
+ Overall Plan 3.40 4.34
+ Arch. Design 3.13 ↓ 4.07 ↓
+ Logic Design 3.60 ↑ 4.50 ↑
+ Config File 3.66 4.45
+ Analysis (전체) 3.72 4.73

표에서 보이듯, 단계를 하나씩 쌓을수록 대체로 성능이 오르고, 전체를 다 붙인 full 파이프라인이 가장 높다. 중간에 arch. design에서 한 번 꺼졌다가 logic design에서 되살아나는 이 곡선이, PaperCoder가 왜 planning을 네 조각으로까지 잘게 나눴는지를 가장 잘 보여 준다.


6. 이 글을 닫으며

이번 글에서는 PaperCoder가 논문 한 편을 코드 레포로 바꾸는 과정 자체를 따라갔다. planning의 네 하위 단계가 논문을 구현 수준 추상으로 옮기고, analysis가 파일별로 그 추상을 구현 직전까지 구체화하며, coding이 실행 순서대로 앞 파일을 보면서 순차 생성한다. 관통하는 두 원칙 — 앞 단계 출력을 뒤 단계가 그대로 물려받는 누적 구조, 그리고 설계·순서·설정을 고정하고 지어내기를 금지하는 프롬프트 규율 — 이 이 파이프라인의 성격을 규정한다.

다음 글에서는 4장 Experiment로 넘어가, 이렇게 만든 레포가 실제로 얼마나 잘 작동하는지를 본다. Paper2CodeBench와 PaperBench에서의 정량 결과, 저자가 직접 평가한 사람 평가, 그리고 “생성된 레포를 실행하는 데 평균 0.81%의 라인만 고치면 됐다”는 실행 가능성 분석까지 — 계획이 결과로 어떻게 확인되는지를 정리할 예정이다.

Leave a comment