- 共享引擎(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 模板
204 lines
8.5 KiB
Python
204 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Build a SA3 DiT TRT engine with a custom dynamic-shape profile.
|
|
|
|
Differs from build_from_onnx.py only in that the optimization-profile `min`
|
|
(and optionally `opt`) can be overridden — used for short-form audio
|
|
work where the canonical min=256 (~24 s) is too high, and for tactic-selection
|
|
experiments where the canonical opt=1292 is much larger than typical L.
|
|
|
|
Usage:
|
|
python build_dit_profile.py sa3-sm-music 32 dit_bf16_min32.trt
|
|
python build_dit_profile.py sa3-m 128 dit_bf16_min128.trt
|
|
python build_dit_profile.py sa3-sm-music 1 dit_bf16_opt64.trt --opt 64
|
|
python build_dit_profile.py sa3-m 1 dit_bf16_opt324.trt --opt 324
|
|
|
|
Args:
|
|
<onnx_target> Subdir under onnx/ (sa3-sm-music | sa3-sm-sfx | sa3-m).
|
|
The .onnx (plus .data sidecar) must already exist locally
|
|
under <repo>/stable-audio-3-optimized/onnx/<target>/.
|
|
<profile_min> Minimum L (e.g., 32, 128). max stays at 4096.
|
|
<output_name> Filename to write under models/<arch>/<target>/.
|
|
|
|
Optional flags:
|
|
--opt N Optimization point on L axis (default 1292). TRT picks
|
|
kernels to run fastest at this shape.
|
|
--precision P "bf16" (default; matches the sa3-m-bf16 recipe, NOT the
|
|
canonical fp16-mixed one — this script builds from the raw
|
|
fp32 dit.onnx) or "fp32" (omits the BF16 flag entirely —
|
|
forces full FP32 execution; engine ~2x larger, build
|
|
slower).
|
|
|
|
Build flags mirror sa3-sm-music / sa3-m in build_from_onnx.py: BF16 +
|
|
EXPLICIT_BATCH, 16 GB workspace. With --precision fp32 the BF16 flag is
|
|
omitted; this lets us test whether BF16 rounding (e.g. in RoPE) is the
|
|
source of the long-clip silence artifact at T_lat=1292.
|
|
"""
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
sys.path.insert(0, str(SCRIPTS_DIR.parent / "scripts"))
|
|
|
|
from _arch import arch_dir, repo_root # noqa: E402
|
|
|
|
|
|
T5_TOKENS = 256
|
|
T5_HIDDEN_DIM = 768
|
|
|
|
|
|
# Maps the target name (also used as the onnx subdir + the output subdir under
|
|
# models/<arch>/) to its on-disk ONNX path. The medium target has a sidecar
|
|
# .data file; the ONNX parser handles it transparently as long as both files
|
|
# sit in the same directory.
|
|
TARGETS = {
|
|
"sa3-sm-music": {
|
|
"onnx_rel": "sa3-sm-music/dit.onnx",
|
|
"out_subdir": "sa3-sm-music",
|
|
},
|
|
"sa3-sm-sfx": {
|
|
"onnx_rel": "sa3-sm-sfx/dit.onnx",
|
|
"out_subdir": "sa3-sm-sfx",
|
|
},
|
|
"sa3-m": {
|
|
"onnx_rel": "sa3-m/dit.onnx", # dit.onnx.data co-located
|
|
"out_subdir": "sa3-m",
|
|
},
|
|
}
|
|
|
|
|
|
def _local_onnx_root() -> Path:
|
|
"""Resolve the local ONNX root the same way _arch.onnx_dir() does, but
|
|
without auto-creating directories — we just want a path that exists.
|
|
|
|
Canonical layout: <github>/stable-audio-3-optimized/onnx/<subdir>/
|
|
"""
|
|
override = os.environ.get("SA3_ONNX_DIR")
|
|
if override:
|
|
return Path(override)
|
|
# repo_root() = .../stable-audio-3/optimized/tensorRT
|
|
# HF sibling = .../stable-audio-3-optimized
|
|
github_root = Path(repo_root()).parent.parent.parent
|
|
return github_root / "stable-audio-3-optimized" / "onnx"
|
|
|
|
|
|
def build_dit(target_name: str, profile_min: int, output_name: str,
|
|
profile_opt: int = 1292, profile_max: int = 4096,
|
|
precision: str = "bf16") -> str:
|
|
if target_name not in TARGETS:
|
|
sys.exit(f"unknown target {target_name!r}; valid: {list(TARGETS)}")
|
|
if precision not in ("bf16", "fp32"):
|
|
sys.exit(f"precision={precision!r} must be one of: bf16, fp32")
|
|
if not (profile_min <= profile_opt <= profile_max):
|
|
sys.exit(f"profile_opt={profile_opt} must satisfy "
|
|
f"min={profile_min} <= opt <= max={profile_max}")
|
|
|
|
rec = TARGETS[target_name]
|
|
onnx_path = _local_onnx_root() / rec["onnx_rel"]
|
|
if not onnx_path.exists():
|
|
sys.exit(f"ONNX not found at {onnx_path}")
|
|
|
|
print(f"\n━━━ build_dit_profile: {target_name} min={profile_min} opt={profile_opt} "
|
|
f"precision={precision} → {output_name} ━━━")
|
|
print(f" onnx: {onnx_path}", flush=True)
|
|
sz_b = onnx_path.stat().st_size
|
|
sidecar = onnx_path.with_suffix(onnx_path.suffix + ".data")
|
|
if sidecar.exists():
|
|
sz_b += sidecar.stat().st_size
|
|
print(f" sidecar: {sidecar.name} ({sidecar.stat().st_size/1e9:.2f} GB)", flush=True)
|
|
print(f" onnx total size: {sz_b/1e9:.2f} GB", flush=True)
|
|
|
|
# Build profile dict: x and local_add_cond get the dynamic L; scalars/T5 stay fixed.
|
|
profile_shapes = {
|
|
"x": [(1, 256, profile_min), (1, 256, profile_opt), (1, 256, profile_max)],
|
|
"t": [(1,), (1,), (1,)],
|
|
"t5_hidden": [(1, T5_TOKENS, T5_HIDDEN_DIM)] * 3,
|
|
"t5_mask": [(1, T5_TOKENS)] * 3,
|
|
"seconds_total": [(1,)] * 3,
|
|
"local_add_cond": [(1, 257, profile_min), (1, 257, profile_opt), (1, 257, profile_max)],
|
|
}
|
|
|
|
import tensorrt as trt
|
|
logger = trt.Logger(trt.Logger.WARNING)
|
|
builder = trt.Builder(logger)
|
|
net_flags = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
|
|
network = builder.create_network(net_flags)
|
|
parser = trt.OnnxParser(network, logger)
|
|
if not parser.parse_from_file(str(onnx_path)):
|
|
for i in range(parser.num_errors):
|
|
print(f" parse error: {parser.get_error(i)}", flush=True)
|
|
sys.exit(2)
|
|
|
|
cfg = builder.create_builder_config()
|
|
cfg.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 16 << 30)
|
|
if precision == "bf16":
|
|
cfg.set_flag(trt.BuilderFlag.BF16)
|
|
else:
|
|
# Pure FP32: do NOT set BF16 (or FP16). We additionally set
|
|
# OBEY_PRECISION_CONSTRAINTS so TRT can't silently downcast layers to
|
|
# lower precision if it judges it "safe" — we want full FP32 to test
|
|
# the hypothesis that BF16 rounding (e.g. in RoPE) causes the
|
|
# long-clip silence artifact.
|
|
try:
|
|
cfg.set_flag(trt.BuilderFlag.OBEY_PRECISION_CONSTRAINTS)
|
|
except AttributeError:
|
|
pass
|
|
|
|
profile = builder.create_optimization_profile()
|
|
for input_name, (lo, opt, hi) in profile_shapes.items():
|
|
profile.set_shape(input_name, lo, opt, hi)
|
|
cfg.add_optimization_profile(profile)
|
|
print(f" profile: x/local_add_cond L=({profile_min}, {profile_opt}, 4096), "
|
|
f"workspace 16 GB, precision={precision.upper()}", flush=True)
|
|
|
|
print(f" building...", flush=True)
|
|
t0 = time.time()
|
|
serialized = builder.build_serialized_network(network, cfg)
|
|
if serialized is None:
|
|
print(f" BUILD FAILED", flush=True)
|
|
sys.exit(3)
|
|
build_s = time.time() - t0
|
|
print(f" built in {build_s:.0f}s ({serialized.nbytes/1e6:.0f} MB)", flush=True)
|
|
|
|
out_dir = Path(arch_dir()) / rec["out_subdir"]
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
target = out_dir / output_name
|
|
with open(target, "wb") as f:
|
|
f.write(serialized)
|
|
print(f" wrote {target}", flush=True)
|
|
print(f" build_time_s={build_s:.1f} size_bytes={target.stat().st_size}", flush=True)
|
|
return str(target)
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
ap = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
ap.add_argument("target_name",
|
|
help="One of sa3-sm-music, sa3-sm-sfx, sa3-m")
|
|
ap.add_argument("profile_min", type=int,
|
|
help="Minimum L for the optimization profile")
|
|
ap.add_argument("output_name",
|
|
help="Filename to write under models/<arch>/<target>/")
|
|
ap.add_argument("--opt", dest="profile_opt", type=int, default=1292,
|
|
help="L value for the profile's opt point (default 1292)")
|
|
ap.add_argument("--max", dest="profile_max", type=int, default=4096,
|
|
help="Maximum L for the profile (default 4096). Set to "
|
|
"match min for a fully-static engine.")
|
|
ap.add_argument("--precision", choices=("bf16", "fp32"), default="bf16",
|
|
help="Builder precision: bf16 (default; matches the "
|
|
"sa3-m-bf16 recipe, not the canonical fp16-mixed one) "
|
|
"or fp32 (omits BF16 flag, forces FP32 throughout).")
|
|
args = ap.parse_args()
|
|
build_dit(args.target_name, args.profile_min, args.output_name,
|
|
profile_opt=args.profile_opt, profile_max=args.profile_max,
|
|
precision=args.precision)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|