mallo

Underdog의 Husky는 왜 Apple의 MLX보다 빠르다고 주장할까?

요약 듣기

0:008:30

원문 보기husky.underdog.ai
요약 보기

이 글에서 Underdog가 내세우는 핵심은 꽤 분명해요. Husky는 아무 모델이나 돌리는 범용 추론 엔진이 아니라, 자사 모델 Woof 하나에 맞춰 만든 엔진이고, 그래서 같은 가중치로 같은 답을 내면서도 MLX보다 더 빨랐다는 주장입니다. 수치로는 Mac에서 Flash를 켠 상태에 초당 최대 730토큰, 일부 작업에서 최대 4.5배, 첫 답변 시작은 최대 5배대까지 더 빨랐다고 말해요. 여기서 먼저 붙잡아야 할 건 비교 대상과 범위예요. 이건 ‘모든 모델에서 Husky가 더 빠르다’가 아니라, Woof를 Apple 실리콘에서 돌릴 때 MLX와 비교한 결과라는 뜻이에요.

숫자를 읽으려면 속도를 세 갈래로 나눠 봐야 해요. 하나는 답변을 써 내려갈 때의 토큰 속도예요. 둘째는 prefill인데, 모델이 긴 프롬프트를 처음 읽어 내부 상태를 만드는 준비 구간이라고 이해하면 돼요. 셋째는 first token, 즉 첫 단어가 나오기까지 걸리는 시간이고요. 채팅에서는 이 셋이 체감에 다르게 작용해요. 이미 이어지던 대화라면 첫 단어가 얼마나 빨리 나오느냐가 중요하고, 처음 보는 긴 문서를 던졌다면 prefill 속도가 더 크게 느껴지죠. 글도 이 차이를 의식해서, 이어지는 대화의 캐시 상황과 처음 보는 차가운 프롬프트 상황을 따로 측정해 보여줍니다.

특히 검수에서 빠졌던 cold prefill은 이 글의 중요한 축이에요. 저자는 처음 보는 긴 프롬프트를 바깥 호출자 기준으로 잴 때 Husky가 859토큰 프롬프트에서 초당 5,150토큰 prefill을 냈고, MLX는 3,094였다고 적어요. 엔진 내부만 보면 Husky 5,460, MLX 4,878로 격차가 조금 다르게 보이는데, 사용자 입장에서는 호출 바깥의 시간도 포함한 수치가 더 체감에 가깝겠죠. 또 같은 긴 프롬프트에서 첫 단어까지 걸린 시간도 Husky는 0.17초, MLX는 0.30초였고, 짧은 프롬프트에서는 0.05초 대 0.15초였다고 합니다. 그러니까 Husky의 장점이 단순히 ‘문장을 쓰기 시작한 뒤에만 빠르다’가 아니라, 처음 보는 입력을 읽어 들이는 구간에서도 우위가 있다고 저자는 말하는 셈이에요.

왜 이런 차이가 날 수 있다고 보느냐면, 저자의 출발점은 의외로 소박해요. 토큰 하나를 만들 때마다 Woof의 2.4GB 가중치를 메모리에서 GPU 쪽으로 계속 읽어 와야 하고, 이 읽기 자체가 이미 몇 밀리초를 차지한다는 거예요. 즉, 아주 대단한 비밀 지름길이 있다기보다, 같은 메모리 읽기 한 번에서 더 많은 결과를 건져내는 쪽으로 바꿨다는 주장입니다. 가상의 비유로 보면, 같은 크기의 창고 문으로 상자를 옮겨야 할 때 문 자체를 넓히지는 못하지만, 한 번 오갈 때 트럭에 더 많이 싣거나, 다음 트럭을 미리 대기시켜 빈 시간이 없게 만드는 식이에요. 글에서 말하는 핵심도 딱 그 방향입니다.

그 방식이 특히 잘 먹히는 곳이 편집 작업이에요. 함수 일부 수정, JSON 필드 추가, 오탈자 수정처럼 많은 작업은 답변의 상당 부분이 프롬프트 안에 이미 들어 있습니다. Husky는 이 형태를 노리고 매 단계에서 토큰 하나만 내놓지 않고, 여덟 개 후보를 한 번에 검사한다고 설명해요. 마지막에 쓴 몇 토큰이 프롬프트 어딘가와 이어지면, 그 뒤에 따라올 토큰들을 프롬프트에서 가져와 제안하고, Woof가 그중 자기가 원래 쓸 토큰만 통과시키는 식이죠. 저자 표현대로라면 이 검사는 토큰 하나 비용의 약 1.5배로 여덟 개 블록을 확인하고, 실제로는 다섯 개나 여섯 개를 한꺼번에 건지는 경우가 많다고 해요. 그래서 함수 편집, JSON 수정 같은 항목에서 3배 안팎, Flash를 켠 경우 4배대까지 차이가 크게 뛴다고 연결합니다.

반대로 새 글을 쓰거나 요약처럼 프롬프트를 그대로 베껴 오기 어려운 작업에서는 편집만큼 큰 이득이 자동으로 생기지 않아요. 그래서 글은 Flash라는 작은 draft를 붙였다고 설명해요. 이 draft는 Woof의 일부 내부 상태를 읽고 다음 일곱 토큰을 미리 제안합니다. 하지만 최종 답을 따로 쓰는 모델은 아니에요. 어디까지나 Woof가 다시 확인해서, 자신이 실제로 쓸 토큰만 남긴다고 저자는 강조합니다. 그래서 ‘같은 가중치, 같은 답’이라는 표현이 계속 나와요. 쓰기 작업에서 Husky 단독으로는 164~175토큰/초 정도의 범위였다가, Flash를 켜면 210~273으로 올라갈 수 있고, 코드 처음 쓰기나 Invoice to JSON처럼 구조가 뚜렷한 작업에서는 더 큰 폭으로 오른다는 설명이에요. 또 매 단계마다 프롬프트 복사가 더 유리한지, draft 추측이 더 유리한지를 골라 쓴다고 하죠.

첫 단어가 빨라지는 이유도 따로 설명해 둡니다. 이어지던 채팅에 메시지 하나를 더 붙여 다시 보냈을 때, Husky는 이전 상태를 기억하는 prefix cache에서 바로 이어서 답하고, 그래서 첫 단어가 약 35밀리초라고 해요. MLX도 캐시를 쓰는 경로가 있지만 약 140밀리초가 들었다고 비교합니다. 또 캐시를 안 쓰고 대화를 다시 전부 읽는 일반 호출 경로와 비교하면 차이는 더 커진다고 적어요. 사용자가 느끼는 ‘바로 반응한다’는 인상은 초당 토큰 수보다 이 구간의 영향이 큰데, 글은 Husky가 여기서도 고정 비용을 줄였다고 주장하는 셈이에요.

그 밑바탕에는 글이 MSI라고 부르는 접근이 있어요. model-specific inference, 즉 한 모델의 모양을 미리 다 알고 그 사실을 끝까지 이용하는 엔진이라는 뜻입니다. 범용 엔진이 여러 모델을 다 받으려고 ‘어떤 크기든 처리 가능한 공용 부품’을 들고 다닌다면, Husky는 Woof의 정확한 크기에 맞춘 전용 부품만 써서 군더더기를 줄이겠다는 쪽이에요. 가상의 비유로 보면, 여러 규격 나사를 다 조이는 만능 공구 대신 한 규격만 다루는 맞춤 공구를 만든 셈이죠. 그래서 커널을 Woof 행렬 크기에 맞춰 컴파일하고, 4비트 가중치를 커널이 읽는 순서대로 저장해 로드 시 변환과 복사를 없애고, 8비트 KV 캐시도 어텐션 커널이 바로 읽는 형태로 두며, 다음 디코드 단계를 현재 단계가 끝나기 전에 미리 제출해 GPU가 호스트를 기다리지 않게 했다고 설명합니다. 긴 일반 문장에서 Husky가 MLX보다 아주 약간만 앞설 때 그 마지막 몇 퍼센트를 만든 것도 이 파이프라이닝이라고 적어요.

다만 이 글을 읽을 때는 성능 수치 못지않게 조건과 한계를 같이 봐야 해요. 실험은 M5 Max 한 대, Woof 한 벌의 가중치, greedy decoding, 16가지 프롬프트 유형에서 이뤄졌고, 각 항목은 조용한 창에서 세 번씩 재서 중앙값을 썼다고 해요. 반복 측정은 12% 이내로 맞았다고도 적습니다. 또 ‘모든 16개 작업에서 더 빠르다’는 말은 맞지만, 그 폭은 작업마다 크게 달라요. Flash 없이 일반 글쓰기는 1.02~1.27배 수준인 항목도 있고, 편집처럼 프롬프트 반복이 많은 곳에서 배수가 크게 뜁니다. 무엇보다 Husky의 철학 자체가 범용성을 버리고 Woof 하나에 맞추는 것이기 때문에, 이 수치를 다른 모델이나 다른 하드웨어로 그대로 일반화할 수는 없어요. 글 끝부분에서 저자들이 다음 방향으로 whole-step megakernel도 시험했지만, 출력은 같았고 속도는 더 빨라지지 않았다고 솔직하게 적은 대목도 중요합니다. 즉, 이 글은 ‘우리가 모든 면에서 압도했다’기보다, 특정 모델과 특정 작업 모양에 맞춘 최적화가 어디에서 실제 이득을 냈는지 보여 주는 보고서에 가깝습니다.