위의 수채화 세 점은 사람이 그린 것이 아니다.
여기까지는 요즘 그다지 놀랍지 않을지도 모르겠다. 그러면 한 가지를 덧붙이겠다. 이 그림들은 이미지 생성 모델이 만든 것도 아니다. 픽셀을 직접 만들어 낸 결과물이 아니라는 뜻이다. 언어 모델이 자바스크립트 코드를 한 편 작성했고, 그 코드가 실행되면서 종이 위에 붓질이 얹혔다.
그리고 그 코드를 쓰는 방법은 사람이 알려준 것이 아니다. 모델이 강화학습으로 익혔다.
브루클린에서 활동하는 디자이너 Surya Narreddi가 Cameron Franz, Alex Wang과 함께 진행한 작업이다. 그런데 이 그림에 도달하기까지 저자를 가장 오래 붙들어 놓은 문제는 모델의 그림 실력이 아니었다. 좋은 그림이 무엇인지를 기계에게 알려주는 방법이었다.
붓질을 코드로 쓴다는 것

왼쪽은 모델이 참고한 히비스커스 사진이고, 오른쪽은 모델이 작성한 sketch.js다. 종이 색을 #f4ebd8로 정하고, 꽃잎을 각도 130도에 길이 260, 퍼짐 45라는 식으로 배열에 적어 둔 다음, 반복문을 돌면서 붓을 움직인다. 여기서 쓰이는 p5.brush는 붓과 잉크와 수채 번짐을 흉내 내는 라이브러리이며, 저자의 표현으로는 일부 예술가만 사용하는 도구다.
저자가 이 방식을 택한 이유는 불만 하나에서 출발한다.
이미지를 직접 편집할 수는 없다. 무엇이든 바꾸려면 모델로 돌아가서 다시 프롬프트를 넣어야 한다. 그 한계가 이 프로젝트를 시작하게 했다.
결과물이 코드라면 사정이 달라진다. 꽃잎 하나가 마음에 들지 않으면 배열에서 그 항목의 숫자만 고치면 된다. 모델에게 다시 부탁하지 않아도 되고, 부탁하면서 나머지 부분까지 바뀌어 버리는 일도 겪지 않는다.
가르쳐 준 사람이 없다
문제는 p5.brush로 그럴듯한 수채화를 그리는 코드를 애초에 누가 쓰느냐는 데 있다. 저자들은 모델에게 그 방법을 가르치는 대신, 스스로 찾아내게 만드는 구조를 짰다. 네 단계로 이루어진 루프이며, 훈련하는 동안 수천 번 반복된다.
- 모델이 프롬프트를 받는다. 예를 들면 draw a peach hibiscus in watercolour 같은 문장이다. 모델은 완결된 p5.brush 자바스크립트 스케치를 작성한다.
- 작성된 스케치는 격리된 Puppeteer 환경에서 실행되어 PNG 이미지를 만들어 낸다.
- 그 이미지는 사람이 직접 등급을 매긴 참조 그림 풀에서 무작위로 뽑은 두 장과 비교되고, 별도의 판정 모델이 더 나은 수채화를 고른다.
- 판정 결과가 보상 신호로 변환되고, GRPO 알고리즘이 모델을 갱신하며, 루프는 다시 처음으로 돌아간다.
저자는 이 구조에서 자명하지 않은 선택이 네 곳에 있다고 말한다. 무엇을 판정할 것인가, 어떻게 판정할 것인가, 참조 풀에 무엇을 넣을 것인가, 시스템 프롬프트를 어떻게 작성할 것인가. 이 네 가지가 결국 좋은 그림의 정의를 기계가 읽을 수 있는 형태로 옮기는 작업이다.
참고로 베이스 모델은 본문에 명시되어 있지 않다. 글 주소(rling-qwen-to-paint-with-code)와 시연 이미지에 남은 표기로 보면 Qwen3-30B-A3B-Thinking이다.
첫 훈련은 여기서 멈췄다

첫 훈련이 진행되는 동안 모델이 내놓은 결과물이다. 0스텝의 빈 종이에서 시작해서, 자홍색 선이 뭉친 덩어리를 지나, 200스텝에서 꽃잎 다섯 장이 달린 분홍색 꽃에 도달한다. 그리고 여기서 더 나아가지 않았다.
당시 채점 기준은 신호를 아홉 개 두고 있었다.
| 신호 | 내용 |
|---|---|
| 컴파일 게이트 | 코드가 실행되는가 |
| brush 사용 확인 | 기본 p5가 아니라 p5.brush를 실제로 사용했는가 |
| 코드 길이 조정 | 3,000 토큰 언저리를 목표로 삼는다 |
| HPSv3 | 인간 선호를 학습한 모델이 매기는 점수 |
| 프롬프트 준수 | GPT-5.4와 Gemini가 함께 판정한다 |
| 품질 판정 4종 | 인식 가능성, 미학, 기법, 깊이 |
빠뜨린 항목이 있는지 찾기 어려울 만큼 성실하게 만든 기준표다. 그런데 모델은 보상 0.65 근처에서 더 나아가지 않았다. 생성된 결과물이 전부 비슷해 보였다. 둥근 꽃잎 다섯 장이 달린 납작한 클립아트 같은 꽃이었다. 보상 수치는 계속 올라가는데 실제 능력은 나아지는 것처럼 보이지 않았다.
하위 보상을 따로 떼어 보았다
원인은 아홉 개의 신호를 하나씩 분리해서 관찰한 다음에야 드러났다. 세 가지가 나왔다.
첫째, 품질 판정 네 개와 프롬프트 준수는 서로 0.85~0.95의 상관을 보였다. 다섯 개 항목이 사실상 같은 대상을 반복해서 측정하고 있었다. 인식 가능성이 높은 그림은 미학 점수도 높았고, 기법 점수도 높았고, 깊이 점수도 높았다. 항목을 나눈 것은 사람이지, 판정 모델은 그 다섯 개를 구분하지 않았다.
둘째, 코드 길이 항목은 전체 보상의 약 3분의 1을 차지했는데 30스텝 만에 포화 상태에 도달했다. 그 이후로는 학습에 필요한 기울기를 전혀 만들어 내지 못했다. 보상의 3분의 1이 고정된 상수처럼 더해지고만 있었다는 뜻이다.
셋째, 실제로 의미 있는 분산을 보인 유일한 신호인 HPSv3는 가중치가 0.10에 불과했다. 유일하게 신호 역할을 하던 항목이 가장 작은 목소리를 내고 있었다.
저자의 표현을 빌리면, 자신들이 만든 채점 기준이 모델에게 같은 말을 하고 또 하고 있었다.
판정자에게 던지는 질문을 바꾸었다
수정은 두 방향으로 이루어졌다. 첫 번째는 절대 점수를 쌍대 비교로 교체한 것이다.
원래 기준은 판정자에게 결과물마다 0점에서 10점 사이의 점수를 매기라고 요구했다. 돌아온 점수는 0점 근처의 좁은 구간에 몰려 있었다. 쌍대 비교는 다른 질문을 던진다. 판정자에게 방금 생성한 결과물 하나와 참조 풀에서 뽑은 그림 두 장을 보여 주고 한 문장만 묻는다. 이 중에서 어느 것이 더 나은 히비스커스 수채화인가? 보상은 이런 비교에서 이긴 비율로 정해진다.
이렇게 바꾸자 점수가 넓은 구간에 퍼졌다. 판정 모델은 추상적인 척도로 채점하는 일보다 상대적으로 비교하는 일을 더 안정적으로 수행한다.
좋은 그림의 목록을 손으로 만들었다
두 번째 수정은 비교 대상을 마련하는 작업이었다. 저자는 생성된 이미지 1,664장을 한 장씩 확인하면서 love, okay, nope 세 등급으로 분류했다. 이 가운데 love 등급을 받은 117장이 비교 풀의 출발점이 되었다. 그 시점부터 모든 결과물은 저자가 직접 좋다고 판단한 그림들을 상대로 평가받았다.
최종적으로 참조 풀에 들어간 그림은 581장이다. love 117장과 okay 266장에 더해, 사람이 직접 평가한 예시가 부족했던 색 계열을 채우려고 별도의 생성 작업에서 198장을 보충했다.

그리고 이 풀에 들어간 이미지는 전부 모델이 만들어 낸 출력물이다. p5.brush가 일부 예술가만 사용하는 도구여서, 사람이 직접 만든 예시를 충분히 구할 수 없었기 때문이다. 생성 작업은 두 갈래로 진행되었다. 하나는 AutoResearch 방식으로, Opus 4.6과 GPT-5.4와 Gemini 3.1 Pro가 참조 사진을 놓고 반복 작업하는 동안 시각 언어 모델 판정자가 점수와 피드백을 주었다. 다른 하나는 Gemini 3.1 Pro에서 돌린 더 큰 규모의 배치 작업이다.
기계에게 좋음의 기준을 알려주기 위해 사람이 1,664장을 손으로 골랐고, 그 1,664장 역시 기계가 그린 것이었다.
같은 모델, 같은 데이터, 다른 결과
새로 만든 채점 기준은 아홉 개 항목을 네 개로 통합했다.
| 항목 | 가중치 |
|---|---|
| 컴파일 성공과 brush 사용을 함께 확인하는 이진 게이트 | 0.05 |
| 길이를 확인하는 이진 게이트 | 0.05 |
| HPSv3 | 0.30 |
| 참조 풀을 상대로 하는 쌍대 판정 | 0.60 |
베이스 모델도 훈련 데이터도 이전과 같았다. 다음 훈련은 이전에 멈췄던 지점에 세 배 빠르게 도달했고, 거기서 멈추지 않고 계속 올라갔다. 그리고 생성되는 코드가 13,500 토큰에서 2,000 토큰 아래로 줄었다.

625스텝에서 나온 결과물이며 보상은 0.79다. 왼쪽 위와 오른쪽 아래에 적힌 표기는 저자가 얹은 것이고, 그림 자체는 brush.stroke("watercolor")와 createCanvas(600, 600)으로 시작하는 코드가 만들어 냈다.
모델이 배운 것은 이기는 구성에 장황한 코드가 필요하지 않다는 사실이었다. 장황함을 줄이라고 지시한 항목을 추가해서가 아니라, 장황함에 보상을 주던 항목을 걷어 냈기 때문에 일어난 변화다.
시스템 프롬프트에도 같은 문제가 있었다
정리해야 할 대상이 하나 더 남아 있었다. 초기 시스템 프롬프트는 400줄짜리 p5.brush API 레퍼런스를 담고 있었다. 이 상태에서 모델은 자신 있고 깔끔하게 정돈된 코드를 만들어 내면서, 존재하지 않는 API를 발명했다.
수정은 GEPA를 사용해서 진행했다. GEPA는 프롬프트를 점수 함수에 대고 진화시키는 프롬프트 최적화 라이브러리다. 저자들은 취향을 기준점으로 삼은 7-shot 판정자를 상대로 200회 반복을 돌렸다. 최적화가 수렴한 결과물은 brush 메서드 여덟 개만 허용하는 엄격한 목록이었고, API 문서도 예시도 들어 있지 않았다. 세 번 생성해서 세 번 모두 알아볼 수 있는 히비스커스 형태가 나온 첫 순간은, 400줄짜리 레퍼런스를 통째로 버리고 다시 작성한 버전에서였다.
저자는 이 발견이 다른 작업에도 일반화된다고 적는다. 시스템 프롬프트에 긴 레퍼런스 문서를 넣으면 모델은 API를 환각한다. 짧고 단호한 허용 목록이 원래의 명세보다 출력을 더 잘 제약한다.
저자가 닫는 자리

훈련을 마친 모델이 내놓은 생성물 가운데 몇 점이다. 각각은 모델이 작성한 자바스크립트가 렌더링된 결과다.
마지막 문단에서 저자는 처음의 질문으로 돌아간다.
강화학습에는 검증 가능한 보상이 필요하다. 수학 문제는 맞거나 틀리다. 게임은 이기거나 진다. 미적 선호는 둘 다 아니다. 주관적인 작업에 강화학습을 하려면 보상을 손으로 저술하고, 일반화될 만큼 신중하게 설계해야 한다. 너무 구체적이면 모델은 당신이 평가한 예시를 베끼는 것만 배운다. 너무 느슨하면 특별히 아무것도 배우지 않는다.
그리고 이 방식이 이미지를 만드는 더 나은 방법이라고는 생각하지 않는다고 분명하게 밝힌다. 실제로 훨씬 느리기 때문이다. 다만 프롬프트와 모델과 결과물 전반에 주의와 노력을 나누어 쓸 수 있게 되었다는 점이 저자가 얻은 것이다.
프로젝트는 아직 진행 중이고, 작업하면서 발견한 문제들을 고치는 마지막 훈련이 한 번 남아 있다. 전체 기술 보고서는 6월에 발표할 예정이라고 적혀 있다.

저자가 이 매체 안에서 가능하다고 본 것들이다.
가장 눈여겨본 것
보상 설계가 실패한 이유가 게으름이 아니라 성실함이었다는 점이다.
아홉 개의 신호를 둔 첫 채점 기준은 대충 만든 물건이 아니다. 컴파일되는가, 지정한 라이브러리를 사용하는가, 길이가 적당한가, 인간 선호 모델이 좋아하는가, 프롬프트를 지켰는가, 알아볼 수 있는가, 아름다운가, 기법이 좋은가, 깊이가 있는가. 평가해야 마땅한 항목을 빠짐없이 적어 내려간 목록이다. 그런데 그중 다섯 개가 서로 0.85~0.95의 상관을 보였다. 성실하게 나누어 적은 항목들이 실제로는 하나의 항목이었다.
여기서 나오는 결과가 묘하다. 보상 수치는 계속 올라갔다. 지표가 개선을 보고하는 동안 결과물은 클립아트 같은 꽃 한 종류에 갇혀 있었다. 평가 항목을 촘촘하게 만들수록 평가가 실제 대상에서 멀어지는 구간이 존재한다는 뜻이다.
고친 방식도 마찬가지로 반직관적이다. 아홉 개를 네 개로 줄이고, 0점에서 10점까지 매기는 정밀해 보이는 도구를 버리고, 둘 중 어느 쪽이 더 나은지 묻는 가장 단순한 질문 하나에 60퍼센트의 가중치를 옮겼다. 그러자 코드가 13,500 토큰에서 2,000 토큰으로 줄었다.
평가를 정교하게 만들려는 노력과 평가를 정확하게 만드는 일이 같은 방향이 아닐 수 있다. 이 프로젝트가 남긴 기록에서 내가 가장 오래 생각한 것은 그 부분이다.
출처
Surya Narreddi, “Training AI to Paint with Code” (2026년 3월 작성, 2026-08-24 공개). 공동 작업자는 Cameron Franz와 Alex Wang이다. 원문: https://surya.website/rling-qwen-to-paint-with-code 논문 발표 영상: https://vimeo.com/1190839818
본문에 실린 이미지는 모두 원문에서 인용했다. 움직이는 이미지 세 점은 원문의 배경이 투명하게 지정되어 있어서 흰색 위에 합성했고, 훈련 진행 애니메이션은 용량을 줄이려고 두 프레임마다 한 장씩 남겼다.
