ADM云GPU私有化部署MOSS-TTS+远程API访问
上篇我们介绍了在云切片上私有部署 Z-Image 开源大模型,然后通过 ComfyUI 远程访问云切片上的 Z-Image 的方法,这一篇我们用 MOSS-TTS 开源大模型演示以下如何用 FastAPI 封装大模型的推理 API,以及远程访问封装的推理 API。关于如何获取免费 GPU 算力的方法,之前的文章也给出了注册链接,本篇的结尾也附上了链接。
1 准备环境
1.1 软件环境
之前的篇章里介绍了基本环境的创建,这里假设你已经创建了 Template。我们仍然以基础容器切片为例,启动切片后打开一个终端 CLI 界面,准备下来 MOSS-TTS。MOSS-TTS 基础模型参数大小是 8B,切片上的 GPU 显存是 48G,跑这个模型够用了,如果你打算同时部署 MOSS-TTS 和其他大模型,可以考虑使用 MOSS-TTS-nano 版本,这个版本的参数小,但是性能接近 MOSS-TTS。
因为是在 AMD 的 GPU 上部署大模型,所以环境变量要设置好,这是通知 pyTorch 底层 API 调用的方式:
export PYTORCH_ROCM_ARCH=gfx1100 export HSA_OVERRIDE_GFX_VERSION=11.0.0然后从 git 上下载 MOSS-TTS 项目代码:
git clone https://github.com/OpenMOSS/MOSS-TTS.git代码下载之后,就可以安装依赖软件包。这一步需要注意,MOSS-TTS 的版本部署说明是要用 conda 创建一个隔离的 python 环境,但是在这个隔离环境上安装 ROCm 环境会遇到很多问题,比如软件包的版本兼容问题,非常耗时且容易出错。所以,我的建议是在云切片环境上直接安装,因为这个环境上的pyTorch 都已经安装好了,所以直接安装 MOSS-TTS 的依赖软件即可。云切片环境的好处就是每次云切片 destroy,所有的环境都会初始化为最初的状态,所以也就没必要再单独弄一个隔离的 python 环境。
可以用以下命令安装依赖软件包:
cd MOSS-TTS pip install --extra-index-url https://download.pytorch.org/whl/rocm7.2 -e .这一步安装需要注意 huggingface-hub 的版本,要求的版本号在 0.34.0 和 1.0.0 之间,我最初就是因为 huggingface-hub 版本号太高,后面写 python api 封装的时候,遇到很多错误。另一个需要说明的是 MOSS-TTS 推理支持 FlashAttention 2 加速,使用该组件可以降低显存占用,加速推理速度。我最开始测试因为不知道云切片上的 GPU 是否支持 FlashAttention 2 组件,所以没有安装这个组件,因为显存够大,所以速度也能接收。后来查了一些资料,说是 AMD 的 GPU 支持 FlashAttention 2 组件,所以你可以用下面的命令安装依赖软件包:
pip install --extra-index-url https://download.pytorch.org/whl/rocm7.2 -e ".[flash-attn]"1.2 模型下载
第一次使用 MOSS-TTS 的 API 的时候会自动下载模型权重文件,因为默认是从 huggingface 下载,所以非常慢,且容易失败。如果遇到这类问题,可以选择手工下载模型权重文件。目前 MOSS-TTS 的版本是 1.5,国内可选择从 modelscope 上下载,关于 modelscope 的安装之前的文章介绍过,这里直接给出下载模型权重文件的命令:
modelscope download --model openmoss/MOSS-TTS-v1.5 --local_dir /workspace/models/moss2 部署与验证
2.1 本地环境验证
上一节的准备工作完成后,就可以在本地验证模型是否可以工作了。打开一个 python 环境,输入下面的代码加载模型:
from transformers import AutoModel, AutoProcessor pretrained_model_name_or_path = "OpenMOSS-Team/MOSS-TTS-v1.5" processor = AutoProcessor.from_pretrained(pretrained_model_name_or_path) model = AutoModel.from_pretrained( pretrained_model_name_or_path, device_map="cuda" ) model.eval() # 设置为推理模式如果是本地下载了 MOSS 模型,可以将 pretrained_model_name_or_path 赋值为本地模型的位置。如果准备使用 FlashAttention 2,则加载模型时要添加 attn_implementation 参数:
model = AutoModel.from_pretrained( pretrained_model_name_or_path, device_map="cuda";, attn_implementation="flash_attention_2" )上述代码执行没有问题,就可以尝试推理一段文本试试:
text = "欢迎使用MOSS-TTS-v1.5,这是一个开源的文本转语音模型。" inputs = processor(text=text, return_tensor="pt").to("cuda") with torch.no_grad(): outputs = model.generate(**inputs) audio = outputs.audio[0].cpu().numpy() torchaudio.save("/workspace/output.wav", audio.unsqueeze(0), processor.model_config.sampling_rate)如果软件环境不出问题,就可以在等待一段时间后得到 output.wav 文件,可以下载到本地听听。
2.2 远程访问 MOSS-TTS
本地测试没有问题,接下来就是如何打通网络,让其他电脑能远程访问服务器上部署的 MOSS-TTS 大模型。一般的思路是将 MOSS-TTS 大模型推理封装成可远程访问的 API,此类工具可选 FastAPI 或 Flask。FastAPI 功能比较丰富,并且文档也很丰富,遇到问题容易获得帮助,所以我选择用 FastAPI 做 web api 的封装。因为 云切片实际上是一个私网环境,需要打通内网穿透,我们选择 ngrok 做内网端口映射。
2.2.1 ngrok
关于 ngrok 的安装和用户注册,上一篇已经介绍过了,这里就不啰嗦了,直接启动 ngrok 服务,注意端口号,要与下一节的 fastAPI 的封装一致,这里使用 8000:
ngrok http 8000这条命令执行成功,会看到几条转换信息,记住其中最重要的一条:
https://****.ngrok-free.dev -> http://localhost:8000这是个转换规则。记住这里的 ****.ngrok-free.dev 和端口号 8000,下一节启动 FastAPI 服务时需要这两个信息。
2.2.2 FastAPI 封装
因为切片环境上已经安装了 FastAPI,所以也不需要额外动手安装。关于 FastAPI 的使用也不是本篇的重点,直接新建一个 python 文件 svr_api.py,输入以下代码创建 API 封装并启动服务:
import os import uuid from fastapi import FastAPI, HTTPException from pydantic import BaseModel import uvicorn from moss_tts import synthesize app = FastAPI(title="MOSS-TTS API", description="一个用于远程语音合成的API服务") class TTSRequest(BaseModel): text: str speaker_wav: str = None language: str = "zh" # 定义响应体格式 class TTSResponse(BaseModel): audio_url: str # 用于保存生成音频的目录 AUDIO_OUTPUT_DIR = "./workspace/output_audio" os.makedirs(AUDIO_OUTPUT_DIR, exist_ok=True) @app.post("/synthesize", response_model=TTSResponse) async def synthesize_speech(request: TTSRequest): if not request.text: raise HTTPException(status_code=400, detail="文本内容不能为空") try: audio_data = synthesize(text=request.text, speaker_wav=request.speaker_wav, language=request.language) # 生成一个音频文件,用 uuid 命名,避免名字冲突 audio_filename = f"{uuid.uuid4()}.wav" audio_path = os.path.join(AUDIO_OUTPUT_DIR, audio_filename) with open(audio_path, "wb") as f: f.write(audio_data) # 返回音频文件的访问URL,我们在 Web 服务器在 / 路径下提供音频服务 audio_url = f"****.ngrok-free.dev/static/{audio_filename}" return TTSResponse(audio_url=audio_url) except Exception as e: print(f"语音合成失败: {e}") raise HTTPException(status_code=500, detail=f"语音合成失败: {str(e)}") # 挂载一个静态文件目录,用于 web url 访问 from fastapi.staticfiles import StaticFiles app.mount("/static", StaticFiles(directory=AUDIO_OUTPUT_DIR), name="static") if __name__ == "__main__": # 启动服务,监听所有地址 uvicorn.run(app, host="0.0.0.0", port=8000)TTSRequest 中的 speaker_wav 参数用于指定一个音色文件,需要是 48000 Hz 采样率,如果不指定则使用默认音色输出语音。language 指定输出语言,一般输入是中文,输出自动适配为中文,如果是其他语言,可以用这个参数指定。synthesize_speech() 函数的作用是从 TTSRequest 请求中接收参数,调用 MOSS-TTS 生成语音输出,并返回音频文件的 URL。
返回的 URL 需要根据实际情况修改,这里的 ****.ngrok-free.dev 就是启动 ngrok 时得到的映射地址,端口号也要与 ngrok 启动时使用的端口号一致。接下来就是把 API 服务跑起来:
python svr_api.py2.2.3 远程 API 调用
客户端的代码很简单,就是生成一个 http 请求,获得一个 json 格式的应答,从中解析出 audio_url 参数。audio_url 是一个完整的音频文件链接,下载这个文件得到音频文件。以下是完整的客户端代码:
import requests import json import os SERVER_URL = "****.ngrok-free.dev" SYNTHESIZE_ENDPOINT = f"{SERVER_URL}/synthesize" # 保存音频文件的本地路径 (Windows) OUTPUT_DIR = "E:/workspace/tts/downloaded_audio" os.makedirs(OUTPUT_DIR, exist_ok=True) def synthesize_and_download(text: str, speaker_wav: str = None, language: str = "zh"): # 构造请求体(与服务端的 TTSRequest 模型对应) payload = { "text": text, "speaker_wav": speaker_wav, "language": language } # 发送 POST 请求 print(f"正在合成语音: {text[:30]}...") try: response = requests.post( SYNTHESIZE_ENDPOINT, json=payload, timeout=120 # 语音合成可能耗时较长 ) response.raise_for_status() # 检查 HTTP 状态码 except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return None # 解析响应,获取音频文件 URL result = response.json() audio_url = result.get("audio_url") if not audio_url: print("服务端返回的响应中没有 audio_url") print(f"响应内容: {result}") return None print(f"音频文件 URL: {audio_url}") # 下载音频文件 try: audio_response = requests.get(audio_url, timeout=60) audio_response.raise_for_status() except requests.exceptions.RequestException as e: print(f"下载音频失败: {e}") return None # 保存到本地 filename = audio_url.split("/")[-1] if not filename: filename = "output.wav" local_path = os.path.join(OUTPUT_DIR, filename) with open(local_path, "wb") as f: f.write(audio_response.content) print(f"音频已保存至: {local_path}") return local_path if __name__ == "__main__": text_to_synthesize = "欢迎使用MOSS-TTS-v1.5,这是一个开源的文本转语音模型。" saved_path = synthesize_and_download( text=text_to_synthesize, language="zh" ) if saved_path: print(f"语音合成完成!文件保存在: {saved_path}") else: print("语音合成失败")这段代码的主体就是 synthesize_and_download() 函数,这个函数的作用就是发送文本到 MOSS-TTS FastAPI 服务,合成语音并下载音频文件。返回的字符串就是本地保存的音频文件路径。
和大多数 TTS 模型一样,MOSS-TTS 在遇到长文本的时候,会出现不稳定的情况,比如因为延迟导致出现长时间的静音,或者短句不自然,音色不能保持等现象。一般 TTS 聚合工具都会提供分句分段的功能,我也简单从某开源工具代码中抄了一个函数,对于长文本,可以拆成一组 chunks,逐次调用 synthesize_and_download 函数,然后将返回的音频文件合并成一个完整的音频文件。
def split_text(text, max_chars=180): marks = "。!?;\n" chunks = [] buf = "" for ch in text: buf += ch if ch in marks or len(buf) >= max_chars: chunks.append(buf.strip()) buf = "" if buf.strip(): chunks.append(buf.strip()) return chunks合并 wav 文件的方法很多,可以使用 pydub,也可以用 wave + NumPy 组合,这不是本篇的重点,这里就不罗嗦了。
3 总结
本篇给出了一个在 AMD 云切片上部署 MOSS-TTS 大模型,并结合了使用 ngrok 做内网穿透,实现了远程 API 访问大模型的操作和部署完整过程。关于如何注册用户,获取免费 GPU 算力的过程,可以从下面的链接开始:
注册入口点击这里…
