feat(gpu): 接入 P4000 NVENC 硬件编码加速 #2055

Merged
xiaoxia merged 6 commits from feature/gpu-nvenc-encode into develop 2026-09-26 21:46:43 +08:00
Owner

背景

新增 P4000 GPU 节点(Tailscale 内网 http://100.105.75.67:8900,Quadro P4000 8G,h264_nvenc/hevc_nvenc),将视频最终编码从 CPU libx264 切换为 GPU h264_nvenc 硬件加速,预计编码环节速度提升 3-5x。

架构

  • CPU 侧不变:字幕/滤镜/拼接/转场等所有复杂操作仍由 CPU 完成,输出 libx264 ultrafast 快速中间片(mezzanine)
  • GPU 侧最终编码:中间片上传 OSS 临时前缀 → P4000 签名 URL 下载 → h264_nvenc 编码 → HTTP PUT 回传到 API 服务 /api/v1/internal/gpu-relay/{key}(token 鉴权)→ worker 下载到最终输出路径
  • 优雅降级:GPU 任何环节(health不通过、上传失败、编码失败、下载失败、超时)自动回退到原 CPU libx264 路径,成片质量不受影响,仅 warning 日志
  • 开关控制:ENABLE_GPU_ENCODE=true/false,所有参数可调(endpoint/relay/secret/preset/crf/bitrate/timeout/fallback)

变更文件

  • packages/config/base.py — GPU_ENCODE_* 配置项(12+2 个字段)
  • packages/shared/gpu_encoder.py — GpuEncoderClient:health/sync 编码/OSS 上传/relay 下载/异常封装
  • apps/api/app/api/routes/gpu_relay.py — internal PUT/GET/HEAD/DELETE /api/v1/internal/gpu-relay/{key},token 鉴权,写入 generated/gpu_relay/
  • apps/api/app/api/router.py — 注册 gpu_relay_router
  • apps/worker/video_processing/unified_render_service.py — _execute_ffmpeg 和 _render_pass_through 两个 libx264 编码点接入 GPU 优先路径
  • apps/worker/worker_app/tasks/_startup.py — worker 启动时探测 P4000 健康并打日志
  • tests/unit/test_gpu_encoder.py — 客户端单测

网络拓扑

  • worker 容器 → P4000(Tailscale 直连,已验证)
  • P4000 → 宿主机 nginx:80(tailscale0:100.69.73.60,已验证 PUT 可达)→ /api/ 反代 → xiaoxia-api:8000
  • worker → API relay 下载(docker 网络 http://xiaoxia-api-staging:8000,已验证 200 OK)

Staging 配置

已写入 staging .env:

ENABLE_GPU_ENCODE=true
GPU_ENCODE_ENDPOINT=http://100.105.75.67:8900
GPU_ENCODE_RELAY_BASE_URL=http://100.69.73.60
GPU_ENCODE_RELAY_SECRET=<generated>
GPU_ENCODE_VCODEC=h264_nvenc
GPU_ENCODE_PRESET=p4
GPU_ENCODE_CRF=23
## 背景 新增 P4000 GPU 节点(Tailscale 内网 http://100.105.75.67:8900,Quadro P4000 8G,h264_nvenc/hevc_nvenc),将视频最终编码从 CPU libx264 切换为 GPU h264_nvenc 硬件加速,预计编码环节速度提升 3-5x。 ## 架构 - **CPU 侧不变**:字幕/滤镜/拼接/转场等所有复杂操作仍由 CPU 完成,输出 libx264 ultrafast 快速中间片(mezzanine) - **GPU 侧最终编码**:中间片上传 OSS 临时前缀 → P4000 签名 URL 下载 → h264_nvenc 编码 → HTTP PUT 回传到 API 服务 /api/v1/internal/gpu-relay/{key}(token 鉴权)→ worker 下载到最终输出路径 - **优雅降级**:GPU 任何环节(health不通过、上传失败、编码失败、下载失败、超时)自动回退到原 CPU libx264 路径,成片质量不受影响,仅 warning 日志 - **开关控制**:ENABLE_GPU_ENCODE=true/false,所有参数可调(endpoint/relay/secret/preset/crf/bitrate/timeout/fallback) ## 变更文件 - `packages/config/base.py` — GPU_ENCODE_* 配置项(12+2 个字段) - `packages/shared/gpu_encoder.py` — GpuEncoderClient:health/sync 编码/OSS 上传/relay 下载/异常封装 - `apps/api/app/api/routes/gpu_relay.py` — internal PUT/GET/HEAD/DELETE /api/v1/internal/gpu-relay/{key},token 鉴权,写入 generated/gpu_relay/ - `apps/api/app/api/router.py` — 注册 gpu_relay_router - `apps/worker/video_processing/unified_render_service.py` — `_execute_ffmpeg` 和 `_render_pass_through` 两个 libx264 编码点接入 GPU 优先路径 - `apps/worker/worker_app/tasks/_startup.py` — worker 启动时探测 P4000 健康并打日志 - `tests/unit/test_gpu_encoder.py` — 客户端单测 ## 网络拓扑 - worker 容器 → P4000(Tailscale 直连,已验证) - P4000 → 宿主机 nginx:80(tailscale0:100.69.73.60,已验证 PUT 可达)→ /api/ 反代 → xiaoxia-api:8000 - worker → API relay 下载(docker 网络 http://xiaoxia-api-staging:8000,已验证 200 OK) ## Staging 配置 已写入 staging .env: ``` ENABLE_GPU_ENCODE=true GPU_ENCODE_ENDPOINT=http://100.105.75.67:8900 GPU_ENCODE_RELAY_BASE_URL=http://100.69.73.60 GPU_ENCODE_RELAY_SECRET=<generated> GPU_ENCODE_VCODEC=h264_nvenc GPU_ENCODE_PRESET=p4 GPU_ENCODE_CRF=23 ```

🚀 预览环境已部署

项目 详情
PR号 #2055
预览链接 https://pr-2055.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #2055 | | 预览链接 | [https://pr-2055.preview.xiaoxiajianji.com](https://pr-2055.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
xiaoxia added 5 commits 2026-09-26 20:44:11 +08:00
- packages/config/base.py: 新增 GPU_ENCODE_* 配置项(开关/endpoint/relay/secret/编码参数/fallback)
- packages/shared/gpu_encoder.py: 新增 GpuEncoderClient,封装 health 探测 + mezzanine 上传 OSS + P4000 nvenc 编码 + relay 回传下载,失败抛 GpuEncodeError 触发 CPU 降级
- apps/api/app/api/routes/gpu_relay.py: 新增 internal PUT/GET/DELETE /api/v1/internal/gpu-relay/{key}(token 鉴权),P4000 PUT 编码结果,worker GET 下载
- apps/api/app/api/router.py: 注册 gpu_relay_router
- apps/worker/video_processing/unified_render_service.py: _execute_ffmpeg 和 _render_pass_through 尝试 GPU 路径:CPU ultrafast mezzanine → P4000 nvenc → 输出到最终路径;任何失败自动回退到原 CPU libx264 路径
- apps/worker/worker_app/tasks/_startup.py: worker_ready 时探测 P4000 健康并打日志
- tests/unit/test_gpu_encoder.py: GpuEncoderClient 单测(health/sync 调用/失败/fallback/relay URL)

架构:
- 输入:CPU 输出 libx264 ultrafast mezzanine → 上传 OSS 临时前缀 → P4000 签名 URL 下载
- 输出:P4000 PUT → 宿主机 nginx(tailscale:80)→ API /api/ 反代 → gpu_relay 路由落盘到 generated/gpu_relay/
- 回传:worker 通过 docker 网络 http://xiaoxia-api-staging:8000 GET 下载最终 mp4 到 output_path
- 降级:GPU 任何环节异常(health/上传/编码/回传/下载)→ 原 CPU 路径继续执行,不影响成片
- Add gpu_encode_relay_internal_base_url config (Docker DNS: http://xiaoxia-api-staging:8000)
- P4000 PUT uses external URL via nginx (host:8092 plain HTTP to avoid SSL issues)
- Worker GET/DELETE uses internal URL (direct Docker network, bypasses UFW/nginx)
- Falls back to relay_base_url when internal URL not set (local dev)

On staging:
- New nginx vhost on :8092 provides plain HTTP proxy for P4000 relay PUTs
- Docker network worker→API reachable at xiaoxia-api-staging:8000 (verified)
- Update tests to use _relay_put_url/_relay_internal_url instead of removed _relay_url
- Fix _fake_response to support chunked read(size) used by _download_to_file
- Fix regex match for error message (rc=1 not ffmpeg_rc=1)
- Add tests for internal/external URL separation and fallback behavior
test(gpu-encoder): 补充分支覆盖率测试(encode全流程/错误路径/singleton/relay路由)+ 持久化宿主机 nginx :8092 vhost 配置
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m19s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m0s
AI Code Review / AI Code Review (pull_request) Successful in 6m42s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m48s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 10s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m30s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 4m33s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 4m55s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 5m21s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 7m56s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 11m37s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 13m11s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 1s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
7b0db03875
- test_gpu_encoder.py: 扩展至 ~45 个用例,覆盖 encode_mezzanine_to_output happy path
  (audio/no-audio/bitrate/extra_video_args)、post_sync 错误分支(URLError/timeout/bad JSON/uploaded=false)、
  _download_to_file 非200/网络错误清理、_relay_delete 异常吞掉、
  OSS helper(import失败/bucket=None/upload失败/delete异常吞掉)、
  _build_client_from_settings 所有分支(import失败/disabled/缺endpoint/缺relay/正常配置)、
  get_gpu_encoder singleton+init异常、constructor 默认值。
- test_gpu_relay.py: 新增 30+ 个用例,覆盖 _relay_dir/_secret/_safe_key/_check_token、
  build_relay_put_url/get_url/generate_key,以及 PUT/GET/HEAD/DELETE 四个 handler
  (写入/404/401/400/写入错误清理/unlink错误)。
- infra/nginx/gpu-relay-staging.conf: 宿主机 nginx :8092 纯 HTTP vhost,
  给 P4000 通过 Tailscale 回传编码结果使用。部署命令见文件注释。
xiaoxia force-pushed feature/gpu-nvenc-encode from 5096079ebd to 7b0db03875 2026-09-26 20:44:11 +08:00 Compare
auto-approve-bot added 1 commit 2026-09-26 21:03:49 +08:00
style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m26s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m5s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 39s
AI Code Review / AI Code Review (pull_request) Successful in 6m43s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 10s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 28s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m21s
706fed9c08
xiaoxia merged commit 778b17a723 into develop 2026-09-26 21:46:43 +08:00

🗑️ 预览环境已清理

PR #2055 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #2055 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.