Voxtral을 vLLM으로 직접 띄워보기: 설치부터 음성 이해 API 구현까지

Mistral의 오픈웨이트 음성 모델 Voxtral을 vLLM으로 로컬 배포하는 설치 절차와 전사·질의응답·요약을 구현하는 Python 코드, 실무 파이프라인 패턴을 정리한다.

2026-08-14 · 최초 발행 2026-03-29

Mistral AI가 공개한 Voxtral은 Apache 2.0 라이선스로 상업적 이용이 자유로운 오픈웨이트 음성 이해 모델이다. 단순 전사(STT)를 넘어 최대 40분 오디오의 의미 분석과 질의응답까지 단일 모델로 처리하며, vLLM을 통해 로컬 서버 배포도 가능하다. 여기서는 환경 설정부터 Python 코드 구현, 실무 활용 패턴까지 단계별로 정리한다.

어떤 크기를 고를 것인가

Voxtral은 두 가지 크기로 제공되며, 사용 환경에 맞게 선택한다.

모델 파라미터 Hugging Face ID 최소 VRAM 권장 환경
Voxtral Mini 3B mistralai/Voxtral-Mini-3B-2507 9.5GB 개인 GPU, 엣지 서버
Voxtral Small 24B mistralai/Voxtral-Small-24B-2507 48GB+ 클라우드 서버, A100/H100

RTX 3090/4090(24GB)이면 Voxtral Mini를 bf16으로 실행할 수 있고, A100 80GB면 Voxtral Small을 단일 GPU로 돌릴 수 있다. 프로토타이핑이나 테스트 단계라면 Mistral Cloud API(분당 $0.001)를 활용하는 편이 빠르다.

오디오가 들어와서 답이 나오기까지

전사 요청이해 요청대안오디오 입력(mp3/wav/flac)vLLM 서버(OpenAI 호환 API)요청 유형audio.transcriptions.create(최대 30분)chat.completions.create(최대 40분)전사 텍스트 반환질의응답/요약 반환Mistral Cloud API($0.001/분)

환경 설치

사전 요구사항은 Python 3.10+, 로컬 GPU를 쓴다면 CUDA 12.1+, 패키지 매니저는 uv(권장) 또는 pip다.

Voxtral은 vLLM의 audio 지원 빌드가 필요하다. 일반 vLLM과 다른 nightly 빌드를 쓴다.

# uv 사용 (권장 - 의존성 충돌 최소화)
uv pip install -U "vllm[audio]" --torch-backend=auto \
    --extra-index-url https://wheels.vllm.ai/nightly

# pip 사용
pip install -U "vllm[audio]" \
    --extra-index-url https://wheels.vllm.ai/nightly

Voxtral 전용 클라이언트 라이브러리도 함께 설치한다.

pip install mistral-common openai huggingface_hub

모델 다운로드에는 HF 토큰이 필요하다.

# HF 토큰 설정
huggingface-cli login
# 또는 환경변수 설정
export HF_TOKEN=hf_your_token_here

vLLM 서버 띄우기

Voxtral Mini(3B) 서버는 다음과 같이 시작한다.

vllm serve mistralai/Voxtral-Mini-3B-2507 \
    --tokenizer_mode mistral \
    --config_format mistral \
    --load_format mistral

4xA100 40GB 환경에서 Voxtral Small(24B)을 멀티 GPU로 돌리려면 다음과 같다.

# 4xA100 40GB 환경
vllm serve mistralai/Voxtral-Small-24B-2507 \
    --tokenizer_mode mistral \
    --config_format mistral \
    --load_format mistral \
    --tensor-parallel-size 4 \
    --dtype bfloat16

서버가 정상 기동되면 http://localhost:8000/v1에서 OpenAI 호환 API를 제공한다. 서버 헬스와 사용 가능한 모델 목록은 다음으로 확인한다.

# 서버 헬스 체크
curl http://localhost:8000/health

# 사용 가능한 모델 목록 확인
curl http://localhost:8000/v1/models

Python으로 전사·질의응답·요약 구현하기

vLLM이 OpenAI 호환 API를 제공하므로 openai 라이브러리를 그대로 활용한다.

from openai import OpenAI

# 로컬 vLLM 서버
client = OpenAI(
    api_key="EMPTY",          # vLLM은 인증 불필요
    base_url="http://localhost:8000/v1"
)

# Mistral Cloud API 사용 시
# client = OpenAI(
#     api_key="your_mistral_api_key",
#     base_url="https://api.mistral.ai/v1"
# )

음성 전사는 다음과 같이 구현한다.

from mistral_common.audio import Audio
from mistral_common.protocol.transcription.request import TranscriptionRequest
from huggingface_hub import hf_hub_download
from openai import OpenAI

client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")

def transcribe_audio(file_path: str, language: str = "en") -> str:
    """음성 파일을 텍스트로 전사"""
    audio = Audio.from_file(file_path, strict=False)

    req = TranscriptionRequest(
        model="Voxtral-Mini-3B-2507",
        audio=audio,
        language=language,
        temperature=0.0      # 결정론적 출력 (재현성 확보)
    ).to_openai()

    response = client.audio.transcriptions.create(**req)
    return response.text

# 사용 예시
# 샘플 오디오 다운로드 (테스트용)
sample_file = hf_hub_download(
    "patrickvonplaten/audio_samples",
    "obama.mp3",
    repo_type="dataset"
)
result = transcribe_audio(sample_file, language="en")
print(result)

오디오 내용에 대한 질의응답은 다음과 같이 구현한다.

from mistral_common.audio import Audio
from mistral_common.protocol.instruct.messages import (
    AudioChunk, TextChunk, UserMessage
)

def audio_qa(file_path: str, question: str) -> str:
    """오디오 내용에 대한 질의응답"""
    audio = Audio.from_file(file_path, strict=False)

    audio_chunk = AudioChunk.from_audio(audio)
    text_chunk = TextChunk(text=question)
    user_msg = UserMessage(content=[audio_chunk, text_chunk]).to_openai()

    response = client.chat.completions.create(
        model="Voxtral-Mini-3B-2507",
        messages=[user_msg],
        temperature=0.0
    )
    return response.choices[0].message.content

# 사용 예시
answer = audio_qa(
    "meeting_recording.mp3",
    "What were the main action items discussed?"
)
print(answer)

오디오 요약은 질의응답 함수를 재사용해 프롬프트만 바꾸면 된다.

def summarize_audio(file_path: str, summary_length: str = "brief") -> str:
    """오디오 내용 요약"""
    prompts = {
        "brief": "Summarize this audio in 3 bullet points.",
        "detailed": "Provide a detailed summary with key topics and conclusions.",
        "action": "Extract all action items and decisions from this meeting."
    }

    return audio_qa(file_path, prompts.get(summary_length, prompts["brief"]))

# 회의록 자동 생성
summary = summarize_audio("team_meeting.mp3", summary_length="action")
print(summary)

실무에서 쓰는 배치·서비스 패턴

여러 오디오 파일을 비동기로 배치 전사하는 파이프라인은 다음과 같은 흐름을 탄다.

단순 전사요약 필요오디오 파일 목록비동기 요청vLLM 서버Voxtral Mini전사 결과후처리텍스트 파일 저장요약 생성(2차 LLM 호출)요약 + 전사 저장
import asyncio
from pathlib import Path
from openai import AsyncOpenAI

async_client = AsyncOpenAI(
    api_key="EMPTY",
    base_url="http://localhost:8000/v1"
)

async def batch_transcribe(audio_files: list[str]) -> list[dict]:
    """여러 오디오 파일 비동기 배치 전사"""

    async def transcribe_one(file_path: str) -> dict:
        audio = Audio.from_file(file_path, strict=False)
        req = TranscriptionRequest(
            model="Voxtral-Mini-3B-2507",
            audio=audio,
            language="en",
            temperature=0.0
        ).to_openai()

        response = await async_client.audio.transcriptions.create(**req)
        return {"file": file_path, "text": response.text}

    tasks = [transcribe_one(f) for f in audio_files]
    return await asyncio.gather(*tasks)

# 실행
audio_files = list(Path("recordings/").glob("*.mp3"))
results = asyncio.run(batch_transcribe([str(f) for f in audio_files]))

FastAPI로 감싸면 업로드 기반 전사·질의응답 엔드포인트를 손쉽게 서비스화할 수 있다.

from fastapi import FastAPI, UploadFile
import tempfile, os

app = FastAPI()

@app.post("/transcribe")
async def transcribe_endpoint(file: UploadFile, language: str = "en"):
    """음성 파일 업로드 → 전사 결과 반환"""
    with tempfile.NamedTemporaryFile(
        suffix=f".{file.filename.split('.')[-1]}",
        delete=False
    ) as tmp:
        content = await file.read()
        tmp.write(content)
        tmp_path = tmp.name

    try:
        text = transcribe_audio(tmp_path, language=language)
        return {"filename": file.filename, "transcript": text}
    finally:
        os.unlink(tmp_path)

@app.post("/qa")
async def qa_endpoint(file: UploadFile, question: str):
    """음성 파일 + 질문 → 답변 반환"""
    with tempfile.NamedTemporaryFile(
        suffix=f".{file.filename.split('.')[-1]}",
        delete=False
    ) as tmp:
        content = await file.read()
        tmp.write(content)
        tmp_path = tmp.name

    try:
        answer = audio_qa(tmp_path, question)
        return {"question": question, "answer": answer}
    finally:
        os.unlink(tmp_path)

로컬 GPU 없이 바로 써보기

로컬 GPU 환경 없이 즉시 테스트하려면 Mistral Cloud API를 활용한다.

import os
from openai import OpenAI

# Mistral API 클라이언트
cloud_client = OpenAI(
    api_key=os.environ["MISTRAL_API_KEY"],
    base_url="https://api.mistral.ai/v1"
)

def cloud_transcribe(file_path: str) -> str:
    """Mistral Cloud API로 전사 ($0.001/분)"""
    audio = Audio.from_file(file_path, strict=False)
    req = TranscriptionRequest(
        model="voxtral-mini-latest",   # Cloud API 모델명
        audio=audio,
        temperature=0.0
    ).to_openai()

    response = cloud_client.audio.transcriptions.create(**req)
    return response.text

비용 예시로는 1시간 회의 녹음 전사가 $0.06(6센트), 하루 100건 × 평균 30분이면 $3.00 수준이다.

어떤 모델과 비교하면 좋은가

WER 성능 비교 (낮을수록 좋음)개선추가 개선Whisper large-v3기준값Voxtral Mini기준값 이하Voxtral Small최저 WER
벤치마크 Whisper large-v3 GPT-4o Mini Voxtral Mini Voxtral Small
Mozilla Common Voice 기준 비슷 우수 최우수
FLEURS (다국어) 기준 비슷 우수 최우수
Multilingual LibriSpeech 기준 비슷 우수 최우수
음성 이해 (QA) 미지원 지원 지원 지원
최대 오디오 길이 ~10분 제한적 40분 40분

지원 언어와 한국어 우회 방법

현재 지원 언어는 영어, 프랑스어, 독일어, 스페인어, 포르투갈어, 힌디어, 네덜란드어, 이탈리아어를 비롯한 다수다.

한국어 음성 처리가 필요하면 Whisper로 먼저 전사한 뒤 Voxtral 텍스트 모드로 넘기는 하이브리드 방식을 쓴다.

# 방법 1: Whisper로 한국어 전사 → Voxtral로 이해
def korean_audio_qa(file_path: str, question: str) -> str:
    # Step 1: Whisper로 한국어 전사
    import whisper
    model = whisper.load_model("large-v3")
    korean_text = model.transcribe(file_path, language="ko")["text"]

    # Step 2: Voxtral(텍스트 모드)로 질의응답
    response = client.chat.completions.create(
        model="Voxtral-Mini-3B-2507",
        messages=[{
            "role": "user",
            "content": f"다음 회의 내용을 분석해줘:\n{korean_text}\n\n질문: {question}"
        }]
    )
    return response.choices[0].message.content

향후 지원 예정 기능으로는 화자 분할(Speaker Diarization), 단어 수준 타임스탬프, 감정·연령 기반 오디오 태그, 비음성 오디오 인식이 있다.

막히면 여기부터 확인한다

증상 원인 해결책
CUDA OOM VRAM 부족 --dtype float16 또는 Mini 모델 사용
모델 로딩 실패 --load_format 누락 --load_format mistral 옵션 추가
토크나이저 오류 기본 토크나이저 사용 --tokenizer_mode mistral 추가
느린 첫 응답 모델 워밍업 서버 시작 후 1-2분 대기
strict=False 경고 오디오 포맷 불일치 정상 동작, 무시 가능

Voxtral은 오픈소스 음성 AI 생태계에서 가장 실용적인 선택지 중 하나로 자리잡았다. vLLM과의 통합으로 로컬 배포가 용이하고, OpenAI 호환 API 덕분에 기존 STT 파이프라인을 최소 변경으로 마이그레이션할 수 있다. Mini(3B) 모델은 9.5GB VRAM만으로 실행 가능해 RTX 3090/4090 환경에서도 충분히 프로덕션 활용이 가능하며, Apache 2.0 라이선스로 상업적 서비스에도 자유롭게 적용할 수 있다. 현재 한국어 미지원은 아쉬운 점이지만, Whisper와의 하이브리드 파이프라인으로 충분히 대응 가능하다.

VoxtralvLLM음성이해MistralAI오픈웨이트