2024年4月

一、Supervisor 介绍

Supervisor 是一个用 Python 编写的进程管理工具,它可以用于监控和控制类 UNIX 操作系统上的多个进程。它是一个客户端/服务器系统,其中 Supervisor 的服务器端称为 supervisord,负责启动管理的子进程、响应客户端命令、重启崩溃或退出的子进程、记录子进程的 stdout 和 stderr 输出,以及处理子进程生命周期中的事件。客户端则称为 supervisorctl,它提供了一个类 shell 的接口,允许用户通过命令行与 supervisord 服务器进程通信,以控制子进程的状态、启动和停止进程,并获取正在运行的进程列表。

Supervisor官网地址:
http://supervisord.org/

二、安装环境介绍

Linux:CentOS Linux release 7.6.1810 (Core)

Supervisor:supervisor-4.2.5

Python:Python 2.7.5

三、安装 Supervisor

1、下载 supervisor 源码包【supervisor-4.2.5.tar.gz】

2、在根目录新建一个名为【service】的目录,并把【supervisor-4.2.5.tar.gz】包移入进去

#新建目录
mkdirservice#解压supervisor源码包
tar -zxvf supervisor-4.2.5.tar.gz

3、进入supervisor目录,内容如下图:

#进入目录
cd supervisor-4.2.5

#查看目录下的文件
ll

4、安装 supervisor

#安装 supervisor
python setup.py install

5、使用帮助命令来检测是否安装成功,结果如下图:

supervisorctl --help

四、配置 Supervisor

1、创建一个文件夹,用于存储相关配置文件

mkdir /etc/supervisor

2、使用命令,创建一个【Supervisor】配置文件。

#符合(>)右侧的是配置文件的路径
echo_supervisord_conf > /etc/supervisor/supervisord.conf

3、找到 supervisord.conf 文件,修改里面的配置参数

#原配置
[unix_http_server]file=/tmp/supervisor.sock   ; the path to the socket file;chmod=0700                 ; socket file mode (default 0700)
;
chown=nobody:nogroup ; socket file uid:gid owner
;username
=user ; defaultis no username (open server)
;password
=123 ; defaultis no password (open server)#修改后的配置,原先的/tmp目录下的内容容易被系统删除,导致启动失败 [unix_http_server]file=/var/run/supervisor.sock ; the path to the socket file;chmod=0700 ; socket file mode (default 0700)
;
chown=nobody:nogroup ; socket file uid:gid owner
;username
=user ; defaultis no username (open server)
;password
=123 ; defaultis no password (open server)------------------------------------------------------------------------------ #原配置 [supervisord]
logfile
=/tmp/supervisord.log ; main log file; default $CWD/supervisord.loglogfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default50MB
logfile_backups
=10 ; #of main logfile backups; 0 means none, default 10 loglevel=info ; log level; default info; others: debug,warn,trace
pidfile
=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon
=false ; start in foreground if true; default falsesilent=false ; no logs to stdout if true; default falseminfds=1024 ; min. avail startup file descriptors; default 1024minprocs=200 ; min. avail process descriptors;default 200 #修改后的配置,修改了服务的日志文件目录,修改了supervisord.pid的存放位置 [supervisord]
logfile
=/etc/supervisor/log/supervisord.log; 避免被系统删除
logfile_maxbytes
=50MB ; max main logfile bytes b4 rotation; default50MB
logfile_backups
=10 ; #of main logfile backups; 0 means none, default 10 loglevel=info ; log level; default info; others: debug,warn,trace
pidfile
=/var/run/supervisord.pid ; 避免被系统删除
nodaemon
=false ; start in foreground if true; default falsesilent=false ; no logs to stdout if true; default falseminfds=1024 ; min. avail startup file descriptors; default 1024minprocs=200 ; min. avail process descriptors;default 200 ------------------------------------------------------------------------------ #原配置 [supervisorctl]
serverurl
=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket ;username=chris ; should be same as in [*_http_server] ifset
;password
=123 ; should be same as in [*_http_server] ifset
;prompt
=mysupervisor ; cmd line prompt (default "supervisor")
;history_file
=~/.sc_history ; use readline history ifavailable#修改后的配置,修改supervisor.sock的存放位置,避免被系统删除 [supervisorctl]
serverurl
=unix:///var/run/supervisor.sock ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket ;username=chris ; should be same as in [*_http_server] ifset
;password
=123 ; should be same as in [*_http_server] ifset
;prompt
=mysupervisor ; cmd line prompt (default "supervisor")
;history_file
=~/.sc_history ; use readline history ifavailable------------------------------------------------------------------------------ #原配置 ;[include]
;files
= relative/directory/*.ini


# 修改后的配置,设置子任务的配置文件目录
[include]
files = /etc/supervisor/conf.d/*.ini

4、保存退出后更新配置文件,如果之前没启动过服务可以不用更新

supervisorctl -c /etc/supervisor/supervisord.conf update

五、配置 Supervisor 的子任务

进入到【/etc/supervisor/conf.d】目录,创建名为【test.ini】的子任务,并写入如下内容:

#进入子任务配置文件目录
cd /etc/supervisor/conf.d#创建子任务配置文件
touch test.ini#编辑配置文件
vi test.ini#配置文件中的具体内容
[program:test]        ;要和文件名称相同
command
=/usr/local/php74/bin/php think testJob ;启动该程序时将运行的命令,我这用的是TP框架中的think命令
directory
=/www/school-task ;表示command命令的执行目录,本文用的时候TP框架,school-task为项目名称
autorestart
=true;自动重启
startsecs
=3;启动后程序需要保持运行的总秒数,以认为启动成功(将进程从STARTING状态移动到running状态)。设置为0表示程序不需要在任何特定的时间内保持运行
startretries
=3;启动失败时的最多重试次数
stdout_logfile
=/etc/supervisor/log/test.out.log     ;输出日志文件路径stderr_logfile=/etc/supervisor/log/test.err.log;错误日志文件路径
stdout_logfile_maxbytes
=2MB ;设置stdout_logfile的文件大小
stderr_logfile_maxbytes
=2MB ;设置stderr_logfile的文件大小
user
=root ;指定运行的用户
priority
=999;程序在启动和关闭顺序中的相对优先级
numprocs
=1process_name=%(program_name)s_%(process_num)02d

六、supervisord相关操作命令

#启动supervisord
supervisord -c /etc/supervisor/supervisord.conf#关闭supervisord
supervisorctl -c /etc/supervisor/supervisord.conf shutdown#查看supervisord管理的进程,【all】是可选参数,表示查看所有的子任务
supervisorctl -c /etc/supervisor/supervisord.conf status all#重新加载配置
supervisorctl -c /etc/supervisor/supervisord.conf update#重新启动supervisord
supervisorctl -c /etc/supervisor/supervisord.conf reload

#停止supervisord管理的进程,【process_name】表示子任务名称 supervisorctl -c /etc/supervisor/supervisord.conf stop process_name#启动supervisord管理的进程,【process_name】表示子任务名称 supervisorctl -c /etc/supervisor/supervisord.conf start process_name

七、添加supervisord的systemctl服务命令和开机自启

1、新建一个服务文件【/usr/lib/systemd/system/supervisord.service】,写入以下内容:

[Unit]
Description
=Supervisor daemon

[Service]
#表示systemd应该以forking模式启动服务。在此模式下,systemd会启动一个父进程,然后父进程会fork出一个或者多个子进程。 Type=forking
ExecStart
=/usr/bin/supervisord -c /etc/supervisor/supervisord.conf
ExecStop
=/usr/bin/supervisorctl -c /etc/supervisor/supervisord.conf shutdown
ExecReload
=/usr/bin/supervisorctl -c /etc/supervisor/supervisord.conf reload
ExecStatus
=/usr/bin/supervisorctl -c /etc/supervisor/supervisord.conf status
ExecRestart
=/usr/bin/supervisorctl -c /etc/supervisor/supervisord.conf restart#表示当systemd需要停止服务时,它会发送信号到服务的主进程。如果服务有子进程,那么子进程不会收到这个信号。 KillMode=process#表示当服务非正常退出时(例如收到SIGKILL信号),systemd会尝试重启服务。 Restart=on-failure#如果服务失败了,systemd会在尝试重启服务前等待30秒 RestartSec=30s#指定运行服务的用户 User=root

[Install]
WantedBy
=multi-user.target

2、重新加载配置文件

systemctl daemon-reload

3、配置系统开机启动

systemctl enable supervisord.service

4、systemctl 操作命令

#启动supervisord
systemctl start supervisord.service#查看supervisord状态
systemctl status supervisord.service#重启supervisord
systemctl restart supervisord.service#停止supervisord
systemctl stop supervisord.service#重载配置
systemctl reload supervisord.service

八、结果展示

TensorRT C# API 项目介绍:基于C#与TensorRT部署深度学习模型

1. 项目介绍

NVIDIA® TensorRT™ 是一款用于高性能深度学习推理的 SDK,包括深度学习推理优化器和运行时,可为推理应用程序提供低延迟和高吞吐量。基于 NVIDIA TensorRT 的应用程序在推理过程中的执行速度比纯 CPU 平台快 36 倍,使您能够优化在所有主要框架上训练的神经网络模型,以高精度校准低精度,并部署到超大规模数据中心、嵌入式平台或汽车产品平台。

TensorRT 基于 NVIDIA CUDA® 并行编程模型构建,使您能够在 NVIDIA GPU 上使用量化、层和张量融合、内核调整等技术来优化推理。TensorRT 提供 INT8 使用量化感知训练和训练后量化和浮点 16 (FP16) 优化,用于部署深度学习推理应用程序,例如视频流、推荐、欺诈检测和自然语言处理。低精度推理可显著降低延迟,这是许多实时服务以及自主和嵌入式应用所必需的。TensorRT 与 PyTorch 和 TensorFlow 集成,因此只需一行代码即可实现 6 倍的推理速度。TensorRT 提供了一个 ONNX 解析器,因此您可以轻松地将 ONNX 模型从常用框架导入 TensorRT。它还与 ONNX 运行时集成,提供了一种以 ONNX 格式实现高性能推理的简单方法。

基于这些优势,TensorRT目前在深度模型部署应用越来越广泛。但是TensorRT目前只提供了C++与Python接口,对于跨语言使用十分不便。目前C#语言已经成为当前编程语言排行榜上前五的语言,也被广泛应用工业软件开发中。为了能够实现在C#中调用TensorRT部署深度学习模型,我们在之前的开发中开发了TensorRT C# API。虽然实现了该接口,但由于数据传输存在问题,当时开发的版本在应用时存在较大的问题。

基于此,我们开发了TensorRT C# API 2.0版本,该版本在开发时充分考虑了上一版本应用时出现的问题,并进行了改进。同时在本版本中,我们对接口进行了优化,使用起来更加简单,并同时提供了相关的应用案例,方便开发者进行使用。

  • TensorRT C# API 项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API.git
  • TensorRT C# API 项目应用源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples.git

2. 接口介绍

下面简单介绍一下该项目封装的接口:

  • class Nvinfer

  • 模型推理类:
    该类主要是封装了转换后的接口,用户可以直接调用该类进行初始化推理引擎。

  • **public static void OnnxToEngine(string modelPath, int memorySize) **


    • 模型转换接口:
      可以调用封装的TensorRT中的ONNX 解释器,对ONNX模型进行转换,并根据本机设备信息,编译本地模型,将模型转换为TensorRT 支持的engine格式。
    • string modelPath:
      本地ONNX模型地址,只支持ONNX格式,且ONNX模型必须为确定的输入输出,暂不支持动态输入。
    • int memorySize:
      模型转换时分配的内存大小
  • **public Nvinfer(string modelPath) **


    • Nvinfer 初始化接口:
      初始化Nvinfer类,主要初始化封装的推理引擎,该推理引擎中封装了比较重要的一些类和指针。
    • string modelPath:
      engine模型路径。
  • **public Dims GetBindingDimensions(int index)/GetBindingDimensions(string nodeName) **


    • 获取节点维度接口:
      通过端口编号或者端口名称,获取绑定的端口的形状信息.
    • int index:
      绑定端口的编号
    • string nodeName:
      绑定端口的名称
    • return Dims:
      接口返回一个
      Dims
      结构体,该结构体包含了节点的维度大小以及每个维度的具体大小。
  • public void LoadInferenceData(string nodeName, float[] data)/LoadInferenceData(int nodeIndex, float[] data)


    • 加载待推理数据接口:
      通过端口编号或者端口名称,将处理好的带推理数据加载到推理通道上。
    • string nodeName:
      待加载推理数据端口的名称。
    • **int nodeIndex: **待加载推理数据端口的编号。
    • float[] data:
      处理好的待推理数据,由于目前使用的推理数据多为float类型,因此此处目前只做了该类型接口。
  • public void infer()


    • 模型推理接口:
      调用推理接口,对加载到推理通道的数据进行推理。
  • public float[] GetInferenceResult(string nodeName)/GetInferenceResult(int nodeIndex)


    • 获取推理结果:
      通过端口编号或者端口名称,读取推理好的结果数据。
    • string nodeName:
      推理结果数据端口的名称。
    • **int nodeIndex: **推理结果数据端口的编号。
    • return float[]:
      返回值为指定节点的推理结果数据。

3. 安装流程

下面演示一下安装方式,下文所有演示都是基于以下环境进行配置的:

  • 操作系统:Windows 11

  • 编译平台:Visual Studio 2022

  • 显卡型号:RTX 2060

  • CUDA型号:12.2

  • Cudnn:8.9.3

  • TensorRT:8.6.1.6

对于CUDA以及Cudnn的安装此处不再作过多演示,大家可以自行安装。

3.1 TensorRT安装

首先确定安装的CUDA版本,在命令提示符中输入
nvcc -V
指令,便可以获取安装的CUDA版本。

接下来访问
TensorRT Download | NVIDIA Developer
下载安装包,此处安装8.x系列,安装最新的版本8.6,如下图所示,通过下载Zip文件进行安装,大家可以根据自己的CUDN版本进行下载。

由于下载的是编译好的文件,因此在下载完成后,大家可以将其解压到常用的安装目录,然后将路径添加到环境变量即可,下面路径为本机安装的TensorRT路径,将其添加到本机的
Path
环境变量中。

D:\Program Files\TensorRT\TensorRT-8.6.1.6\lib

3.2 下载项目源码

由于CUDA以及TensorRT程序集文件较大,无法打包成NuGet Package,因此需要用户自己进行编译。

首先第一步下载项目源码,使用Git命令将源码下载到本地,如下图所示

git clone https://github.com/guojin-yan/TensorRT-CSharp-API.git

然后使用
Visual Studio 2022
打开解决方案文件,如下图所示:

该解决方案中包含两个项目,一个是C++项目,该项目是封装的TensorRT接口,将接口封装到动态链接库中;另一个是C#项目,该项目是读取动态链接库中的C++接口,然后重新封装该接口。

3.3 配置C++项目

接下来配置C++项目,主要分为以下几步:

第一步:设置项目输出类型

C++项目主要用于生成动态链接库文件,因此将配置类型改为
动态库(.dll)

第二部:设置包含目录

当前C++项目主要使用两个依赖库,主要是CUDA(CUDNN)以及TensorRT,因此此处主要配置这两个依赖项,用户根据自己的安装情况进行配置即可。

以下是本设备CUDA(CUDNN)以及TensorRT的包含目录位置,用户在使用时这需要根据自己本机安装情况进行设置即可。

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2\include
D:\Program Files\TensorRT\TensorRT-8.6.1.6\include

第三步:设置库目录

下面设置安装的CUDA(CUDNN)以及TensorRT的库目录情况,如下图所示。

以下是本设备CUDA(CUDNN)以及TensorRT的库目录位置,用户在使用时这需要根据自己本机安装情况进行设置即可。

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.2\lib\x64
D:\Program Files\TensorRT\TensorRT-8.6.1.6\lib

第四步:设置附加依赖项

下面设置附加依赖项,附加依赖项文件在上一步设置的库目录路径中,此处主要添加
.lib
文件。

以下是CUDA(CUDNN)以及TensorRT的附加依赖项目录,但不同版本可能会不一致,因此用户在使用时需要根据自己的安装情况进行设置。

cublas.lib
cublasLt.lib
cuda.lib
cudadevrt.lib
cudart.lib
cudart_static.lib
cudnn.lib
cudnn64_8.lib
cudnn_adv_infer.lib
cudnn_adv_infer64_8.lib
cudnn_adv_train.lib
cudnn_adv_train64_8.lib
cudnn_cnn_infer.lib
cudnn_cnn_infer64_8.lib
cudnn_cnn_train.lib
cudnn_cnn_train64_8.lib
cudnn_ops_infer.lib
cudnn_ops_infer64_8.lib
cudnn_ops_train.lib
cudnn_ops_train64_8.lib
cufft.lib
cufftw.lib
cufilt.lib
curand.lib
cusolver.lib
cusolverMg.lib
cusparse.lib
nppc.lib
nppial.lib
nppicc.lib
nppidei.lib
nppif.lib
nppig.lib
nppim.lib
nppist.lib
nppisu.lib
nppitc.lib
npps.lib
nvblas.lib
nvJitLink.lib
nvJitLink_static.lib
nvjpeg.lib
nvml.lib
nvptxcompiler_static.lib
nvrtc-builtins_static.lib
nvrtc.lib
nvrtc_static.lib
OpenCL.lib
nvinfer.lib
nvinfer_dispatch.lib
nvinfer_lean.lib
nvinfer_plugin.lib
nvinfer_vc_plugin.lib
nvonnxparser.lib
nvparsers.lib

第五步:设置预处理器

由于项目中应用了一些不安全方法,所以需要在宏定义中添加以下定义,如下图所示:

_CRT_SECURE_NO_WARNINGS

第六步:编译项目源码

接下来生成C++项目,此处选择
生成
,不要选择运行,如下图所示:

最终可以看出生成的动态链接库文件名称以及文件路径,这个路径在下一步中十分重要。

3.4 编译C#项目

接下来编译C#项目,C#项目只需要修改一下位置即可,修改
NativeMethods.cs
文件中的dll文件路径,该路径及上一步中C++项目生成的动态链接库文件,如下图所示:

E:\GitSpace\TensorRT-CSharp-API\x64\Release\TensorRtExtern.dll

接下来就可以运行C#项目,生成类库文件,如下图所示:

4. 接口调用

4.1 创建并配置C#项目

首先创建一个简单的C#项目,然后添加项目配置。

首先是添加TensorRT C# API 项目引用,如下图所示,添加上文中C#项目生成的dll文件即可。

接下来添加OpenCvSharp,此处通过NuGet Package安装即可,此处主要安装以下两个程序包即可:

配置好项目后,项目的配置文件如下所示:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>TensorRT_CSharp_API_demo</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="OpenCvSharp4.Extensions" Version="4.9.0.20240103" />
    <PackageReference Include="OpenCvSharp4.Windows" Version="4.9.0.20240103" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="TensorRtSharp">
      <HintPath>E:\GitSpace\TensorRT-CSharp-API\src\TensorRtSharp\bin\Release\net6.0\TensorRtSharp.dll</HintPath>
    </Reference>
  </ItemGroup>

</Project>

4.2 添加推理代码

此处演示一个简单的图像分类项目,以Yolov8-cls项目为例:

static void Main(string[] args)
{
    Nvinfer predictor = new Nvinfer("E:\\Model\\yolov8\\yolov8s-cls_2.engine");
    Dims InputDims = predictor.GetBindingDimensions("images");
    int BatchNum = InputDims.d[0];

    Mat image1 = Cv2.ImRead("E:\\ModelData\\image\\demo_4.jpg");
    Mat image2 = Cv2.ImRead("E:\\ModelData\\image\\demo_5.jpg");

    List<Mat> images = new List<Mat>() { image1, image2 };
    for (int begImgNo = 0; begImgNo < images.Count; begImgNo += BatchNum)
    {
        DateTime start = DateTime.Now;
        int endImgNo = Math.Min(images.Count, begImgNo + BatchNum);
        int batchNum = endImgNo - begImgNo;
        List<Mat> normImgBatch = new List<Mat>();
        int imageLen = 3 * 224 * 224;
        float[] inputData = new float[2 * imageLen];
        for (int ino = begImgNo; ino < endImgNo; ino++)
        {
            Mat input_mat = CvDnn.BlobFromImage(images[ino], 1.0 / 255.0, new OpenCvSharp.Size(224, 224), 0, true, false);
            float[] data = new float[imageLen];
            Marshal.Copy(input_mat.Ptr(0), data, 0, imageLen);
            Array.Copy(data, 0, inputData, ino * imageLen, imageLen);
        }
        predictor.LoadInferenceData("images", inputData);

        DateTime end = DateTime.Now;
        Console.WriteLine("[ INFO ] Input image data processing time: " + (end - start).TotalMilliseconds + " ms.");
        predictor.infer();
        start = DateTime.Now;
        predictor.infer();
        end = DateTime.Now;
        Console.WriteLine("[ INFO ] Model inference time: " + (end - start).TotalMilliseconds + " ms.");
        start = DateTime.Now;

        float[] outputData = predictor.GetInferenceResult("output0");
        for (int i = 0; i < batchNum; ++i)
        {
            Console.WriteLine(string.Format("\n[ INFO ] Classification Top {0} result : \n", 10));
            Console.WriteLine("[ INFO ] classid probability");
            Console.WriteLine("[ INFO ] ------- -----------");
            float[] data = new float[1000];
            Array.Copy(outputData, i * 1000, data, 0, 1000);
            List<int> sortResult = Argsort(new List<float>(data));
            for (int j = 0; j < 10; ++j)
            {
                string msg = "";
                msg += ("index: " + sortResult[j] + "\t");
                msg += ("score: " + data[sortResult[j]] + "\t");
                Console.WriteLine("[ INFO ] " + msg);
            }
        }
        end = DateTime.Now;
        Console.WriteLine("[ INFO ] Inference result processing time: " + (end - start).TotalMilliseconds + " ms.");
    }


}

public static List<int> Argsort(List<float> array)
{
    int arrayLen = array.Count;
    List<float[]> newArray = new List<float[]> { };
    for (int i = 0; i < arrayLen; i++)
    {
        newArray.Add(new float[] { array[i], i });
    }
    newArray.Sort((a, b) => b[0].CompareTo(a[0]));
    List<int> arrayIndex = new List<int>();
    foreach (float[] item in newArray)
    {
        arrayIndex.Add((int)item[1]);
    }
    return arrayIndex;
}

4.3 项目演示

配置好项目并编写好代码后,运行该项目,项目输出如下所示:

[03/31/2024-22:27:44] [I] [TRT] Loaded engine size: 15 MiB
[03/31/2024-22:27:44] [I] [TRT] [MemUsageChange] TensorRT-managed allocation in engine deserialization: CPU +0, GPU +12, now: CPU 0, GPU 12 (MiB)
[03/31/2024-22:27:44] [I] [TRT] [MemUsageChange] TensorRT-managed allocation in IExecutionContext creation: CPU +0, GPU +4, now: CPU 0, GPU 16 (MiB)
[03/31/2024-22:27:44] [W] [TRT] CUDA lazy loading is not enabled. Enabling it can significantly reduce device memory usage and speed up TensorRT initialization. See "Lazy Loading" section of CUDA documentation https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#lazy-loading
[ INFO ] Input image data processing time: 6.6193 ms.
[ INFO ] Model inference time: 1.1434 ms.

[ INFO ] Classification Top 10 result :

[ INFO ] classid probability
[ INFO ] ------- -----------
[ INFO ] index: 386     score: 0.87328124
[ INFO ] index: 385     score: 0.082506955
[ INFO ] index: 101     score: 0.04416279
[ INFO ] index: 51      score: 3.5818E-05
[ INFO ] index: 48      score: 4.2115275E-06
[ INFO ] index: 354     score: 3.5188648E-06
[ INFO ] index: 474     score: 5.789438E-07
[ INFO ] index: 490     score: 5.655325E-07
[ INFO ] index: 343     score: 5.1091644E-07
[ INFO ] index: 340     score: 4.837259E-07

[ INFO ] Classification Top 10 result :

[ INFO ] classid probability
[ INFO ] ------- -----------
[ INFO ] index: 293     score: 0.89423335
[ INFO ] index: 276     score: 0.052870292
[ INFO ] index: 288     score: 0.021361532
[ INFO ] index: 290     score: 0.009259541
[ INFO ] index: 275     score: 0.0066174944
[ INFO ] index: 355     score: 0.0025512716
[ INFO ] index: 287     score: 0.0024535337
[ INFO ] index: 210     score: 0.00083151844
[ INFO ] index: 184     score: 0.0006893527
[ INFO ] index: 272     score: 0.00054959994

通过上面输出可以看出,模型推理仅需1.1434ms,大大提升了模型的推理速度。

5. 总结

在本项目中,我们开发了TensorRT C# API 2.0版本,重新封装了推理接口。并结合分类模型部署流程向大家展示了TensorRT C# API 的使用方式,方便大家快速上手使用。

为了方便各位开发者使用,此处开发了配套的演示项目,主要是基于Yolov8开发的目标检测、目标分割、人体关键点识别、图像分类以及旋转目标识别,由于时间原因,还未开发配套的技术文档,此处先行提供给大家项目源码,大家可以根据自己需求使用:

  • Yolov8 Det 目标检测项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples/blob/master/model_samples/yolov8_custom/Yolov8Det.cs
  • Yolov8 Seg 目标分割项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples/blob/master/model_samples/yolov8_custom/Yolov8Seg.cs
  • Yolov8 Pose 人体关键点识别项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples/blob/master/model_samples/yolov8_custom/Yolov8Pose.cs
  • Yolov8 Cls 图像分类项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples/blob/master/model_samples/yolov8_custom/Yolov8Cls.cs
  • Yolov8 Obb 旋转目标识别项目源码:
https://github.com/guojin-yan/TensorRT-CSharp-API-Samples/blob/master/model_samples/yolov8_custom/Yolov8Obb.cs

同时对本项目开发的案例进行了时间测试,以下时间只是程序运行一次的时间,测试环境为:

  • CPU:i7-165G7

  • CUDA型号:12.2

  • Cudnn:8.9.3

  • TensorRT:8.6.1.6

Model Batch 数据预处理 模型推理 结果后处理
Yolov8s-Det 2 25 ms 7 ms 20 ms
Yolov8s-Obb 2 49 ms 15 ms 32 ms
Yolov8s-Seg 2 23 ms 8 ms 128 ms
Yolov8s-Pose 2 27 ms 7 ms 20 ms
Yolov8s-Cls 2 16 ms 1 ms 3 ms

最后如果各位开发者在使用中有任何问题,欢迎大家与我联系。


背景


在高并发的业务场景中,因为MySQL数据库是操作磁盘效率比较低,因此大多数情况下数据库都是高并发系统的瓶颈。因为Redis操作数据是在内存中进行,所以就需要使用Redis做一个缓存。让请求先访问到Redis,而不是直接访问MySQL数据库。效果图如下

Untitled


查询数据


上面的业务场景,就是一个典型的MySQL存储数据和Redis缓存数据的业务场景。下面来看看一般的查询流程,如下图:

Untitled

上面的查询流程如下:

  1. 用户请求系统,系统先查询Redis中是否有数据?
  2. 如果Redis中有数据,则直接将缓存中的数据响应给用户。
  3. 如果Redis中没有数据,则取查询MySQL数据库中是否有数据?
  4. 如果MySQL中有数据,则先将数据更新到Redis中,再将数据响应给用户。
  5. 如果MySQL中没有数据,则响应空数据给用户,请求结束。

上面的查询流程很简单,通过先查Redis缓存,避免大量请求访问MySQL数据库,从而大大提高系统响应效率。而且如果Redis中没有而MySQL中有,当从MySQL拿到数据以后,先将数据更新到Redis缓存中,这样下次请求同一份数据的时候就能从Redis中获取了。


更新数据


上面的查询流程是没什么问题,可是数据很有可能会更新。那么更新的时候怎么操作才能保证Redis和MySQL中的数据都更新成功并且一致呢?

下面看看一种常用的解决方案(双删缓存):

Untitled

第一个问题:为什么是删除Redis缓存数据而不是更新数据?

假设我们缓存的数据是要做一个很复杂的计算,而且还不一定能用到。那如果你更新MySQL数据之后去更新Redis缓存不是就很耗时了,而且有可能做无用功。

第二个问题:为什么是先删除Redis缓存数据而不是先更新数据库呢?

我们不妨假设先更新MySQL中的数据,然后再删除缓存。如果更新完MySQL但是删除Redis失败了(别问为什么会失败?系统故障行不行,全球断电行不行?),那下次查询请求过来,因为Redis中有缓存数据,所以直接返回Redis缓存的旧数据了,是不是就出问题了?

那我们再看看,如果先删除Redis缓存,再更新MySQL。如果删除完Redis成功,但是更新MySQL失败。下次查询的时候,查询到Redis缓存发现没有,再去查MySQL,然后更新到Redis,虽然MySQL更新失败了,但是Redis中的数据和MySQL是一致的。

第三个问题:为什么更新完MySQL后还要再删一次Redis缓存呢?

假设我们第一次删完Redis结束,正在更新MySQL但是还没更新成功的时候,这时候有另外一个请求来查询数据。第二个请求查询Redis没有,然后查询MySQL这时候因为MySQL还没更新完,所以查询到的还是旧数据,同时把旧数据更新到Redis中了,等下一个请求再来查询的时候发现Redis有数据,就直接返回旧数据了。

因此更新完MySQL后需要再次删除Redis缓存。这样即使更新数据中间有其他个请求把旧数据更新到Redis中了,因为再次删了Redis缓存中的旧数据,依然能够避免其他请求获取到旧数据。

我们认为数据是否更新成功是以MySQL中的数据为准,因此MySQL还没更新完成前或者更新失败,获取到旧数据不算是问题。所以我们只要保证Redis和MySQL中的数据一致就行。

PrimiHub
一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。

随着信息时代的发展,个人数据的收集和处理已经成为了许多行业和领域的核心活动之一。然而,随之而来的数据隐私和安全问题也日益突出。个人的敏感信息可能被滥用或泄露,这给个人隐私带来了严重的威胁。在这样的背景下,隐私计算作为一种新兴的技术手段,正在成为解决这一问题的关键利器。

什么是隐私计算?

隐私计算是一种能够在不暴露个体私密数据的情况下进行数据处理和分析的计算模式。其核心目标是在保护数据隐私的前提下,实现有效的数据共享、处理和分析。简言之,隐私计算通过加密、匿名化等技术手段,使得数据在进行计算时仍然保持隐私不被泄露。

隐私计算的关键技术

加密技术

加密是隐私计算的基础。通过对数据进行加密处理,可以在不暴露原始数据的情况下进行计算。常见的加密技术包括对称加密、非对称加密和同态加密等。其中,同态加密尤为重要,因为它允许在加密数据的同时进行计算,而无需解密数据。

差分隐私

差分隐私是一种保护个体数据隐私的技术。它通过在数据中引入噪声,使得在数据集中添加或删除一条记录后,对最终结果的影响微乎其微,从而保护了个体数据的隐私。差分隐私的核心思想是通过模糊化个体数据,使得攻击者无法通过分析结果推断出个体的具体信息。

安全多方计算

安全多方计算允许多个参与方在不泄露各自私密输入的情况下,对这些输入进行计算,并得到计算结果。这种技术确保了参与方之间的数据隐私性,即使其中某些参与方是不信任的。安全多方计算的实现需要借助密码学的方法,如零知识证明、秘密共享等。

隐私计算的应用场景

医疗健康

在医疗健康领域,隐私计算可以用于医疗数据的共享与分析。医疗机构可以通过隐私计算技术,在不泄露患者隐私的前提下,进行疾病预测、流行病分析等工作。例如,多个医院可以合作进行癌症患者的基因组学研究,而无需共享患者的敏感基因数据。

金融领域

在金融领域,隐私计算可以用于金融数据的处理与分析。银行可以通过隐私计算技术,对客户的交易数据进行分析,发现异常交易行为,保护客户资产安全。例如,银行可以利用安全多方计算技术,联合分析客户的交易数据,以检测潜在的欺诈行为,而无需泄露客户的个人交易信息。

社交网络

在社交网络领域,隐私计算可以用于保护用户的隐私信息。社交平台可以通过隐私计算技术,对用户数据进行加密处理,防止用户隐私被泄露或滥用。例如,社交平台可以使用差分隐私技术,对用户的社交行为数据进行模糊处理,以保护用户的隐私。


隐私计算作为一种保护个体数据隐私的重要技术手段,正在逐渐成为各个行业和领域的关注焦点。随着技术的不断进步和应用的不断拓展,相信隐私计算将在保护数据隐私、促进数据共享与应用方面发挥越来越重要的作用。隐私计算的发展将为个人数据的安全和隐私保护提供强有力的支撑,推动数据驱动时代的可持续发展。

PrimiHub
一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。