순수 C로 Voxtral Realtime 4B를 CPU에서 돌리는 법과 의미

Voxtral Realtime 4B는 Mistral AI의 스트리밍 음성-텍스트(STT) 모델로, “말하자마자 자막이 따라오는” 실시간 전사를 목표로 합니다. 흥미로운 건 모델 자체보다도, 이를 순수 C(Pure C)로 구현한 추론 파이프라인이 공개되면서 “Python도 CUDA도 없이, 표준 C 라이브러리만으로 빌드 가능한 STT”에 한 발 가까워졌다는 점입니다1. 이 글에서는 왜 이 접근이 배포 현장에서 강력한지, 스트리밍 설계는 어떻게 되어 있는지, 그리고 CPU 전용으로 쓸 때 어디를 조심해야 하는지까지 한 번에 정리해봅니다.
순수 C STT가 갑자기 중요해진 이유(의존성 최소화)
오픈 웨이트 모델은 많습니다. 그런데 현장에서 “진짜로 널리 쓰이느냐”는 종종 성능이 아니라 배포 난이도에서 갈립니다. Python 런타임, 특정 프레임워크, GPU 드라이버와 같은 큰 의존성은 보안 심사와 공급망 관리의 부담이 되고, 온디바이스나 사내망(폐쇄망) 환경에서는 그 자체로 장벽이 됩니다.
이번 Voxtral Realtime 4B의 Pure C 추론 구현은 그 장벽을 정면으로 낮춥니다. 읽을 수 있고, 빌드할 수 있고, 이식할 수 있는 “얇은 레퍼런스”가 생기면, Windows 포팅이든 모바일 런타임이든 각자 환경에 맞춘 확장이 쉬워집니다. 특히 제품팀 입장에선 “일단 돌아가는 최소 구현”이 있는 것만으로도 일정이 크게 당겨지곤 합니다1.
Voxtral Realtime 4B 스트리밍 구조: 청크, 겹침, 그리고 즉시 출력
실시간 STT에서 핵심은 속도만이 아닙니다. “길어지는 입력을 어떻게 감당하느냐”가 더 중요할 때가 많습니다. 이 구현은 오디오를 한 덩어리로 먹이지 않고, 청크 단위로 인코더를 반복 호출하는 방식으로 처리합니다. 청크에는 겹침(오버랩) 구간이 있어, 단어가 경계에서 잘리는 문제를 줄이면서도 메모리 사용량이 입력 길이에 비례해 폭증하지 않도록 설계했습니다1.
출력도 스트리밍에 충실합니다. 토큰이 생성되면 바로 stdout으로 흘려보내고, C 레벨에서는 vox_stream_t 같은 스트리밍 API로 “오디오 조금 feed → 토큰 get” 패턴을 제공해 제품 코드에 붙이기 좋습니다1. 여기서 실무적으로 재밌는 포인트가 flush() 동작입니다. 스트림을 완전히 종료하지 않아도 “밀린 토큰을 확정해서 뱉어내는” 식으로 쓸 수 있어, 무음 구간에서 자막을 빠르게 확정하는 UX(예: 회의록 자동 줄바꿈, 문장 단위 확정)에 유리합니다1.
CPU 전용 추론 파이프라인에서 체감 차이를 만드는 옵션: -I의 함정
스트리밍 STT를 처음 만지면 이런 유혹이 생깁니다. “지연시간이 싫으니까 인코더를 0.1초마다 호출하면 되겠네?” 그런데 현실은 반대일 수 있습니다.
이 구현에서 -I <seconds>는 인코더를 얼마나 자주 돌릴지 정하는데, 인코더 호출에는 고정 오버헤드가 존재합니다. 그래서 너무 촘촘히 쪼개면 GPU든 CPU든 오버헤드 비용을 계속 내느라 전체 처리량이 무너집니다. 예시로 60초 클립에서 배치 모드 인코더가 약 2.9초인 반면, -I 0.1로 잘게 쪼개면 인코더 시간이 약 15.8초까지 늘어날 수 있다고 설명합니다1.
실무 감각으로는 -I를 “사용자 체감 지연”만 보고 무작정 줄이면, 전력·발열·서버 동시 처리량이 같이 나빠집니다. 시작점은 1~2초가 무난하고, 그 다음에 UX 요구(자막이 얼마나 빨리 떠야 하는지)와 장치 성능(동시 세션 수)을 같이 보며 튜닝하는 편이 안전합니다1.
Apple Silicon 최적화(MPS) vs 범용 CPU(BLAS): 기대치를 정하는 법
이 프로젝트는 백엔드가 크게 두 갈래입니다. 하나는 Apple Silicon에서 Metal(MPS)로 달리는 빠른 경로, 다른 하나는 OpenBLAS/Accelerate 같은 BLAS 기반의 범용 CPU 가속 경로입니다1.
벤치마크 기준으로는 M3 Max(40‑core GPU)에서 MPS가 인코더 284ms(3.6초 오디오), 프리필 252ms, 디코더는 짧은 시퀀스에서 23.5ms/step을 제시합니다1. 반면 BLAS 경로는 인코더가 약 8초, 디코더가 335ms/step처럼 “돌긴 도는데 확실히 느린” 느낌에 가깝습니다1. 이유 중 하나가 가중치 포맷 처리인데, BF16 가중치를 매번 FP32로 바꾸는 비용이 발목을 잡는 식입니다1.
즉, “CPU-only로 누구나 실시간 STT”라기보다는, 의존성 최소화와 이식 가능한 레퍼런스라는 가치가 더 크고, 성능은 특히 Apple Silicon(MPS)에서 빛을 보는 구조라고 이해하는 게 정확합니다1.
장시간 전사에서 무너지지 않게: mmap, 롤링 KV 캐시, 메모리 상한
긴 회의 3시간을 전사한다고 상상해보면, 가장 무서운 건 속도보다 “메모리가 계속 쌓여서 죽는” 상황입니다. 이 구현은 가중치를 safetensors에서 mmap으로 매핑해 로딩을 빠르게 하고, 필요할 때 OS가 페이지 단위로 읽어오도록 합니다1. 디스크 기준 가중치는 약 8.9GB(BF16)로 제시됩니다1.
또 하나의 핵심은 디코더의 KV 캐시입니다. 전사 길이가 길어질수록 어텐션이 과거를 더 훑어야 해서 느려지는데, 메모리까지 무한정 늘면 운영이 불가능해집니다. 그래서 8192 포지션 슬라이딩 윈도우 기반의 롤링 KV 캐시로 자동 컴팩트를 하며, KV 캐시 메모리를 최대 약 1.8GB 수준으로 상한 관리한다고 설명합니다1.
다만 이 수치들은 “고사양 통합 메모리”가 있는 환경(특히 Apple Silicon 상위 모델)에서는 납득 가능한데, 일반 PC나 메모리 빡빡한 서버에선 배포 제약이 될 수 있습니다. 결론적으로 이 프로젝트의 강점은 “가벼운 메모리”가 아니라 “가벼운 의존성”에 가깝습니다.
실전에서 바로 써먹는 사용 시나리오: stdin, ffmpeg, 마이크
재미있는 부분은 입출력 쪽이 꽤 현실적이라는 겁니다. 파일 입력만 되는 데모가 아니라 --stdin으로 파이프 입력을 받아서 ffmpeg와 엮으면, 사실상 어떤 포맷이든 “실시간 변환 → 실시간 전사”로 이어붙일 수 있습니다1. macOS에서는 --from-mic로 마이크 캡처도 지원하고, 무음 감지를 통해 무음 구간을 잘라 연산을 줄이는 접근도 들어가 있습니다1.
여기서 제품 아이디어가 바로 나옵니다. “무음 감지 → flush로 중간 확정 → 자막/회의록을 문장 단위로 빠르게 고정” 같은 UX는 사용자가 체감하는 품질을 크게 올려줍니다. STT는 정답률도 중요하지만, 사용자는 종종 ‘흘러나오는 속도’와 ‘확정 타이밍’으로 품질을 판단하니까요.
시사점 내용 (핵심 포인트 정리 + 개인적인 생각 또는 실용적 조언)...
Voxtral Realtime 4B의 Pure C 추론 구현은 “STT를 더 빠르게”라기보다 “STT를 더 배포 가능하게” 만드는 쪽에 무게가 실립니다. 프레임워크 의존성을 줄인 최소 레퍼런스가 생기면, 보안/운영 제약이 큰 조직이나 온디바이스 환경에서 선택지가 늘어납니다1.
실무 팁으로는 세 가지를 권합니다. 첫째, Apple Silicon이라면 MPS 경로를 우선 고려하세요. 둘째, 스트리밍 지연을 줄이겠다고 -I를 과하게 낮추기 전에 오버헤드와 처리량을 반드시 같이 보세요. 셋째, 장시간 전사는 KV 캐시와 메모리 상한이 핵심이니, 프로덕션에 넣기 전 “몇 시간짜리”로 스트레스 테스트를 꼭 해보는 게 안전합니다1.
참고
1순수 C, CPU 전용 추론으로 Mistral Voxtral Realtime 4B 음성-텍스트 변환 모델 사용