fix: .gitignore 가 소스 패키지 src/livesub/models 를 통째로 먹고 있었다
Some checks failed
Windows / verify (push) Has been cancelled
Some checks failed
Windows / verify (push) Has been cancelled
`models/` 처럼 슬래시 없이 쓴 규칙은 깊이에 상관없이 같은 이름의 폴더를 전부 무시한다. 의도는 루트의 모델 캐시였는데 소스 패키지까지 같이 걸렸고, 그래서 asr/translator/manager/tiers/glossary/packs/speech_level 8개 파일이 한 번도 커밋된 적이 없다. 증상: Gitea 에서 clone 하면 `ModuleNotFoundError: No module named 'livesub.models'` 로 앱이 아예 임포트되지 않는다. 로컬 작업 트리에는 파일이 있으니 개발 중에는 절대 안 보인다. 방금 붙인 Windows CI VM 이 첫 검증에서 바로 잡아냈다. /models/, /adapters/, /data/ 로 루트에 고정하고 빠진 파일을 추가한다.
This commit is contained in:
209
src/livesub/models/tiers.py
Normal file
209
src/livesub/models/tiers.py
Normal file
@@ -0,0 +1,209 @@
|
||||
"""모델 품질 티어 정의.
|
||||
|
||||
사용자에게는 실제 모델 이름 대신 5단계 티어(속도우선 → 품질우선)로 노출한다.
|
||||
각 티어는 ASR(음성인식) 1개 + MT(번역) 1개의 조합이다.
|
||||
|
||||
선정 근거는 docs/MODELS.md 참고.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
from ..constants import DEFAULT_TIER
|
||||
|
||||
|
||||
class MTBackend(str, Enum):
|
||||
"""번역 모델 실행 백엔드."""
|
||||
|
||||
CTRANSLATE2 = "ctranslate2" # NLLB 계열 seq2seq
|
||||
TRANSFORMERS = "transformers" # LLM 계열 (Seed-X / Qwen3)
|
||||
|
||||
|
||||
class PromptStyle(str, Enum):
|
||||
"""LLM 백엔드의 프롬프트 형식. 모델 계열마다 요구사항이 다르다."""
|
||||
|
||||
#: 해당 없음 (seq2seq)
|
||||
NONE = "none"
|
||||
#: Seed-X 전용. chat template 이 없는 번역 전용 completion 모델이라
|
||||
#: 모델 카드에 적힌 고정 형식 + 끝의 `<언어코드>` 태그를 그대로 지켜야 한다.
|
||||
SEEDX = "seedx"
|
||||
#: Qwen3 등 일반 instruct 모델. chat template + 자유 지시문 사용 가능.
|
||||
INSTRUCT = "instruct"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AsrSpec:
|
||||
repo: str
|
||||
compute_type: str
|
||||
beam_size: int = 1
|
||||
vram_mb: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MTSpec:
|
||||
repo: str
|
||||
backend: MTBackend
|
||||
compute_type: str = "float16"
|
||||
vram_mb: int = 0
|
||||
prompt_style: PromptStyle = PromptStyle.NONE
|
||||
#: LoRA 추가학습 난이도 1(쉬움) ~ 3(까다로움)
|
||||
finetune_ease: int = 2
|
||||
|
||||
@property
|
||||
def supports_prompt_glossary(self) -> bool:
|
||||
"""프롬프트로 용어집을 지시할 수 있는가.
|
||||
|
||||
지시문을 이해하는 instruct 모델만 가능하다. Seed-X 는 번역만 하도록
|
||||
학습된 completion 모델이라 "이 용어는 이렇게 옮겨라" 같은 문장을
|
||||
넣으면 학습 분포를 벗어나 오히려 번역이 망가진다. seq2seq 도 마찬가지다.
|
||||
이 둘은 플레이스홀더 치환 방식으로 용어를 보호한다.
|
||||
"""
|
||||
return self.prompt_style is PromptStyle.INSTRUCT
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Tier:
|
||||
key: str
|
||||
order: int
|
||||
name: str
|
||||
tagline: str
|
||||
asr: AsrSpec
|
||||
mt: MTSpec
|
||||
approx_latency_s: float
|
||||
min_vram_gb: float
|
||||
notes: str = ""
|
||||
recommended: bool = False
|
||||
best_after_finetune: bool = False
|
||||
|
||||
@property
|
||||
def total_vram_mb(self) -> int:
|
||||
return self.asr.vram_mb + self.mt.vram_mb
|
||||
|
||||
@property
|
||||
def download_gb(self) -> float:
|
||||
return round(self.total_vram_mb / 1024 * 1.25, 1)
|
||||
|
||||
|
||||
TIERS: dict[str, Tier] = {}
|
||||
|
||||
|
||||
def _register(tier: Tier) -> Tier:
|
||||
TIERS[tier.key] = tier
|
||||
return tier
|
||||
|
||||
|
||||
LIGHTNING = _register(
|
||||
Tier(
|
||||
key="lightning",
|
||||
order=1,
|
||||
name="번개",
|
||||
tagline="속도 최우선 · 저사양 GPU",
|
||||
asr=AsrSpec("Systran/faster-whisper-small", "int8_float16", 1, 700),
|
||||
mt=MTSpec(
|
||||
"entai2965/nllb-200-distilled-600M-ctranslate2",
|
||||
MTBackend.CTRANSLATE2,
|
||||
"int8_float16",
|
||||
800,
|
||||
finetune_ease=1,
|
||||
),
|
||||
approx_latency_s=0.6,
|
||||
min_vram_gb=2,
|
||||
notes="짧은 대사·게임 음성에 충분. 긴 문장이나 전문용어는 품질이 떨어질 수 있습니다.",
|
||||
)
|
||||
)
|
||||
|
||||
SWIFT = _register(
|
||||
Tier(
|
||||
key="swift",
|
||||
order=2,
|
||||
name="신속",
|
||||
tagline="빠르면서 인식률은 최상급",
|
||||
asr=AsrSpec("Systran/faster-whisper-large-v3-turbo", "int8_float16", 1, 1600),
|
||||
mt=MTSpec(
|
||||
"entai2965/nllb-200-distilled-600M-ctranslate2",
|
||||
MTBackend.CTRANSLATE2,
|
||||
"float16",
|
||||
1400,
|
||||
finetune_ease=1,
|
||||
),
|
||||
approx_latency_s=0.9,
|
||||
min_vram_gb=4,
|
||||
notes="음성인식을 최상급으로 올리고 번역만 경량으로 유지한 구성. 가성비가 가장 좋습니다.",
|
||||
)
|
||||
)
|
||||
|
||||
BALANCE = _register(
|
||||
Tier(
|
||||
key="balance",
|
||||
order=3,
|
||||
name="균형",
|
||||
tagline="기본값 · 속도와 품질의 중간",
|
||||
asr=AsrSpec("Systran/faster-whisper-large-v3-turbo", "float16", 2, 2400),
|
||||
mt=MTSpec(
|
||||
"entai2965/nllb-200-distilled-1.3B-ctranslate2",
|
||||
MTBackend.CTRANSLATE2,
|
||||
"float16",
|
||||
2900,
|
||||
finetune_ease=2,
|
||||
),
|
||||
approx_latency_s=1.2,
|
||||
min_vram_gb=6,
|
||||
notes="8GB VRAM 환경에서 가장 무난한 선택.",
|
||||
)
|
||||
)
|
||||
|
||||
PRECISION = _register(
|
||||
Tier(
|
||||
key="precision",
|
||||
order=4,
|
||||
name="정밀",
|
||||
tagline="추천 · 현 시점 최고 번역 품질",
|
||||
asr=AsrSpec("Systran/faster-whisper-large-v3-turbo", "float16", 2, 2400),
|
||||
mt=MTSpec(
|
||||
"ByteDance-Seed/Seed-X-PPO-7B-AWQ-Int4",
|
||||
MTBackend.TRANSFORMERS,
|
||||
"int4",
|
||||
5600,
|
||||
prompt_style=PromptStyle.SEEDX,
|
||||
finetune_ease=3,
|
||||
),
|
||||
approx_latency_s=2.0,
|
||||
min_vram_gb=10,
|
||||
notes="번역 전용으로 학습된 7B 모델. 한/영/일/중 구어체와 문맥 처리가 확연히 좋습니다.",
|
||||
recommended=True,
|
||||
)
|
||||
)
|
||||
|
||||
ULTIMATE = _register(
|
||||
Tier(
|
||||
key="ultimate",
|
||||
order=5,
|
||||
name="극한",
|
||||
tagline="품질 최우선 · 추가학습(LoRA)에 최적",
|
||||
asr=AsrSpec("Systran/faster-whisper-large-v3", "float16", 5, 3100),
|
||||
mt=MTSpec(
|
||||
"Qwen/Qwen3-8B",
|
||||
MTBackend.TRANSFORMERS,
|
||||
"float16",
|
||||
11000,
|
||||
prompt_style=PromptStyle.INSTRUCT,
|
||||
finetune_ease=1,
|
||||
),
|
||||
approx_latency_s=3.0,
|
||||
min_vram_gb=16,
|
||||
notes="게임·방송 용어를 LoRA로 추가학습해 붙일 때 가장 잘 먹히는 구성입니다.",
|
||||
best_after_finetune=True,
|
||||
)
|
||||
)
|
||||
|
||||
assert DEFAULT_TIER in TIERS, f"constants.DEFAULT_TIER({DEFAULT_TIER}) 가 티어 목록에 없습니다"
|
||||
|
||||
|
||||
def ordered_tiers() -> list[Tier]:
|
||||
return sorted(TIERS.values(), key=lambda t: t.order)
|
||||
|
||||
|
||||
def get_tier(key: str) -> Tier:
|
||||
return TIERS.get(key, TIERS[DEFAULT_TIER])
|
||||
Reference in New Issue
Block a user