
GPU 없는 노트북으로 주말 동안 음성 코딩 에이전트를 만들었습니다
요약
저사양 노트북 환경에서 Claude를 두뇌로 활용하는 음성 코딩 에이전트 'Mabara' 개발 프로젝트를 소개합니다. 클라우드 음성 서비스 대신 로컬 CPU만을 사용하여 음성 인식 및 합성을 처리함으로써 하드웨어 제약을 극복했습니다.
핵심 포인트
- Claude Agent SDK를 활용한 추론과 로컬 음성 처리의 분리 구조
- GPU 없는 저사양 환경에서도 실행 가능한 로컬 음성 모델 활용
- 음성 우선(Voice-first) 인터페이스를 통한 자연스러운 코딩 경험 제공
- 클라우드 의존성을 최소화한 로컬 AI 모델 실행 실험
저는 소프트웨어가 음성 우선 (voice-first)의 미래를 향해 가고 있다고 믿습니다. 앱에 단순히 덧붙여진 눈속임으로서의 음성이 아니라, 진정한 인터페이스로서의 음성 말입니다. 도구와 대화하는 것이 동료와 대화하는 것만큼 자연스럽게 느껴지는 그런 종류의 인터페이스 말이죠.
이 프로젝트의 불꽃은 아주 작은 것에서 시작되었습니다. 저는 Claude와 실시간 음성 세션을 진행 중이었는데, 타이핑을 전혀 하지 않고 말하면 실시간으로 대답해 주는 방식이었습니다. 그 경험 중 무언가가 제 머릿속에 남았습니다. 그것은 앞으로 세상이 나아갈 방향을 엿본 것 같은 느낌이었습니다.
비슷한 시기에, 저는 로컬 AI 모델 (local AI models)을 제대로 다뤄볼 구실을 찾고 있었습니다. 저는 API 호출이나 클라우드 의존성 없이, 오직 사용자와 하드웨어만 존재하는, 자신의 기기에서 모델을 실행한다는 아이디어에 항상 끌려왔습니다. 관련 내용을 읽어보고 조금씩 시도해 보긴 했지만, 깊게 파고든 적은 없었습니다.
그래서 저는 이 두 가지 호기심을 하나의 주말 프로젝트로 결합했습니다. 음성 코딩 에이전트 (voice coding agent)를 만드는 것이죠. 음성 관련 부분은 로컬 (locally)에서 실행하고, Claude를 두뇌로 사용하게 하는 것입니다.
그 프로젝트가 바로 Mabara가 되었습니다.
Mabara가 실제로 하는 일
Mabara를 사용하면 동료에게 말하듯 코드베이스 (codebase)와 대화할 수 있습니다. 키를 누른 상태에서 질문을 하거나 지시를 내리면, 에이전트가 소리 내어 응답합니다. 프로젝트 구조가 어떻게 되어 있는지 설명할 수 있고, 사용자가 음성으로 승인하면 파일의 내용을 변경할 수 있으며, 만약 마음에 들지 않는 동작을 수행한다면
기발한 부분은 사고(thinking)가 일어나는 곳과 듣기(listening) 및 말하기(speaking)가 일어나는 곳을 분리했다는 점입니다. Claude Agent SDK를 통해 실행되는 Claude가 두뇌 역할을 합니다. Claude는 코드를 이해하고 무엇을 할지 결정합니다. 하지만 귀와 입, 즉 사용자의 말을 듣고 다시 말을 하는 부분은 전적으로 제 개인 노트북에서 실행됩니다. 클라우드 음성 서비스(cloud speech service)는 사용하지 않았습니다. 오직 제 CPU만 사용했습니다.
그리고 그 노트북은 고성능이 아닙니다. 8GB RAM과 GPU가 없는 i5-10210U 모델입니다. 누군가가 AI 벤치마크(benchmark)를 돌릴 만한 기계가 아닙니다. 하지만 바로 그 제약 조건이 이 이야기가 흥미로운 이유입니다.
이것은 사실 무언가를 측정하는 것에 관한 이야기이며, 가정이 아닌 데이터를 신뢰하는 법을 배우는 과정에 대한 이야기입니다.
면책 조항 (Disclaimer): 저는 음성 모델(speech models)이나 실시간 시스템(real-time systems) 전문가가 아닙니다. 제가 측정한 것과 그것이 저에게 가르쳐준 것을 공유할 뿐입니다. 만약 제가 틀린 부분을 발견하신다면 알려주세요. 저는 배우기 위해 여기 있습니다.
교훈 1: 벤치마크가 진실이며, 그 외의 모든 것은 마케팅이다
AI 세계에서 모두가 "알고 있는" 사실이 하나 있습니다. 모델을 int8로 양자화(Quantizing)하면 CPU에서 더 빨라진다는 것입니다. 숫자가 작아지면 연산이 빨라지니까요. 모든 최적화 가이드가 그렇게 말합니다.
그래서 제 텍스트 음성 변환(text-to-speech) 모델이 너무 느려졌을 때, 저는 당연히 성능이 개선될 것이라 기대하며 int8 버전을 다운로드했습니다.
결과는 전체 정밀도(full precision) 모델보다 2.5배 더 느렸습니다.
조금 느려진 수준이 아니었습니다. 전체 정밀도 모델은 제 CPU에서 실시간 대비 1.6배(1.6x real-time) 속도를 기록했습니다. 반면 int8 버전은 0.6배(0.6x)에 그쳤는데, 이는 생성해야 할 음성 속도보다도 느린 수치였습니다.
그 이유를 이해하는 과정은 제가 수강했던 그 어떤 강의보다 하드웨어에 대해 더 많은 것을 가르쳐 주었습니다. int8 가속은 Intel 칩의 VNNI라고 불리는 특수 CPU 명령어(special CPU instructions)에 의존하며, 이 명령어는 양자화된 연산을 네이티브로 처리합니다. 제 Comet Lake CPU에는 해당 명령어가 없습니다. 이 명령어가 없으면 CPU는 매 연산마다 양자화(quantize), 역양자화(dequantize), 그리고 포맷을 재배치(shuffle)해야 합니다. 이는 이득은 전혀 없이 순수하게 오버헤드(overhead)만 발생하는 작업입니다.
비슷한 패턴이 곧 다시 반복되었습니다. Whisper보다 6배 빠르다고 마케팅되는 Distil-Whisper는 제 사용 사례(use case)에서는 속도가 단 0%도 빨라지지 않았습니다. Push-to-talk 클립은 짧으며, 짧은 클립은 거의 모든 시간을 인코더(encoder)에서 보내는데, 증류(distillation)는 이 인코더를 축소하지 않습니다. 그 유명한 속도 향상은 오직 한 시간 분량의 녹음 파일에서만 나타납니다.
Whisper의 처리 창(processing window)을 줄여줄 것으로 기대했던 튜닝 파라미터(tuning parameter)도 아무런 효과가 없었습니다. 저는 세 가지 다른 값을 벤치마크(benchmark)해 보았습니다. 소수점 자리까지 동일한 시간이 걸렸습니다.
첫째 날이 끝날 무렵, 저는 개인적인 규칙을 세웠습니다. 이 노트북에서 직접 수행한 벤치마크를 통과하지 못한다면, Mabara의 그 어떤 것도 출시(ship)하지 않겠다는 규칙입니다. 다른 사람의 README에 적힌 벤치마크가 아니라, 바로 저의 벤치마크 말입니다.
두 번째 교훈: 아름다운 목소리를 1.2초와 맞바꿨고, 그것은 이번 빌드에서 가장 쉬운 결정이었다
목소리를 찾아가는 과정은 저를 겸손하게 만들었습니다.
저는 진정으로 사람처럼 들리는 8,200만 파라미터(parameter) 모델인 Kokoro로 시작했습니다. 제 CPU에서 이 모델은 대략 실시간의 1.0배 속도로 합성(synthesize)합니다. 그 수치는 그것이 실제로 무엇을 의미하는지 이해하기 전까지는 괜찮게 들립니다. 시스템이 말을 하는 속도와 정확히 똑같은 속도로 음성을 생성한다는 뜻이기 때문입니다. 오차를 허용할 여유가 전혀 없습니다. 모든 문장의 경계는 매끄러운 발화와 어색한 공백 사이에서 동전 던지기처럼 결정됩니다.
그래서 저는 7배 실시간 속도로 실행되는, 더 작고 인정할 수밖에 없이 더 로봇처럼 들리는 모델인 Piper로 전환했습니다. 공백은 완전히 사라졌습니다. 하지만 목소리가 거슬렸습니다. 모든 답변이 제가 동료가 아닌 기계와 대화하고 있다는 사실을 상기시켜 주었습니다.
그러다 Kokoro만큼이나 좋게 들리면서 제 하드웨어에서 2배 실시간 속도로 실행되는 6,600만 파라미터 모델인 Supertonic을 발견했습니다. 저는 이를 제대로 통합(integrate)하는 데 몇 시간을 소비했습니다. 마음에 드는 목소리를 골랐고, 느낌이 적절해질 때까지 속도(pacing)를 조절했습니다.
그리고 실제로 사용해 보았습니다. 20분도 채 되지 않아 저는 다시 Piper로 돌아갔습니다.
그 이유는 다음과 같습니다. Supertonic은 모든 답변의 첫 단어를 생성하는 데 약 1.6초가 걸렸습니다. 반면 Piper는 0.4초가 걸렸습니다. 글로 적어놓으면 1.2초라는 차이가 그리 커 보이지 않을 수도 있습니다. 하지만 실제 대화 속에서는 동료와 대화하는 것과 시스템이 따라오기를 기다리는 것 사이의 차이와 같습니다.
저는 개발을 하는 하루 동안은 Piper의 로봇 같은 느낌을 기꺼이 참아낼 수 있었습니다. 하지만 실제로 20분 동안 사용하면서 1.2초의 추가적인 침묵을 참을 수는 없었습니다.
이 경험은 제가 대화형 인터페이스 (conversational interfaces)를 설계할 때 규칙으로 삼는 교훈을 주었습니다. 응답 지연 시간 (Response latency)이 목소리의 아름다움보다 중요합니다. 사용자들은 자연스러운 목소리를 원한다고 말하지만, 그들의 인내심은 빠른 속도를 원한다고 말합니다. 말(words)이 아니라 인내심을 관찰하십시오.
잠시 여담을 하자면, 저는 나이지리아인이며 이 프로젝트에 진심으로 나이지리아 억양이 담긴 목소리를 사용하고 싶었습니다. 제가 진심으로 존경하는 젊은 나이지리아 엔지니어가 만든 YarnGPT라는 멋진 프로젝트가 바로 이 역할을 수행합니다. 하지만 이 프로젝트는 텍스트 음성 변환 (Text to speech)을 위해 자기회귀 언어 모델 (Autoregressive language model) 아키텍처를 사용하는데, 제 CPU로는 이를 실시간으로 실행할 수 없습니다. 언젠가 꼭 해보고 싶은 목록에 넣어두었습니다.
세 번째 교훈: 음성 에이전트는 파이프라인이며, 모든 단계는 중첩되어야 한다
이번 주말 내내 체감할 수 있었던 가장 큰 속도 향상은 제가 어떤 모델을 선택했느냐와는 전혀 상관이 없었습니다.
초기에는 Mabara가 단 한 마디를 내뱉기 전에 Claude의 응답이 완전히 끝날 때까지 기다렸습니다. 긴 답변의 경우 30초 동안 침묵이 흐른 뒤, 에세이 전체가 한꺼번에 쏟아져 나왔습니다. 사용할 수 없는 수준이었습니다.
해결책은 전체 과정을 파이프라인 (Pipeline)으로 취급하는 것입니다. Claude는 텍스트를 토큰 (token) 단위로 스트리밍 (streaming)합니다. 완전한 문장이 생성되는 즉시 음성 합성기 (speech synthesizer)로 전송됩니다. 그 문장의 오디오가 생성되는 즉시 재생이 시작되며, 그동안 두 번째 문장은 합성 중이고 세 번째 문장은 여전히 Claude에 의해 생성되고 있습니다. 세 단계가 모두 동시에 실행되는 것입니다.
이제 음성은 전체 응답이 끝난 후가 아니라 첫 번째 문장이 끝난 직후에 시작됩니다. 완료까지 걸리는 총 시간은 동일합니다. 하지만 체감되는 경험은 완전히 다릅니다.
시스템의 다른 모든 곳에도 동일한 사고방식이 적용되었습니다. 마이크는 결코 완전히 닫히지 않습니다. 키를 누른 후에만 장치를 열면 첫 음절이 잘리고 전사 (Transcription)를 망치기 때문에, 계속해서 롤링 프리롤 버퍼 (rolling pre-roll buffer)를 유지합니다. 큐 (Queue)가 쌓이면 문장들은 단일 합성 호출 (synthesis calls)로 배치되는데, 이는 호출의 길이에 상관없이 모든 호출에 고정된 오버헤드 (overhead)가 발생하기 때문입니다. 그리고 큐에 쌓인 모든 오디오 조각은 에포크 (epoch) 번호를 포함하고 있습니다. 덕분에 사용자가 대화 키를 다시 누르는 것만으로 문장 중간에 Mabara를 중단시키면, 0.2초 이내에 입을 다물게 되며, 이전 에포크의 오래된 오디오는 사용자가 이미 말을 시작한 후 어색하게 재생되는 대신 조용히 폐기됩니다.
이 중 어느 것도 이색적인 엔지니어링은 아닙니다. 실시간 시스템 (real-time systems)에서는 다음 작업을 시작하기 전에 어떤 작업이든 완전히 끝날 때까지 기다리는 것이 거의 항상 잘못된 설계라는 점을 단순히 받아들인 것뿐입니다. 적어도 제 경험상으로는 그렇습니다.
교훈 네 번째: 인터페이스가 신뢰라면, 그 신뢰를 엔지니어링하라
사용자의 코드를 편집하는 음성 에이전트는 독특한 문제를 안고 있습니다. 에이전트가 무엇을 하려는지 실행하기 전에는 눈으로 볼 수 없기 때문입니다. 인터페이스 전체가 곧 신뢰 그 자체입니다. 그래서 안전 모델 (safety model)은 제가 Mabara에서 가장 자랑스럽게 생각하는 부분이 되었습니다.
읽기 작업은 무료이며 승인이 필요하지 않습니다. 하지만 모든 파일 편집과 모든 셸 명령 (shell command)은 "page.tsx 파일을 편집하고 싶습니다. 승인하시겠습니까?"와 같이 소리 내어 말하며, 저의 구두 승인을 요구합니다. "전체 작업에 대해 승인"이라고 말하면 해당 작업의 나머지 편집 사항들도 승인됩니다. 15개의 개별 편집을 하나씩 승인하는 것은 고역이기 때문입니다. 셸 명령은 매번, 단 한 번도 빠짐없이 항상 물어봅니다. 왜냐하면 그곳에 진정으로 복구 불가능한 실수들이 존재하기 때문입니다.
이 모든 것의 밑바탕에는 git이 실질적인 안전 장치 역할을 수행합니다. Mabara는 git 저장소(repository) 외부의 어떤 것도 수정하기를 거부합니다. 어떤 작업이든 처음 승인된 수정 사항은 자동으로 체크포인트(checkpoint)를 생성합니다. "그거 되돌려줘(revert that)"라고 말하면, 마지막 작업이 건드린 모든 것이 복구됩니다. 여기에는 제가 커밋하지 않은 파일들을 삭제하는 대신 백업에서 복구하는 기능도 포함되는데, 이는 제가 초기에 발견하지 못했다면 첫 주에 실제 작업물을 모두 날려버렸을 예외적인 상황(edge case)이었습니다. "이거 커밋해줘(commit this)"라고 말하면, 해당 작업이 실제로 건드린 파일들만 포함하여 좋은 작업 내용을 적절한 git 커밋(commit)으로 변환합니다.
저는 도구 측면뿐만 아니라 모델 측면에서도 신뢰에 관한 교훈을 얻었습니다. 비용을 절감하기 위해 더 작고 저렴한 모델로 Mabara를 실행해 보려 했습니다. 한 시간도 채 되지 않아 모델은 제 백엔드(backend)가 PDF 생성 라이브러리로 구축되었다고 자신 있게 말했습니다. 실제 백엔드는 requirements.txt에 명확히 선언된 FastAPI였지만, 모델은 이 파일을 읽는 수고조차 하지 않은 듯했습니다. 모델은 실제 코드가 아닌 느낌(vibes)과 문서의 산문(prose)을 바탕으로 답변한 것입니다.
해결책은 부분적으로 프롬프트(prompt) 규칙을 세우는 것이었습니다. '코드가 진실이며, 문서는 의도일 뿐이다. 답변하기 전에 검증하라'는 규칙입니다. 하지만 더 깊은 교훈이 제 마음속에 남았습니다. 따뜻한 인간의 목소리로 자신 있게 전달되는 오답은 도구가 만들어낼 수 있는 가장 값비싼 출력물입니다. 저는 다시 더 큰 모델을 기본값으로 사용하기 시작했습니다. 속도는 협상 가능하지만, 신뢰는 협상 대상이 아닙니다.
솔직한 부분
Mabara는 Claude Code와 함께 페어 빌드(pair built)되었습니다. AI가 실제 코드 라인의 대부분을 작성했습니다. 저는 이 점을 솔직하게 밝히고 싶습니다. 왜냐하면 이 이야기에서 진정으로 흥미로운 부분은 코드의 줄 수가 아니라 노동의 분업이라고 생각하기 때문입니다.
이 프로젝트의 모든 결정은 저의 것이었습니다. 어떤 벤치마크(benchmark)를 실행할지, 어떤 수치를 실제로 믿을지, 그리고 20분간의 실제 사용 결과가 2시간 동안의 통합(integration) 작업과 일치하지 않을 때 언제 그 작업을 포기할지 말입니다. AI는 int8 열풍이나 distil-Whisper 열풍에 대해 단 한 번도 반박하지 않았습니다. 벤치마크가 반박했습니다. 저는 벤치마크를 실행하기로 선택했고, 그것이 말하는 바에 귀를 기울이기로 선택했습니다.
저는 이번 주말을 보내며 이것이 현재 엔지니어링의 모습이라는 생각을 하게 되었습니다. 타이핑(typing)은 저렴해졌습니다. 판단(judgment)이 곧 업무입니다.
이것을 만든 이유
제가 Mabara를 만든 이유는 제가 가진 믿음, 즉 음성(voice)이 단순한 신기한 기능이 아니라 소프트웨어를 위한 진정한 인터페이스 계층(interface layer)이 될 것이라는 믿음을 테스트하고 싶었기 때문입니다. 그리고 단순히 모델에 대해 읽기만 하는 대신, 로컬 모델(local models)을 직접 다루며 실전 경험을 쌓고 싶었습니다.
두 가지 모두 이루어졌습니다. 이제 저는 어떤 기사도 가르쳐줄 수 없는 방식으로 로컬 음성 모델(local speech models)의 실질적인 트레이드오프(tradeoffs)를 이해하게 되었습니다. 또한, 모든 것을 측정하고 직접 테스트하지 않은 것은 아무것도 믿지 않을 준비가 되어 있다면, 겸손한 하드웨어에서도 음성 우선(voice-first) 도구를 구축할 수 있다는 작고 정직한 증거를 얻었습니다.
Mabara는 저의 GitHub에서 오픈 소스로 공개되어 있습니다. 단 하나의 Python 파일, 하나의 프로세스, 5개의 스레드, 프레임워크 없음. 그 안의 모든 기본 설정(default)은 제 특정 노트북에서의 싸움에서 승리한 것들이며, 이는 여러분의 기기에서는 일부가 패배할 수도 있음을 의미합니다. 따라서 패배한 설정들은 모두 플래그(flags) 뒤에 남아 여러분의 벤치마크(benchmark)가 결정해주기를 기다리고 있습니다.
여러분은 '알려진' 최적화(optimization)를 벤치마킹했다가 그것이 실패하는 것을 본 적이 있나요? 아니면 음성 기반의 무언가를 직접 만들어 본 적이 있나요? 무엇이 여러분을 놀라게 했는지 진심으로 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기