您可以通过函数计算控制台、SDK或Serverless Devs来体验GPU实例的最佳实践。本文以Python语言为例,说明如何使用Serverless Devs开发工具,将原始视频经过函数代码的转码处理,从.mp4转换为.flv格式。
应用场景和优势
随着越来越多的强交互应用场景的出现,例如社交直播、在线课堂以及远程医疗等,互联网流量正在向实时、准实时的趋势演进。视频平台通常要将原始视频内容根据码率、分辨率、渠道贴片、播放平台等维度,以1∶N的方式转码输出多种分发视频格式,以服务不同网络质量、各种播放平台的观看者。视频转码是视频生产分发中的关键一环,理想的视频转码解决方案需要在成本(人民币/流)和功率效率(瓦/流)方面具有成本效益。
在不同的应用场景下,函数计算提供的GPU实例与CPU相比所具备的优势如下:
-
实时、准实时的应用场景
提供数倍于CPU的转码效率,从而快速将生产内容推向终端用户。
-
成本优先的GPU应用场景
提供弹性预留模式,从而按需为您保留工作GPU实例,对比自购VM拥有较大成本优势。
-
效率优先的GPU应用场景
屏蔽运维GPU集群的繁重负担(驱动/CUDA版本管理、机器运行管理、GPU坏卡管理),使得开发者专注于代码开发、聚焦业务目标的达成。
GPU实例的更多信息,请参见实例类型及使用模式。
性能对比
函数计算GPU实例基于的Turing架构支持以下编码和解码格式:
- 编码格式
H.264 (AVCHD) YUV 4:2:0 |
H.264 (AVCHD) YUV 4:4:4 |
H.264 (AVCHD) Lossless |
H.265 (HEVC) 4K YUV 4:2:0 |
H.265 (HEVC) 4K YUV 4:4:4 |
H.265 (HEVC) 4K Lossless |
H.265 (HEVC) 8k |
HEVC10-bitsupport |
HEVCB Framesupport |
- 解码格式
MPEG-1 |
MPEG-2 |
VC-1 |
VP8 |
VP9 |
H.264 (AVCHD) |
H.265 (HEVC) 4:2:0 |
*H.265 (HEVC) 4:4:4 |
8 bit |
10 bit |
12 bit |
8 bit |
10 bit |
12 bit |
8 bit |
10 bit |
12 bit |
原始视频信息如下表所示。
视频信息 |
数据 |
时长 |
2分05秒 |
码率 |
4085 Kb/s |
视频流信息 |
h264 (High), yuv420p (progressive), 1920x1080 [SAR 1:1 DAR 16:9], 25 fps, 25 tbr,
1k tbn, 50 tbc
|
音视频信息 |
aac (LC), 44100 Hz, stereo, fltp |
CPU和GPU的测试机器指标如下表所示。
对比项 |
CPU机型 |
GPU机型 |
CPU |
CPU Xeon® Platinum 8163 4C |
CPU Xeon® Platinum 8163 4C |
RAM |
16 GB |
16 GB |
GPU |
N/A |
T4 |
FFmpeg |
git-2020-08-12-1201687 |
git-2020-08-12-1201687 |
视频转码(1∶1)
性能测试:1路输入、1路输出
分辨率 |
CPU转码耗时 |
GPU转码耗时 |
H264∶1920x1080 (1080p) (Full HD) |
3分19.331秒 |
0分9.399秒 |
H264∶1280x720 (720p) (Half HD) |
2分3.708秒 |
0分5.791秒 |
H264∶640x480 (480p) |
1分1.018秒 |
0分5.753秒 |
H264∶480x360 (360p) |
44.376秒 |
0分5.749秒 |
视频转码(1∶N)
性能测试:1路输入、3路输出
分辨率 |
CPU转码耗时 |
GPU转码耗时 |
H264∶1920x1080 (1080p) (Full HD) |
5分58.696秒 |
0分45.268秒 |
H264∶1280x720 (720p) (Half HD) |
H264∶640x480 (480p) |
转码命令
- CPU转码命令
- 单路转码(1∶1)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg -y -i input.mp4 -c:v libx264 -vf scale=1920:1080 -b:v 5M output.mp4
- 多路转码(1∶N)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -i input.mp4 \
-c:a copy -c:v libx264 -vf scale=1920:1080 -b:v 5M output_1080.mp4 \
-c:a copy -c:v libx264 -vf scale=1280:720 -b:v 5M output_720.mp4 \
-c:a copy -c:v libx264 -vf scale=640:480 -b:v 5M output_480.mp4
表 1. 参数说明
参数 |
说明 |
-c:a copy |
无需任何重新编码即可复制音频流。 |
-c:v h264 |
为输出流选择软件H.264编码器。 |
-b:v 5M |
将输出比特率设置为5 Mb/s。 |
- GPU转码命令
- 单路转码(1∶1)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg -y -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 -c:v h264_nvenc -vf scale_cuda=1920:1080:1:4 -b:v 5M output.mp4
- 多路转码(1∶N)
docker run --rm -it --volume $PWD:/workspace --runtime=nvidia willprice/nvidia-ffmpeg \
-y -hwaccel cuda -hwaccel_output_format cuda -i input.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=1920:1080 -b:v 5M output_1080.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=1280:720 -b:v 5M output_720.mp4 \
-c:a copy -c:v h264_nvenc -vf scale_npp=640:480 -b:v 5M output_480.mp4
表 2. 参数说明
参数 |
说明 |
-hwaccel cuda |
选择合适的硬件加速器。 |
-hwaccel_output_format cuda |
将解码的帧保存在GPU内存中。 |
-c:v h264_nvenc |
选择NVIDIA硬件加速H.264编码器。 |
通过Serverless Devs部署GPU应用
前提条件
操作步骤
- 创建项目。
s init devsapp/start-fc-custom-container-event-python3.9 -d fc-gpu-prj
创建的项目目录如下所示。
fc-gpu-prj
├── code
│ ├── app.py # 函数代码
│ └── Dockerfile # Dockerfile:将代码打包成镜像的Dockerfile
├── README.md
└── s.yaml # 项目配置:包含了镜像如何部署在函数计算
- 进入项目所在目录。
- 按实际情况修改目录文件的参数配置。
- 编辑s.yaml文件。
YAML文件的参数详解,请参见YAML规范。
edition: 1.0.0
name: container-demo
access: default
vars:
region: cn-shenzhen
services:
customContainer-demo:
component: devsapp/fc
props:
region: ${vars.region}
service:
name: tgpu_ffmpeg_service
internetAccess: true
function:
name: tgpu_ffmpeg_func
description: test gpu for ffmpeg
handler: not-used
timeout: 600
caPort: 9000
memorySize: 16384
gpuMemorySize: 8192
instanceType: g1
runtime: custom-container
customContainerConfig:
# 1. 请检查阿里云ACR容器镜像仓库已提前创建相应的命名空间(namespace:demo)与仓库(repo:gpu-transcoding_s)。
# 2. 后续更新函数时,请修改此处的tag,由v0.1修改为v0.2后,重新执行s build && s deploy。
image: registry.cn-shanghai.aliyuncs.com/demo/gpu-transcoding_s:v0.1
codeUri: ./code
- 编辑app.py文件。
示例如下:
# -*- coding: utf-8 -*-
# python2 and python3
from __future__ import print_function
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import sys
import logging
import os
import time
import urllib.request
import subprocess
class Resquest(BaseHTTPRequestHandler):
def download(self, url, path):
print("enter download:", url)
f = urllib.request.urlopen(url)
with open(path, "wb") as local_file:
local_file.write(f.read())
def upload(self, url, path):
print("enter upload:", url)
headers = {
'Content-Type': 'application/octet-stream',
'Content-Length': os.stat(path).st_size,
}
req = urllib.request.Request(url, open(path, 'rb'), headers=headers, method='PUT')
urllib.request.urlopen(req)
def trans(self, input_path, output_path, enable_gpu):
print("enter trans input:", input_path, " output:", output_path, " enable_gpu:", enable_gpu)
cmd = ['ffmpeg', '-y', '-i', input_path, "-c:a", "copy", "-c:v", "h264", "-b:v", "5M", output_path]
if enable_gpu:
cmd = ["ffmpeg", "-y", "-hwaccel", "cuda", "-hwaccel_output_format", "cuda", "-i", input_path, "-c:v", "h264_nvenc", "-b:v", "5M", output_path]
try:
subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
except subprocess.CalledProcessError as exc:
print('\nreturncode:{}'.format(exc.returncode))
print('\ncmd:{}'.format(exc.cmd))
print('\noutput:{}'.format(exc.output))
print('\nstderr:{}'.format(exc.stderr))
print('\nstdout:{}'.format(exc.stdout))
def trans_wrapper(self, enable_gpu):
src_url = "https://your.domain/input.mp4" # 需替换为您个人阿里云账号下的OSS,且您有可读写的权限。
dst_url = "https://your.domain/output.flv" # 需替换为您个人阿里云账号下的OSS,且您有可读写的权限。
src_path = "/tmp/input_c.flv"
dst_path = "/tmp/output_c.mp4"
if enable_gpu:
src_url = "https://your.domain/input.mp4" # 需替换为您个人账号下的OSS,且您有可读写的权限。
dst_url = "https://your.domain/output.flv" # 需替换为您个人账号下的OSS,且您有可读写的权限。
src_path = "/tmp/input_g.flv"
dst_path = "/tmp/output_g.mp4"
local_time = time.time()
self.download(src_url, src_path)
download_time = time.time() - local_time
local_time = time.time()
self.trans(src_path, dst_path, enable_gpu)
trans_time = time.time() - local_time
local_time = time.time()
self.upload(dst_url, dst_path)
upload_time = time.time() - local_time
data = {'result':'ok', 'download_time':download_time, 'trans_time':trans_time, 'upload_time':upload_time}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def pong(self):
data = {"function":"trans_gpu"}
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def dispatch(self):
mode = self.headers.get('TRANS-MODE')
if mode == "ping":
self.pong()
elif mode == "gpu":
self.trans_wrapper(True)
elif mode == "cpu":
self.trans_wrapper(False)
else:
self.pong()
def do_GET(self):
self.dispatch()
def do_POST(self):
self.dispatch()
if __name__ == '__main__':
host = ('0.0.0.0', 9000)
server = HTTPServer(host, Resquest)
print("Starting server, listen at: %s:%s" % host)
server.serve_forever()
- 编辑Dockerfile文件。
示例如下:
FROM registry.cn-shanghai.aliyuncs.com/serverless_devs/nvidia-ffmpeg:latest
WORKDIR /usr/src/app
RUN apt-get update --fix-missing
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
COPY . .
ENTRYPOINT [ "python3", "-u", "/usr/src/app/app.py" ]
EXPOSE 9000
- 构建镜像。
s build --dockerfile ./code/Dockerfile
- 部署代码至函数计算。
s deploy
说明 服务名称和函数名称不变,重复执行以上命令时,请选择本地配置,即use local
。
- 配置预留模式的实例。
s provision put --target 1 --qualifier LATEST
- 查询预留模式的实例是否就绪。
s provision get --qualifier LATEST
如果查询到current
参数为1,则说明GPU实例的预留模式已就绪,示例如下。
[2021-12-14 08:45:24] [INFO] [S-CLI] - Start ...
[2021-12-14 08:45:24] [INFO] [FC] - Getting provision: tgpu_ffmpeg_service.LATEST/tgpu_ffmpeg_func
customContainer-demo:
serviceName: tgpu_ffmpeg_service
functionName: tgpu_ffmpeg_func
qualifier: LATEST
resource: 188077086902****#tgpu_ffmpeg_service#LATEST#tgpu_ffmpeg_func
target: 1
current: 1
scheduledActions: (empty array)
targetTrackingPolicies: (empty array)
currentError:
- 调用函数。
- 查看线上函数版本
s invoke
- 使用CPU进行转码
s invoke -e '{"method":"GET","headers":{"TRANS-MODE":"cpu"}}'
- 使用GPU进行转码
s invoke -e '{"method":"GET","headers":{"TRANS-MODE":"gpu"}}'
- 释放GPU实例。
s provision put --target 0 --qualifier LATEST
通过函数计算控制台部署GPU应用
- 部署镜像。
- 建容器镜像服务的企业版实例或个人版实例。
- 创建命名空间和镜像仓库。
- 在容器镜像服务控制台,根据界面提示完成Docker相关操作步骤。然后将上述示例app.py和Dockerfile推送至实例镜像仓库,文件信息,请参见通过ServerlessDevs部署GPU应用时/code目录中的app.py和Dockerfile。
- 创建服务。具体操作步骤,请参见创建服务。
- 创建函数。具体操作步骤,请参见创建Custom Container函数。
说明 实例类型选择GPU实例,请求处理程序类型选择处理 HTTP 请求。
- 修改函数的执行超时时间。
- 单击目标服务下目标函数右侧操作列的配置。
- 在环境信息区域,修改执行超时时间,然后单击保存。
说明 CPU转码耗时会超过默认的60s,因此建议您修改执行超时时间为较大的值。
- 配置GPU预留实例。
- 在函数详情页面,单击弹性管理页签,然后单击创建规则。
- 在创建弹性伸缩限制规则页面,按需配置参数,预留GPU实例,然后单击创建。
配置完成后,您可以在规则列表查看预留的GPU实例是否就绪。即当前预留实例数是否为设置的预留实例数。
- 使用cURL测试函数。
- 在函数详情页面,单击触发器管理页签,查看触发器的配置信息,获取触发器的访问地址。
- 在命令行执行如下命令,调用GPU函数。
- 查看线上函数版本
curl -v "https://tgpu-ff-console-tgpu-ff-console-ajezot****.cn-shenzhen.fcapp.run"
{"function": "trans_gpu"}
- 使用CPU进行转码
curl "https://tgpu-ff-console-tgpu-ff-console-ajezot****.cn-shenzhen.fcapp.run" -H 'TRANS-MODE: cpu'
{"result": "ok", "upload_time": 8.75510573387146, "download_time": 4.910430669784546, "trans_time": 105.37688875198364}
- 使用GPU进行转码
curl "https://tgpu-ff-console-tgpu-ff-console-ajezotchpx.cn-shenzhen.fcapp.run" -H 'TRANS-MODE: gpu'
{"result": "ok", "upload_time": 8.313958644866943, "download_time": 5.096682548522949, "trans_time": 8.72346019744873}
执行结果
您可通过在浏览器中访问以下域名,查看转码后的视频:https://cri-zbtsehbrr8******-registry.oss-cn-shenzhen.aliyuncs.com/output.flv
本域名仅为示例,需以实际情况为准。