faster-whisper 的 GPU 利用率呈尖刺波(峰=批量解码满载,谷=CPU 提取 Mel 特征 + 处理结果时 GPU 空闲),平均利用率低。瓶颈不在算力而在 CPU/GPU 未重叠。 - batch_size 16→32:拉长单次 GPU 解码时间,相对掩盖 CPU 特征提取间隙, 尖刺变宽变平,平均利用率上升。turbo FP16 仅 ~1.6GB,3090 24G 充裕。 - beam_size 5→2:turbo 模型鲁棒,候选数 5→2 大幅减少解码步数,让 GPU 峰更密、间隙更短。保留 1 个候选做歧义发音保险,质量损失小。 - beam_size 从硬编码提到 config 可调,CPU/CPU 模板/GPU/示例 四份配置对齐 - /health 增加 asr_beam_size,模型加载日志同步输出 batch+beam word_timestamps 保留 True:segmenter 强依赖词级时间戳做精确断句, 关闭会触发匀速估算退化路径,得不偿失。
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
"""运行时配置:所有参数从 config.yaml 读取。
|
||
|
||
CPU dev / GPU prod 仅靠 device / model / compute_type 三项切换,代码完全不变。
|
||
默认值与 config.example.yaml 对齐,确保无 yaml 时也能用最小配置启动。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from functools import lru_cache
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
from pydantic import BaseModel, Field, field_validator
|
||
|
||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||
DEFAULT_CONFIG_PATH = PROJECT_ROOT / "config.yaml"
|
||
|
||
|
||
class ServerConfig(BaseModel):
|
||
host: str = "0.0.0.0"
|
||
port: int = 8000
|
||
workers: int = 1
|
||
|
||
|
||
class StorageConfig(BaseModel):
|
||
upload_dir: str = "/data/uploads"
|
||
work_dir: str = "/data/.work"
|
||
output_dir: str = "/data/outputs"
|
||
chunk_bytes: int = 1024 * 1024
|
||
chunk_session_ttl_seconds: int = 300
|
||
# 缓存保留:超期任务产物(输出字幕 / 中间音频 / 保留的原始视频)连同 DB 记录一并清理
|
||
cache_retention_days: int = 7
|
||
# 定时清理间隔(小时):启动时跑一次,之后按此间隔循环
|
||
cache_cleanup_interval_hours: int = 24
|
||
|
||
|
||
class ProcessingConfig(BaseModel):
|
||
delete_original_after_extract: bool = True
|
||
keep_audio: bool = False
|
||
|
||
|
||
class AsrConfig(BaseModel):
|
||
model: str = "tiny.en" # CPU: tiny.en;GPU: large-v3-turbo
|
||
device: str = "cpu" # cpu | cuda
|
||
compute_type: str = "int8" # cpu: int8;gpu: float16
|
||
language: str = "en"
|
||
word_timestamps: bool = True
|
||
vad_filter: bool = True
|
||
batch_size: int = 8 # BatchedInferencePipeline 的音频块批大小;GPU 建议 16
|
||
beam_size: int = 5 # beam search 宽度;GPU turbo 可降到 2 加速(候选数↓ 解码步数↓),质量损失小
|
||
|
||
|
||
class TranslationConfig(BaseModel):
|
||
model: str = "Helsinki-NLP/opus-mt-en-zh" # CPU: opus-mt(轻量);GPU: facebook/nllb-200-distilled-1.3B
|
||
device: str = "cpu" # cpu | cuda
|
||
src_lang: str = "eng_Latn" # NLLB 语言码:英语
|
||
tgt_lang: str = "zho_Hans" # NLLB 语言码:简体中文
|
||
batch_size: int = 8 # 排序后单批最大条数;CPU: 8,GPU: 32(显存独占可用大 batch)
|
||
max_length: int = 256 # 单条最大生成 token 数
|
||
sort_by_length: bool = True # 按句子长度排序后分批,减少批内 padding 浪费(GPU 收益大)
|
||
|
||
|
||
class SegmentationConfig(BaseModel):
|
||
max_words_per_line: int = 14
|
||
max_duration_seconds: float = 7.0
|
||
min_duration_seconds: float = 1.0
|
||
max_chars_per_line: int = 42
|
||
|
||
|
||
class LoggingConfig(BaseModel):
|
||
"""日志配置:控制台 + 内存缓冲的最低级别,以及缓冲条数。
|
||
|
||
分层语义:
|
||
- debug:进度详情(任务 [status pct%]、转写/翻译逐批统计、ffmpeg 命令)
|
||
- info:任务流转里程碑(音频提取/ASR/翻译 的开始与完成、模型加载与卸载)
|
||
- error:详细错误(完整 traceback,由 logger.exception 自带)
|
||
"""
|
||
|
||
level: str = "info" # debug | info | warning | error
|
||
buffer_size: int = 2000 # /logs 页面内存缓冲条数
|
||
|
||
@field_validator("level", mode="before")
|
||
@classmethod
|
||
def _normalize_level(cls, v):
|
||
return str(v).lower() if v else "info"
|
||
|
||
|
||
class DocsConfig(BaseModel):
|
||
"""/docs Basic Auth 凭据(明文,对齐 server)。"""
|
||
|
||
enabled: bool = True
|
||
username: str = "admin"
|
||
password: str = ""
|
||
realm: str = "audio2text docs"
|
||
|
||
@field_validator("password", mode="before")
|
||
@classmethod
|
||
def _coerce(cls, v):
|
||
return "" if v is None else str(v)
|
||
|
||
|
||
class Settings(BaseModel):
|
||
server: ServerConfig = ServerConfig()
|
||
storage: StorageConfig = StorageConfig()
|
||
processing: ProcessingConfig = ProcessingConfig()
|
||
asr: AsrConfig = AsrConfig()
|
||
translation: TranslationConfig = TranslationConfig()
|
||
segmentation: SegmentationConfig = SegmentationConfig()
|
||
logging: LoggingConfig = LoggingConfig()
|
||
docs: DocsConfig = DocsConfig()
|
||
|
||
def upload_dir(self) -> Path:
|
||
return Path(self.storage.upload_dir)
|
||
|
||
def work_dir(self) -> Path:
|
||
return Path(self.storage.work_dir)
|
||
|
||
def output_dir(self) -> Path:
|
||
return Path(self.storage.output_dir)
|
||
|
||
|
||
def _load_yaml(path: Path) -> dict:
|
||
if not path.exists():
|
||
raise FileNotFoundError(
|
||
f"未找到配置文件 {path};请先 cp config.example.yaml config.yaml 并填值。"
|
||
)
|
||
return yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||
|
||
|
||
@lru_cache(maxsize=1)
|
||
def get_settings() -> Settings:
|
||
path = Path(os.getenv("CONFIG_PATH", str(DEFAULT_CONFIG_PATH)))
|
||
return Settings.model_validate(_load_yaml(path))
|
||
|
||
|
||
def reload_settings() -> Settings:
|
||
"""清缓存并重新读取,供脚本与测试使用。"""
|
||
get_settings.cache_clear()
|
||
return get_settings()
|