LLM推理框架Triton Inference Server学习笔记(二): Triton模型部署流程(stey by stey)

news2024/11/30 14:36:32

官方文档查阅: TritonInferenceServer文档

1. 写在前面

上一篇文章对triton inference server进行了一个整体的介绍,解答了三个经典问题what, why, how。 这篇文章就开始转入实践, 从实践的角度整理Triton模型部署的全流程, 如果我有一个训练好的模型了,究竟如何部署到triton server,并提供服务给到客户端, 客户端发请求之后,怎么把数据推理得到结果等。 这篇文章, 会对这些问题做出解答。

大纲如下:

  • Triton模型部署概览
  • 模型仓库准备
  • 模型配置文件编写
  • Triton Server启动
  • TritonServer客户端访问

OK, let’s go!

2. Triton模型部署概览

部署triton模型,整个流程大概是3步:

  1. 准备model repository, 包含需要served的所有模型, Triton 会loader这些模型, 根据模型提前在配置里指定好的backend运行到server端具体的cpu or GPU
  2. 客户端发送推理请求
  3. Triton调度器把请求调度到相应的instancer执行推理结果返回给client

在这里插入图片描述

整理流程看完,我们下面会进行实际操作,实际操作可以手动编译triton, 也可以使用NGC提供的镜像,里面把triton编译好了,可以基于官方的镜像安装包打成自己的镜像。

在下面实际操作之前,需要先准备环境,打出一个镜像来,可以运行后面的triton, 我这里选择了官方的编译镜像,在这个基础上安装了自己的一些包,比如pytorch, torchvision等。

这里使用的官方镜像的这个版本 nvcr.io/nvidia/tritonserver:24.01-py3, 版本这里要注意的问题,就是需要和conda, tensor rt等都匹配,如果不兼容, 后面的模型可能无法导入。

Dockerfile文件如下:

FROM nvcr.io/nvidia/tritonserver:24.01-py3
MAINTAINER wuzhongqiang <wuzhongqiang@163.com>

COPY bigmodellearning /home/work/bigmodellearning
RUN cd /home/work/bigmodellearning
RUN pip install huggingface-hub -i 可指定源
RUN pip install pandas -i 
RUN pip install torch -i 
RUN pip install torchvision -i 
RUN pip install transformers -i 
RUN pip install tritonclient[all] -i     # 得安装all的,否则不能用grpc client推理
RUN pip install pillow -i 
RUN pip install onnx -i 
RUN pip install onnxruntime-gpu -i 

这样执行命令:

docker build -t wuzhongqiang/triton_img:v1 -f /home/wuzhongqiang/PycharmHome/big_model/bigmodellearning/Dockerfile .
docker push wuzhongqiang/trition_img:v0

镜像准备好之后, 就可以进行下面的实践工作。

3. 准备模型仓库

首先,需要准备模型仓库,即把所有训练好的模型按照triton规定的格式放到一个统一的目录里面,启动triton server的时候,告诉模型这个目录, triton就会去这个目录下面去加载模型。

模型目录的格式如下:
在这里插入图片描述
模型库目录

  • 模型名字
    • config.pbtxt: 包含模型配置参数,决定served时候的具体行为
    • output-labels-file(densenet_labels.txt): 分类模型的辅助功能专属, 把分类模型输出的概率转成分类标签
    • version: 版本号
      • model-definition-file: 具体模型文件,不同格式的模型文件会有不同:
        • TensorRT: model.plan
        • Onnx: model.onnx
        • TorchScripts: model.pt
        • TensorFlow: model.graphdef or model.savedmodel
        • Python: model.py
        • DALI: model.dali
        • OpenVINO: model.xml and model.bin
        • Custom: model.so
      • 目录的名字是版本号,用于版本控制

基于上面的知识,我们准备模型库。这里我先准备3种格式的模型resnet50_torch, resnet50_trt, resnet50_onnx。

import os
import torch
from torchvision import models

class PrepareModel(object):

    @staticmethod
    def torch_model(save_dir, model):
        model.eval().cuda()
        input_ = torch.randn(1, 3, 224, 224).cuda()
        resnet50_traced = torch.jit.trace(model, input_)        # or resnet50_traced = torch.jit.script(model)
        print(model(input_).shape)      # [1, 1000]  resnet50做的1000分类
        resnet50_traced.save(f'{save_dir}/model_repo/resnet50_torch/1/model.pt')

    @staticmethod
    def onnx_model(save_dir, model):
        model.eval().cuda()
        input_ = torch.randn(1, 3, 224, 224).cuda()
        input_names = ["actual_input_1"]   # 这个名字不要变, 官方的输入名字应该是定死了, 变了之后,后面模型加载的时候会失
        output_names = ["output_1"]
        # 需要安装onnx
        # pip install onnx  -i https://pkgs.d.xiaomi.net/artifactory/api/pypi/pypi-virtual/simple(换别的源)
        # netro.app工具可以在线把模型图可视化出来 https://netron.app/
        # opset_version这里指定15, 默认会是17,triton server加载的时候会报版本过高错误
        torch.onnx.export(model, input_, f'{save_dir}/model_repo/resnet50_onnx/1/model.onnx',
                          input_names=input_names, output_names=output_names,
                          dynamic_axes={'actual_input_1': {0: 'batch_size'}, 'output_1': {0: 'batch_size'}},
                          opset_version=15
                          )

if __name__ == "__main__":

    parent_dir = os.getcwd()
    model = models.resnet50(pretrained=True)

    # 生成torch model
    print("torch model save...")
    PrepareModel.torch_model(parent_dir, model)

    # 生成onnx model
    print("onnx model save...")
    PrepareModel.onnx_model(parent_dir, model)

    # 生成tensor rt model
    # 命令行去搞

TensorRT 为inference(推理)为生,是NVIDIA研发的一款针对深度学习模型在GPU上的计算,显著提高GPU上的模型推理性能,优势:

  1. Reduced Precision:将模型量化成INT8或者FP16的数据类型(在保证精度不变或略微降低的前提下),以提升模型的推理速度。
  2. Layer and Tensor Fusion:通过将多个层结构进行融合(包括横向和纵向)来优化GPU的显存以及带宽。
  3. Kernel Auto-Tuning:根据当前使用的GPU平台选择最佳的数据层和算法。
  4. Dynamic Tensor Memory:最小化内存占用并高效地重用张量的内存。
  5. Multi-Stream Execution:使用可扩展设计并行处理多个输入流。
  6. Time Fusion:使用动态生成的核去优化随时间步长变化的RNN网络。

制作tensorrt版本的模型有些麻烦,步骤如下:

# 验证是否装了cuda
sudo apt install nvidia-cuda-toolkit
nvcc -V

# 构建tensor rt模型 

# 先安装tensor rt
# 下载地址: https://developer.nvidia.com/nvidia-tensorrt-8x-download  下载符合系统和cuda的版本,我这里下载的deb
# sudo dpkg -i nv-tensorrt-local-repo-ubuntu2004-8.6.1-cuda-12.0_1.0-1_amd64.deb
# sudo apt-get install -y aptitude

# 这个会报错 原因是很多依赖没有装,所以tensorrt用下面的方式安装
# 安装cuda toolkit包 https://developer.nvidia.com/cuda-downloads?target_os=Linux&target_arch=x86_64&Distribution=Ubuntu&target_version=20.04&target_type=deb_local
# 安装cudnn
sudo apt-get -y install cudnn9-cuda-12
# 安装tensor rt   tensort只支持GPU
# 这里注意安装的版本要与triton server的版本匹配,否则后面triton server无法加载
# 各个模块的兼容矩阵 https://docs.nvidia.com/deeplearning/frameworks/support-matrix/index.html
sudo apt-get install tensorrt
# 加入到环境变量
vim ~/.bashrc
export PATH=$PATH:/usr/src/tensorrt/bin

# 上面准备工作完成, 把上面的onnx模型转成tensorrt版 一行命令:
trtexec --onnx=/home/zhongqiang/bigmodellearning/triton_learning/model_repo/resnet50_onnx/1/model.onnx --explicitBatch --optShapes=actual_input_1:16x3x224x224 --maxShapes=actual_input_1:32x3x224x224 --minShapes=actual_input_1:1x3x224x224 --best --saveEngine=/home/zhongqiang/bigmodellearning/triton_learning/model_repo/resnet50_trt/1/model.plan

最后的结果:

在这里插入图片描述
三个模型都已经创建完毕, 接下来就是给每个模型编写配置文件。

这里每个模型先写最简单的配置文件,具体说明放到下一个章节, 这里先看看能不能都正常加载。

cd resent50_onnx
vim config.pbtxt
platform: "onnxruntime_onnx"
max_batch_size: 32
input [
 {
    name: "actual_input_1"
    data_type: TYPE_FP32
    dims: [ 3, 224, 224 ]
 }
]
output [
 {
    name: "output_1"
    data_type: TYPE_FP32
    dims: [ 1000 ]
 }
]

cd resnet50_torch
vim config.pbtxt
platform: "pytorch_libtorch"
max_batch_size: 32
input [
 {
    name: "input__0"
    data_type: TYPE_FP32
    format: FORMAT_NCHW
    dims: [ 3, 224, 224 ]
  }
]
output {
    name: "output__0"
    data_type: TYPE_FP32
    dims: [ 1000 ]
}

cd resnet50_trt
vim config.pb.txt
name: "resnet50_trt"
platform: "tensorrt_plan"
max_batch_size: 8
input [
  {
    name: "actual_input_1"
    data_type: TYPE_FP32
    format: FORMAT_NCHW
    dims: [ 3, 224, 224 ]
  }
]
output {
    name: "output_1"
    data_type: TYPE_FP32
    dims: [ 1000 ]
    label_filename: "imagenet-simple-labels.json"
}

dynamic_batching{
    preferred_batch_size: [ 2, 4 ]
}

下面我们启动加载一下:

# 启动镜像  这里要指定gpus参数, 使得容器能用宿主主机的GPU, 否则tensor rt模型不能使用
sudo docker run -ti --rm --network=host --gpus all -v ~/bigmodellearning:/mnt/bigmodellearning --name triton-server nvcr.io/nvidia/tritonserver:24.01-py3
sudo docker run -ti --rm --network=host --gpus all -v ~/bigmodellearning:/mnt/bigmodellearning --name triton-server-v1 micr.cloud.mioffice.cn/wuzhongqiang/triton_img:v1

# 启动triton server
tritonserver --model-repository=./model_repo/ 

最后可以发现,三个模型都已经加载成功。

在这里插入图片描述

4. 模型配置文件

这个就是每个模型下面的config.pbtxt文件, 上面我只是列了最简单的参数,这里详细看看这个文件的其他参数。

注:如果在开启triton服务的时候指定strict-model-config=false, TensorRT, TensorFlow saved-model和Onnx model可以不写config.pbtxt,因为trion server可以从这三种模型的模型文件里面直接读取所需要的batch size, input和output信息。 后面我们具体看下。

4.1 必备的参数

这块是config.pbtxt文件里面必须要具备的参数:

  1. backend或者platform, 模型使用的是什么backend, 有的模型二者选其一指定就可以,有的必须指定某一种

    在这里插入图片描述
    绿色区域是2者选其1, 红色是必选, 黄色是可选

  2. max_batch_size: 定义了模型最大推理的batch是多少,通常用在限制模型推理过程中不会超过GPU的显存(可以事先通过测试性能确定下来)

    下面通过例子, 看下batch_size的功效, 代码在下面的client部分会给出。

    事先在test_data目录下面截取了10张图片,通过指定不同的batch size,可以看到不同的推理效果:

    batch_size=8
    在这里插入图片描述
    batch_size=1
    在这里插入图片描述
    tensor rt模型的config.pbtxt里面max_batch_size最大设置的参数是8,如果我们超过了这个数,就会报错:
    在这里插入图片描述

  3. inputoutput: 输入tensor和输出tensor的名字, 决定数据要从哪里喂进去, 得出来的推理结果从哪里获取

    下面是max_batch_size, input和ouput怎么设定的例子, 这三个有关联:
    在这里插入图片描述

    • 左一(最常见): max_batch_size大于0

      • 模型是tensorrt模型
      • 最大batch_size是8
      • 两个输入input0input1, 大小是[3, 224, 224], 注意这里没有batch_size的大小,只是图片[通道数, 长,宽]
      • batch_size大于0的情况下, 输入是不需要指定batch size那一维的, 这种情况, triton默认batch size那一维可变
      • 输出outpu0是16维的向量, 如果模型文件要求输出必须是个4维的张量, 那么这里output参数设置里面就可以通过reshape,变成模型要求的大小

      输入和输出的这个名字, 在client调用推理服务的时候,会指定出来。 名字必须对应上才可以。

    • 左二: max_batch_size等于0

      • 模型是tensorrt模型
      • 最大batch_size是0, 指定成0, 意味这个模型输入和输出是不包含batch_size那一维的,这时候下面的input和output的所有维度都得写出来
      • 模型两个输入input0和input1, 大小是[3, 224, 224], 注意这里的模型输入就是[3,224,224], 也就是只能输入一张图片,不能输入一个batch的图片
      • batch_size为0,如果想支持batch的推理,batch那一维必须增加input和output的batch维度, 并且这个维度就不支持可变长度
      • 输出是16维的向量,就是一个向量,不支持batch
    • 左三: pytorch模型的特殊点

      • 模型是pytorch模型
      • 最大batch_size是8
      • 模型两个输入INPUT__0INPUT__1, 大小是[3, -1, -1]pytorch的torch script模型, 模型文件里面是不包含input和output丰富信息的,所以torch script模型的输入输出名称, 结构需要固定(字符串__num), 如果模型支持可变维度的话,可以把可变的维度设置成-1, 这里的h,w设置-1, 就支持任意尺寸图片的推理。
      • 输出是16维的向量
    • 左四: reshape的作用

      • 模型是tensorrt模型
      • 最大batch_size是0
      • 模型两个输入input0input1, 大小是[3, 224, 224], 注意这里没有batch_size的大小,只是图片[通道数, 长,宽]
      • 可以加Reshape操作, 把input tensor reshape成想要的维度,batch_size为0, 必须按照模型input指定的维度去输入,但假设模型文件的输入,是一个四维的输入,那这时候,就需要reshape在input里面加上batch那一维度。
      • 输出是16维的向量

下面做一个实验,测试batch size=0, 首先我们修改resnet50_onnx模型的config.pbtxt文件,把max_batch_size参数设置为0, 此时,重启tritonserver的时候报错:
在这里插入图片描述
这里我指定reshape改成[1, 3, 224, 224], 发现也不好使,原因是我发现我torch保存onnx模型的时候, 第一维默认是动态的batch维度,是可以改动的。

在这里插入图片描述
所以,如果把onnx模型的max_batch_size设置为0, 不管input和output的维度怎么设置,都不是动态改变维度了,和模型设定有冲突,所以总是报错,就到这吧,为0这种情况比较极端,一般不用。

4.2 重要参数

重要参数这里介绍几个,合理利用可以提升推理效率。

  1. 重要的参数instance_group

    • 对应triton重要的feature: 并行的模型实例,对同一个模型可以开启多个excution instance, 可以在GPU上并行执行多个实例。
    • 可以提高GPU利用率,增加模型吞吐。
      在这里插入图片描述
    • 一个GPU或CPU上可以开启多个instance, 类似于多线程的个数
    • 如果不指定是哪块GPU或者CPU, 默认是会在每个GPU上开count数量的instance


    这里我们也做一个实验,不过我只有1块GPU, 我没法上面这样指定了,就一个GPU上开多个实例,跑一个性能测试看看。 处理过程:

    首先, 3个模型的config.pbtxt文件里面加上参数:

    instance_group [
        {
            count: 1
            kind: KIND_GPU
        }
    ]
    

    然后,就是在triton server启动的时候,加上一个参数 tritonserver --model-repository=./model_repo/ --model-control-mode explicit 这个意思是服务端启动的时候,先不加载任何模型, 模型由客户端去指定加载,此时启动之后:

    在这里插入图片描述
    来到客户端,用下面命令加载resnet50_torch:

     curl -X POST http://localhost:8000/v2/repository/models/resnet50_torch/load
    

    看服务器端的日志,会发现resnet50_torch模型被加载

    在这里插入图片描述
    加载成功之后,用perf_analyzer工具,对模型resnet50_torch进行性能分析,去测试serve的 吞吐,延时等

    perf_analyzer -m resnet50_torch -b 1 --concurrency-range 64 --max-threads 32 -u localhost:8001 -i gRPC
    
    # -m resnet50_torch: 指定模型名称为resnet50_torch
    # -b 1: 指定批处理大小为1
    # -concurrency-range 64: 设置并发客户端请求的数量为64
    # --max-threads 32: 设置客户端使用的最大线程数为32
    # -u localhost:8001: 指定服务的URL为本地主机的8001端口
    # -i gRPC: 指定使用gRPC接口进行测试
    
    # 结果
    *** Measurement Settings ***
      Batch size: 1   # 批处理大小为1
      Service Kind: Triton   # 使用Triton服务
      Using "time_windows" mode for stabilization  # 使用稳定模式的"time_windows"
      Measurement window: 5000 msec     # 测量窗口为5000毫秒
      Using synchronous calls for inference   # 使用同步调用进行推理
      Stabilizing using average latency   # 使用平均延迟进行稳定
    
    # 在请求的并发为64时,客户端和服务器的性能表现如下
    Request concurrency: 64
      Client: 
        Request count: 11210   # 客户端发送了11210个请求,平均每秒能进行622.538次推理 (infer/sec)
        Throughput: 622.538 infer/sec
        Avg latency: 102503 usec (standard deviation 14891 usec)  # 客户端的平均延迟约为102503微秒 (usec)  标准偏差为14891微秒,表现出延迟的波动
        p50 latency: 101190 usec
        p90 latency: 102145 usec
        p95 latency: 102523 usec
        p99 latency: 111301 usec
        Avg gRPC time: 102489 usec ((un)marshal request/response 65 usec + response wait 102424 usec)
      Server: 
        Inference count: 11210
        Execution count: 11210
        Successful request count: 11210
        Avg request latency: 101966 usec (overhead 19 usec + queue 100365 usec + compute input 91 usec + compute infer 1477 usec + compute output 12 usec)
    
    # GPU上承载的服务实例,在64个并发请求时,能达到每秒622次推理的吞吐量,而平均延迟接近1秒。这为评估在特定载荷水平下模型的性能提供了数值化的信息
    Inferences/Second vs. Client Average Batch Latency
    Concurrency: 64, throughput: 622.538 infer/sec, latency 102503 usec   # GPU上开一个实例, 达到的吞吐是1s 622次推理, 延时是1s 左右
    
    # 卸载模型
    curl -X POST http://localhost:8000/v2/repository/models/resnet50_torch/unload
    
    # 调整instance的个数为2, 重新serve和重新加载,再进行压测
    *** Measurement Settings ***
      Batch size: 1
      Service Kind: Triton
      Using "time_windows" mode for stabilization
      Measurement window: 5000 msec
      Using synchronous calls for inference
      Stabilizing using average latency
    
    Request concurrency: 64
      Client: 
        Request count: 16344
        Throughput: 907.455 infer/sec
        Avg latency: 70382 usec (standard deviation 13000 usec)
        p50 latency: 69536 usec
        p90 latency: 70124 usec
        p95 latency: 70329 usec
        p99 latency: 70893 usec
        Avg gRPC time: 70368 usec ((un)marshal request/response 61 usec + response wait 70307 usec)
      Server: 
        Inference count: 16344
        Execution count: 16344
        Successful request count: 16344
        Avg request latency: 69894 usec (overhead 22 usec + queue 67697 usec + compute input 89 usec + compute infer 2072 usec + compute output 13 usec)
    
    Inferences/Second vs. Client Average Batch Latency
    Concurrency: 64, throughput: 907.455 infer/sec, latency 70382 usec    # GPU上开2个实例, 达到的吞吐是1s 907次推理, 延时是700 ms 左右 , 性能提升了,但不是成比例提升的
    

    这里普及一个点:

    p50p90这样的指标通常用来表示延迟的百分位数。具体地:

    p50表示50th百分位数,也就是中位数(median)。意味着有50%的请求延迟是低于或等于这个值的。p90表示90th百分位数。意味着有90%的请求延迟是低于或等于这个值的。

    举个例子,如果p90 latency是102145微秒,这表示在所有测量的请求中,有90%的请求其延迟是低于或等于102145微秒的。

    这些百分位数是评估服务质量的重要指标,它们能提供比平均值更全面的延迟分布情况。在考虑用户体验时非常关键,特别是p95和p99延迟,因为它们表示极端情况下的延迟,即最糟糕的用户体验。

    一个GPU上开1个instance, GPU利用率:
    在这里插入图片描述
    一个GPU上开2个instance, GPU利用率:
    在这里插入图片描述
    我这里达到了100%, 确实一个GPU上开多个instance,能够提升GPU的利用率。

  2. 调度策略: 下面的一个重要参数Scheduler和Batching
    在这里插入图片描述
    指明了triton应该使用哪种调度策略去调度送进来的推理请求。不同的策略也可以提升GPU的提升性能。

    1. Default scheduler: 不做batch, 推理请求送进来多少, 给模型就推理多少, 如果不指定,默认就是它

    2. dynamic Batcher: 对于一个请求,先不进行推理,等个几毫秒,把这几毫秒的所有请求拼接成一个batch进行推理,这样可以充分利用硬件,提升并行能力,当然缺点就是个别用户等待时间变长,不适合低频次请求的场景。 常用的两个参数 期望server端打的batch是多少(preferred_batch_size)以及打batch的时间限制是100微妙(max_queue_delay_microseconds),可以理解成打batch的时间窗口, 在这个间隔内的请求才会打成一个batch。这个值越大,说明愿意等待多个请求打成一个batch一块推理,吞吐会更大,但相对应的延迟可能会变长。
      在这里插入图片描述
      这里做一个实验,再resnet50_torch的配置文件里面再加一行:

      dynamic_batching{
          preferred_batch_size: [ 2, 4, 8, 16]
      }
      
      # 重新开启triton server, 并手动加载resnet50_torch模型
      tritonserver --model-repository=./model_repo/ --model-control-mode explicit
      
      # 客户端
      curl -X POST http://localhost:8000/v2/repository/models/resnet50_torch/load
      perf_analyzer -m resnet50_torch -b 1 --concurrency-range 64 --max-threads 32 -u localhost:8001 -i gRPC
      
      # 压测结果
      *** Measurement Settings ***
        Batch size: 1
        Service Kind: Triton
        Using "time_windows" mode for stabilization
        Measurement window: 5000 msec
        Using synchronous calls for inference
        Stabilizing using average latency
      
      Request concurrency: 64
        Client: 
          Request count: 20563
          Throughput: 1141.31 infer/sec
          Avg latency: 55969 usec (standard deviation 16177 usec)
          p50 latency: 55030 usec
          p90 latency: 55425 usec
          p95 latency: 55568 usec
          p99 latency: 57847 usec
          Avg gRPC time: 55947 usec ((un)marshal request/response 144 usec + response wait 55803 usec)
        Server: 
          Inference count: 20563
          Execution count: 1287
          Successful request count: 20563
          Avg request latency: 54301 usec (overhead 167 usec + queue 40529 usec + compute input 2294 usec + compute infer 11277 usec + compute output 33 usec)
      
      Inferences/Second vs. Client Average Batch Latency
      Concurrency: 64, throughput: 1141.31 infer/sec, latency 55969 usec   
      # 加上dynamic_batching参数, 达到的吞吐是1s 1141次推理, 延时是500 ms 左右 , 性能比在GPU上开2个实例都好
      
      

      还有3个高级的选项:

      • perserve_ordering: 指定之后,可以保证使用dynamic batching之后,推理结果返回的顺序和推理请求送进来的顺序完全保持一致
      • priority_levels: 定义不同优先级的请求处理的顺序,可以选择优先级高的请求,打成batch, 送进backend进行推理
      • queue policy: 可以设置请求等待队列的一些行为,比如行为队列设置成多长,比如可以设置一个计时器, 当时间一过,请求可以直接推掉,不推理等
    3. sequence batcher: 专门用于statefule model的调度器,可以保证同一个streaming的序列,推理的时候,所有的请求能够发送到同一个instance上推理,从而保证model instance的状态
      在这里插入图片描述

    4. ensemble scheduer 这个调度器可以组合不同的模块,形成一个pipeline。 后面的聚合模型部分会整理到。

    上面的调度器和调度策略和服务器的性能是直接相关的,需要重点关注。

  3. 优化策略optimization, 目前支持两类模型: tensor-rt加速器加速
    在这里插入图片描述
    这里我做一个实验,就是在resnet50的onnx模型的配置文件里面加上加速器参数:我这里测试的差不多,并没有多大提升。

    optimization {
        execution_accelerators {
            gpu_execution_accelerator: [{
                name: "tensorrt"
                parameters: { key: "precision_mode" value: "FP16"}
                parameters: { key: "max_workspace_size_bytes" value: "1073741824"}
            }]
        }
    }
    
    # 服务端
    tritonserver --model-repository=./model_repo/ --model-control-mode explicit
    # 客户端
    curl -X POST http://localhost:8000/v2/repository/models/resnet50_onnx/load
    perf_analyzer -m resnet50_onnx -b 1 --concurrency-range 64 --max-threads 32 -u localhost:8001 -i gRPC
    
    # 这里测试resnet50_onnx模型,不加optimization参数压测结果:
    *** Measurement Settings ***
      Batch size: 1
      Service Kind: Triton
      Using "time_windows" mode for stabilization
      Measurement window: 5000 msec
      Using synchronous calls for inference
      Stabilizing using average latency
    
    Request concurrency: 64
      Client: 
        Request count: 26016
        Throughput: 1443.47 infer/sec
        Avg latency: 44340 usec (standard deviation 278 usec)
        p50 latency: 44334 usec
        p90 latency: 44680 usec
        p95 latency: 44794 usec
        p99 latency: 45074 usec
        Avg gRPC time: 44319 usec ((un)marshal request/response 143 usec + response wait 44176 usec)
      Server: 
        Inference count: 26016
        Execution count: 1626
        Successful request count: 26016
        Avg request latency: 42865 usec (overhead 170 usec + queue 31815 usec + compute input 2283 usec + compute infer 8560 usec + compute output 35 usec)
    
    Inferences/Second vs. Client Average Batch Latency
    Concurrency: 64, throughput: 1443.47 infer/sec, latency 44340 usec
    
    # 加上otpimization的参数压测结果
     *** Measurement Settings ***
      Batch size: 1
      Service Kind: Triton
      Using "time_windows" mode for stabilization
      Measurement window: 5000 msec
      Using synchronous calls for inference
      Stabilizing using average latency
    
    Request concurrency: 64
      Client: 
        Request count: 25888
        Throughput: 1436.23 infer/sec
        Avg latency: 44554 usec (standard deviation 286 usec)
        p50 latency: 44543 usec
        p90 latency: 44911 usec
        p95 latency: 45043 usec
        p99 latency: 45319 usec
        Avg gRPC time: 44532 usec ((un)marshal request/response 139 usec + response wait 44393 usec)
      Server: 
        Inference count: 25888
        Execution count: 1618
        Successful request count: 25888
        Avg request latency: 43080 usec (overhead 167 usec + queue 31976 usec + compute input 2302 usec + compute infer 8601 usec + compute output 34 usec)
    
    Inferences/Second vs. Client Average Batch Latency
    Concurrency: 64, throughput: 1436.23 infer/sec, latency 44554 usec
    # onnx模型吞吐是1s 1436次推理, 延时是445 ms 左右, 果然onnx模型要快
    
    # 这里顺便测试了下tensor rt模型的推理性能, 这个会远超pytorch 和onnx
    *** Measurement Settings ***
      Batch size: 1
      Service Kind: Triton
      Using "time_windows" mode for stabilization
      Measurement window: 5000 msec
      Using synchronous calls for inference
      Stabilizing using average latency
    
    Request concurrency: 64
      Client: 
        Request count: 87816
        Throughput: 4839.96 infer/sec
        Avg latency: 13213 usec (standard deviation 1759 usec)
        p50 latency: 13161 usec
        p90 latency: 15368 usec
        p95 latency: 16039 usec
        p99 latency: 17489 usec
        Avg gRPC time: 13183 usec ((un)marshal request/response 176 usec + response wait 13007 usec)
      Server: 
        Inference count: 87823
        Execution count: 6824
        Successful request count: 87823
        Avg request latency: 11408 usec (overhead 474 usec + queue 7328 usec + compute input 1844 usec + compute infer 1066 usec + compute output 694 usec)
    
    Inferences/Second vs. Client Average Batch Latency
    Concurrency: 64, throughput: 4839.96 infer/sec, latency 13213 usec  # 1s 4839次推理, 延时是132 ms 左右
    

这里就把上面的几组实验结果放到表格对比, 看看参数的有效性。

在这里插入图片描述

4.3 其他参数

这里再介绍两个参数, 不是很重要,但有时候会用到。

  1. version_policy: 模型版本serve
    一个模型里面可以包含很多个版本目录,当一个模型里面包含多个版本的时候,我们究竟要serve哪一个或者哪几个呢?可以通过version_policy指定。

    • all策略: 所有的模型版本都serve上去
    • latest: 把最新的几个版本serve上去, 这里的1是serve1个最新的
    • specffic: 就是指定具体的模型版本了

    下面,我在resnet50_onnx模型下面再新建一个2版本,然后在config.pbtxt文件里面试下这个参数:

    # config.pbtxt
    version_policy: { all { } }   # 所有版本都serve
    version_policy: { latest { num_versions: 1 } }   # 最后的1个版本serve
    version_policy: { specific { versions: 1 } }   # 特定的版本  这里是1版本
    

    重启下:

    在这里插入图片描述
    这里就能成功指定两个版本了, 推理的时候,可以指定特定的版本推理了。

    root@zouyilin:/mnt/bigmodellearning/triton_learning/triton_cli# python3 img_cli.py --model_name resnet50_onnx --model_version 2 --img_dir /mnt/bigmodellearning/triton_learning/test_data/pic --batch_size 5 --cli_type http
    pics nums: 10
    model_name: resnet50_onnx, model_version: 2, cli_type: http, cur_batch: 0_5, batch size: 5
        elephant.png: 12.28624439239502 (101) = tusker
        horse.png: 15.936531066894531 (339) = common sorrel
        koala.png: 17.28411102294922 (105) = koala
        tree.png: 12.409147262573242 (975) = lakeshore
        apple.png: 6.7075605392456055 (923) = plate
    model_name: resnet50_onnx, model_version: 2, cli_type: http, cur_batch: 5_10, batch size: 5
        cat.png: 13.10097885131836 (282) = tiger cat
        airliner.png: 17.840190887451172 (404) = airliner
        banana.png: 15.781821250915527 (954) = banana
        dog.png: 15.80997371673584 (263) = Pembroke Welsh Corgi
        pandas.png: 13.727736473083496 (388) = giant panda
    
  2. model_warmup

    • 有些模型在参数初始化的时候,执行推理请求去推理的性能是不太稳定的,可能比较慢, 所以需要一个热身的过程,使得模型的推理过程趋于一个稳定

    • 通过指定model warmup字段,来定义一个热身的过程
      在这里插入图片描述

    • 指定完了这样的参数之后, triton在加载某个模型的时候,就会先用热身请求给模型热身,达到一个模型热身的效果。

    • warm up的过程中, triton是没办法往外提供服务的,所以这个参数会增加模型加载时间,响应变长

    下面我们也通过一个实验看下这个过程:

    # 在resnet50_onnx模型中加入model_warmup的参数
    model_warmup [{
        batch_size: 32
        name: "warmup_requests"
        inputs {
            key: "actual_input_1"
            value: {
                random_data: true
                dims: [3, 224, 224]
                data_type: TYPE_FP32
            }
        }
    }]
    
    # 启动triton server 
    tritonserver --model-repository=./model_repo --log-verbose 1
    

    这里通过日志会看到, tritonserver启动之后,会对onnx模型进行一个热身操作,完成之后,模型才会被加载

    在这里插入图片描述

5. 启动triton server

启动之前需要先编译triton 或者 使用NGC上的docker image在container执行命令。

在这里插入图片描述
启动容器:

  • —gpus: 看到所有的gpu, 可以通过—device限制使用的gpu
  • -it: 可以和container进行交互
  • —rm: container任务执行完了之后, 自动关掉
  • —shm-size: 指定container可以访问的共享内存的打小,这个比较有用,比如backend之间的交互,example model的各个模块之间的数据传输,这个大小可能会影响服务在运行中的一个情况
  • -p: 指定需要监听的端口,host_端口:映射到container的端口。
    • 8000: 用于http请求的访问
    • 8001: 用于grpc请求的访问
    • 8002: metics的访问(健康性检查)
  • -v: 目录的映射,一般会把主机上的模型仓库映射到容器里面
  • 最后是ngc 容器镜像的名称

启动起来,会展示可加载的模型, 一些参数的设置, 以及不同的网络协议监听的端口是多少。

triton server启动了之后, 发送下面命令可以检查server的健康状态, 这个就不用写一个client去检查了, 如果这个reday, 就说明这个server能用了

curl -v localhost:8000/v2/health/ready

root@zouyilin:/mnt/bigmodellearning/triton_learning# curl -v localhost:8000/v2/health/ready
*   Trying 127.0.0.1:8000...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET /v2/health/ready HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK   # 已经ready了
< Content-Length: 0
< Content-Type: text/plain
< 
* Connection #0 to host localhost left intact

常用的一些options:

  1. log-verbose: 日志展示层级, warm up的时候用到过,可以看到啥时候做了warm up的信息等, 模型执行到哪一步,调用了什么函数等信息,可以通过把这个层级设置成大于1的值来展示

  2. strict-model-config: 如果是之前提到TensorRT, TensorFlow saved-model和Onnx model模型, 不需要给模型提供config.pbtxt配置文件,即没有这个文件也能reday。 这个参数默认是false
    这里我们测试下, 假设我把resnet50_onnx和resnet50_trt目录下面的config.pbtxt文件删除掉,如果把这个参数设置为true,此时reset50_onnx和resnet50_trt就无法启动了:

    tritonserver --model-repository=./model_repo/ --strict-model-config=true

    在这里插入图片描述
    加上就可以正常启动。 另外torch scripts是必须有config.pbtxt, 如果去掉,即使指定这个参数为False也是会报错:
    在这里插入图片描述

  3. strict-readiness: 上面检查server状态的信息, 这里是定义什么情况下检查状态会reday,如果是true,就是当模型库里面所有的模型reday,才会返回true, 而false的话,是有模型reday了,就返回true

  4. exit-on-error: 设置成false, 如果模型仓库里面的某些模型启动挂了,这个server也是能启动起来的, 如果是true,必须是所有模型都reday, 这个server才能启动起来(这个比较有用)

  5. http-port, grpc-port, metrics-port: 指定服务启动时监听的端口号,默认是8000, 8001, 8002, 如果不是这3个, 需要自己去指定

    # 如果启动发现8000, 8001, 8002端口已经被其他triton server容器占用了,这个参数就会起到作用
    
    # 启动容器的时候
    sudo docker run --gpus all -it --rm --net host --shm-size 1g -p8003:8003 -p8004:8004 -p8005:8005 -v ~/bigmodellearning:/mnt/bigmodellearning --name triton-server-v1 micr.cloud.mioffice.cn/wuzhongqiang/triton_img:v1
    
    # 启动triton server
    tritonserver --model-repo model_repo/ --http-port 8003 --grpc-port 8004 --metrics-port 8005
    
  6. model-control-model: 以一种什么样的方式管理模型库,也是比较常用的

    1. none是server开启的时候,会把所有的模型都load进来,模型一旦开启服务的话,没法动态的卸载或更新
    2. explicit是server启动的时候,不加载任何模型, 然后用model control api在客户端,动态的加载或者卸载模型
    3. poll: 动态的更新served的模型,比如server启动好了,把模型加载进来了,此时如果在model仓库中再增加新模型, 这里会自动再把新模型也load进来,如果改模型的config,也会动态的把config改变,这个就类似于uvicorn 启动后端app的时候加上—reload的功效,如果后端代码有改动,就会重新加载新代码。 注意poll模式下,就不能通过control API去加载或者卸载模型了。
  7. reposity-poll-secs: 自动检查模型是否有新更新的时间间隔, 模型库控制方式为poll的时候才有效

  8. load-model: 在server启动的时候, 可以指定特定的模型加载, 在模型库控制方式为explicit的时候有效

    # 服务器端也可以通过这个参数指定的特定模型加载
    tritonserver --model-repository=./model_repo --strict-model-config=false --model-control-mode explicit --load-model resnet50_onnx
    
    # 客户端
    curl -X POST http://localhost:8000/v2/repository/models/resnet50_onnx/load
    curl -X POST http://localhost:8000/v2/repository/models/resnet50_onnx/upload
    
  9. pinned-memory-pool-byte-size: triton server能够分配的所有pinned的cpu内存大小,这个pinned内存在模型推理时可以有效提高cpu, gpu的数据传输效率

  10. cuda-memory-pool-byte-size: 可以分配的最大的cuda memory的大小, 默认时64M

  11. backend-directory: 自己指定backends的存放位置, 后面如果实现自己的一些backends,需要告诉triton server,去哪里找自己的backend,需要设置这个

  12. repoagent-directory: 用来预处理模型库的程序,比如模型库load进去的时候做一个加密操作,就可以把加密的程序做一个repoagent放到这个目录下面,然后指定这个参数

6. 发请求到Triton server

怎么写client, 去发送请求进行推理? 主要有3种: http请求, grpc请求或者直接调接口。

我这里主要是实现了http和grpc的两种,这里直接放代码就可以, 上面实验里面也是用的下面的img_cli.py文件。

import argparse
import json
import os
import queue
import time
from functools import partial

import numpy as np
import tritonclient.grpc as tritongrpcclient
import tritonclient.http as tritonhttpclient
from PIL import Image
from torchvision import transforms


def completion_callback(user_data, result, error):
    user_data._completed_requests.put((result, error))


class UserData(object):

    def __init__(self):
        self._completed_requests = queue.Queue()


class ImageClient(object):

    VERBOSE = False
    INPUT_DTYPE = 'FP32'
    HTTP_URL = 'localhost:8000'
    GRPC_URL = 'localhost:8001'

    def __init__(self,
                 model_name: str = 'resnet50_trt',
                 model_version: str = '1',
                 batch_size: int = 1,
                 cli_type: str = 'http'):
        assert model_name in ['resnet50_torch', 'resnet50_onnx', 'resnet50_trt'], "model name is invalid!"

        self._model_name = model_name
        self._model_version = model_version
        self._http_client = tritonhttpclient.InferenceServerClient(url=ImageClient.HTTP_URL,
                                                                   verbose=ImageClient.VERBOSE)
        self._grpc_client = tritongrpcclient.InferenceServerClient(url=ImageClient.GRPC_URL,
                                                                   verbose=ImageClient.VERBOSE)
        self._cli_type = cli_type
        self._batch_size = batch_size

        if model_name == 'resnet50_torch':
            self._input_name, self._output_name = 'input__0', 'output__0'
        elif model_name == 'resnet50_onnx':
            self._input_name, self._output_name = 'actual_input_1', 'output_1'
        elif model_name == 'resnet50_trt':
            self._input_name, self._output_name = 'actual_input_1', 'output_1'

        self._user_data = UserData()

    def _preprocess(self, img):
        imagenet_mean = [0.485, 0.456, 0.406]
        imagenet_std = [0.485, 0.456, 0.406]

        resize = transforms.Resize((256, 256))
        center_crop = transforms.CenterCrop(224)
        to_tensor = transforms.ToTensor()
        normalize = transforms.Normalize(mean=imagenet_mean, std=imagenet_std)

        transform = transforms.Compose([resize, center_crop, to_tensor, normalize])
        image_tensor = transform(img).unsqueeze(0).cuda()
        return image_tensor

    def _img_process(self, img):
        _, file_extension = os.path.splitext(img)
        if file_extension[1:] == 'png':
            # png图片格式有4个通道(RGBA, A是透明度), 后面transform处理,期望是3个颜色通道的图像
            # 所以需要转换一层
            image = Image.open(img).convert('RGB')
        else:
            image = Image.open(img)
        image_tensor = self._preprocess(image)
        image_numpy = image_tensor.cpu().numpy()
        return image_numpy

    def _check_model(self, triton_client):
        model_metadata = triton_client.get_model_metadata(model_name=self._model_name,
                                                          model_version=self._model_version)
        model_config = triton_client.get_model_config(model_name=self._model_name, model_version=self._model_version)

        return model_metadata, model_config

    def _gen_triton_input_output(self, image_numpy, input_shape):
        if self._cli_type == 'http':
            input_0 = tritonhttpclient.InferInput(self._input_name, input_shape, ImageClient.INPUT_DTYPE)
            input_0.set_data_from_numpy(image_numpy)
            output = tritonhttpclient.InferRequestedOutput(self._output_name)
            # output里面还可以指定class_count参数, 告诉triton执行推理的模型是分类模型, 最后返回结果里面把概率值直接转成分类的标签
            # 模型的配置文件必须提供label的那个文件才可以指定这个参数,如果不指定这个参数, 就只返回模型的推理结果, 根据推理结果客户端也可以输出类别
            # output = tritonhttpclient.InferRequestedOutput(self._output_name, class_count=1)
        else:
            input_0 = tritongrpcclient.InferInput(self._input_name, input_shape, ImageClient.INPUT_DTYPE)
            input_0.set_data_from_numpy(image_numpy)
            output = tritongrpcclient.InferRequestedOutput(self._output_name)
            # output里面还可以指定class_count参数, 告诉triton执行推理的模型是分类模型, 最后返回结果里面把概率值直接转成分类的标签
            # 模型的配置文件必须提供label的那个文件才可以指定这个参数,如果不指定这个参数, 就只返回模型的推理结果, 根据推理结果客户端也可以输出类别
            # output = tritongrpcclient.InferRequestedOutput(self._output_name, class_count=1)

        return input_0, output

    def _httpcli_infer(self, input_0, output):
        # model_metadata, model_config = self._check_model(self._http_client)
        # print(f"model_metadata: {model_metadata}")
        # print(f"model_config: {model_config}")
        response = self._http_client.infer(self._model_name,
                                           model_version=self._model_version,
                                           inputs=[input_0],
                                           outputs=[output])
        return response

    def _grpccli_infer(self, input_0, output):
        # model_metadata, model_config = self._check_model(self._grpc_client)
        # print(f"model_metadata: {model_metadata}")
        # print(f"model_config: {model_config}")
        # https://docs.nvidia.com/deeplearning/triton-inference-server/archives/triton_inference_server_220/user-guide/docs/python_api.html#tritongrpcclient.InferenceServerClient.infer
        response = self._grpc_client.infer(self._model_name,
                                           model_version=self._model_version,
                                           inputs=[input_0],
                                           outputs=[output])
        return response

    def _grpccli_infer_async(self, input_0, output):
        # https://docs.nvidia.com/deeplearning/triton-inference-server/archives/triton_inference_server_220/user-guide/docs/python_api.html#tritongrpcclient.InferenceServerClient.async_infer
        # 异步方式需要提供一个回调函数, 当triton server推理完毕之后,就把推理的结果通过回调函数,放到user_data里面去
        # 这样后面就可以在user_data里面去拿结果
        self._grpc_client.async_infer(self._model_name,
                                      callback=partial(completion_callback, self._user_data),
                                      model_version=self._model_version,
                                      inputs=[input_0],
                                      outputs=[output])
        print("异步推理可以处理别的事情, 过一段时间之后来拿结果")
        time.sleep(5)
        (results, error) = self._user_data._completed_requests.get()
        return results

    def _infer_process(self, images_list):
        images_numpy = np.concatenate(images_list)
        input_0, output = self._gen_triton_input_output(images_numpy, input_shape=images_numpy.shape)
        if self._cli_type == 'http':
            response = self._httpcli_infer(input_0, output)
        else:
            response = self._grpccli_infer(input_0, output)

            # grpc的async方式
            # response = self._grpccli_infer_async(input_0, output)

        return response.as_numpy(self._output_name)

    def _postprocess(self, logits):
        with open(
                '/mnt/bigmodellearning/triton_learning/model_repo/resnet50_torch/imagenet-simple-labels.json') as file:
            labels = json.load(file)
        result = []
        for logit in logits:
            logit = np.asarray(logit, dtype=np.float32)
            class_name = labels[np.argmax(logit)]
            score = np.max(logit)
            loc = np.argmax(logit)
            result.append([class_name, score, loc])
        return result

    def run_sync(self, img_dir: str):

        pics = [os.path.join(img_dir, pic) for pic in os.listdir(img_dir)]
        print(f"pics nums: {len(pics)}")

        for i in range(0, len(pics), self._batch_size):
            cur_batch_imgs = pics[i:i + self._batch_size]
            cur_batch_img_list = []
            for cur_batch_img in cur_batch_imgs:
                cur_img_numpy = self._img_process(cur_batch_img)
                cur_batch_img_list.append(cur_img_numpy)

            # infer batch
            if not cur_batch_img_list:
                break
            batch_response = self._infer_process(cur_batch_img_list)
            batch_results = self._postprocess(batch_response)

            print(f"model_name: {self._model_name}, "
                  f"model_version: {self._model_version}, "
                  f"cli_type: {self._cli_type}, "
                  f"cur_batch: {i}_{i+self._batch_size}, "
                  f"batch size: {self._batch_size}")
            for j, res in enumerate(batch_results):
                file_name = cur_batch_imgs[j].split('/')[-1]
                print(f"    {file_name}: {res[1]} ({res[2]}) = {res[0]}")


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--model_name', dest='model_name', type=str, help='model name')
    parser.add_argument('--model_version', dest='model_version', type=str, help='model version')
    parser.add_argument('--img_dir', dest='img_dir', type=str, help='img dir')
    parser.add_argument('--batch_size', dest='batch_size', type=str, help='batch size')
    parser.add_argument('--cli_type', dest='cli_type', type=str, default='http', help='cli type')

    args = parser.parse_args()

    img_client = ImageClient(model_name=args.model_name,
                             model_version=args.model_version,
                             batch_size=int(args.batch_size),
                             cli_type=args.cli_type)

    img_client.run_sync(args.img_dir)

# python3 img_cli.py
# --model_name resnet50_torch or resnet50_onnx or resnet50_trt
# --model_version 1
# --img_dir /mnt/bigmodellearning/triton_learning/test_data/pic
# --batch_size 2
# --cli_type http or grpc

7. 小总

Triton server学习系列的第二篇文章到这里就结束啦,这里简单小结一下,总体上是从实践的角度去介绍了triton server如何去部署模型进行serve的, 从创建模型仓库,编写配置文件, 启动服务,客户端发送服务四个步骤详细介绍了全流程, 通过这篇文章,就可以把一个训练好的模型通过triton去进行部署了。

后面的一篇文章,在这个基础上扩展两个内容,第一个是python backend的模型如何部署, 这个类似于实现了一个自定义的模型, 第二个就是如何搭建一个ensemble的模型, 依然是通过例子去完成练习实践。

有了这些基础,再搭建复杂的模型就容易了,因为原理本质上都是相通的,流程也是通用的,模型都已经封装好了,无非就是根据triton要求的形式调整配置文件等, 后面还打算写一篇文章,是关于backend的,也就是简单看看整个过程背后的一个所以然,通过triton服务的关键源码,看看给了一个配置文件之后,它背后是如何进行工作的,这对我们了解整个triton的设计应该有很大的帮助。

所以,冲吧哈哈 😉

参考:

  • B站上的这个课程

  • https://github.com/leejinho610/TRT_Triton_HandsOn/blob/main/1. Model preperation (starting Point).ipynb

  • Triton Inference Server介绍

  • Triton inference server tutorials

  • Nvidia Triton使用教程:从青铜到王者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1602810.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一篇文章带你快速认识边缘计算(必看)

引言 5G时代带来了万物互联的飞跃&#xff0c;但随之而来的是数据量爆炸性增长与集中式数据中心处理模式的瓶颈&#xff1a;响应延迟、带宽压力、安全隐忧。边缘计算应运而生&#xff0c;借鉴生物智能分布式原理&#xff0c;将计算与分析推向数据源头&#xff0c;有效解决了以上…

Promise模块化编程ES6新特性

文章目录 Promise&模块化编程1.Promise基本介绍2.快速入门1.需求分析2.原生ajax jQuery3.Promise使用模板 3.课后练习1.原生ajax jQuery2.promise 4.模块化编程基本介绍5.CommonJS基本介绍6.ES5模块化编程1.题目2.示意图3.代码实例—普通导入导出function.jsuse.js 4.代码…

一文搞懂Kotlin符号处理接口KSP

公众号「稀有猿诉」 原文链接 一文搞懂Kotlin符号处理接口KSP Kotlin符号处理&#xff08;Kotlin Symbol Processing&#xff09;即KSP是可以用于开发轻量级编译器插件的一套API。是Kotlin原生的&#xff0c;Kotlin语法友好的编译器插件。使用简单且易于上手&#xff0c…

同城货运系统的开发与货运搬家软件的技术性探讨和市场分析

一、市场前景展望 随着城市化进程的加快和电商物流的蓬勃发展&#xff0c;同城货运市场展现出了巨大的潜力。尤其是在快节奏的生活环境中&#xff0c;个人和企业对于快速、便捷、可靠的货运搬家服务需求日益增长。同城货运系统与货运搬家软件作为连接货主与货运司机的桥梁&…

2024还想走c++后端的同学,该如何准备才有机会成功上岸拿到offer

c后端&#xff0c;一个被网上说没有市场的c方向。但是对于想从事c后端的同学该如何准备呢&#xff1f; 就目前的市场需求来说&#xff0c;c后端的需求市真不大&#xff0c;中小厂基本没有&#xff0c;大部分集中在大厂。 那么&#xff0c;如果大家想求职c后端&#xff0c;第一…

野生动物保护视频AI智能监管方案,撑起智能保护伞,守护野生动物

一、背景 在当今世界&#xff0c;野生动物保护已经成为全球性的重要议题。然而&#xff0c;由于野生动物生存环境的不断恶化以及非法狩猎等活动的盛行&#xff0c;保护野生动物变得尤为迫切。为了更有效地保护野生动物&#xff0c;利用视频智能监管技术成为一种可行的方案。 …

docker方式 部署jenkins服务,实现持续集成(CI/CD)功能

一、背景&#xff1a; 因公司需求&#xff0c;需要部署一套jenkins自动化部署服务&#xff0c;并且是通过docker容器的方式部署的。 二、jenkins简介&#xff1a; 什么是Jenkins &#xff1f; Jenkins是一个开源软件&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用…

VS集成vcpkg

VS集成vcpkg 下载vcpkg 下载vcpkg git clone https://github.com/Microsoft/vcpkg.git安装vcpgk&#xff0c;文件目录 .\bootstrap-vcpkg.bat集成到vs2022中 # 集成到项目 vcpkg integrate project vcpkg integrate installPS C:\Users\Administrator> vcpkg integrate…

Python入门教程完整版(懂中文就能学会)

网友虐我千百遍&#xff0c;我待网友如初恋&#xff0c;因为今天又给大家带来了干货&#xff0c;Python入门教程完整版&#xff0c;完整版啊&#xff01;完整版&#xff01; 为了吸取教训&#xff0c;小编一定要分享一下攻略&#xff0c;“怎样获得小编分享的教程呢&#xff1…

多线程同步:使用 std::mutex 和 std::unique_lock 保护共享资源

在当今的软件开发中&#xff0c;多线程编程是一项至关重要的技术&#xff0c;它允许程序同时执行多个任务&#xff0c;从而提高应用程序的效率和响应速度。然而&#xff0c;多线程环境也带来了数据安全和一致性的挑战。在多个线程需要访问和修改同一数据资源的情况下&#xff0…

【SAP NWDI】创建DC(Development component)(三)

一、准备DC组件包 首先需要下载下面这7个sca 的组件包,找到对应的ME版本的组件包,可以找对应的Basis帮忙下载。然后把这7个组件包放入到服务器中根目录的这个目录中,如果目录没有的需要自己创建出来。 二、导入DC组件包 注意:下面的的图中 有需要填写 in 和 out 的连个目…

RabbitMQ-核心特性

已经不需要为RabbitMQ交换机的离去而感到伤心了&#xff0c;接下来登场的是RabbitMQ-核心特性!!! 文章目录 核心特性消息过期机制消息确认机制死信队列 核心特性 消息过期机制 官方文档&#xff1a;https://www.rabbitmq.com/ttl.html 可以给每条消息指定一个有效期&#xf…

富文本在线编辑器 - tinymce

tinymce 项目是一个比较好的富文本编辑器. 这里有个小demo, 下载下来尝试一下, 需要配置个本地服务器才能够访问, 我这里使用的nginx, 下面是我的整个操作过程: git clone gitgitee.com:chick1993/layui-tinymce.git cd layui-tinymcewget http://nginx.org/download/nginx-1.…

监控系统泛滥:CTO 面临的隐形成本危机

在信息技术飞速发展的今天&#xff0c;构建和维护现代化的数字系统变得日益复杂和关键&#xff1b;在这样的背景下&#xff0c;监控系统的作用变得尤为突出。正如业界广泛流传的一句经验之谈“无监控&#xff0c;不运维”所揭示的道理一样&#xff0c;对于任何具有一定复杂性的…

Redis(Windows版本下载安装和使用)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

lua 光速入门

文章目录 安装注释字符串变量逻辑运算条件判断循环函数Table (表)常用全局函数模块化 首先明确 lua 和 js Python一样是动态解释性语言&#xff0c;需要解释器执行。并且不同于 Python 的强类型与 js 的弱类型&#xff0c;它有点居中&#xff0c;倾向于强类型。 安装 下载解释…

AI预测福彩3D第38弹【2024年4月17日预测--第8套算法开始计算第6次测试】

今天咱们继续测试第8套算法和模型&#xff0c;今天是第5次测试&#xff0c;目前的测试只是为了记录和验证&#xff0c;为后续的模型修改和参数调整做铺垫&#xff0c;所以暂时不建议大家盲目跟买~废话不多说了&#xff0c;直接上结果&#xff01; 2024年4月17日3D的七码预测结果…

vivado 与 VIO 核输出探针进行交互

与 VIO 核输出探针进行交互 VIO 核输出探针用于将值写入实际硬件中的 FPGA 或 ACAP 中运行的设计。 VIO 输出探针通常用作为待测设计的低带 宽控制信号。 VIO 调试探针需手动添加到 VIO 仪表板的“ VIO 探针 (VIO Probes) ”窗口中。请参阅“在‘调试探针 (Debug Pr…

中国12.5米DEM地形瓦片数据免费领取!

之前向大家公开了中国34个省12.5米DEM地形瓦片数据的免费领取链接&#xff0c;大家对12.5米DEM数据的使用需求很强烈&#xff0c;领取也很积极&#xff0c;也有不少读者反馈能否提供全国范围的12.5米DEM地形瓦片数据&#xff0c;因为分省级地形瓦片数据想要合并成全国数据&…

CUDA 以及MPI并行矩阵乘连接服务器运算vscode配置

一、CUDA Vscode配置 &#xff08;一&#xff09;扩展安装 本地安装 服务器端安装 &#xff08;二&#xff09; CUDA 配置 .vscode c_cpp_properties.json {"configurations": [{"name": "Linux","includePath": ["${workspa…