[기획: AI 트렌드] CUDA 그래프를 512노드씩 끊은 날 — RTX Spark에서 약 0.4%, 그리고 같은 날 깨진 빌드
10월 23일, 그래프를 512개씩
10월 23일, llama.cpp에 PR #30804가 머지됐습니다. llama.cpp는 내 컴퓨터에서 AI 모델을 돌리는 오픈소스 프로그램입니다. PR은 코드 수정 요청이고, 머지는 그것을 본 코드에 합치는 일입니다. am17an이 쓰고 직접 머지한 이 PR로 llama.cpp는 CUDA 그래프를 조각으로 나눠 실행하게 됐습니다. 같은 날 빌드가 한 번 깨졌습니다.
GPU(AI 계산을 맡는 칩)가 할 계산은 CPU가 하나씩 줄 세워 넣습니다. 계산 하나를 넣는 이 일을 커널 런치라고 하며, 넣을 때마다 비용이 붙습니다. GPU는 들어온 순서대로 처리하고, CPU는 그동안 다음 계산을 준비할 수 있습니다.
llama.cpp는 모델의 계산을 노드(계산 하나하나)의 목록, 곧 계산 그래프로 짜 둡니다. CUDA 그래프는 그 목록을 GPU용으로 한 번 녹화해 두고 통째로 다시 실행하는 방식입니다. 녹화는 캡처, 다시 실행은 재생이라 부릅니다.
작성자의 근거는 그래프 런치 비용이 그래프 크기를 따라 커지고, 작은 조각으로 나눠 돌리면 그 비용을 분산할 수 있다는 것입니다. 심부름 목록에 비유하면, 목록을 다 쓴 뒤에야 건네면 받는 사람은 그동안 손을 놓고 기다립니다. 몇 장씩 써지는 대로 건네면 첫 장을 처리하는 동안 다음 장이 도착합니다.
바뀐 코드는 계산 그래프를 기본 512노드씩 끊습니다. 한 조각의 녹화가 끝나면 바로 런치하고, 그다음에 다음 조각의 녹화를 시작합니다. GPU가 앞 조각을 계산하는 동안 CPU가 다음 조각을 녹화하는 셈입니다(우리 판단). 녹화본을 실행할 수 있게 만든 것(그래프 인스턴스)도 하나에서 여럿이 됐고, 코드 속 이름은 instance에서 instances로 바뀌었습니다.
얼마나 빨라졌나
작성자는 토큰 생성이 1~2% 빨라진다고 적었습니다. 토큰은 모델이 글을 잘게 자른 조각이고, 토큰 생성은 답을 한 토큰씩 써 내는 단계입니다. 잰 조건은 적혀 있지 않습니다.
| 잰 사람 | 장비 | 결과 |
|---|---|---|
| am17an(작성자) | 미표시 | 토큰 생성 1~2% 빨라짐 |
| ORippler(아이디어 제공·리뷰어) | RTX Spark | 초당 40.564 → 40.744토큰(약 0.44%) |
| IMbackK | AMD GPU(HIP/CDNA) | 「성능 중립」 |
| am17an(작성자) | 멀티 GPU | 개선 없음 |
출처 f2·f5·f7·f8 · 각자 스스로 잰 수치 · 모델·설정·오차 범위 미표시 · 지표도 서로 다름 · 0.44%는 우리 계산 · 독립 재현 아님
작성자는 이 아이디어를 ORippler에게서 얻었다고 적었습니다. 그 ORippler가 「RTX Spark에서는 이득이 작아 보인다」며 「많지는 않지만 정직한 일」이라고 적었습니다. 그의 측정으로는 약 0.4% 빨라진 셈입니다(우리 계산). 리뷰에서 나온 질문은 크기보다 조건 쪽이었습니다.
am17an은 128부터 1024까지 네 크기를 시험했고, ORippler는 그 「마법의 숫자」를 어떤 하드웨어·모델·OS에서 정했느냐고 물었습니다.
aendk에 따르면 조각이 너무 크면 첫 조각의 런치가 길어져, 뒤 런치들이 GPU 계산 뒤에 가려지기까지 오래 걸립니다. 너무 작으면 Linux에서 그래프 사이마다 생기는 3마이크로초의 틈이 늘어납니다.
2025년 2월에는 멈췄던 생각
비슷한 생각은 전에도 있었습니다. 2025년 2월 aendk가 올린 PR #11867은 CPU 쪽 일을 작은 조각으로 나눠, CUDA 그래프를 만드는 일과 GPU 계산을 겹치자는 제안이었습니다. 이 PR은 지금도 열려 있습니다.
slaren은 이 제안이 이미 「너무 깨지기 쉽고 복잡해서 제대로 유지보수하기 어려운 코드」에 복잡도를 더한다며 반대했습니다. 그는 「이 코드를 유지보수할 책임을 질 생각이 없다」고 적었습니다.
이번 PR은 파일 두 개에서 64줄을 더하고 32줄을 뺐습니다. aendk는 이번 PR에 #11867에서 자신이 이 생각을 제안했다고 적고, 「CUDA 그래프가 더 안정됐으니 이제 머지하는 게 더 말이 된다」고 덧붙였습니다. 비용은 다른 곳에서 나왔습니다.
같은 날, 빌드가 깨졌다
머지 당일, CISC는 「CI가 대거 깨졌다, EditorConfig와 모든 CUDA 빌드」라고 적었습니다. 빌드는 코드를 실행 파일로 만드는 일, CI는 그 빌드와 검사를 자동으로 돌리는 장치입니다. CUDA 빌드의 오류는 「ggml_cuda_graph 클래스에 instance라는 멤버가 없다」였습니다.
CUDA 빌드가 깨진 이유는 사흘 전인 10월 20일에 머지된 #26771과의 충돌이었습니다. #26771은 메모리 풀(미리 받아 둔 GPU 메모리)을 정리한 뒤 그래프를 다시 캡처하게 하는 수정입니다. 녹화된 그래프를 재생하다 낡은 주소에 써서 나는 페이지 폴트(잘못된 메모리 접근 오류)를 막으려는 것이었습니다. 이 수정의 함수가 옛 이름 graph->instance를 부르고 있었습니다. 다시 캡처할 그래프 수를 기록용으로 세는 줄이었습니다.
#30804가 바꾼 이름을, 그보다 먼저 들어와 있던 코드가 아직 부르고 있었습니다. 각 PR 안에서는 맞았던 이름이 둘이 합쳐지자 어긋난 것입니다(우리 판단).
복구는 같은 날 끝났습니다. #31053이 그 옛 이름을 새 이름으로 고쳤고, ServeurpersoCom이 「빌드를 되살리려는 빠른 머지」라며 그날 머지했습니다.
회귀는 코드를 바꾼 뒤 전보다 나빠지는 일입니다. 머지 전 10월 19일 리뷰에서 am17an은 여러 장치와 모델로 시험했지만, CUDA 그래프 회귀를 확인하는 테스트 묶음이 없어 CI에 넣을 테스트를 모르겠다고 적었습니다.
더 큰 이득은 다른 문제에 있다는 초안
joelagnel의 PR #31004는 첫 노드의 모양이 다른 CUDA 그래프를 여럿 두고 모양에 따라 골라 쓰게 합니다. 그래프를 자꾸 다시 만드는 일(그래프 스래싱)을 막겠다는 것입니다. 10월 22일 검토 요청 상태가 됐다가 다음 날 다시 초안으로 돌아갔습니다.
작성자가 보고한 이득은 투기 디코딩에서 약 4~5%였습니다. 투기 디코딩은 초안 토큰 몇 개를 먼저 만들어 두고 모델이 한꺼번에 확인하는 생성 방식입니다. 보통 생성에서는 약 0.15%였습니다. am17an은 토큰 수가 다른 그래프를 서로 다른 메모리 영역으로 나누자고 했습니다. 토큰 수가 다른 계산이 번갈아 들어오면 그래프를 번갈아 다시 만들게 되기 때문입니다(우리 판단). 이번 변경의 수치와는 모델·장비가 달라 바로 견줄 수 없습니다(우리 판단).
am17an은 이 문제를 llama.cpp 층에서 풀어야 한다고 본다고 답했습니다. 그의 설명으로 그래프 다시 만들기는 스케줄러(계산을 장치별로 나누고 메모리를 배정하는 부분)·llama.cpp(모델의 계산 그래프를 짜는 부분)·CUDA 백엔드(GPU용 실행 코드) 세 층에서 일어나고, 이 PR은 그중 마지막 층의 수정이었습니다. joelagnel도 「더 높은 층에서 먼저 다루는 편이 나을 것」이라며 동의했습니다.
앞선 글에서 우리는 효율의 결함은 답을 채점하는 테스트로는 보이지 않고, 그것을 따로 묻는 질문을 만들어야 보인다고 봤습니다답은 맞았고 GPU마다 전문가가 전부 실려 있었다, transformers에 그것을 확인하는 테스트가 추가됐다. 남은 일은 조각 크기의 장비별 재조정과 #31004의 행방입니다.
이번 변경의 이득은 측정표에 적혔고, 재는 사람마다 달랐습니다. 비용은 측정표 밖에서 나왔습니다. 이번에 드러난 어긋남은 이름 하나였고, 컴파일러가 잡아 같은 날 고쳐졌습니다. 사흘 전 #26771이 고친 것은 그렇지 않았습니다. 녹화된 그래프 속 주소가 그대로 유효하다는 가정은 컴파일 오류가 아니라, 특정 AMD GPU에서 재현된 페이지 폴트로 드러났습니다. 그래프를 쪼개면 녹화본이 늘고, 녹화본마다 이런 가정이 따라갑니다(우리 판단). 그 가정을 확인할 CUDA 그래프 회귀 테스트 묶음은 아직 없다고 작성자가 적었습니다.
유의
루틴 18회차 · 캐스케이드 ①(GitHub 1차 · 메인 재열람 인용 대조) · 분량 렌더 10,090B(10KB 상단 경계 소폭 초과 · 트림 1회 후) · 델타 R2 nit 7 미반영 fail-forward(L58 모델 미표시 · L64 「어긋남은 이름 하나」 범위 · 「아직 없다」 시점[f9는 10-19 머지 전 발언] · 「그 가정」 결합은 우리 연결 · 「부르고」→참조 등 · 재판정·스폰 상한 8 소진) · 원천은 WebFetch 요약 경유로 핵심 문장만 대조 · 변경 규모 f3·#31056 f13은 miner 단독 · 성능 수치 전부 작성자·리뷰어 자기보고(독립 재현 아님) · 10-23 머지의 창 끝 이전 여부는 날짜 기준 개연 · planner·reviser 메인 수행(스폰 상한 8) · 예산 초과(writer 약 33분)출처
- 기준일 = 2026년 10월 24일 오전(한국 시간).
- 아래 원문은 모두 우리가 직접 연 GitHub 페이지와 코드 파일입니다. 요약 모델을 거치는 웹 열람 도구(WebFetch)로 읽어 핵심 문장만 인용으로 대조했고, 페이지에 시각이 없어 날짜만 적었습니다. 성능 수치는 작성자·리뷰어가 스스로 잰 것이고 독립 재현이 아닙니다. 변경 규모는 다시 대조하지 못했습니다.
- 이해관계: Refract는 Anthropic의 Claude로 글을 분석하고 씁니다. #31053의 작성자는 남은 옛 이름을 찾는 데 AI를 썼다고 밝혔고, 어느 AI인지는 원문에 없습니다.
- 그래프 조각 PR(2026-10-23 머지)·리뷰·빌드 깨짐 보고 — PR #30804 · 머지 커밋 · 바뀐 코드
- 빌드 수정 PR(2026-10-23 머지) — PR #31053
- 그래프 재캡처 PR(2026-10-20 머지) — PR #26771
- 겹치기 PR(2025-02 · 열림) — PR #11867
- 그래프 모양별 분리 PR(초안) — PR #31004