- 共享引擎(app/src/main):GestureEngine 6 手势识别、AudioEngine 三层音频 (ExoPlayer BGM 交叉淡入 / 对白 ducking / SoundPool 低延迟)、Game/GameEngine 状态机接口、MainActivity 全屏封面 + TalkBack - 《声命线》storyline:末世互动广播剧,3 幕 8 抉择点 4 结局 - 《声东击西》reflex:听声辨位反应,12 波战斗 + BGM 三档 BPM 变速 - 《暗室》escape:3x3 网格密室逃脱,钥匙→铁柜→保险丝→电箱→大门谜题链 - 文档:setup(从零搭建)/ architecture(架构)/ engine-api(引擎 API)/ asset-pipeline(资产流水线)/ games/*(三款游戏设计文档) - 资产管线:Qwen3-TTS 对白 + 程序合成音效/BGM + Mureka BGM 的 gen 脚本与台账, 全部 ogg 资产入库可复现 - env.d 环境脚本:android/tts 入库;sfx/mureka 含密钥不入库,仅提交 .example.sh 模板
73 lines
2.2 KiB
Python
73 lines
2.2 KiB
Python
import pytest
|
|
import torch
|
|
from functools import partial
|
|
|
|
from stable_audio_3 import StableAudioModel
|
|
from stable_audio_3.models.lora import (
|
|
LoRAParametrization,
|
|
add_lora,
|
|
get_lora_state_dict,
|
|
save_lora_safetensors,
|
|
)
|
|
from tests.conftest import ACCEL_DEVICE
|
|
|
|
_LORA_RANK = 4
|
|
_LORA_ALPHA = 4.0
|
|
|
|
|
|
def _lora_cfg(rank, alpha):
|
|
return {
|
|
torch.nn.Linear: {
|
|
"weight": partial(
|
|
LoRAParametrization.from_linear,
|
|
rank=rank,
|
|
lora_alpha=alpha,
|
|
adapter_type="lora",
|
|
),
|
|
},
|
|
torch.nn.Conv1d: {
|
|
"weight": partial(
|
|
LoRAParametrization.from_conv1d,
|
|
rank=rank,
|
|
lora_alpha=alpha,
|
|
adapter_type="lora",
|
|
),
|
|
},
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def synthetic_lora_ckpt(tmp_path_factory):
|
|
"""Build and save a minimal LoRA checkpoint from the small-music model for testing."""
|
|
model = StableAudioModel.from_pretrained("small-music", device=ACCEL_DEVICE)
|
|
add_lora(model.model.model, _lora_cfg(_LORA_RANK, _LORA_ALPHA))
|
|
add_lora(model.model.conditioner, _lora_cfg(_LORA_RANK, _LORA_ALPHA))
|
|
state_dict = {
|
|
**get_lora_state_dict(model.model.model),
|
|
**get_lora_state_dict(model.model.conditioner),
|
|
}
|
|
config = {"rank": _LORA_RANK, "alpha": _LORA_ALPHA, "adapter_type": "lora"}
|
|
ckpt_path = tmp_path_factory.mktemp("lora") / "synthetic.safetensors"
|
|
save_lora_safetensors(state_dict, config, ckpt_path)
|
|
del model
|
|
return ckpt_path
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def lora_model(synthetic_lora_ckpt):
|
|
"""Small-music model with a synthetic LoRA checkpoint loaded."""
|
|
model = StableAudioModel.from_pretrained("small-music", device=ACCEL_DEVICE)
|
|
model.load_lora([str(synthetic_lora_ckpt)])
|
|
return model
|
|
|
|
|
|
def test_lora_inference(lora_model, maybe_save_audio):
|
|
audio = lora_model.generate(prompt="drums", duration=1.0, steps=4, seed=42)
|
|
|
|
assert isinstance(audio, torch.Tensor)
|
|
assert audio.ndim == 3 # (batch, channels, samples)
|
|
assert audio.shape[0] == 1
|
|
assert audio.shape[-1] > 0
|
|
|
|
maybe_save_audio(audio, lora_model.model.sample_rate, "lora_drums")
|