Local speech with llamadart: giving Llama Village a voice
llamadart로 구현하는 로컬 음성 대화: Llama Village에 목소리 더하기
llamadart와 Qwen 모델을 조합해 음성을 인식하고 대화 응답을 생성한 뒤 음성으로 합성하는 macOS 명령줄 예제를 만들었습니다. 3B 모델 조합과 더 작은 모델 조합을 비교한 결과, 작은 조합은 다운로드 용량과 메모리를 줄였지만 이 테스트에서는 전체 응답 시간이 더 길었습니다.
- 주제
에디터 노트
작은 모델 조합의 장점은 저장 공간과 메모리 절감으로 확인됐지만, 완성된 음성 응답이 더 빨라지지는 않았습니다. 대화 생성만 떼어 보면 0.8B 모델이 빨랐어도 TTS 시간과 답변 길이까지 합치면 전체 턴은 5.83초로 3B 조합의 4.27초보다 길었습니다. 작은 모델이 한 문장 지시를 어긴 결과도 시간을 비교할 때 응답 내용이 같지 않다는 점을 보여줍니다. 캐릭터가 알 수 없는 사실을 만들어낸 사례는 음성 인식보다 대화 계층의 제약을 드러냅니다. 글은 허용된 의도를 먼저 고르는 게임 로직을 제안하며, 댓글은 전사와 후속 동작을 연결해 실패 지점을 추적하자는 보완점을 냅니다. 단일 합성 질문과 개발용 Mac에서 나온 수치는 기기 성능이나 실제 대화의 지연을 대표하지 않지만, 모델 크기만으로 체감 속도를 판단하기 어렵다는 점은 측정 조건 안에서 분명하게 드러납니다.
AI 요약
llamadart를 활용하는 연재의 세 번째 글입니다. 게임 속 캐릭터 Dash가 음성으로 Mo에게 Pip의 빨간 스카프를 찾았는지 묻고, Mo가 연못 옆 갈대밭에 있다고 답하는 상황을 로컬 모델로 구성했습니다. 다만 실행 환경은 아직 Llama Village 게임 안이 아닙니다. macOS에서 실행하는 명령줄 프로그램이며 질문 음성도 마이크 녹음이 아니라 합성 파일입니다. 게임에는 아직 음성 조작 기능이 없습니다.
두 가지 음성 처리 경로
텍스트 경로는 Qwen3-ASR이 음성을 문자로 바꾸고, 대화 모델이 답한 뒤 Qwen3-TTS가 음성을 합성합니다. 전사 결과가 남기 때문에 캐릭터 이름 Pip을 잘못 알아들었는지 확인하기 쉽습니다. 직접 오디오 경로는 Qwen2.5-Omni에 오디오를 전달해 답변을 생성합니다. 이 경로에는 별도 전사 결과가 없으며, 사용한 GGUF 변환은 오디오 입력만 지원하고 음성 생성은 지원하지 않아 답변에는 별도의 TTS를 사용합니다. 두 경로 비교에는 같은 3B 대화 모델을 썼습니다. 텍스트 경로에서는 문자 입력을 받고 직접 오디오 경로에서는 오디오 projector도 불러옵니다. 글은 이 설정을 테스트한 결과로 한정하며 모든 멀티모달 모델에 별도 TTS가 필요하다고 주장하지 않습니다.
모델과 실행 방식
기본 설정은 Qwen3-ASR 0.6B, Qwen3.5 0.8B 대화 모델, Qwen3-TTS 0.6B Base입니다. staged 모드는 기존 Qwen2.5-Omni 3B와 TTS 1.7B 조합을 비교용으로 불러옵니다. audio 모드는 ASR을 건너뛰고 Omni에 오디오를 직접 전달합니다. Dart 3.10.7 이상과 macOS가 필요하며, 작은 모델 구성의 다섯 파일은 약 2.31GB입니다. 모델 파일은 llamadart의 다운로드 관리자가 내려받고 캐시합니다. 첫 실행에는 네트워크가 필요하고 이후 생성은 내려받은 모델을 로컬에서 처리합니다. 예제는 macOS의 say, afconvert, afplay 명령을 쓰므로 휴대전화나 브라우저 데모는 아닙니다.
프로그램은 엔진을 한 번에 하나씩 불러오고 사용이 끝나면 dispose()로 해제합니다. 입력 WAV를 ASR에 전달할 때는 Mo, Pip, Dash, Llama Village를 문맥으로 줍니다. 대화 프롬프트에는 Mo의 역할과 스카프 위치 같은 사실을 넣고 한 문장으로 답하도록 지시합니다. TTS에는 답변 문장과 언어를 전달해 reply.wav를 만듭니다. 선택적으로 음성 참조 파일을 전달하면 같은 TTS 모델로 캐릭터별 목소리를 만들 수 있습니다. 다만 테스트한 Base 모델 API에는 긴장된 목소리처럼 말투를 지시하는 기능이 없습니다. 이를 지원하려면 해당 모드가 있는 모델과 네이티브 런타임, Dart API가 필요합니다.
측정 결과와 한계
M4 Max, 메모리 64GB, llamadart 0.11.0 환경에서 텍스트 경로를 비교했습니다. 3B 대화 모델과 1.7B TTS 조합은 모델 파일 4.61GB, 전체 응답 중앙값 4.27초, 최대 프로세스 RSS 3.86GiB였습니다. 0.8B 대화 모델과 0.6B TTS 조합은 2.31GB, 5.83초, 2.51GiB였습니다. 작은 모델은 다운로드 용량이 절반 수준이고 최대 RSS도 약 35% 낮았지만, 완성된 응답 한 번에 걸리는 시간은 더 길었습니다. 측정에는 모델을 불러오고 해제하는 시간이 포함됐으며 프로세스 시작, 다운로드, 녹음, 재생은 제외됐습니다. 각 조합에서 첫 실행을 버리고 새 프로세스로 세 차례 측정했습니다.
구성 요소별로 보면 대화 생성 시간은 Omni 3B가 중앙값 171ms, Qwen3.5 0.8B가 96ms였습니다. TTS는 두 모델 모두 중앙값 1.78초였지만 0.6B 모델 음성은 4.80~6.48초로 1.7B 모델의 4.16~4.56초보다 길었습니다. 작은 대화 모델은 한 문장으로 답하라는 지시를 어기고 더 긴 문장을 만들기도 했습니다. 두 모델 모두 스카프의 위치와 색을 답했지만 누가 스카프를 두었냐는 질문에는 제공되지 않은 사실을 만들어 Pip을 지목했습니다. 음성 출력 여섯 개가 의도한 문장으로 다시 전사됐다는 결과는 발화 내용을 알아들을 수 있는지 확인한 것이며 자연스러움이나 감정 표현, 음성 유사도 평가는 아닙니다.
게임에 적용할 때
대화 프롬프트만으로 캐릭터가 공개할 수 있는 사실을 통제하지 말고, 대화 생성 전에 DecisionEngine이 허용된 의도를 고르는 방식을 제안합니다. 첫 게임 버전에는 누르고 말하기를 쓰고 Mo가 말하는 동안 녹음을 멈추며, 새 입력이 오면 이전 응답을 버리는 흐름을 제시합니다. TTS가 끝날 때까지 기다리는 동안에는 텍스트 응답을 먼저 보여주는 편이 낫다고 설명합니다. 음성 기능을 끄거나 모델을 불러오지 못해도 텍스트를 제공해야 합니다. 음성 합성 진행 이벤트는 재생 가능한 오디오 청크가 아니므로 완성된 파일을 받은 뒤 재생해야 합니다.
dev.to 반응
- @alexshev — 단계형 경로에서 전사 결과를 남기는 것은 디버깅에 유용합니다. 특히 고유명사를 잘못 알아들었을 때 그렇습니다. 오디오 구간, 전사 토큰, 이후 실행된 동작을 연결하는 가벼운 기록을 남기면 잘못된 캐릭터 응답의 원인이 ASR인지 대화 생성인지 TTS인지 구분하기 쉬워집니다. 전체 과정을 불투명한 하나의 실패로 취급하지 않아도 됩니다.
원문: dev.to / 번역·요약: Trawling