Ubuntu20.04生产环境部署:Fish Speech+Nginx负载均衡配置

最近在帮一个做有声内容的朋友搭建语音合成服务,他们之前用单机部署的Fish Speech,平时用着还行,一到业务高峰期,比如晚上集中生成有声书章节的时候,服务器就卡得不行,并发一高就崩。他们问我有没有办法在不加太多硬件成本的情况下,把服务撑起来。

我看了下他们的配置,一台RTX 4090的服务器,跑着Fish Speech 1.5,平时也就处理几十个并发请求。问题其实不在模型本身,而是部署方式太“裸奔”了,资源没管好,请求来了也没个调度。折腾了两天,给他们搞了一套基于Nginx负载均衡的生产级部署方案,硬是把单台服务器的并发处理能力从原来的50请求/秒,提到了300+。整个过程踩了不少坑,也总结了一些经验,今天就跟大家详细聊聊怎么操作。

这套方案的核心思路很简单:把一台服务器的潜力榨干。不是靠堆硬件,而是通过GPU资源隔离、请求负载均衡、以及一些运维上的优化,让现有的设备跑得更稳、更快。如果你也在为TTS服务的性能瓶颈发愁,或者打算把Fish Speech用到正式的业务里,那接下来的内容应该能帮到你。

1. 基础环境与Fish Speech部署

咱们先从最基础的开始,把Fish Speech在Ubuntu 20.04上跑起来。虽然网上有很多一键脚本,但生产环境我建议还是手动走一遍,心里有底,后面排查问题也方便。

1.1 系统准备与依赖安装

首先,确保你的系统是Ubuntu 20.04 LTS,并且有NVIDIA显卡驱动。用nvidia-smi命令检查一下驱动和CUDA版本,推荐驱动版本在525以上,CUDA 12.1或更高。

# 更新系统包
sudo apt update && sudo apt upgrade -y

# 安装基础编译工具和依赖
sudo apt install -y build-essential cmake git wget curl libsox-dev ffmpeg python3-pip python3-venv

# 创建专门的用户来运行服务(安全起见,别用root)
sudo useradd -m -s /bin/bash fishspeech
sudo usermod -aG audio fishspeech

接下来,配置Python环境。我习惯用Miniconda来管理,比较干净。

# 下载并安装Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3
echo 'export PATH="$HOME/miniconda3/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

# 为Fish Speech创建独立的虚拟环境
conda create -n fish-speech python=3.10 -y
conda activate fish-speech

# 安装PyTorch(匹配你的CUDA版本)
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

1.2 拉取并配置Fish Speech

现在来获取Fish Speech的代码和模型。建议直接从官方仓库拉取,模型从Hugging Face下载。

# 切换到我们创建的用户
sudo -u fishspeech bash
cd /home/fishspeech

# 克隆代码仓库
git clone https://github.com/fishaudio/fish-speech.git
cd fish-speech

# 安装项目依赖
pip install -e .

# 下载预训练模型(1.5版本)
# 这里需要git-lfs,如果没安装请先运行:sudo apt install git-lfs
git lfs install
git clone https://huggingface.co/fishaudio/fish-speech-1.5 models/

下载模型可能会花点时间,毕竟好几个GB。完成后,你可以先简单测试一下模型是否能正常工作。

# 一个快速测试脚本,检查核心功能
python -c "
from fish_speech.models.text2semantic import Text2Semantic
model = Text2Semantic.from_pretrained('models/fish-speech-1.5')
print('模型加载成功!')
"

如果没报错,说明基础环境没问题了。

1.3 启动基础API服务

Fish Speech官方提供了WebUI,但生产环境我们更需要一个稳定的API服务。我们可以用他们提供的服务器模块,稍作调整。

先创建一个简单的启动脚本 /home/fishspeech/start_api.py

#!/usr/bin/env python3
import argparse
from fish_speech.server import serve

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", type=str, default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8000)
    parser.add_argument("--model-path", type=str, default="/home/fishspeech/fish-speech/models/fish-speech-1.5")
    parser.add_argument("--device", type=str, default="cuda")
    args = parser.parse_args()

    serve(
        host=args.host,
        port=args.port,
        model_path=args.model_path,
        device=args.device,
        max_workers=4  # 初始工作进程数,后面会调整
    )

给脚本执行权限,并创建一个Systemd服务来管理它,这样服务崩溃了能自动重启。

sudo nano /etc/systemd/system/fish-speech-api.service

写入以下内容:

[Unit]
Description=Fish Speech API Service
After=network.target

[Service]
Type=simple
User=fishspeech
WorkingDirectory=/home/fishspeech/fish-speech
Environment="PATH=/home/fishspeech/miniconda3/bin"
ExecStart=/home/fishspeech/miniconda3/envs/fish-speech/bin/python /home/fishspeech/start_api.py --host 0.0.0.0 --port 8000
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

然后启动服务:

sudo systemctl daemon-reload
sudo systemctl start fish-speech-api
sudo systemctl enable fish-speech-api
sudo systemctl status fish-speech-api  # 检查状态

现在,你的Fish Speech API应该已经在本地8000端口跑起来了。可以用curl简单测一下:

curl -X POST http://127.0.0.1:8000/generate \
  -H "Content-Type: application/json" \
  -d '{"text": "你好,这是一个测试语音。", "language": "zh"}'

如果返回一个任务ID或者音频数据,那就恭喜你,基础服务部署成功了。不过,这只是单机裸奔模式,扛不住多少压力。接下来,我们要开始上“生产级”的硬菜了。

2. GPU资源隔离与优化

单卡服务器想要同时处理多个语音合成请求,最大的瓶颈就是GPU内存和计算核心的争抢。一个长文本的合成任务可能就把显存占满了,其他请求只能干等着。我们的目标是把这块GPU“划出几个隔间”,让不同的任务互不干扰。

2.1 使用NVIDIA MPS提升并发

NVIDIA Multi-Process Service (MPS) 是个好东西,它允许GPU上的多个进程共享上下文,减少进程切换的开销,特别适合像我们这种需要同时跑多个模型推理进程的场景。

首先,确保你的NVIDIA驱动支持MPS(大多数现代驱动都支持)。然后按以下步骤设置:

# 停止可能正在运行的X Server或显示管理器(如果是无头服务器,通常没有)
sudo systemctl stop gdm  # 或 lightdm,根据你的桌面环境

# 设置GPU为独占模式,为MPS做准备
sudo nvidia-smi -i 0 -c EXCLUSIVE_PROCESS  # -i 0 指定第一块GPU,如果你有多块请调整

# 启动MPS服务
sudo nvidia-cuda-mps-control -d

验证MPS是否启动成功:

echo "status" | nvidia-cuda-mps-control

你应该能看到MPS is alive之类的信息。接下来,我们需要修改Fish Speech的启动方式,让它的进程在MPS环境下运行。这步很关键,能显著降低每个进程的显存开销。

修改之前的Systemd服务文件,在[Service]部分添加环境变量:

Environment="CUDA_VISIBLE_DEVICES=0"
Environment="CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps"
Environment="CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log"

然后重启服务:

sudo systemctl daemon-reload
sudo systemctl restart fish-speech-api

2.2 配置进程池与显存限制

即使开了MPS,如果一个进程失控把显存吃光,其他进程还是会饿死。我们需要在应用层面控制每个工作进程的资源使用。

修改/home/fishspeech/start_api.py,利用PyTorch的内存管理功能:

# 在serve函数调用前,或模型加载后,添加以下代码
import torch
torch.cuda.set_per_process_memory_fraction(0.3)  # 每个进程最多使用30%的显存
torch.cuda.empty_cache()  # 清空缓存

同时,调整启动脚本,允许我们控制工作进程的数量,并让每个进程绑定到不同的CPU核心,减少上下文切换。

# 创建一个新的启动脚本 /home/fishspeech/start_api_optimized.sh
#!/bin/bash
export CUDA_VISIBLE_DEVICES=0
export OMP_NUM_THREADS=2  # 限制每个进程的OpenMP线程数

# 使用taskset将进程绑定到特定的CPU核心(例如核心0和1)
taskset -c 0,1 /home/fishspeech/miniconda3/envs/fish-speech/bin/python /home/fishspeech/start_api.py --host 0.0.0.0 --port 8000 --max-workers 4

然后更新Systemd服务的ExecStart指向这个新脚本。别忘了给脚本执行权限。

这样配置后,你的单个API服务进程就不会霸占全部GPU资源了。但真正的并发提升,靠单个服务实例是不够的。接下来,我们要在单机上启动多个Fish Speech服务实例,让它们监听不同的端口。

2.3 启动多服务实例

思路是启动多个相同的API服务进程,每个进程使用不同的端口和略微不同的资源限制,让Nginx在后面做负载均衡。

创建另一个Systemd服务文件,比如fish-speech-api-8001.service,将端口改为8001,并且可以绑定到不同的CPU核心(例如核心2,3)。

# /etc/systemd/system/fish-speech-api-8001.service
[Service]
...
Environment="CUDA_VISIBLE_DEVICES=0"
Environment="OMP_NUM_THREADS=2"
ExecStart=/bin/bash -c 'taskset -c 2,3 /home/fishspeech/miniconda3/envs/fish-speech/bin/python /home/fishspeech/start_api.py --host 0.0.0.0 --port 8001'
...

我建议根据你GPU的显存大小来决定启动几个实例。对于一块24GB显存的RTX 4090,在做了上述内存限制后,启动3-4个实例是比较安全的。你可以创建fish-speech-api-8002.servicefish-speech-api-8003.service等。

启动并启用所有实例:

sudo systemctl start fish-speech-api-8001
sudo systemctl enable fish-speech-api-8001
# ... 对其他实例重复此操作

现在,你本机的8000、8001、8002等端口上,都运行着独立的Fish Speech服务了。它们共享同一块GPU,但通过MPS和资源限制,实现了基本的隔离。下一步,就是请出“调度员”Nginx,把外部的请求合理地分发给这些“工人”。

3. Nginx负载均衡配置

Nginx在这里扮演两个角色:一是作为反向代理,把外部请求转发到后端的多个Fish Speech实例;二是作为负载均衡器,决定哪个请求发给哪个后端,避免某个实例过载。

3.1 安装与基础配置

首先安装Nginx:

sudo apt install nginx -y

我们先为Fish Speech服务创建一个独立的Nginx配置文件,这样管理起来清晰。

sudo nano /etc/nginx/sites-available/fish-speech

写入以下配置内容。这里假设你的服务器公网IP是your_server_ip,或者你有一个域名tts.yourcompany.com

upstream fish_speech_backend {
    # 配置负载均衡后端服务器列表
    # 这里列出我们刚才启动的所有本地实例
    server 127.0.0.1:8000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8001 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8002 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8003 max_fails=3 fail_timeout=30s;

    # 使用最少连接负载均衡算法,谁当前处理的请求少,新请求就给谁
    least_conn;

    # 保持连接,提升性能
    keepalive 32;
}

server {
    listen 80;
    server_name your_server_ip; # 或你的域名 tts.yourcompany.com

    # 客户端请求体大小限制(根据你的需求调整)
    client_max_body_size 50M;

    # 增加超时时间,语音合成可能比较耗时
    proxy_connect_timeout 300s;
    proxy_send_timeout 300s;
    proxy_read_timeout 300s;

    location / {
        # 拒绝直接访问根路径,或者可以返回一个简单的状态页
        return 404;
    }

    location /api/ {
        # 将 /api/ 开头的请求代理到后端集群
        proxy_pass http://fish_speech_backend;

        # 重要的头部信息转发
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 禁用缓冲,对于长时间运行的生成任务,让数据流式返回可能更好
        # 但对于TTS,通常是一次性返回整个音频,所以缓冲没问题。
        # proxy_buffering off;
    }

    # 可选:添加一个健康检查端点
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

保存后,创建符号链接启用这个站点配置,并测试Nginx配置是否正确。

sudo ln -s /etc/nginx/sites-available/fish-speech /etc/nginx/sites-enabled/
sudo nginx -t  # 测试配置
sudo systemctl reload nginx  # 重新加载配置

现在,外部客户端访问 http://your_server_ip/api/generate 的请求,就会被Nginx均匀地分发到后端的8000-8003端口服务上。你可以用压测工具简单试一下,看看多个并发请求是不是被分到了不同的端口。

3.2 配置HTTPS(SSL/TLS)

生产环境必须上HTTPS,保证数据传输安全,而且很多现代的API客户端也要求使用HTTPS。这里我们使用Let‘s Encrypt的免费证书。

安装Certbot工具:

sudo apt install certbot python3-certbot-nginx -y

获取并安装证书(如果你有域名的话):

sudo certbot --nginx -d tts.yourcompany.com

Certbot会自动修改你的Nginx配置文件,添加SSL相关设置,并设置自动续期。如果你只是用IP地址,那么可能需要使用自签名证书,或者考虑使用支持IP证书的其它服务商,但自签名证书客户端需要手动信任,比较麻烦。

配置完成后,你的Nginx server块会多出一个监听443端口的配置,并且将HTTP请求重定向到HTTPS。确保在配置中,proxy_pass仍然指向后端的HTTP服务(http://fish_speech_backend),因为Nginx和后端服务在同一台机器,内网通信可以不用HTTPS。

3.3 高级调优:缓存与限流

为了进一步提升性能和稳定性,我们可以加入两个小功能。

微缓存: 对于完全相同的文本和参数合成请求,短时间内没必要让GPU再算一次。我们可以用Nginx的proxy_cache功能,在内存里缓存一小段时间的结果。

在Nginx的http块内(通常在/etc/nginx/nginx.conf)添加缓存路径定义:

http {
    ...
    # 定义缓存路径和参数
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=fish_cache:10m max_size=1g inactive=60m use_temp_path=off;
    ...
}

然后在刚才的server配置的location /api/块内添加缓存规则:

location /api/ {
    proxy_cache fish_cache;
    proxy_cache_key "$scheme$request_method$host$request_uri$request_body";
    proxy_cache_valid 200 302 5m;  # 成功响应缓存5分钟
    proxy_cache_valid 404 1m;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cache-Status $upstream_cache_status;

    proxy_pass http://fish_speech_backend;
    ... # 其他proxy_set_header等设置保持不变
}

限流: 防止恶意用户或失控客户端瞬间发送大量请求,拖垮后端服务。

location /api/块内添加限流:

location /api/ {
    # 限制每个IP地址每秒最多10个请求,突发不超过20个
    limit_req zone=req_limit burst=20 nodelay;
    limit_req_status 429;

    proxy_pass http://fish_speech_backend;
    ...
}

同时需要在http块定义这个限流区域:

http {
    ...
    limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
    ...
}

做完这些优化,重启Nginx:

sudo systemctl restart nginx

现在,你的Fish Speech服务前面就有了一个具备负载均衡、HTTPS、缓存和限流能力的强大网关。但这还不够,一个健壮的生产系统还需要考虑日志和监控。

4. 生产运维关键点

服务跑起来只是第一步,让它长期稳定运行,出了问题能快速定位,才是运维的难点。

4.1 结构化日志与切割

默认的日志可能都混在一起,我们需要把Nginx访问日志、错误日志,以及每个Fish Speech后端实例的日志分开管理,并定期切割,避免单个日志文件过大。

首先,配置Nginx的日志格式,加入更多信息。在http块内:

log_format fish_speech_json '{ "time": "$time_iso8601", '
                           '"remote_addr": "$remote_addr", '
                           '"request": "$request", '
                           '"status": $status, '
                           '"body_bytes_sent": $body_bytes_sent, '
                           '"request_time": $request_time, '
                           '"upstream_addr": "$upstream_addr", '
                           '"upstream_response_time": "$upstream_response_time", '
                           '"upstream_cache_status": "$upstream_cache_status" }';

access_log /var/log/nginx/fish-speech-access.log fish_speech_json;
error_log /var/log/nginx/fish-speech-error.log warn;

然后,使用logrotate工具来管理日志切割。创建配置文件:

sudo nano /etc/logrotate.d/fish-speech-nginx

内容如下:

/var/log/nginx/fish-speech-*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
    endscript
}

对于Fish Speech后端服务的日志(输出到stdout/stderr,被Systemd捕获),我们同样可以配置Journald的日志限制和转发。修改或创建Systemd服务的[Service]部分:

[Service]
...
StandardOutput=journal
StandardError=journal
# 限制日志大小
LogRateLimitIntervalSec=30s
LogRateLimitBurst=1000

4.2 监控与告警

基础监控可以用简单的脚本来完成。创建一个健康检查脚本/usr/local/bin/check_fish_speech.sh

#!/bin/bash
# 检查后端服务端口是否存活
PORTS=(8000 8001 8002 8003)
ALL_HEALTHY=true

for port in "${PORTS[@]}"; do
    if ! curl -f -s http://127.0.0.1:$port/health > /dev/null; then
        echo "后端服务端口 $port 健康检查失败!"
        ALL_HEALTHY=false
        # 可以尝试重启对应的服务
        sudo systemctl restart fish-speech-api-$port 2>/dev/null || true
    fi
done

# 检查Nginx状态
if ! systemctl is-active --quiet nginx; then
    echo "Nginx服务未运行!"
    sudo systemctl restart nginx
    ALL_HEALTHY=false
fi

if [ "$ALL_HEALTHY" = true ]; then
    echo "所有服务状态正常。"
    exit 0
else
    exit 1
fi

给脚本执行权限,然后把它加入Crontab,每分钟执行一次,并将错误输出发送到你的邮箱或监控系统。

sudo chmod +x /usr/local/bin/check_fish_speech.sh
sudo crontab -e
# 添加一行:
* * * * * /usr/local/bin/check_fish_speech.sh >> /var/log/fish-speech-monitor.log 2>&1

对于更直观的资源监控,比如GPU使用率、显存占用、请求QPS等,我推荐使用Prometheus + Grafana。你可以部署nvidia-gpu-exporter来暴露GPU指标,用nginx-exporter来暴露Nginx指标,然后在Grafana里制作一个仪表盘。这部分内容稍微复杂一些,但对于长期运维来说非常值得投入。

4.3 压力测试与性能验证

配置都做完后,到底效果如何?我们需要压测一下。可以用wrkab这样的工具。

# 安装wrk
sudo apt install wrk -y

# 进行一个简单的压测,30个线程,100个连接,持续30秒
wrk -t30 -c100 -d30s --timeout 2s -s post.lua http://your_server_ip/api/generate

你需要准备一个post.lua文件,里面定义POST请求的body:

wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"text": "这是一个用于压力测试的示例文本,内容需要足够长以模拟真实负载。", "language": "zh"}'

压测时,同时用nvidia-smihtopsudo tail -f /var/log/nginx/fish-speech-access.log来观察系统状态。目标是找到系统的瓶颈:是GPU算力到头了?还是某个后端进程崩溃了?或者是Nginx或网络成了瓶颈?

根据压测结果,你可能需要回头调整一些参数,比如:

  • 增加或减少后端实例数量 (max_workers)
  • 调整Nginx的keepalive连接数
  • 修改GPU进程的内存分数 (set_per_process_memory_fraction)
  • 优化内核网络参数 (sysctl.conf)

5. 总结

折腾完这一套,朋友那边的服务稳定多了,晚上高峰期的请求排队现象基本消失,合成任务的平均响应时间也降了下来。回顾一下,这套方案的核心其实就是**“分而治之”“精细化管理”**。

把单个重型模型服务拆分成多个轻量级实例,用Nginx做智能调度,再配上资源隔离和运维优化,单台服务器的处理能力就有了质的提升。从50到300+的并发提升,并不是模型突然变快了,而是我们让请求排队和处理的效率变高了。

当然,这套方案也不是银弹。如果业务量继续暴涨,最终还是要走向真正的多机集群。但在此之前,把单机性能榨干,无疑是成本最低、见效最快的选择。如果你正准备把Fish Speech或其他AI模型投入生产,不妨试试这些方法。过程中可能会遇到各种环境依赖、版本冲突的问题,耐心点,一步步排查,总能搞定。

最后,再提一句安全。本文重点在性能部署,但生产环境的安全配置同样重要,包括防火墙规则、服务账户权限、数据库加密(如果你存了用户数据)等,千万别忽略了。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐