TuBrief
Subscribed Channels
Videos
Community

Mengatasi Kebocoran Memori dan Latensi Saat Pengembang Solo Menjalankan Supertonic 3 di Server 2 vCPU

TuBrief Editorial
August 24, 2026
0
Computing/Software

Written with AI assistance from the source video. The video is the authority.

Bahasa Indonesia한국어EnglishEspañol中文العربيةहिन्दीDeutschFrançaisPortuguêsРусский日本語

Related Video

Pengembang Akhirnya Punya Model TTS Lokal yang Nggak Jelek7:58

Pengembang Akhirnya Punya Model TTS Lokal yang Nggak Jelek

Better Stack

More from the community

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

September 13, 2026

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

September 13, 2026

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

September 13, 2026

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

September 13, 2026

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

September 12, 2026

Apple Won the AI Race

September 12, 2026

Comments (0)

Log in to leave a comment

No posts yet

© 2026 . All rights reserved.

TuBrief
Subscribed Channels
Videos
Community
Log in

Mengatasi Kebocoran Memori dan Latensi Saat Pengembang Solo Menjalankan Supertonic 3 di Server 2 vCPU

Saat mengelola SaaS mandiri dengan anggaran di bawah 500.000 won per bulan, tagihan API TTS berbayar menjadi beban yang berat setiap bulannya. Latensi jaringan juga menjadi masalah. Jika butuh waktu 1 hingga 2 detik hingga suara keluar setelah tombol ditekan di layar, pengguna akan langsung menutup tab tersebut.

Supertonic 3 (Supertonic 3), model sumber terbuka dengan 99 juta parameter, adalah alternatif yang menarik. Menjalankannya secara lokal di server dapat memangkas biaya API menjadi 0.

Namun, menjalankan beberapa baris kode contoh Python sangat berbeda dengan penerapan di produksi nyata. Berikut adalah rangkuman cara saya langsung menyelesaikan masalah hambatan memori dan pemrosesan asinkron yang dihadapi saat menjalankan model ini di server berspesifikasi rendah.

1. Alasan Proses Berhenti di Server RAM 1GB dan Penyetelan Sesi ONNX

Ukuran file bobot ONNX Supertonic 3 adalah sekitar 305MB. Saat model pertama kali dimuat ke dalam memori, Resident Set Size (RSS) berada di kisaran 350MB.

Masalah terjadi ketika pengguna mengirim permintaan dan proses penghitungan tensor audio 44,1kHz dimulai. Puncak memori sesaat melampaui 900MB. Jika Anda menggunakan instance termurah dengan spesifikasi 1 vCPU / 1GB RAM, OOM Killer Linux akan langsung aktif dan menghentikan paksa proses Python.

Batas aman untuk pengoperasian yang stabil adalah instance 2 vCPU / 2GB RAM.

Spesifikasi Instance Server RAM Idle RAM Puncak Komputasi Penggunaan CPU Rata-rata Real-Time Factor (RTF) Estimasi Penghematan Biaya Bulanan
1 vCPU / 1GB RAM 280 MB 890 MB (Risiko Penghentian Paksa) 98% 0.85 (Butuh 0.85 detik untuk menghasilkan 1 detik audio) $130 (Dibandingkan API komersial)
2 vCPU / 2GB RAM 320 MB 920 MB (Zona Aman) 48% (Saat utas dibatasi) 0.28 (Butuh 0.28 detik untuk menghasilkan 1 detik audio) $120 (Dibandingkan instance GPU)
4 vCPU / 4GB RAM 350 MB 950 MB 25% 0.15 $90 (Penyetelan instance over-provisioned)

Untuk mencegah penggunaan CPU melonjak hingga 100% saat beberapa permintaan masuk di server CPU berspesifikasi rendah, Anda harus mengontrol kumpulan utas runtime ONNX secara manual.

  • Sesuaikan intra_op_num_threads dengan jumlah inti fisik server (2 inti).
  • Tetapkan execution_mode ke ORT_SEQUENTIAL dan tentukan inter_op_num_threads ke 1 untuk mencegah context switching yang tidak perlu.
  • Aktifkan enable_cpu_mem_arena untuk mencegah alokasi ulang memori heap yang terlalu sering, dan atur allow_spinning ke 0 untuk menghentikan fenomena CPU menunggu dalam idle loop.

`python
import onnxruntime as ort

def get_optimized_session_options(cpu_cores: int = 2) -> ort.SessionOptions:
options = ort.SessionOptions()
options.intra_op_num_threads = cpu_cores
options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
options.inter_op_num_threads = 1
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
options.enable_cpu_mem_arena = True
options.add_session_config_entry("session.dynamic_block_base", "4")
options.add_session_config_entry("session.intra_op.allow_spinning", "0")
return options

`

Dengan menerapkan opsi ini dan menjalankan pekerja (worker), penggunaan CPU rata-rata dapat dikendalikan di bawah 50% di lingkungan 2 vCPU. Anda dapat menghemat biaya infrastruktur sekitar $120 per bulan tanpa harus menggunakan server GPU yang mahal.

2. Konflik Pustaka C++ dan Penanganan Pengecualian Pra-pemrosesan Teks

Konflik pustaka dinamis C++ sering terjadi saat memuat SDK di lingkungan lokal atau kontainer penerapan.

  • Lingkungan Windows: Jika ImportError: DLL load failed muncul, instal Paket Redistributable Microsoft Visual C++ 2015-2022 dan pastikan lingkungan virtual Python adalah 64-bit.
  • Lingkungan Mac: Terjadi error libomp.dylib karena kompiler Clang tidak memiliki OpenMP. Jalankan brew import libomp di terminal dan tambahkan jalur lib ke variabel lingkungan (export DYLD_LIBRARY_PATH="$(brew --prefix libomp)/lib:$DYLD_LIBRARY_PATH").
  • Lingkungan Docker: Saat menggunakan citra python:3.10-slim, instal terlebih dahulu paket build-essential dan libgomp1 melalui apt.

Setelah menyesuaikan lingkungan, Anda perlu memasang pembersih teks masukan. Jika singkatan bahasa Inggris, angka, dan simbol tercampur, model dapat merusak pelafalan atau menghasilkan suara mesin yang aneh.

`python
import re
from typing import Dict

class SupertonicTextNormalizer:
def init(self):
self.lexicon_map: Dict[str, str] = {
"FastAPI": "FastAPI",
"SaaS": "SaaS",
"TTS": "TTS",
"ONNX": "ONNX",
"Python": "Python",
"SDK": "SDK",
"API": "API",
}
self.currency_pattern = re.compile(r'(\d+)\s원')
self.date_pattern = re.compile(r'(\d{4})년\s
(\d{1,2})월\s*(\d{1,2})일')
self.time_pattern = re.compile(r'(\d{1,2}):(\d{2})')
self.special_char_pattern = re.compile(r'[^\w\s.,!?~<>]')

def normalize(self, text: str) -> str:
    if not text or not text.strip():
        raise ValueError("Teks masukan kosong.")

    for word, pronunciation in self.lexicon_map.items():
        text = re.sub(rf'\b{re.escape(word)}\b', pronunciation, text, flags=re.IGNORECASE)

    text = self.date_pattern.sub(r'\1년 \2월 \3일', text)
    text = self.time_pattern.sub(r'\1시 \2분', text)

    tags = re.findall(r'<[^>]+>', text)
    text_placeholder = re.sub(r'<[^>]+>', ' ___TAG___ ', text)
    text_cleaned = self.special_char_pattern.sub('', text_placeholder)
    for tag in tags:
        text_cleaned = text_cleaned.replace('___TAG___', tag, 1)

    return re.sub(r'\s+', ' ', text_cleaned).strip()

`

Untuk mencegah modul inferensi C++ masuk ke dalam kondisi tunggu tak terbatas pada pola teks tertentu, terapkan batas waktu (timeout) menggunakan asyncio.wait_for dan siapkan kode pertahanan untuk mengembalikan suara panduan kesalahan yang siap pakai jika terjadi kegagalan.

`python
import asyncio
import logging

logger = logging.getLogger("TTSPipeline")

async def synthesize_with_fallback(tts_engine, text: str, voice_style, timeout_sec: float = 3.0) -> bytes:
try:
normalizer = SupertonicTextNormalizer()
cleaned_text = normalizer.normalize(text)

    loop = asyncio.get_running_loop()
    wav_data = await asyncio.wait_for(
        loop.run_in_executor(
            None, 
            lambda: tts_engine.synthesize(text=cleaned_text, lang="ko", voice_style=voice_style)
        ),
        timeout=timeout_sec
    )
    return wav_data
except Exception as err:
    logger.error(f"Inferensi TTS gagal atau waktu habis: {err}")
    with open("static/audio/fallback_system_error.wav", "rb") as f:
        return f.read()

`

Melalui pipa pra-pemrosesan ini, kesalahan pemutaran suara akibat pelafalan yang salah akan berkurang secara drastis. Anda dapat menghemat waktu lima hingga enam jam setiap minggu yang biasanya dihabiskan untuk memeriksa dan memperbaiki masalah pelafalan satu per satu selama tahap QA.

3. Kumpulan Proses dan Streaming In-Memory yang Tidak Menghalangi Event Loop FastAPI

Masalah akan timbul jika Anda menjalankan inferensi Supertonic 3—yang merupakan fungsi sinkron—secara langsung di dalam router asinkron FastAPI. Seluruh event loop tunggal akan berhenti total hingga komputasi C++ selesai, menyebabkan permintaan API ringan dari pengguna lain ikut berada dalam status menunggu.

Tugas inferensi yang intensif komputasi harus dialihkan ke ProcessPoolExecutor terpisah.

`python
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from concurrent.futures import ProcessPoolExecutor
import asyncio
import io
import os

app = FastAPI()
process_pool = ProcessPoolExecutor(max_workers=min(4, os.cpu_count() or 1))

def sync_tts_inference(text: str, voice_style_name: str):
from supertonic import TTS
tts = TTS(auto_download=False)
style = tts.get_voice_style(voice_style_name)
wav, _ = tts.synthesize(text=text, lang="ko", voice_style=style)
return wav.tobytes()

@app.post("/api/v1/tts/realtime")
async def generate_speech_realtime(text: str, voice: str = "M1"):
loop = asyncio.get_running_loop()
try:
audio_bytes = await loop.run_in_executor(process_pool, sync_tts_inference, text, voice)
return StreamingResponse(io.BytesIO(audio_bytes), media_type="audio/wav")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

`

Struktur yang menulis audio yang dihasilkan ke diska sebagai file lalu membacanya kembali untuk dikembalikan akan menguras I/O diska pada server berspesifikasi rendah. Jauh lebih cepat untuk melakukan streaming langsung di memori menggunakan io.BytesIO tanpa melewati penyimpanan file.

Jika antrean memanjang akibat lonjakan lalu lintas atau Anda perlu memproses kalimat panjang, pisahkan permintaan menggunakan antrean Redis dan pekerja Celery.

  • Ketika pengguna mengirim teks, server langsung menerbitkan task_id dan mengakhiri respons dengan HTTP 202.
  • Pekerja Celery menjalankan model di proses latar belakang untuk menghasilkan suara.
  • Setelah pembuatan selesai, data biner diteruskan ke klien melalui WebSocket melalui Redis Pub/Sub.

Jika struktur tersebut terpaksa meninggalkan singgah file sementara (temporary file cache) di diska, jalankan tugas pembersihan latar belakang untuk mencegah kegagalan Disk Full.

`python
import os
import time
import glob

AUDIO_CACHE_DIR = "/tmp/supertonic_audio_cache"
MAX_FILE_AGE_SECONDS = 600

def cleanup_ephemeral_audio_files():
now = time.time()
if not os.path.exists(AUDIO_CACHE_DIR):
return
for filepath in glob.glob(os.path.join(AUDIO_CACHE_DIR, "*.wav")):
try:
if now - os.path.getmtime(filepath) > MAX_FILE_AGE_SECONDS:
os.remove(filepath)
except Exception:
pass

`

Dengan menerapkan isolasi proses dan streaming in-memory, Anda dapat mempertahankan latensi p95 dalam kisaran 200 milidetik bahkan di bawah permintaan serentak pada instance 2 vCPU. Anda dapat menghubungkan layanan suara on-device mandiri ke layanan Anda secara stabil tanpa mengkhawatirkan tagihan biaya API eksternal yang membengkak.