Articleaws.amazon.com·2026년 10월 22일·0

Stream low-latency speech from AI agents using Kokoro TTS on Amazon SageMaker AI

Quick Summary

Amazon SageMaker AI에 배포한 Kokoro 82M으로 에이전트가 생성한 문장부터 순차 합성하고, 지속적인 양방향 연결과 적절한 오디오 전송 방식으로 전체 응답을 기다리는 음성 지연을 줄이는 구현을 설명한다.

Stream low-latency speech from AI agents using Kokoro TTS on Amazon SageMaker AI 관련 대표 이미지

🖼️ 인포그래픽

Stream low-latency speech from AI agents using Kokoro TTS on Amazon SageMaker AI 내용을 설명하는 본문 이미지

🖼️ 4컷 인포그래픽

Stream low-latency speech from AI agents using Kokoro TTS on Amazon SageMaker AI의 핵심 내용을 4단계로 요약한 인포그래픽
Stream low-latency speech from AI agents using Kokoro TTS on Amazon SageMaker AI 핵심 내용을 4단계로 압축한 4컷 인포그래픽

💡 한 줄 요약

Amazon SageMaker AI에 배포한 Kokoro-82M으로 에이전트가 생성한 문장부터 순차 합성하고, 지속적인 양방향 연결과 적절한 오디오 전송 방식으로 전체 응답을 기다리는 음성 지연을 줄이는 구현을 설명한다.

📌 핵심 요약

  • 전체 응답 생성 후 음성 합성을 시작하면 첫 문장이 600밀리초에 준비되어도 5초를 기다릴 수 있다. 제안 방식은 8,200만 파라미터의 Kokoro-82M을 Amazon SageMaker AI의 ml.g5.xlarge에서 실행하고, 문장이 완성되는 즉시 합성을 시작한다.
  • SageMaker AI의 양방향 전송은 컨테이너에 바이너리 WebSocket 프레임을 전달하므로 텍스트와 바이너리를 모두 처리해야 한다. 각 문장의 synthesis_complete 이후에도 연결을 유지하고 명시적인 close 메시지가 있을 때 종료한다.
  • IncrementalSentenceChunker는 입력 조각에서 완성된 문장을 추출하고 flush()로 마지막 잔여 텍스트를 보낸다. 합성은 세션 안에서 순차 실행되며, 실시간 엔드포인트에서 세 문장의 오디오 반환을 확인했지만 전체 응답을 기다리는 방식 대비 첫 오디오 지연 감소량은 제시되지 않았다.
  • 24개 클립 비교에서 원시 PCM·MP3·AAC를 base64로 인코딩하면 페이로드가 약 33% 증가했고, Amazon Transcribe를 통한 확인에서는 인코딩 간 품질 차이가 나타나지 않았다. 기존 SSE를 재사용하려면 버퍼링과 재조립이 필요하며, 새 음성 경로나 지연 우선 환경에는 원시 바이너리 오디오와 JSON 제어 프레임을 함께 보내는 양방향 방식이 제안된다.
  • 배포에는 AWS 권한, AWS CDK v2, Python 3.12 이상, 양방향 스트리밍을 지원하는 컨테이너와 SDK 등이 필요하다. 샘플이 고정한 이전 PyTorch에는 PYSEC-2026-2286이 있으므로 개념 검증을 넘어 사용하기 전에 torch 2.10.0에서 수정된 취약점을 검토하고 버전을 업그레이드해야 하며, 제공된 원문은 실제 에이전트 통합을 다루는 Step 5 도입부에서 끊긴다.

🧩 주요 포인트

  1. 첫 문장 준비와 전체 응답 완성 사이의 대기 제거 → 핵심 효과는 합성의 동시 실행이 아니라 합성 시작 시점의 앞당김이며, 실제 첫 오디오 지연 개선 폭은 별도 확인이 필요하다.
  2. 바이너리 프레임 수신과 synthesis_complete 이후 연결 유지 → 엔드포인트의 InService 상태만으로 정상 동작을 판단할 수 없고, 프레임 처리와 문장 간 세션 지속성이 구현의 핵심 조건이다.
  3. 기존 SSE 재사용과 원시 바이너리·JSON 양방향 전송의 선택 → 약 33%의 base64 페이로드 증가 및 재조립 부담을 기존 인프라 재사용의 이점과 비교해야 한다.

🧠 상세 정리

1. 전체 응답을 기다리는 음성 지연과 문장 단위 합성

글은 AI 음성 에이전트가 이미 답변을 생성하고 있는데도 사용자는 5초 동안 아무 소리도 듣지 못하는 상황에서 출발한다. 한 문장은 600밀리초에 준비될 수 있지만, 통합 방식이 전체 응답 완성을 기다리면 음성 합성도 그만큼 늦게 시작된다는 설명이다. 해결책은 에이전트가 문장을 완성하는 즉시 Kokoro-82M에 전달해, 뒤쪽 문장이 생성되는 동안 앞쪽 문장의 합성을 시작하는 것이다. Kokoro-82M은 8,200만 파라미터 모델이며, 글은 중간급 GPU 인스턴스 하나에서 실행할 수 있는 크기를 낮은 추론 비용과 지연의 근거로 제시한다. 모델은 Amazon SageMaker AI의 ml.g5.xlarge 실시간 엔드포인트에 사용자 정의 컨테이너로 배포되며, 완성된 입력 하나의 응답을 스트리밍하는 POST /invocations와 여러 메시지를 양방향으로 주고받는 WS /invocations-bidirectional-stream 경로를 제공한다.

2. 배포 선행 조건과 엔드포인트·에이전트 구성

사전 조건에는 SageMaker AI 엔드포인트, Amazon Bedrock AgentCore 런타임, IAM 역할, Amazon ECR 저장소를 생성할 수 있는 AWS 계정 권한이 포함된다. 최소 권한으로 sagemaker:CreateEndpoint, sagemaker:CreateEndpointConfig, sagemaker:CreateModel, sagemaker:InvokeEndpointWithBidirectionalStream, bedrock:InvokeModelWithResponseStream이 제시된다. 대상 계정과 리전에 설치·부트스트랩된 AWS CDK v2, uv, Docker, 양방향 호출을 지원하는 Boto3 및 WebSocket·Starlette 또는 유사 ASGI 프레임워크에 대한 이해도 필요하다. 기반 HTTP/2 클라이언트의 사전 빌드 휠이 Python 3.12 이상에만 제공되므로 해당 버전 조건을 충족해야 하며, 컨테이너에는 com.amazonaws.sagemaker.capabilities.bidirectional-streaming=true Docker 라벨이 필요하다. 샘플이 고정한 이전 PyTorch에는 PYSEC-2026-2286이 있고 torch 2.10.0에서 수정되었으므로, 개념 검증을 넘어 사용하기 전 고정 버전을 검토하고 업그레이드해야 한다. 먼저 uv run speech-infra deploy kokoro-82m으로 IAM 역할, Amazon S3 모델 가중치 캐시, 컨테이너 이미지와 엔드포인트를 구성한 뒤, uv run agent-infra deploy --bedrock-model-id <your-bedrock-model-id>로 해당 엔드포인트를 호출하는 Strands 에이전트의 AgentCore 런타임을 배포한다.

3. 바이너리 WebSocket 프레임으로 인한 숨은 실패

SageMaker AI의 양방향 전송은 클라이언트 메시지를 컨테이너에 바이너리 WebSocket 프레임으로 전달하므로, 텍스트 프레임만 가정하는 수신 코드는 모든 요청에서 실패할 수 있다. Starlette의 receive_text()는 메시지의 text 키를 읽는데, 이 키가 없으면 KeyError가 발생한다. 일반 예외 처리기가 str(e)를 그대로 전달하면 클라이언트에는 'text'라는 오류만 보이므로, 프레임 형식 문제를 입력 필드 누락으로 오해하기 쉽다. 엔드포인트는 연결을 수락하고 매번 오류 응답도 반환하므로 InService 상태를 유지하며, 상태 표시만으로는 이 실패를 발견하기 어렵다. 제시된 처리는 receive()로 메시지를 받은 뒤 연결 해제를 구분하고, text가 없으면 bytes를 UTF-8로 디코딩하는 방식이며, 정상 형식의 오류 응답 뒤에 문제가 숨을 수 있으므로 바이너리 프레임을 실제로 보내 검증해야 한다.

4. 문장 사이에 연결을 유지하는 세션 수명

한 메시지의 음성 합성이 끝났다고 HTTP 요청처럼 세션을 종료하면, 문장마다 연결을 다시 만들어야 하므로 지연 감소 효과의 상당 부분을 잃게 된다. 컨테이너는 해당 메시지의 오디오와 synthesis_complete 제어 프레임을 보낸 후, 같은 연결에서 다시 receive()를 기다려야 한다. 연결 종료는 클라이언트가 type 값이 close인 명시적 메시지를 보냈을 때 수행하는 구조로 설명된다. 이 방식에서는 여러 문장을 하나의 지속 세션으로 차례로 전달할 수 있고, 문장 사이에 재연결 비용을 지불하지 않는다. 따라서 synthesis_complete는 한 메시지의 합성 완료를 뜻하며, 여러 메시지를 처리하는 양방향 세션 전체의 종료와 구분해야 한다.

5. 문장 추출, 순차 합성의 한계와 실시간 검증

IncrementalSentenceChunker는 처음부터 완성된 문자열과 실시간 토큰 스트림을 같은 방식으로 처리해, 전송 가능한 문장 단위 입력을 만든다. feed()는 조각을 버퍼에 누적하고 마침표·느낌표·물음표 뒤의 공백을 기준으로 완성된 부분을 반환하며, 아직 끝나지 않은 텍스트는 보관한다. 입력 종료 시 flush()를 한 번 호출하면 끝에 문장부호가 없는 잔여 텍스트도 전달하므로 마지막 부분이 누락되는 것을 막는다. 다만 양방향 핸들러는 메시지 수신, 합성, 오디오 전송, synthesis_complete 전송을 순서대로 수행하므로 첫 문장의 오디오를 보내는 동안 둘째 문장을 동시에 합성하지는 않는다. 이 방식의 이점은 전체 응답 완성을 기다리지 않고 첫 문장이 준비된 순간 합성을 시작한다는 데 있으며, 실시간 엔드포인트에서 하나의 세션으로 세 문장을 보내 각 입력에 맞는 오디오가 반환되는 것을 확인했다. 검증 결과는 “Hello there.”가 73,200바이트·1.525초, “This is a post deployment check.”가 108,000바이트·2.250초, “It has three sentences.”가 98,400바이트·2.050초였으며, 이 수치는 반환된 오디오의 크기와 길이로 제시되었을 뿐 첫 오디오 도착 지연의 비교 측정값은 아니다.

6. SSE로 오디오를 보낼 때의 인코딩 비용

문장을 모델에 점진적으로 보내는 것과 별개로, 생성된 오디오를 브라우저 등 재생 클라이언트까지 전달하는 경로도 필요하다. 기존 에이전트·채팅 프런트엔드는 텍스트를 단방향 SSE로 전송하는 경우가 많아, 오디오를 base64로 인코딩해 JSON 이벤트에 넣는 접근을 자연스럽게 선택할 수 있다. 글은 24개 클립에서 원시 PCM, MP3 48k, AAC 32k fMP4를 비교하고 Amazon Transcribe를 통해 인코딩 간 품질 차이가 나타나지 않았다고 설명한다. 원시 PCM은 2,396,400바이트에서 base64 적용 후 3,195,200바이트로 늘었고 비트레이트는 384kbps였으며, MP3 48k는 308,304바이트에서 411,072바이트로 늘고 측정 비트레이트는 49.5kbps였다. AAC 32k fMP4는 258,039바이트에서 344,084바이트로 증가했고 측정 비트레이트는 41.6kbps였으며, 모든 경우 base64가 페이로드를 예상 수준인 약 33% 늘렸다.

7. 전송 청크와 이벤트 경계의 불일치 처리

SSE를 사용할 때는 base64 크기 증가 외에도 이벤트 경계, 오디오 프레임 경계, 실제 전송 청크 경계가 서로 일치하지 않는 문제를 처리해야 한다. SSE 이벤트는 빈 줄로 구분되지만 내부 오디오는 MP3 프레임으로 나뉘며, 전송 계층이 데이터를 전달하는 단위는 이 두 경계에 맞춰진다고 보장되지 않는다. 실제 측정에서는 논리적인 MP3 프레임 네 개가 전달 청크 여섯 개로 도착했고, 그중 두 청크는 프레임 중간에서 끝났다. 따라서 전달받은 청크 하나를 완성된 처리 단위로 간주하는 소비자는 정상 트래픽에서도 실패할 수 있다. 제시된 코드는 PayloadPart의 바이트를 버퍼에 누적한 뒤 두 개의 줄바꿈으로 된 SSE 구분자가 발견될 때만 완성된 이벤트를 꺼내 처리하고, 남은 바이트는 다음 수신까지 보관한다.

8. 바이너리·JSON 통합 전송의 선택 기준과 원문 범위

대안은 문장 입력을 전달하는 기존 양방향 세션에서 원시 바이너리 오디오와 JSON 제어 프레임을 함께 보내는 것으로, 글은 현재 모델과 통신하는 구간에 이 방식을 사용한다고 설명한다. 수신 청크 전체를 UTF-8로 디코딩하고 JSON 객체로 파싱할 수 있으면 제어 프레임으로 처리하며, 그렇지 않으면 오디오로 그대로 전달하므로 base64 인코딩 단계가 필요하지 않다. 원시 PCM도 JSON 객체의 시작 문자와 같은 0x7b 바이트로 시작할 수 있지만, 첫 바이트가 아니라 청크 전체의 유효한 JSON 객체 파싱을 요구해 오분류를 피하며, 글은 PCM 프레임 전체가 이 조건을 만족할 가능성을 사실상 없다고 설명한다. 이미 텍스트용 SSE가 정상 작동한다면 약 33%의 추가 페이로드와 버퍼링·재조립 비용을 감수하고 기존 인프라를 재사용하는 것이 실용적인 선택으로 제시된다. 반대로 음성 경로를 새로 만들거나 지연이 우선이면 바이너리·JSON 통합 방식이 두 비용을 피하면서 문장 입력에 쓰는 연결도 재사용할 수 있다. 이어지는 Step 5는 토큰을 하나씩 생성하는 실제 에이전트 통합을 소개하기 시작하지만, 제공된 원문이 문장 중간에서 끊겨 구체적인 통합 구현과 후속 결과는 확인할 수 없다.

🧾 핵심 주장 / 시사점

  • 지연 개선의 핵심은 Kokoro-82M의 크기만이 아니라 에이전트의 문장 생성과 합성 시작을 연결하는 방식에 있다. 세션 내부 합성은 순차적이므로 이를 동시 합성에 따른 처리량 증가로 해석해서는 안 된다.
  • 정상적인 InService 상태와 형식에 맞는 오류 응답이 함께 존재할 수 있으므로, 서비스 상태와 실제 바이너리 프레임 처리 성공 여부를 구분해야 한다.
  • 전송 방식은 기존 SSE 인프라의 재사용 가치와 base64의 약 33% 페이로드 증가·재조립 부담 사이의 선택이다. 제공된 측정은 데이터 크기와 오디오 반환을 보여주지만, 전체 시스템의 첫 오디오 지연 개선 폭까지 입증하지는 않는다.

✅ 액션 아이템

  • 개념 검증을 넘어 사용하기 전 PyTorch 고정 버전과 PYSEC-2026-2286을 검토하고, torch 2.10.0에서의 수정 사항을 반영한 업그레이드 진행.
  • 바이너리 WebSocket 프레임 수신과 synthesis_complete 이후 연결 유지를 검증하고, 문장 단위 전송의 첫 오디오 지연 개선 폭 확인.
  • 기존 SSE 재사용 필요성과 약 33%의 base64 페이로드 증가·재조립 부담을 비교해 원시 바이너리·JSON 양방향 전송 적용 여부 결정.

❓ 열린 질문

  • 전체 응답을 기다리는 방식과 비교할 때, 문장 단위 전송은 실제 첫 오디오 지연을 얼마나 줄이는가?
  • PYSEC-2026-2286을 해소하도록 PyTorch를 업그레이드한 뒤에도 Kokoro-82M의 합성과 양방향 스트리밍이 정상 동작하는가?
  • 대상 프런트엔드에서는 기존 SSE 재사용의 이점이 약 33%의 base64 페이로드 증가와 재조립 부담보다 큰가?

관련 문서

공통 태그와 주제 흐름을 기준으로 같이 보면 좋은 문서를 이어서 제안합니다.