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

官方文档查阅: 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.mfbz.cn/a/552314.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

循环神经网络(RNN):概念、挑战与应用

循环神经网络&#xff08;RNN&#xff09;&#xff1a;概念、挑战与应用 1 引言 1.1 简要回顾 RNN 在深度学习中的位置与重要性 在深度学习的壮丽图景中&#xff0c;循环神经网络&#xff08;Recurrent Neural Networks&#xff0c;RNN&#xff09;占据着不可或缺的地位。自从…

边缘计算网关

在信息化高速发展的今天&#xff0c;数据已经成为企业运营的核心资产。然而&#xff0c;随着数据量的爆炸式增长&#xff0c;传统的中心化数据处理方式已经无法满足企业对实时性、安全性和效率性的需求。这时&#xff0c;边缘计算网关应运而生&#xff0c;它作为连接物理世界和…

ollama与open-webui、lobe-chat简单使用案例

参考: https://github.com/ollama/ollama https://github.com/open-webui/open-webui ollama最为大模型后端服务 open-webui、lobe-chat前端聊天页面 ollama直接下载客服端安装win cpu安装推理;open-webui、lobe-chat使用docker安装 1、ollama 安装好后可以直接运行,cpu使…

基于springboot实现人口老龄化社区服务与管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现人口老龄化社区服务与管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了人口老龄化社区服务与管理平台的开发全过程。通过分析人口老龄化社区服务与管理平台方面的不足&#xff…

Linux时间同步练习

题目如下&#xff1a; 一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 2.server主机的IP为&#xff1a; 172.25.254.100 3.server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 4.配置server主机的时间同步服务要求可以被所…

MySQL基础知识——MySQL事务

事务背景 什么是事务&#xff1f; 一组由一个或多个数据库操作组成的操作组&#xff0c;能够原子的执行&#xff0c;且事务间相互独立&#xff1b; 简单来说&#xff0c;事务就是要保证一组数据库操作&#xff0c;要么全部成功&#xff0c;要么全部失败。 注&#xff1a;MyS…

aws云靶场和一些杂记

aws靶场 在AWS靶场中&#xff0c;存在三个安全问题&#xff1a;1) 一个S3存储桶政策配置错误&#xff0c;允许公共访问&#xff0c;通过访问特定域名可获取flag。2) SQS消息队列的政策没有限制角色&#xff0c;允许发送和接收消息&#xff0c;通过aws sqs命令行工具的receive-…

Pr2024安装包(亲测可用)

目录 一、软件简介 二、软件下载 一、软件简介 Premiere简称“Pr”&#xff0c;是一款超强大的视频编辑软件&#xff0c;它可以提升您的创作能力和创作自由度&#xff0c;它是易学、高效、精确的视频剪辑软件&#xff0c;提供了采集、剪辑、调色、美化音频、字幕添加、输出、D…

动态内存管理 柔性数组

文章目录 动态内存函数 malloc freecallocrealloc 重新开辟空间realloc 也可以第一个参数为NULL&#xff0c;则是直接开辟内存&#xff0c;类似于malloc用法 常见的动态内存错误对空指针进行解引用操作对开辟的内存越界访问对非动态开辟的内存使用free释放使用free释放动态开辟…

Playwright 集成在 Jenkins 中进行端到端自动化测试

根据之前的文章&#xff0c;我们大致了解了前端测试的类型以及 Playwright 的基本使用。在我们完成了 Playwright 的搭建后&#xff0c;我们需要将测试引入到正常开发流程中来。 不过&#xff0c;在流程中总不可能每次在产品端发生变化后&#xff08;通常涉及多个组件&#xf…

如何正确查看容器的CPU使用率

进入容器中top&#xff0c;虽然看到的PID是容器的&#xff0c;但是%Cpu的统计信息却是宿主机的。 如图 原理 进程的cpu使用率是如何计算出来的&#xff1f; 每个进程的状态是放在文件里的&#xff0c;在/proc目录下&#xff0c;每个进程有自己pid命名的文件夹&#xff0c; …

基于Python大数据的微博舆情分析,微博评论情感分析可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

冲上热搜-奇安信今年的年终奖。。

最近,奇安信宣布全员无年终奖&#xff0c;同时冲上了脉脉热搜榜第一。作为网安界的一哥&#xff0c;奇安信的决定无疑给许多期待年终奖的员工带来了沉重的打击。 从公司内部的绩效考核机制来看,奇安信将员工分为了5个档次:S、A、B、B、B-。而大多数员工被评定为中等的B档,这意味…

宝塔面板实现内网端口映射教程

内网穿透是一种技术&#xff0c;它允许外部网络&#xff08;如互联网&#xff09;的设备或用户访问内网&#xff08;如本地局域网&#xff09;中的服务。在使用宝塔面板&#xff08;BT-Panel&#xff09;时&#xff0c;你可以通过安装和配置一些插件来实现内网穿透。 以下是一…

2024年团队程序设计天梯赛模拟赛 L1 + L2

目录 L1 - 6 剪切复制 L1 - 8 小偷踩点 L2 - 1 推宝塔 L2 - 2 含茶量 L2 - 3 到底爱不爱我 L2 - 4 寻宝图 L1 - 6 剪切复制 使用计算机进行文本编辑时常见的功能是剪切功能&#xff08;快捷键&#xff1a;Ctrl X&#xff09;。请实现一个简单的具有剪切和粘贴功能的文本…

每天五分钟计算机视觉:基于卷积操作完成滑动窗口的图片分类?

本文重点 我们前面学习了使用不同大小的滑动窗口来滑动图片,然后切分成许多小的图片,然后依次应用到我们已经训练好的图像分类模型中,但是这种方式效率太低了,本节课程我们学习一种新的方式,来看一下如何并行识别这些剪切的图片。 原始结构 首先我们先来看一下,如何把…

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

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

朗致集团面试-Java架构师

总结 三轮面试&#xff0c;第一轮是逻辑测试性格测试&#xff0c;第二轮是技术面试&#xff08;面试官-刘老师&#xff09;&#xff0c;第三轮是CTO面试&#xff08;面试官-屠老师&#xff09;。如果第三轮面试通过&#xff0c;考官会问你薪资意向&#xff0c;如果满意的话HR就…

【网络编程】web服务器shttpd源码剖析——命令行和文件配置解析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之web服务器shttpd源码剖析——命令行解析&#xff0c;在这篇文章中&#xff0c;你将会学习到在Linux内核中如何创建一个自己的并发服务器shttpd&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘…

WAF攻防-权限控制代码免杀异或运算变量覆盖混淆加密传参

知识点 1、脚本后门基础&原理 2、脚本后门查杀绕过机制 3、权限维持-覆盖&传参&加密&异或等 章节点&#xff1a; WAF绕过主要集中在信息收集&#xff0c;漏洞发现&#xff0c;漏洞利用&#xff0c;权限控制四个阶段。 代码表面层免杀-ASP&PHP&JSP&a…
最新文章