흩어진 커널을 허브로 모으겠다는 선언

Hugging Face의 Kernels 업데이트를 따라가며 버전 관리 허브, 실행 시점 디스패치, autotuning과 캐시, Transformers 통합이 추론 현장에서 무엇을 바꾸는지 해설합니다.

원문 논문 보기
'흩어진 커널을 허브로 모으겠다는 선언' 기사 커버 이미지

Hugging Face가 공개한 🤗 Kernels: Major Updates는 추론용 커널을 버전 관리되는 허브 형태로 모아 배포하고 실행 시점에 하드웨어에 맞는 구현을 골라 쓰게 하겠다는 제안입니다. 이 글은 커널마다 코드와 메타데이터와 벤치마크를 함께 묶어 두는 방식이 왜 등장했는지, 로더의 자동 선택이 어떤 조건 위에서 동작하는지, 그리고 Transformers와 Text Generation Inference 통합이 현장의 작업 흐름을 어떻게 바꾸는지를 따라갑니다. 원문 링크는 제목 아래에 걸려 있습니다.

이번 업데이트를 한 문장으로 정리하면 이렇습니다. 각 커널을 재현 가능한 저장소 단위로 표준화한 뒤 Triton과 CUDA와 CUTLASS 구현 가운데 실행 환경에 맞는 것을 자동으로 고르고 맞지 않으면 PyTorch eager로 돌아가게 만들었다는 것입니다.

안녕하세요, 패트릭입니다. 저는 추론 최적화 글을 볼 때면 먼저 발표 문장보다 작업자의 하루를 떠올리는 편인데요. 학습이 끝난 모델을 받아서 서비스에 올리는 과정에서 누군가는 같은 attention 코드를 다시 쓰고, 누군가는 같은 정규화 코드를 다시 포팅하니까요. 그래서 이번 글은 숫자 자랑보다 그 반복을 줄이려는 설계에 눈이 갔습니다. 그렇다면 이 허브라는 발상은 어디에서 출발한 걸까요.

저를 멈추게 만든 문장은 생각보다 소박했습니다

저는 보통 새로운 인프라 발표를 보면 과장된 표현부터 걷어내는 편입니다. 그런데 이번에는 반대로 소박한 대목에서 멈췄습니다. 각 커널을 코드와 메타데이터와 벤치마크로 묶은 저장소로 다루겠다는 설명이었는데요. 얼핏 들으면 파일 정리 이야기로 들리지만, 저는 이 대목에서 운영의 냄새를 맡았습니다. 버전이 없으면 재현이 없고, 재현이 없으면 최적화도 매번 처음부터 다시 해야 하기 때문입니다.

여러 연구실이 각자 빠른 코드를 갖고 있다는 사실은 오래전부터 알려져 있었죠. 문제는 그 코드가 논문 속 수식이 아니라 기계와 드라이버와 라이브러리 버전 위에 서 있다는 점입니다. 같은 파일이라도 Hopper에서 돌던 설정이 Blackwell에서는 어색해질 수 있고, dtype이 바뀌면 가장 빠른 경로도 달라지니까요. 이런 조건을 코드 바깥에 적어 두지 않으면 빠른 코드는 금방 전설이 됩니다. 예전에는 빨랐는데 지금은 왜 느린지 아무도 모르는 상태가 되는 것이죠.

그래서 저는 저장소라는 단위를 이렇게 읽었습니다. 실행 가능한 코드만 두는 곳이 아니라 실행 조건을 함께 적어 두는 곳이라는 의미로 말이죠. 메타데이터에는 어떤 하드웨어와 dtype과 입력 형태를 겨냥했는지가 남고, 벤치마크에는 어떤 기준으로 맞다고 판단했는지가 남습니다. 이렇게 되면 나중에 온 사람이 같은 실수를 반복하지 않아도 됩니다. 이미 확인된 조합에서 출발하면 되니까요.

물론 저장소가 있다고 해서 모든 마찰이 사라지지는 않습니다. 오히려 저장소가 늘어날수록 어떤 버전을 골라야 하는지라는 새로운 질문이 생기죠. 그런데 바로 그 질문을 로더가 대신 받겠다고 선언한 대목이 이번 발표를 다르게 만듭니다. 그렇다면 로더는 실행 순간에 무엇을 보고 판단할까요.

흩어진 커스텀 커널들이 버전 관리 허브 하나로 모이는 개념도
흩어진 커스텀 커널들이 버전 관리 허브 하나로 모이는 개념도

모델은 공개되는데 속도는 왜 매번 따로 만들까요

모델 가중치가 공개되는 일은 이제 낯설지 않습니다. Transformers 라이브러리에서 모델 이름을 바꾸면 가중치가 내려오고, 토크나이저와 설정이 함께 따라오죠. 그런데 속도는 같은 방식으로 따라오지 않았습니다. 빠른 attention 구현은 어딘가의 개인 저장소에 있고, 양자화 행렬곱은 또 다른 포크에 있으며, 설치 방법은 글마다 조금씩 달랐습니다. 저는 이 간극을 볼 때마다 아쉬웠습니다. 지능은 공유되는데 실행은 공유되지 않는 셈이니까요.

이런 분산은 우연이 아닙니다. 커널 코드는 하드웨어에 단단히 묶여 있기 때문입니다. Triton으로 쓴 구현이 특정 GPU에서는 매끄럽게 돌아가도 다른 아키텍처에서는 예상과 다르게 움직일 수 있는데, 이런 차이를 문서 몇 줄로 넘기기는 어렵습니다. 그래서 각 팀은 자기 환경에서 가장 잘 도는 버전을 안쪽에 숨기는 쪽을 택했습니다. 바깥에 공개해도 남들이 쓰기 어렵다면 공개할 동기가 약해지죠.

여기서 재미있는 질문이 생깁니다. 속도를 공유하기 어렵게 만드는 장벽은 실력일까요, 아니면 포장일까요. 저는 후자에 가깝다고 봅니다. 빠른 코드 자체보다 그 코드를 언제 어떻게 써야 하는지에 대한 설명이 빠져 있으니 재사용이 막히는 것이죠. 설치 경로가 제각각이면 리뷰도 어렵고, 테스트 방법이 다르면 신뢰도 쌓이지 않습니다. 결국 같은 발상이 여러 실험실에서 닫힌 채로 다시 발명됩니다.

이번 발표는 그 지점을 정면으로 찌릅니다. huggingface_hub와 kernels라는 파이썬 패키지를 통해 설치 경로를 하나로 모으겠다고 했기 때문입니다. 익숙한 방식으로 내려받고 익숙한 방식으로 불러오면 심리적 장벽이 낮아집니다. 물론 패키지 하나가 모든 호환성 문제를 풀어주지는 않지만, 적어도 어디에서 무엇을 가져왔는지는 분명해집니다. 저는 그래서 이 대목을 포장 기술로 읽지 않았습니다. 중복된 노동을 줄이는 합의에 가깝다고 느꼈습니다. 그렇다면 합의를 지탱하는 기술적 장치는 무엇일까요.

하드웨어와 입력 조건에 따라 실행 경로를 고르는 디스패처 개념도
하드웨어와 입력 조건에 따라 실행 경로를 고르는 디스패처 개념도

허브라는 말이 이번에는 조금 다르게 들립니다

허브라는 말은 AI 생태계에서 너무 자주 쓰여서 자칫 식상하게 들릴 수 있습니다. 모델 허브, 데이터셋 허브, 스페이스까지 이어지는 이름들을 생각하면 또 하나의 저장소인가 싶기도 하죠. 그런데 저는 이번에 그 말이 조금 다르게 들렸습니다. 커널은 모델 가중치처럼 정적인 파일이 아니라 실행 환경과 짝을 이뤄야 의미가 있는 코드이기 때문입니다. 그래서 허브가 단순한 보관함이 아니라 선택의 출발점이 되어야 합니다.

발표가 설명하는 구조는 이렇습니다. 각 커널은 독립된 저장소로 존재하고, 그 안에는 구현 코드와 함께 메타데이터와 벤치마크가 들어 있습니다. 사용자는 허브에서 원하는 커널을 내려받아 로더를 통해 불러오는데, 이때 로더는 감지된 GPU 아키텍처와 dtype과 입력 형태를 보고 Triton과 CUDA와 CUTLASS 가운데 맞는 구현을 고릅니다. 조건에 맞는 최적 구현이 없으면 PyTorch eager로 돌아가 실행 자체는 멈추지 않습니다. 이런 식으로 빠른 경로와 안전한 경로를 함께 들고 있는 것이죠.

이 구조에서 제가 눈여겨본 부분은 실패를 다루는 태도입니다. 보통 최적화 라이브러리는 가장 빠른 경우만 이야기하고 나머지는 사용자의 몫으로 남기는데요. 이번 설계는 fallback을 처음부터 전제로 깔고 있습니다. 새로운 GPU가 나오거나 특이한 입력 형태가 들어와도 일단 돌아간다는 보장이 있으면 도입 부담이 크게 줄어듭니다. 실제 서비스에서는 한 번의 충돌이 밤샘 대응으로 이어지기도 하니까요. 돌아가면서 빨라지는 방식은 현장에서 훨씬 설득력이 있습니다.

다만 여기서 바로 다음 질문이 생깁니다. 자동으로 고른다는 말은 편리하지만, 그 판단이 틀리면 오히려 느려질 수도 있지 않을까요. 그래서 선택의 근거가 투명해야 합니다. 어떤 하드웨어에서 어떤 dtype과 형태일 때 어떤 구현이 선택됐는지를 나중에라도 확인할 수 있어야 하죠. 메타데이터와 벤치마크가 저장소 안에 함께 들어 있다는 점은 이런 확인을 가능하게 만드는 장치로 보입니다. 그렇다면 이건 기존의 빠른 코드 모음과 무엇이 다를까요.

그래서 이건 또 다른 빠른 코드 모음일까요

처음 이 소식을 들으면 기존 최적화 모음과 겹쳐 보일 수 있습니다. 이미 fused attention 구현도 있고, 정규화와 위치 임베딩을 빠르게 처리하는 코드도 있으며, 양자화 행렬곱 라이브러리도 따로 존재하니까요. 그렇다면 허브라는 간판을 단다고 무엇이 달라질까요. 저는 이 질문을 피하지 않고 그대로 따라가 봤습니다.

차이는 코드의 양이 아니라 코드 사이의 약속에 있습니다. 개별 라이브러리는 각자 설치 방법과 호출 규약과 테스트 방식을 갖고 있어서 조합하는 일이 사용자의 몫이었습니다. 반면 이번 구조는 커널마다 같은 모양의 저장소를 강제하고, 로더라는 같은 문을 통해 불러오게 만듭니다. 모양이 같아지면 비교가 가능해지고, 비교가 가능해지면 어떤 구현이 어떤 조건에서 좋은지가 쌓입니다. 이런 축적이야말로 모음과 허브를 가르는 지점이라고 저는 봅니다.

또 하나 다른 점은 라이선스와 기여를 대하는 태도입니다. 발표는 permissive한 오픈소스 라이선스와 커뮤니티 기여를 분명히 내세우는데요. 저는 이 대목을 선언적인 수사로 읽지 않았습니다. 닫힌 커널 개발이 반복되는 이유 가운데 하나는 기여해도 돌아오는 것이 불분명하기 때문입니다. 기여한 코드가 어떤 테스트를 거쳐 어떤 벤치마크로 평가되는지가 정해져 있으면 외부 기여도 훨씬 수월해집니다. 기준이 보이면 참여할 마음이 생기죠.

물론 약속이 많을수록 처음에는 무겁게 느껴질 수 있습니다. 제출할 때마다 정확성 테스트와 성능 측정을 붙여야 한다면 가벼운 실험 코드를 올리기는 어려워지니까요. 하지만 추론 경로는 한 번 들어가면 오래 머무는 코드입니다. 들어갈 때 깐깐한 편이 들어간 뒤에 고생하는 것보다 낫습니다. 저는 그래서 이 무게를 비용이 아니라 입장료로 봤습니다. 그렇다면 입장료를 내고 들어온 커널은 실행 순간에 어떻게 움직일까요.

다운로드부터 캐시 실행까지 이어지는 로딩 흐름도
다운로드부터 캐시 실행까지 이어지는 로딩 흐름도

실행 순간에 로더는 무엇을 보고 고를까요

로더 이야기를 들으면 마법 같은 자동화를 상상하기 쉽습니다. 알아서 가장 빠른 것을 골라준다는 말은 달콤하니까요. 그런데 실제 로더가 하는 일은 마법보다 분주한 사무 작업에 가깝습니다. 내려받은 커널 묶음 안에서 지금 기계와 입력에 맞는 카드를 꺼내는 일이죠. 저는 이 과정을 사무적으로 따라가 보는 편이 이해에 도움이 된다고 생각합니다.

순서를 상상해 보면 이렇습니다. 먼저 허브에서 커널 저장소를 가져오면 코드와 함께 그 코드가 어떤 조건을 겨냥했는지를 적은 메타데이터가 따라옵니다. 이어서 로더는 현재 GPU 아키텍처가 무엇인지, dtype은 무엇인지, 입력 형태는 어떤 범위에 들어가는지를 확인합니다. 그리고 준비된 Triton 구현과 CUDA 구현과 CUTLASS 구현 가운데 조건에 맞는 후보를 고릅니다. 후보가 없거나 확신이 서지 않으면 PyTorch eager로 실행해서 정확하게 동작하는 쪽을 택합니다. 빠르지만 의심스러운 길보다 느리지만 확실한 길을 고르는 셈이죠.

이 선택이 매번 처음부터 다시 계산된다면 그 자체가 부담이 될 수 있습니다. 그래서 autotuning과 캐시가 함께 등장합니다. 하드웨어별로 블록 크기 같은 설정을 탐색한 뒤 좋은 설정을 저장해 두면 다음 실행부터는 탐색을 반복하지 않아도 됩니다. 처음 한 번은 둘러보고, 그다음부터는 지름길로 가는 방식이라고 할까요. 이런 캐시가 쌓일수록 서비스의 꼬리 지연도 안정되는 쪽으로 움직입니다.

여기서는 조금 조심해서 읽을 필요가 있습니다. 자동 선택이 항상 최적이라는 보장은 어디에도 없기 때문입니다. 입력 분포가 바뀌거나 드라이버가 바뀌면 어제 좋았던 선택이 오늘은 어색해질 수 있습니다. 그래서 저는 자동이라는 말을 믿음의 대상으로 보지 않고 관찰의 대상으로 봅니다. 어떤 조건에서 무엇이 선택됐는지를 기록으로 남기고, 이상하면 수동으로 고정할 수 있어야 합니다. 좋은 자동화일수록 수동 개입의 손잡이를 남기죠. 그렇다면 그 선택을 뒷받침하는 세부 장치는 어떻게 생겼을까요.

autotuning이라는 말 뒤에 숨은 작업을 풀어보면

autotuning이라는 말은 발표 자료에서 자주 등장하지만, 막상 그 안을 들여다보면 손이 많이 가는 작업입니다. 블록 크기와 타일 모양과 스레드 배치 같은 설정을 하드웨어에 맞춰 하나씩 바꿔 보면서 실제로 재보는 과정이니까요. 생각해보면 사람도 비슷하게 배웁니다. 한 번 정한 공부법이 모든 시험에 맞는 것은 아니어서 과목마다 조금씩 바꿔 보죠. 커널도 같은 이치로 기계마다 맞는 옷을 찾아 입습니다.

발표가 언급하는 대상에는 NVIDIA의 Hopper와 Blackwell 같은 아키텍처가 들어 있습니다. 저는 이 언급을 특정 회사의 홍보로 읽지 않았습니다. 메모리 계층과 연산기 구조가 세대에 따라 달라지면 같은 코드라도 병목이 옮겨가기 때문입니다. 예전 세대에서 통하던 타일 크기가 새 세대에서는 캐시를 비효율적으로 쓰거나 반대로 연산기를 놀릴 수 있습니다. 그래서 아키텍처 이름을 명시하는 일은 마케팅이 아니라 조건을 분명히 하는 일에 가깝습니다.

캐시 이야기도 함께 짚고 싶습니다. 탐색 결과를 저장해 둔다는 말은 단순한 속도 향상을 넘어 운영의 예측 가능성을 만듭니다. 매 요청마다 설정을 다시 고르면 지연이 들쭉날쭉해질 수 있는데, 한 번 정한 설정을 재사용하면 분포가 안정됩니다. 밤사이 트래픽이 몰리는 시간에도 어제와 같은 선택이 반복되니 대응이 쉬워지죠. 저는 이런 안정감을 작은 숫자보다 크게 평가하는 편입니다. 평균이 조금 빨라지는 것보다 꼬리가 덜 튀는 편이 서비스에서는 체감이 크니까요.

다만 autotuning이 만능은 아닙니다. 탐색할 때 본 입력 분포와 실제 서비스 분포가 다르면 저장된 선택이 어긋날 수 있습니다. 길이가 짧은 입력 위주로 탐색했는데 실제로는 긴 입력이 많이 들어오면 다시 봐야 하죠. 그래서 저는 autotuning 결과를 절대값이 아니라 조건부 진술로 읽습니다. 어떤 하드웨어에서 어떤 dtype과 길이 구간에 대해 무엇이 좋았다는 식으로 말이죠. 조건이 빠진 최고 기록은 현장에서 힘이 약합니다. 그렇다면 이렇게 고른 커널은 기존 모델 코드와 어떻게 만날까요.

fused attention부터 양자화 행렬곱까지 이어지는 추론 블록 개념도
fused attention부터 양자화 행렬곱까지 이어지는 추론 블록 개념도

코드 한 줄 없이 바뀐다는 말은 어디까지 사실일까요

발표에서 제가 가장 실무적으로 읽은 대목은 Transformers와 Text Generation Inference 통합입니다. 백엔드 플래그나 자동 매핑을 통해 코드 변경 없이 커스텀 커널을 쓸 수 있다는 설명이었는데요. 이 문장은 짧지만 현장의 감각으로는 꽤 묵직합니다. 모델 코드를 포크해서 고치는 순간부터 업스트림 업데이트를 따라가기가 어려워지기 때문입니다.

저희가 새로운 AI 아키텍처를 볼 때도 benchmark 숫자만 보지는 않습니다. 실제로 무엇이 바뀌었고, 그 변화가 시스템에서 어떤 비용 구조를 만드는지를 함께 봅니다. 커널 교체가 코드 수정을 요구하면 모델 업데이트 때마다 충돌을 풀어야 하고, 실험 브랜치가 쌓일수록 배포 라인이 복잡해집니다. 반면 플래그 하나로 경로만 바꾸면 기존 학습 코드와 평가 코드는 그대로 두고 실행 경로만 교체할 수 있습니다. 이런 분리는 연구와 운영이 서로를 덜 방해하게 만듭니다.

자동 매핑이라는 표현도 비슷한 맥락에서 읽힙니다. 모델 구조를 보고 쓸 수 있는 커널을 알아서 연결해 준다는 것인데, 이는 매번 수동으로 대응표를 만들지 않아도 된다는 의미입니다. 물론 자동이라는 말이 모든 모델에 통한다는 뜻은 아닙니다. 새로운 변형 구조가 나오면 매핑이 비어 있을 수 있고, 그때는 직접 지정하거나 fallback으로 돌아가야 하죠. 저는 그래서 자동 매핑을 완전한 자동화로 보지 않고 기본값의 품질로 봅니다. 기본값이 좋으면 대다수의 작업이 가벼워집니다.

여기서 한 가지 현실적인 장면을 떠올려 봅니다. 금요일 오후에 새로운 가중치가 공개됐는데 다음 주 데모에서 써야 하는 상황을 생각해 보세요. 커널 코드를 뜯어고쳐야 한다면 주말이 사라지지만, 플래그만 바꿔 끼우면 성능 곡선을 먼저 그려볼 수 있습니다. 이런 차이가 쌓이면 팀의 시도 횟수가 달라집니다. 빠른 시도가 빠른 학습을 낳는 구조죠. 그렇다면 허브가 처음부터 담으려는 커널 목록은 왜 하필 그 모양일까요.

attention부터 양자화 행렬곱까지 왜 이 목록일까요

지원 목록을 보면 fused attention과 RMSNorm과 RoPE와 SwiGLU MLP, 그리고 FP8과 INT4 양자화 GEMM이 나란히 놓여 있습니다. 저는 이 나열을 보고 목록이라기보다 추론의 지도처럼 느꼈습니다. 토큰이 모델을 통과하면서 실제로 시간을 쓰는 지점들을 순서대로 짚은 형태이기 때문입니다. 질문도 바뀌었습니다. 어떤 연산이 빠른가가 아니라 어디에서 시간이 새는가로 말이죠.

먼저 attention은 긴 문맥에서 병목을 만들기 쉽습니다. 쿼리와 키와 밸류가 오가는 동안 메모리 접근이 늘어나고, 마스크와 소프트맥스 주변에서 불필요한 이동이 생기기도 하죠. 이를 하나로 묶은 fused 구현은 중간 결과를 밖에다 썼다 지웠다 하지 않아도 되니 움직임 자체가 줄어듭니다. 움직임이 줄면 속도가 붙고 전력도 아낄 수 있습니다. 긴 입력이 흔해진 지금 이런 묶음은 선택이 아니라 상수처럼 느껴집니다.

RMSNorm과 RoPE와 SwiGLU는 각각 작아 보이지만 모이면 존재감이 생깁니다. 정규화는 매 층마다 반복되고, 위치 회전은 토큰마다 적용되며, MLP는 파라미터가 몰린 곳이라 연산량이 큽니다. 하나하나를 따로 최적화해도 전체 체감은 작을 수 있는데, 세 개를 함께 정리하면 파이프라인이 매끈해집니다. 저는 이런 부분을 자잘한 최적화로 보지 않습니다. 매 요청마다 반복되는 고정비를 내리는 일이라서 요청 수가 쌓일수록 효과가 커지니까요.

양자화 GEMM은 또 다른 층위의 이야기입니다. FP8과 INT4는 가중치와 활성화 값을 더 작은 형태로 담아 메모리 이동을 줄이고 처리량을 끌어올리는 방식인데, 정확도와의 줄다리기가 늘 따라다닙니다. 어떤 층을 어느 정밀도로 둘 것인지, 스케일은 어떻게 잡을 것인지 같은 결정이 필요하죠. 그래서 저는 이 항목을 속도 항목이 아니라 타협 항목으로 읽었습니다. 얼마나 줄일 수 있는가가 아니라 어디까지 줄여도 괜찮은가를 묻는 것이죠. 그렇다면 이런 타협이 하드웨어 세대 위에서는 어떻게 달라질까요.

하드웨어별 설정 탐색과 캐시를 보여주는 autotuning 개념도
하드웨어별 설정 탐색과 캐시를 보여주는 autotuning 개념도

Hopper와 Blackwell 사이에서 달라지는 것들

하드웨어 이름이 발표에 등장하면 흔히 신제품 자랑으로 넘기기 쉽습니다. 그런데 저는 이번 언급을 세대 차이의 선언으로 읽었습니다. 같은 연산이라도 메모리 대역폭과 연산기 배치와 지원 정밀도가 세대에 따라 달라지면 가장 좋은 구현도 함께 바뀌기 때문입니다. 어제의 정답을 오늘 그대로 쓰면 어색한 이유가 여기에 있습니다.

예를 들어 특정 양자화 정밀도는 새 아키텍처에서 훨씬 매끄럽게 지원될 수 있습니다. 예전 세대에서는 흉내 내듯 처리하던 연산을 새 세대에서는 하드웨어가 직접 받쳐주니 코드 경로 자체가 달라지죠. 반대로 어떤 Triton 구현은 특정 세대의 캐시 구조에 잘 맞아서 예상보다 오래 선전하기도 합니다. 그래서 저는 하드웨어별 구현을 계급처럼 보지 않습니다. 조건에 따라 번갈아 이기는 선수들에 가깝다고 생각합니다.

이런 관점에서 보면 로더의 존재 이유가 분명해집니다. 사람이 매번 세대와 정밀도와 입력 길이를 조합해 표를 외울 수는 없습니다. 기계가 그 조합을 보고 맞는 카드를 꺼내는 편이 현실적이죠. 특히 데모 머신과 운영 머신이 다른 팀에서는 이런 자동 분기가 생명줄이 됩니다. 노트북에서 돌던 코드가 서버에서는 다른 경로로 돌고, 그래도 결과는 같아야 하니까요.

다만 세대가 늘어날수록 테스트 행렬도 커집니다. 모든 조합을 다 재볼 수는 없으니 어디를 대표 구간으로 삼을지가 중요해집니다. 저는 앞으로 허브에 쌓이는 벤치마크가 바로 그 대표 구간을 둘러싼 합의가 될 거라고 봅니다. 어떤 길이와 배치와 정밀도를 기준으로 삼을 것인지가 모이면 다음 기여자는 그 위에서 출발할 수 있습니다. 합의가 쌓이면 중복이 줄죠. 그렇다면 이 합의를 믿어도 되는지는 어떻게 확인할까요.

제출될 때마다 무엇이 함께 검증될까요

저는 최적화 코드를 볼 때 속도 그래프보다 테스트 문장을 먼저 찾습니다. 빠르다는 말은 조건이 바뀌면 흔들리지만, 무엇과 비교해 맞다고 했는지는 남기 때문입니다. 이번 발표는 각 제출물이 참조 구현과의 정확성 테스트와 성능 벤치마크를 함께 갖는다고 설명하는데요. 저는 이 대목을 형식적인 절차로 읽지 않았습니다. 신뢰를 만드는 문법으로 봤습니다.

정확성 테스트는 기준 구현과 결과가 같은지를 묻습니다. 부동소수점 연산은 순서에 따라 끝자리에서 달라질 수 있어서 무조건 같은 비트를 요구하기는 어렵지만, 허용 범위를 정하고 그 안에 드는지를 확인하는 방식은 가능합니다. 이런 비교가 저장소마다 같은 방식으로 붙어 있으면 나중에 온 사람이 안심하고 가져다 쓸 수 있습니다. 남의 빠른 코드를 쓸 때 가장 무서운 것은 틀리게 빠른 경우니까요.

성능 벤치마크도 비슷한 역할을 합니다. 얼마나 빠른지를 자랑하는 숫자라기보다 어떤 조건에서 재었는지를 밝히는 기록으로 읽어야 합니다. 하드웨어와 dtype과 입력 형태가 함께 적혀 있으면 그 숫자는 다음 선택의 재료가 됩니다. 반대로 조건이 빠진 숫자는 구경거리에 가깝습니다. 저는 그래서 벤치마크를 점수표가 아니라 지도처럼 봅니다. 어디가 빠르고 어디가 느린지를 보여주는 지형도라는 것이죠.

여기서 한 가지 짚고 싶은 점은 재현 가능성입니다. 코드와 메타데이터와 측정 방법이 함께 있어야 남이 다시 재볼 수 있습니다. 다시 재볼 수 있어야 토론이 됩니다. 이 토론이 쌓이면 허브는 단순한 배포 채널을 넘어 연구의 앞마당이 됩니다. 물론 모든 제출이 완벽할 수는 없겠지만, 형식이 같으면 고치는 일도 쉬워집니다. 그렇다면 이런 장치가 모여 결국 무엇을 남길까요.

코드 변경 없이 백엔드 플래그로 연결되는 통합 개념도
코드 변경 없이 백엔드 플래그로 연결되는 통합 개념도

다음에는 무엇을 지켜보면 좋을까요

여기까지 읽으면 자연스럽게 다음 질문이 생깁니다. 허브가 섰으니 이제 끝일까요. 저는 아직 시작에 가깝다고 봅니다. 저장소와 로더와 테스트라는 뼈대는 섰지만, 그 위에 어떤 합의가 쌓이는지가 앞으로의 관전 포인트이기 때문입니다. 뼈대는 약속이고, 살은 앞으로 붙을 기록들이죠.

제가 앞으로 보고 싶은 것은 세 가지입니다. 먼저 어떤 커널이 실제로 자주 재사용되는지입니다. 목록에 있다고 다 쓰이는 것은 아니어서 현장에서 살아남는 구현과 그렇지 않은 구현이 갈릴 겁니다. 자주 쓰이는 쪽에는 기여가 몰리고 테스트도 단단해지겠죠. 이런 쏠림은 나쁜 현상이 아니라 생태계가 학습하는 과정에 가깝습니다.

다음으로는 하드웨어 세대가 바뀔 때 로더의 선택이 어떻게 바뀌는지입니다. 새로운 아키텍처가 오면 기존의 autotuning 결과가 흔들리고, 다시 탐색한 결과가 캐시를 갱신할 겁니다. 그 변화가 얼마나 매끄럽게 일어나는지가 이 설계의 진짜 시험대라고 생각합니다. 처음의 매끄러움보다 세대가 바뀔 때의 매끄러움이 더 중요하니까요.

마지막으로는 닫힌 개발이 얼마나 줄어드는지입니다. 발표가 겨냥한 문제의식이 중복된 비공개 개발이었으니, 시간이 지나면 같은 발상의 공개 버전이 허브에 모이는지를 보면 됩니다. 기여 장벽이 낮아지고 테스트 형식이 자리를 잡으면 각 실험실이 안쪽에 숨기던 코드를 바깥으로 꺼낼 동기가 생기죠. 물론 모든 코드가 공개되지는 않겠지만, 공통으로 쓰는 부분부터 모이면 전체 파이가 커집니다.

이런 질문들을 안고 발표를 다시 보면 첫인상과 다른 그림이 남습니다. 빠른 코드 몇 개가 아니라 빠른 코드를 나누는 방법이 보이기 때문입니다. 저는 그래서 이번 업데이트를 성능 발표가 아니라 협업 발표로 기억하려 합니다. 속도는 각자 내는 것이 아니라 함께 고르는 것이 될 수 있다는 제안이니까요. 여러분의 팀에서는 지금 어떤 커널이 매번 다시 만들어지고 있나요.

참고 자료

  1. 🤗 Kernels: Major Updates · huggingface.co

    리뷰 원문