
브라우저 완전 로컬 AI 음악 생성 구현: WebGPU/ONNX를 통한 안정적 구동 및 장기 음성 접합 실전 해설
요약
WebGPU와 ONNX Runtime을 활용하여 브라우저 환경에서 Stable Audio 모델을 로컬로 구동하는 기술적 구현 방법을 다룹니다. 메모리 관리, 모델 분할 다운로드, 샘플러 최적화 및 장기 음성 접합 기술 등 실전적인 해결책을 제시합니다.
핵심 포인트
- WebGPU 환경에서 메모리 폭발 방지를 위한 GPU 세션 직렬화 초기화
- 브라우저 타임아웃 방지를 위한 100MB 단위 모델 가중치 분할
- 양자화보다 샘플러 결함이 품질에 더 큰 영향을 미침
- 장기 음성 생성을 위한 단기 추론 및 스마트 루프 접합 기술 적용
- 4bit 양자화를 통한 다운로드 용량 및 메모리 소비 절감
WebGPU와 ONNX Runtime의 진화로 인해, 서버리스(Serverless) 브라우저 전용 AI 음악 생성이 실용적으로 변했습니다.
하지만 실제로 Stable Audio 모델을 브라우저에 도입하며 알게 된 점은,
"모델을 구동하는 것 자체는 쉽다"
"정말로 어려운 것은 브라우저 환경 특유의 메모리·캐시·샘플러·장기 처리의 조정이다"
라는 점입니다.
본 기사에서는 오픈 소스 영상 에디터인 Timeline Studio에 구현한, 브라우저 완전 로컬 AI 음악 생성의 기술적 상세 내용, 시행착오 포인트, 해결책을 정리합니다.
GitHub: https://github.com/martindelophy/ai-video-editor
-
WebGPU / 브라우저 단말 AI 개발에 관심이 있는 분
-
ONNX 모델의 프론트엔드 구현을 배우고 싶은 분
-
음성 Diffusion 모델의 샘플러(Sampler) 설계 및 품질 개선을 알고 싶은 분
-
장기 음성의 심리스(Seamless) 접합 기술을 조사 중인 분
-
브라우저 AI 음악의 품질 문제는 양자화(Quantization)보다 샘플러 결함이 압도적으로 많음
-
대규모 모델은 다운로드 병렬화·GPU 초기화 직렬화가 철칙
-
장기 음성은 완전히 추론하지 않고 단기 추론 + 스마트 루프(Smart Loop) 접합으로 안정화
-
브라우저 캐시는 쓰기 권한을 단일화하지 않으면 Quota 에러가 발생
-
본 프로덕션 운영에는 모델 버전 고정이 필수
이번에 채택한 것은 Stable Audio 3 Small Music의 Q4 양자화 ONNX 모델입니다.
전체 크기는 683MB로, M1 16GB와 같은 일반적인 PC에서 브라우저 추론이 가능합니다.
모델은 4개의 모듈로 나뉘어 있습니다.
| 모듈 | 역할 | 크기 |
|---|---|---|
| Text Encoder | 프롬프트를 조건 벡터로 인코딩 | 213MB |
| ... |
100MB 이하로 파일 분할
단일 거대 파일은 브라우저의 타임아웃 및 파싱 실패를 유발하기 때문에 가중치를 분할하고 있습니다.
4bit 양자화 (Q4)
- 행렬 연산:
MatMul → MatMulNBits - 임베딩 층:
Embedding → GatherBlockQuantized - 기타 파라미터는 FP32 유지
양자화를 통해 다운로드 용량과 메모리 소비를 대폭 절감했습니다. 트레이드오프(Trade-off)로서 고주파·잔향 꼬리 부분에 약간의 입자 노이즈가 발생하지만, 브라우저 로컬 실행 범위 내에서 허용 가능한 수준입니다.
다수의 모델 조각을 Promise.all로 일괄 취득하여 최초 구동 시간을 단축하고 있습니다.
const responses = await Promise.all(
paths.map(path => fetchModelFile(path))
);
WebGPU는 여러 개의 대규모 세션을 동시에 초기화하면 메모리 폭발이 일어납니다.
특히 Apple Silicon의 통합 메모리 환경에서는 높은 확률로 크래시(Crash)가 발생합니다.
따라서 초기화 순서를 고정하고 있습니다.
Text Encoder → Number Conditioner → DiT → Audio Decoder
네트워크 I/O는 병렬로, GPU 리소스 초기화는 직렬로 진행합니다.
또한, 한 번 초기화한 WebGPU 세션은 유지하여, 두 번째 이후의 생성은 모델 로드 없이 빠르게 실행할 수 있습니다.
Stable Audio는 영어 프롬프트 전용이기 때문에, 일본어 입력을 지원하는 독자적인 플로우를 구현했습니다.
사용자 일본어 입력
→ 언어 판정
→ 브라우저 번역
...
곡풍·분위기·BPM·악기·보컬 없음 제약을 자동으로 부여하여 생성 안정도를 높이고 있습니다.
입력: 비 오는 카페에서 듣는 우울한 피아노 곡
출력:
melancholic jazz piano in a rainy café,
cinematic soundtrack, dreamy, piano,
90 BPM, instrumental music,
...
번역을 지원하지 않는 브라우저에서는 폴백(Fallback) 표시를 수행하여, 부적절한 프롬프트로 인한 암묵적인 실패를 방지하고 있습니다.
초기 구현에서는 "고음 노이즈·악기 흐릿함·리듬 붕괴"가 발생하여, 처음에는 Q4 양자화 때문이라고 판단했습니다.
하지만 진짜 원인은 Rectified Flow 샘플러의 스케줄 구현 실수였습니다.
시간 파라미터 t는 1 → 0까지 완전히 감쇠해야 합니다.
이전 구현에서는 t가 0.27까지만 내려갔기 때문에, 최종 잠재 변수 (Latent Variable)에 27%의 노이즈가 잔류하여 음질이 붕괴되었습니다.
const logSnr = 2 - t * 8.2;
const sigma = 1 / (1 + Math.exp(logSnr));
schedule[0] = 1;
...
이 수정을 통해 샘플링 (Sampling) 횟수를 늘리는 것보다 훨씬 큰 폭으로 음질이 개선되었습니다.
단말기 AI의 품질 불량은 양자화 (Quantization) 보다 전처리 및 샘플러 (Sampler) 로직의 결함인 경우가 압도적으로 많습니다.
음성 길이에 따라 잠재 길이 (Latent Length)를 동적으로 계산합니다.
latentLength =
Math.ceil((seconds + 6) * 44100 / 8192) * 2;
DiT 출력 후, 디코더 (Decoder)에서 출력되는 형식은 다음과 같습니다.
-
형태:
[1, 2, audioFrames] -
포맷: Float32
-
범위: -1 ~ 1
-
샘플링 레이트 (Sampling Rate): 44100Hz
브라우저 상에서 16bit PCM으로 변환하여 WAV를 생성합니다.
sample16 = sample < 0 ? sample * 32768 : sample * 32767;
생성 후에는 자동으로 에셋 등록, 파형 분석, 타임라인 등록까지 수행되어 에디터 워크플로우 (Editor Workflow)에 직결됩니다.
120초와 같은 장기 음성을 **전량 추론 (Full Inference)**하면, WebGPU 버퍼 확보 실패, 메모리 초과, 탭 크래시 (Tab Crash)가 발생합니다.
따라서 다음과 같은 전략을 채택하고 있습니다.
- 90초 지정 → 45초 추론 + 루프 접합 (Loop Stitching)
- 120초 지정 → 60초 추론 + 루프 접합 (Loop Stitching)
추론 비용을 절반으로 줄여 브라우저의 부하를 대폭 낮추고 있습니다.
단순한 루프 재생은 음량 점프, 위상 어긋남, 드럼 끊김, 폭음이 발생합니다.
이를 해결하기 위해 마지막 5초의 파형을 분석하여, 복합 스코어 (Composite Score)로 최적의 절단점을 선정합니다.
score =
rms * 0.7
+ amplitudeJump * 0.8
...
- RMS: 저에너지 구간 우선
- 진폭 변화 (Amplitude Change): 음량 점프 억제
- 기울기 변화 (Slope Change): 파형의 불연속성 억제
- 단축 페널티 (Shortening Penalty): 부자연스러운 급작스러운 끊김 방지
또한 페이드 시간 (Fade Time)을 동적으로 조정합니다 (0.25s~1.5s).
fadeSeconds = clamp(
0.25 + localRms * 4,
0.25,
...
고음량 구간은 길게 페이드 처리하고, 무음 구간은 짧게 처리하여 자연스러운 루프를 구현합니다.
683MB의 모델을 매번 다운로드하면 실용적이지 않기 때문에, Service Worker + Cache Storage를 통한 영구 캐시를 구현했습니다.
-
캐시 히트(Cache Hit)임에도 다운로드 표시가 뜨는 문제
크로스 오리진 요청 (Cross-Origin Request) 시 커스텀 헤더가 유실되는 문제. 응답 헤더 판정을 폐지하고, Worker에서 직접 Cache Storage를 조회하는 방식으로 변경했습니다. -
QuotaExceededError (용량 초과)
Worker와 Service Worker의 이중 쓰기로 인한 일시적인 용량 초과를 방지하기 위해, 쓰기 권한을 Service Worker로 일원화했습니다.
대규모 모델 캐시는 "쓰기 주체를 하나로 만드는 것"이 안정화의 핵심입니다.
외부 HuggingFace 리포지토리 의존은 삭제, 덮어쓰기, 업데이트로 인한 갑작스러운 장애가 발생할 수 있습니다.
본 프로덕션 환경에서는 **완전 미러링(Mirroring) + 고정 커밋(Fixed Commit)**으로 안정성을 확보하고 있습니다.
-
미러링 대상:
haixin/stable-audio-3-small-music-onnx -
고정 커밋:
0b8a05e0bc3511e674b4cb3413d3ef6c48880cdb
모든 파일에 대해 SHA256 검증을 수행하며, 오래된 캐시와의 호환성도 유지합니다.
한계점
- Q4 양자화로 인한 미세한 음질 저하
- 장기 음성은 루프 합성 방식에 의존
- WebGPU 환경 의존성
장점
- 프롬프트 및 음성 데이터가 완전 로컬 처리되어 프라이버시 보호 및 서버 비용 0
- 1회 다운로드로 영구 캐시 사용
- 정적 호스팅만으로 공개 가능
- 에디터 워크플로우에 심리스(Seamless)하게 통합
브라우저에서의 AI 음악 생성은 "모델을 구동하는 기술"보다 "브라우저의 제약 사항에 적응시키는 엔지니어링"이 압도적으로 어렵습니다.
이번 구현을 통해 얻은 지식은 WebGPU를 이용한 모든 대규모 단말기 AI 모델에 통용됩니다.
본 기능은 완전 오픈 소스로 공개되어 있습니다.
꼭 Star와 Fork로 응원해 주세요!
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기