tritonserver学习之七:cache管理器

tritonserver学习之一:triton使用流程

tritonserver学习之二:tritonserver编译 

tritonserver学习之三:tritonserver运行流程

tritonserver学习之四:命令行解析

tritonserver学习之五:backend实现机制

tritonserver学习之六:自定义c++、python custom backend实践

1、概念

       triton中cache的设计,主要是用来提升推理性能,实现机制为缓存不同的推理请求,当相同的请求到达triton,则可以直接从缓存中获取结果,而不再经过推理,这样不仅降低了整个过程的耗时,同时也节省了算力。

       缓存中key的生成,是根据模型名称、版本、输入tensor名称及模型输入进行hash而成,唯一的标识了一个推理请求。

       triton支持的cache有两种,一种为内存(local cache),另外一种为redis(redis cache),在启动triton时,通过不同的命令行参数进行设置。

2、cache实现机制

开启cache命令行:

tritonserver --model-repository=/models --log-verbose=1 --cache-config=local,size=1048576 --log-file=1.txt

tritonserver --model-repository=/models --log-verbose=1 --cache-config=redis,host=172.17.0.1 --cache-config redis,port=6379 --cache-config redis,password=“xxx”

triton中,cache的技术方案,和backend类似,在triton与cache之间约定了四个api:

TRITONSERVER_Error*
TRITONCACHE_CacheInitialize(TRITONCACHE_Cache** cache, const char* cache_config)

TRITONSERVER_Error*
TRITONCACHE_CacheFinalize(TRITONCACHE_Cache* cache)

TRITONSERVER_Error*
TRITONCACHE_CacheLookup(
    TRITONCACHE_Cache* cache, const char* key, TRITONCACHE_CacheEntry* entry,
    TRITONCACHE_Allocator* allocator)

TRITONSERVER_Error*
TRITONCACHE_CacheInsert(
    TRITONCACHE_Cache* cache, const char* key, TRITONCACHE_CacheEntry* entry,
    TRITONCACHE_Allocator* allocator)

 这四个api分别为初始化、析构、查找缓存、插入缓存,每种cache都需要实现这四个api,triton在enable缓存后,会相应的调用这四个api,这种机制的好处是解耦triton与cache的设计,两者之间通过上述4个标准的api进行交互。

3、redis cache

github地址:GitHub - triton-inference-server/redis_cache: TRITONCACHE implementation of a Redis cache

3.1 TRITONCACHE_CacheInitialize

该函数位于:redis_cache/src/cache_api.cc,该函数实现主要功能:根据输入的配置,创建redis_cache类对象。

TRITONSERVER_Error*
TRITONCACHE_CacheInitialize(TRITONCACHE_Cache** cache, const char* cache_config)
{
  if (cache == nullptr) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INVALID_ARG, "cache was nullptr");
  }
  if (cache_config == nullptr) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INVALID_ARG, "cache config was nullptr");
  }

  std::unique_ptr<RedisCache> rcache;
  RETURN_IF_ERROR(RedisCache::Create(cache_config, &rcache));
  *cache = reinterpret_cast<TRITONCACHE_Cache*>(rcache.release());
  return nullptr;  // success
}

其中,Redis::Create函数创建RedisCache对象,为静态函数,这种写法为triton的常用写法,类中设计静态函数:Create,创建类对象,这样代码更易读,是一个很好的设计,该函数如下:

TRITONSERVER_Error*
RedisCache::Create(
    const std::string& cache_config, std::unique_ptr<RedisCache>* cache)
{
  rapidjson::Document document;

  document.Parse(cache_config.c_str());
  if (!document.HasMember("host") || !document.HasMember("port")) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INVALID_ARG,
        "Failed to initialize RedisCache, didn't specify address. Must at a "
        "minimum specify 'host' and 'port' in the configuration - e.g. "
        "tritonserver --cache-config redis,host=redis --cache-config "
        "redis,port=6379 --model-repository=/models ...");
  }

  sw::redis::ConnectionOptions options;
  sw::redis::ConnectionPoolOptions poolOptions;

  // try pulling user/password from environment fist
  // override if present in the config
  setOptionFromEnv(USERNAME_ENV_VAR_NAME, options.user);
  setOptionFromEnv(PASSWORD_ENV_VAR_NAME, options.password);

  setOption("host", options.host, document);
  setOption("port", options.port, document);
  setOption("user", options.user, document);
  setOption("password", options.password, document);
  setOption("db", options.db, document);
  setOption("connect_timeout", options.connect_timeout, document);
  setOption("socket_timeout", options.socket_timeout, document);
  setOption("pool_size", poolOptions.size, document);
  setOption("wait_timeout", poolOptions.wait_timeout, document);
  if (!document.HasMember("wait_timeout")) {
    poolOptions.wait_timeout = std::chrono::milliseconds(1000);
  }

  // tls options
  if (document.HasMember("tls_enabled")) {
    options.tls.enabled =
        strcmp(document["tls_enabled"].GetString(), "true") == 0;
    setOption("cert", options.tls.cert, document);
    setOption("key", options.tls.key, document);
    setOption("cacert", options.tls.cacert, document);
    setOption("cacert_dir", options.tls.cacertdir, document);
    setOption("sni", options.tls.sni, document);
  }

  try {
    cache->reset(new RedisCache(options, poolOptions));
  }
  catch (const std::exception& ex) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INTERNAL,
        ("Failed to initialize RedisCache: " + std::string(ex.what())).c_str());
  }
  return nullptr;  // success
}

这个函数最核心的功能只有几行代码:

try {
    cache->reset(new RedisCache(options, poolOptions));
  }
  catch (const std::exception& ex) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INTERNAL,
        ("Failed to initialize RedisCache: " + std::string(ex.what())).c_str());
  }

解析输入的配置json串,创建RedisCache对象,该类的构造函数非常简单,使用redis++库,创建redis连接客户端,并调用成员函数:ping(),确认redis是否连接成功。

核心函数:

std::unique_ptr<sw::redis::Redis>
init_client(
    const sw::redis::ConnectionOptions& connectionOptions,
    sw::redis::ConnectionPoolOptions poolOptions)
{
  std::unique_ptr<sw::redis::Redis> redis =
      std::make_unique<sw::redis::Redis>(connectionOptions, poolOptions);
  const auto msg = "Triton RedisCache client connected";
  if (redis->ping(msg) != msg) {
    throw std::runtime_error("Failed to ping Redis server.");
  }

  LOG_VERBOSE(1) << "Successfully connected to Redis";
  return redis;
}

3.2 TRITONCACHE_CacheFinalize

该函数类似类的析构函数,在退出时调用,删除相关的资源。

TRITONSERVER_Error*
TRITONCACHE_CacheFinalize(TRITONCACHE_Cache* cache)
{
  if (cache == nullptr) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INVALID_ARG, "cache was nullptr");
  }

  delete reinterpret_cast<RedisCache*>(cache);
  return nullptr;  // success
}

3.3 TRITONCACHE_CacheInsert

这个函数是实现cache的核心函数,将要缓存的内容保存到redis中,函数原型:

TRITONSERVER_Error*
TRITONCACHE_CacheInsert(
    TRITONCACHE_Cache* cache, const char* key, TRITONCACHE_CacheEntry* entry,
    TRITONCACHE_Allocator* allocator)
{
  RETURN_IF_ERROR(CheckArgs(cache, key, entry, allocator));
  const auto redis_cache = reinterpret_cast<RedisCache*>(cache);
  CacheEntry redis_entry;
  size_t numBuffers = 0;
  RETURN_IF_ERROR(TRITONCACHE_CacheEntryBufferCount(entry, &numBuffers));
  std::vector<std::shared_ptr<char[]>> managedBuffers;
  for (size_t i = 0; i < numBuffers; i++) {
    TRITONSERVER_BufferAttributes* attrs = nullptr;
    RETURN_IF_ERROR(TRITONSERVER_BufferAttributesNew(&attrs));
    std::shared_ptr<TRITONSERVER_BufferAttributes> managed_attrs(
        attrs, TRITONSERVER_BufferAttributesDelete);
    void* base = nullptr;
    size_t byteSize = 0;
    int64_t memoryTypeId;
    TRITONSERVER_MemoryType memoryType;
    RETURN_IF_ERROR(TRITONCACHE_CacheEntryGetBuffer(entry, i, &base, attrs));
    RETURN_IF_ERROR(TRITONSERVER_BufferAttributesByteSize(attrs, &byteSize));
    RETURN_IF_ERROR(
        TRITONSERVER_BufferAttributesMemoryType(attrs, &memoryType));
    RETURN_IF_ERROR(
        TRITONSERVER_BufferAttributesMemoryTypeId(attrs, &memoryTypeId));

    if (!byteSize) {
      return TRITONSERVER_ErrorNew(
          TRITONSERVER_ERROR_INTERNAL, "Buffer size was zero");
    }
    // DLIS-2673: Add better memory_type support - SL - keeping this in place,
    // presumably we're going to have to pull out the other bits that are
    // important some day.
    if (memoryType != TRITONSERVER_MEMORY_CPU &&
        memoryType != TRITONSERVER_MEMORY_CPU_PINNED) {
      return TRITONSERVER_ErrorNew(
          TRITONSERVER_ERROR_INVALID_ARG,
          "Only input buffers in CPU memory are allowed in cache currently");
    }

    std::shared_ptr<char[]> managedBuffer(new char[byteSize]);

    // Overwrite entry buffer with cache-allocated buffer.
    // No need to set new buffer attrs for now, will reuse the one we got above.
    TRITONCACHE_CacheEntrySetBuffer(
        entry, i, static_cast<void*>(managedBuffer.get()), nullptr /* attrs */);

    managedBuffers.push_back(managedBuffer);
    redis_entry.items.insert(std::make_pair(
        getFieldName(i, fieldType::bufferSize), std::to_string(byteSize)));
    redis_entry.items.insert(std::make_pair(
        getFieldName(i, fieldType::memoryType), std::to_string(memoryType)));
    redis_entry.items.insert(std::make_pair(
        getFieldName(i, fieldType::memoryTypeId),
        std::to_string(memoryTypeId)));
  }

  // Callback to copy directly from Triton buffers to RedisCache managedBuffers
  TRITONCACHE_Copy(allocator, entry);
  for (size_t i = 0; i < numBuffers; i++) {
    auto bytesToCopy =
        std::stoi(redis_entry.items.at(getFieldName(i, fieldType::bufferSize)));
    redis_entry.items.insert(std::make_pair(
        getFieldName(i, fieldType::buffer),
        std::string(managedBuffers.at(i).get(), bytesToCopy)));
  }

  // sanity check to make sure we are inserting items into the cache that are
  // comprised of the right number of fields to allow us to marshal
  // the buffer back from Redis later on.
  if (redis_entry.items.size() % FIELDS_PER_BUFFER != 0) {
    return TRITONSERVER_ErrorNew(
        TRITONSERVER_ERROR_INVALID_ARG,
        "Attempted to add incomplete entry to cache");
  }

  RETURN_IF_ERROR(redis_cache->Insert(key, redis_entry));
  return nullptr;  // success
}

在这个函数中,需要注意entry这个参数,其实际类型为:class CacheEntry对象(定义位于cache_entry.cc),是triton对缓存内容的组织形式,这个概念是区别于cache的内容管理的,可以简单的这样理解,当tritonserver拿到要缓存的内容后,需要将内容进行统一的管理,最后的结果就是一个CacheEntry,而TRITONCACHE_CacheInsert函数的功能就是解析CacheEntry,将要缓存的内容解析到redis cache中的CacheEntry中,在redis cache中,CacheEntry的定义如下:

struct CacheEntry {
  size_t numBuffers = 0;
  std::unordered_map<std::string, std::string> items;
};

该函数的主流程如下:

中间还有一步:

TRITONCACHE_Copy(allocator, entry);

将缓存内容,从triton的缓存中拷贝到cache_manager类定义的缓存中。

3.4 TRITONCACHE_CacheLookup

该函数主要实现从redis hash表中查找并读取数据,同时将缓存数据拷贝到triton的cache_entry以及cache_manager的allocator中。

4、redis_cache实现

redis_cache的实现相对local_cache来说比较简单,对内容的缓存,使用了redis中hash表数据结构,这部分代码主要三个模块:初始化、insert、lookup。

4.1 redis初始化

对redis的操作使用了redis++库,初始化部分,主要实现在redis_cache类的构造函数中:

std::unique_ptr<sw::redis::Redis>
init_client(
    const sw::redis::ConnectionOptions& connectionOptions,
    sw::redis::ConnectionPoolOptions poolOptions)
{
  std::unique_ptr<sw::redis::Redis> redis =
      std::make_unique<sw::redis::Redis>(connectionOptions, poolOptions);
  const auto msg = "Triton RedisCache client connected";
  if (redis->ping(msg) != msg) {
    throw std::runtime_error("Failed to ping Redis server.");
  }

  LOG_VERBOSE(1) << "Successfully connected to Redis";
  return redis;
}

还是老样子,triton中每个类,都会设计一个静态的create函数,用于创建本类的对象,reids_cache也一样,create函数完成对象的创建并赋值给入参【cache】,同时建立与redis的链接。

static TRITONSERVER_Error* Create(
      const std::string& cache_config, std::unique_ptr<RedisCache>* cache);

 4.2 insert

insert的核心即为将缓存内容插入到redis的hash表中,代码最重要的也就一行:

TRITONSERVER_Error*
RedisCache::Insert(const std::string& key, CacheEntry& entry)
{
  try {
    _client->hmset(key, entry.items.begin(), entry.items.end());
  }
  catch (const sw::redis::TimeoutError& e) {
    return handleError("Timeout inserting key: ", key, e.what());
  }
  catch (const sw::redis::IoError& e) {
    return handleError("Failed to insert key: ", key, e.what());
  }
  catch (const std::exception& e) {
    return handleError("Failed to insert key: ", key, e.what());
  }
  catch (...) {
    return handleError("Failed to insert key: ", key, "Unknown error.");
  }

  return nullptr;  // success
}

_client->hmset(key, entry.items.begin(), entry.items.end());这行代码的含义是,将entry结构中items这个map中的内容全部插入到hash表中。

4.3 lookup

lookup的功能可以简单的理解为redis的命令hgetall,通过该命令将redis hash表中某个key的所有内容放入entry结构的items字段中。

std::pair<TRITONSERVER_Error*, CacheEntry>
RedisCache::Lookup(const std::string& key)
{
  // CacheEntry结构体,成员map+int
  CacheEntry entry;

  try {
    // 获取 hash 表的所有字段和值
    this->_client->hgetall(
        key, std::inserter(entry.items, entry.items.begin()));

    // determine the number of buffers by dividing the size by the number of
    // fields per buffer
    entry.numBuffers = entry.items.size() / FIELDS_PER_BUFFER;
    return {nullptr, entry};
  }
  catch (const sw::redis::TimeoutError& e) {
    return {handleError("Timeout retrieving key: ", key, e.what()), {}};
  }
  catch (const sw::redis::IoError& e) {
    return {handleError("Failed to retrieve key: ", key, e.what()), {}};
  }
  catch (const std::exception& e) {
    return {handleError("Failed to retrieve key: ", key, e.what()), {}};
  }
  catch (...) {
    return {handleError("Failed to retrieve key: ", key, "Unknown error."), {}};
  }
}

5、问题求助

在使用triton的过程中,我尝试使用一下cache,但是一直没有看到推理的结果缓存到redis中,不知道是什么原因,我在两个地方使能了cache功能:

第一个,启动triton时增加使能cache功能:

tritonserver --model-repository=/models --log-verbose=1 --cache-config=redis,host=172.17.0.1 --cache-config redis,port=6379 --cache-config redis,password="xxxx" --log-file=1.txt

第二,在模型配置文件中,使能response cache:

response_cache {
  enable: true
}

之后通过client请求模型进行推理,但是在redis中一直看不到缓存,至今没有找到原因,如果有同学使用过这个功能,欢迎留言指教,非常感谢

也欢迎大家关注公众号交流:

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

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

相关文章

通过视触觉多模态学习实现机器人泛化操作

这篇文章的主题是探讨如何通过融合视觉和触觉信息来提高强化学习的效率和泛化能力。作者提出了一种名为Masked Multimodal Learning&#xff08;M3L&#xff09;的新型学习策略。M3L的核心思想是在策略梯度更新和基于掩蔽自编码的表示学习阶段之间交替进行。 在策略梯度更新和基…

电商API接口获取电商平台商品详情|JAVA API接口|一篇文章带你搞定Java的数据库访问

一、前言 在应用程序开发中&#xff0c;需要使用数据库管理和存储各种数据。在Java中&#xff0c;提供了一个JDBC技术(Java Database Connectivity&#xff0c;JDBC&#xff0c;Java数据库连接)&#xff0c;它的作用是连接数据库并访问。电商平台数据的采集就经常用到JAVA请求…

密码学基本概念

密码学基本概念 密码学的安全目标至少包含三个方面: (1)保密性(Confidentiality):信息仅被合法用户访问(浏览、阅读、打印等),不被泄露给非授权的用户、实体或过程。 提高保密性的手段有:防侦察、防辐射、数据加密、物理保密等。 (2)完整性(Integrity):资源只有…

第十三章[管理]:13.3:pycharm的常用设置

一,pycharm配置注释模板 1,打开配置界面: pycharm->preference 英文:Editor->File and Code Templates->Python Script 中文:编辑器->文件和代码模板->Python Script 如图: 我们输入的内容: # @Project : ${PROJECT_NAME} # @File : ${NAME}.py # @Author …

相机图像质量研究(38)常见问题总结:编解码对成像的影响--呼吸效应

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

计算机视觉基础【OpenCV轻松入门】:获取图像的ROI

OpenCV的基础是处理图像&#xff0c;而图像的基础是矩阵。 因此&#xff0c;如何使用好矩阵是非常关键的。 下面我们通过一个具体的实例来展示如何通过Python和OpenCV对矩阵进行操作&#xff0c;从而更好地实现对图像的处理。 ROI&#xff08;Region of Interest&#xff09;是…

16.隐式类的定义和使用

目录 概述实践代码执行 结束 概述 实践 代码 package com.fun.scalaimport java.io.File import scala.io.Sourceobject ImplicitClassApp {def main(args: Array[String]): Unit {val file new File("data/wc.data")println(file.read())}implicit class FileE…

计算机设计大赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

【Docker】有用的命令

文章目录 DockerDocker 镜像与容器的差异Docker的好处Hypervisor运维 一、安装docker二、启动docker三、获取docker镜像四、创建镜像使用命令行创建镜像使用dockerfile创建镜像 五、docker报错 Docker docker镜像&#xff08;Image&#xff09; docker镜像类似于虚拟机镜像&…

概念、背景和代码

1. 概念、背景和代码的通用介绍 图1.抽象 概念、背景和代码在不同的语境下有不同的含义&#xff0c;在这里可以尝试进行如下解释&#xff1a; 概念&#xff1a;通常指对事物本质属性或规律的抽象概括。在学术研究、教育、软件开发等领域中&#xff0c;概念是理论体系的基础单元…

HTML知识点

HTML 【一】HTML简介 【1】什么是HTML HTML是一种用于创建网页结构和内容的超文本标记语言&#xff0c;它是构建网页的基础。为了让浏览器正确渲染页面&#xff0c;我们必须遵循HTML的语法规则。浏览器在解析网页时会将HTML代码转换为可视化的页面&#xff0c;所以我们在浏览…

评估睡眠阶段分类:年龄和早晚睡眠对分类性能的影响

摘要 睡眠阶段分类是专家用来监测人类睡眠数量和质量的常用方法&#xff0c;但这是一项耗时且费力的任务&#xff0c;观察者之间和观察者内部的变异性较高。本研究旨在利用小波进行特征提取&#xff0c;采用随机森林进行分类&#xff0c;寻找并评估一种自动睡眠阶段分类的方法…

JAVA设计模式结构型模式

一、前言 java设计模式主要分为创建型模式&#xff0c;结构型模式和行为型模式。上一篇主要总结了行为型设计模式&#xff0c;本章总结&#xff0c;结构型模式。像创建型模式就不写了&#xff0c;比较简单。大概知道是工厂模式和建造者模式&#xff0c;原型模式就行&#xff0…

Atcoder ABC340 E - Mancala 2

Mancala 2&#xff08;曼卡拉 2&#xff09; 时间限制&#xff1a;2s 内存限制&#xff1a;1024MB 【原题地址】 所有图片源自Atcoder&#xff0c;题目译文源自脚本Atcoder Better! 点击此处跳转至原题 【问题描述】 【输入格式】 【输出格式】 【样例1】 【样例输入1】 …

主流开发语言和开发环境介绍

主流开发语言和开发环境介绍文章目录 ⭐️ 主流开发语言&#xff1a;2024年2月编程语言排行榜&#xff08;TIOBE前十&#xff09;⭐️ 主流开发语言开发环境介绍1.Python2.C3.C4.Java5.C#6.JavaScript7.SQL8.GO9.Visual Basic10.PHP ⭐️ 主流开发语言&#xff1a;2024年2月编程…

2024年2月的TIOBE指数,go语言排名第8,JAVA趋势下降

二月头条&#xff1a;go语言进入前十 本月&#xff0c;go在TIOBE指数前10名中排名第8。这是go有史以来的最高位置。当谷歌于2009年11月推出Go时&#xff0c;它一炮而红。在那些日子里&#xff0c;谷歌所做的一切都是神奇的。在Go出现的几年前&#xff0c;谷歌发布了GMail、谷歌…

SpringBoot+WebSocket实现即时通讯(二)

前言 紧接着上文《SpringBootWebSocket实现即时通讯&#xff08;一&#xff09;》 本博客姊妹篇 SpringBootWebSocket实现即时通讯&#xff08;一&#xff09;SpringBootWebSocket实现即时通讯&#xff08;二&#xff09;SpringBootWebSocket实现即时通讯&#xff08;三&…

NestJS入门8:拦截器

前文参考&#xff1a; NestJS入门1&#xff1a;创建项目 NestJS入门2&#xff1a;创建模块 NestJS入门3&#xff1a;不同请求方式前后端写法 NestJS入门4&#xff1a;MySQL typeorm 增删改查 NestJS入门5&#xff1a;加入Swagger NestJS入门6&#xff1a;日志中间件 Nes…

LeetCode 0105.从前序与中序遍历序列构造二叉树:分治(递归)——五彩斑斓的题解(若不是彩色的可以点击原文链接查看)

【LetMeFly】105.从前序与中序遍历序列构造二叉树&#xff1a;分治&#xff08;递归&#xff09;——五彩斑斓的题解&#xff08;若不是彩色的可以点击原文链接查看&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/construct-binary-tree-from-preorder-a…

小清新卡通人物404错误页面源码

小清新卡通人物404错误页面源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云&#xff1a;https://wfr.lanzout.com/i6XbU1olftde