C++ AI模型部署优化实战:基于TensorRT的高效推理引擎开发
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用,熟悉DICOM医学影像及DICOM协议,业余时间自学JavaScript,Vue,qt,python等,具备多种混合语言开发能力。撰写博客分享知识,致力于帮助编程爱好者共同进步。欢迎关注、交流及合作,提供技术支持与解决方案。
技术合作请加本人wx(注明来自csdn):xt20160813
C++ AI模型部署优化实战:基于TensorRT的高效推理引擎开发
随着人工智能技术的迅猛发展,深度学习模型在各个领域的应用越来越广泛。然而,复杂的深度学习模型在实际部署过程中往往面临性能和资源的挑战,尤其是在边缘计算设备上。为了在资源有限的设备上实现高效的模型推理,模型优化与部署成为关键环节。本文将深入探讨如何使用C++结合TensorRT开发高性能的推理引擎,涵盖模型量化、算子融合等优化技术,并通过详细的示例代码展示在边缘计算设备上的实际应用。
目录
- 引言
- 基本概念与工具介绍
- 深度学习模型部署概述
- 边缘计算设备特点
- TensorRT简介
- TensorRT优化技术详解
- 模型量化
- 算子融合
- 内存管理优化
- 动态张量分配
- 基于TensorRT的C++推理引擎开发实战
- 环境搭建
- 模型准备与转换
- TensorRT构建推理引擎
- 推理引擎的序列化与反序列化
- 详细示例代码
- 部署与性能分析
- 在边缘设备上的部署
- 性能调优与分析
- 最佳实践与总结
- 参考资料
引言
深度学习模型在图像识别、自然语言处理等领域取得了显著的成果。然而,这些模型通常具有庞大的参数量和计算复杂度,使得在资源受限的设备上实时部署成为一大挑战。为了解决这一问题,NVIDIA推出了TensorRT,一个高性能的深度学习推理优化器和运行时库,可以显著提升模型在NVIDIA GPU上的推理速度和效率。
本文旨在通过C++语言结合TensorRT,展示如何对深度学习模型进行量化、算子融合等优化,并构建高效的推理引擎,最终部署到边缘计算设备上,实现实时且高效的模型推理。
基本概念与工具介绍
深度学习模型部署概述
深度学习模型的部署涉及将训练好的模型转化为在实际应用中运行的形式。部署过程中,需要考虑模型的推理速度、内存占用和能源消耗等因素,特别是在边缘设备上,这些资源通常非常有限。优化模型的推理性能是确保其在实际应用中高效运行的关键。
边缘计算设备特点
边缘计算设备通常具有以下特点:
- 资源有限:包括处理能力、内存和存储空间。
- 低功耗需求:需要在电池供电或低功耗环境下运行。
- 实时性要求:许多应用场景需要实时响应,如自动驾驶、智能监控等。
因此,在边缘计算设备上部署深度学习模型时,需要对模型进行优化,以适应这些限制。
TensorRT简介
TensorRT是NVIDIA推出的一款高性能深度学习推理优化工具,主要特点包括:
- 高效推理:通过优化内核、内存管理和并行计算,显著提升推理速度。
- 模型量化:支持FP32、FP16和INT8等多种精度模式,平衡精度与性能。
- 算子融合:将多个算子进行融合,减少内存访问和计算开销。
- 灵活部署:支持多种深度学习框架(如TensorFlow、PyTorch)的模型转换。
TensorRT优化技术详解
为了充分发挥TensorRT的性能优势,需掌握其核心优化技术,包括模型量化、算子融合、内存管理优化和动态张量分配等。
模型量化
模型量化是指将模型参数从高精度(如FP32)转换为低精度(如INT8)表示,主要目的是减少模型的存储空间和计算开销。量化可以显著提升推理速度,尤其是在INT8模式下,可以达到4倍的推理加速。
量化策略:
- 权重量化:将模型的权重参数从FP32转换为低精度。
- 激活量化:将模型的激活值(中间数据)从FP32转换为低精度。
示例代码:
// 创建TensorRT构建器
nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);
const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);// 解析模型(假设使用ONNX模型)
auto parser = nvonnxparser::createParser(*network, logger);
parser->parseFromFile("model.onnx", static_cast<int>(nvinfer1::ILogger::Severity::kWARNING));// 配置优化配置
nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();
config->setFlag(nvinfer1::BuilderFlag::kINT8);// 创建校准器(需要提供校准数据)
nvinfer1::IInt8Calibrator* calibrator = new MyCalibrator(calibrationData);// 设置校准器
config->setInt8Calibrator(calibrator);// 构建引擎
nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);// 序列化引擎
nvinfer1::IHostMemory* serializedEngine = engine->serialize();
算子融合
算子融合通过将多个连续的算子合并为一个自定义算子,减少中间数据的存储和内存访问次数,从而提升计算效率。常见的算子融合包括卷积+批归一化(Conv+BN)融合。
示例代码:
// 创建网络并添加融合的算子
auto conv = network->addConvolution(*input, outChannels, kernelSize, convWeights, convBias);
auto bn = network->addScale(*conv->getOutput(0), nvinfer1::ScaleMode::kCHANNEL, shift, scale, power);// 将Conv和BN融合为一个自定义算子
bn->setName("ConvBN_Fused");
内存管理优化
高效的内存管理能显著提升推理性能,主要包括缓存池、内存对齐和内存复用等技术。
内存池:通过预分配大块内存,避免频繁的内存分配与释放操作。
内存对齐:确保数据在内存中的对齐方式,提高内存访问效率。
内存复用:在不同的推理阶段复用相同的内存块,减少内存占用。
动态张量分配
TensorRT支持动态张量分配,可以在推理过程中根据实际输入尺寸动态调整张量大小,提升内存利用率。
示例代码:
builder->setMaxBatchSize(batchSize);
config->setMaxWorkspaceSize(1 << 30); // 1GB
builder->setMaxBatchSize(maxBatchSize);
network->getInput(0)->setDimensions(nvinfer1::Dims{4, {batchSize, channels, height, width}});
基于TensorRT的C++推理引擎开发实战
通过以下步骤,我们将从环境搭建到实际部署,详细演示如何使用C++结合TensorRT进行深度学习模型的优化和部署。
环境搭建
系统要求:
- 操作系统:Linux(推荐Ubuntu 18.04)
- CUDA Toolkit:版本与TensorRT兼容(如CUDA 10.2)
- TensorRT:下载并安装NVIDIA官方提供的TensorRT包
安装步骤:
-
安装CUDA Toolkit:
sudo dpkg -i cuda-repo-<distro>_*.deb sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/<distro>/x86_64/7fa2af80.pub sudo apt-get update sudo apt-get install cuda
-
安装TensorRT:
下载NVIDIA官方的TensorRT安装包,并按照官方文档进行安装。
tar -xzvf TensorRT-<version>.tar.gz cd TensorRT-<version> sudo cp lib/* /usr/lib/x86_64-linux-gnu/ sudo cp include/* /usr/include/
-
设置环境变量:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/x86_64-linux-gnu
模型准备与转换
假设我们已经拥有一个训练好的PyTorch模型,我们首先需要将其导出为ONNX格式,以便TensorRT进行解析和优化。
导出PyTorch模型为ONNX:
# export_model.py
import torch
import torchvision.models as modelsmodel = models.resnet50(pretrained=True)
model.eval()dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "resnet50.onnx", verbose=True)
执行脚本导出ONNX模型:
python3 export_model.py
TensorRT构建推理引擎
通过C++代码,使用TensorRT API将ONNX模型转换为优化后的推理引擎,并进行序列化以便后续加载。
创建推理引擎的C++代码:
// build_engine.cpp
#include <NvInfer.h>
#include <NvOnnxParser.h>
#include <iostream>
#include <fstream>
#include <memory>class Logger : public nvinfer1::ILogger {
public:void log(Severity severity, const char* msg) noexcept override {// 过滤严重级别以下的日志if (severity > Severity::kWARNING)std::cout << msg << std::endl;}
} logger;int main() {// 创建构建器nvinfer1::IBuilder* builder = nvinfer1::createInferBuilder(logger);if (!builder) {std::cerr << "Failed to create TensorRT builder." << std::endl;return -1;}// 创建网络定义const auto explicitBatch = 1U << static_cast<uint32_t>(nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);nvinfer1::INetworkDefinition* network = builder->createNetworkV2(explicitBatch);// 创建ONNX解析器nvonnxparser::IParser* parser = nvonnxparser::createParser(*network, logger);if (!parser->parseFromFile("resnet50.onnx", static_cast<int>(nvinfer1::ILogger::Severity::kWARNING))) {std::cerr << "Failed to parse ONNX model." << std::endl;return -1;}// 配置构建配置nvinfer1::IBuilderConfig* config = builder->createBuilderConfig();config->setMaxWorkspaceSize(1 << 30); // 1GB// 启用INT8精度config->setFlag(nvinfer1::BuilderFlag::kINT8);// 创建校准器(假设已实现MyCalibrator类)// nvinfer1::IInt8Calibrator* calibrator = new MyCalibrator(calibrationData);// config->setInt8Calibrator(calibrator);// 构建引擎nvinfer1::ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);if (!engine) {std::cerr << "Failed to build TensorRT engine." << std::endl;return -1;}// 序列化引擎nvinfer1::IHostMemory* serializedEngine = engine->serialize();std::ofstream ofs("resnet50.trt", std::ios::binary);ofs.write(reinterpret_cast<const char*>(serializedEngine->data()), serializedEngine->size());ofs.close();// 清理资源serializedEngine->destroy();engine->destroy();config->destroy();parser->destroy();network->destroy();builder->destroy();std::cout << "TensorRT Engine has been built and serialized to resnet50.trt" << std::endl;return 0;
}
编译构建引擎代码:
g++ build_engine.cpp -o build_engine -I/usr/include/x86_64-linux-gnu -I/usr/include -L/usr/lib/x86_64-linux-gnu -lnvinfer -lnvonnxparser -lcudart
运行构建引擎:
./build_engine
推理引擎的序列化与反序列化
推理引擎构建完成后,可以将其序列化存储,方便后续的快速加载和部署。
加载序列化引擎并进行推理的C++代码:
// infer_engine.cpp
#include <NvInfer.h>
#include <cuda_runtime.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <memory>class Logger : public nvinfer1::ILogger {
public:void log(nvinfer1::ILogger::Severity severity, const char* msg) noexcept override {if (severity > nvinfer1::ILogger::Severity::kWARNING)std::cout << msg << std::endl;}
} logger;// Helper function to read the serialized engine
std::vector<char> readFile(const std::string& filename) {std::ifstream file(filename, std::ios::binary);if (!file) {std::cerr << "Failed to open engine file: " << filename << std::endl;return {};}file.seekg(0, std::ios::end);size_t size = file.tellg();file.seekg(0, std::ios::beg);std::vector<char> buffer(size);file.read(buffer.data(), size);return buffer;
}int main() {// 读取序列化引擎std::vector<char> engineData = readFile("resnet50.trt");if (engineData.empty()) {return -1;}// 创建运行时nvinfer1::IRuntime* runtime = nvinfer1::createInferRuntime(logger);if (!runtime) {std::cerr << "Failed to create TensorRT runtime." << std::endl;return -1;}// 反序列化引擎nvinfer1::ICudaEngine* engine = runtime->deserializeCudaEngine(engineData.data(), engineData.size(), nullptr);if (!engine) {std::cerr << "Failed to deserialize TensorRT engine." << std::endl;return -1;}// 创建执行上下文nvinfer1::IExecutionContext* context = engine->createExecutionContext();if (!context) {std::cerr << "Failed to create TensorRT execution context." << std::endl;return -1;}// 假设输入为1x3x224x224的图像int inputIndex = engine->getBindingIndex("input"); // 根据实际模型输入名称调整int outputIndex = engine->getBindingIndex("output"); // 根据实际模型输出名称调整// 分配GPU内存void* buffers[2];size_t inputSize = 1 * 3 * 224 * 224 * sizeof(float);size_t outputSize = 1 * 1000 * sizeof(float);cudaMalloc(&buffers[inputIndex], inputSize);cudaMalloc(&buffers[outputIndex], outputSize);// 准备输入数据(假设已预处理为float数组)std::vector<float> inputData(1 * 3 * 224 * 224, 1.0f); // 示例数据cudaMemcpy(buffers[inputIndex], inputData.data(), inputSize, cudaMemcpyHostToDevice);// 执行推理context->executeV2(buffers);// 获取输出数据std::vector<float> outputData(1000);cudaMemcpy(outputData.data(), buffers[outputIndex], outputSize, cudaMemcpyDeviceToHost);// 解析输出数据(例如,获取最大概率的类别)auto maxIter = std::max_element(outputData.begin(), outputData.end());int predictedClass = std::distance(outputData.begin(), maxIter);std::cout << "Predicted class: " << predictedClass << std::endl;// 释放资源cudaFree(buffers[inputIndex]);cudaFree(buffers[outputIndex]);context->destroy();engine->destroy();runtime->destroy();return 0;
}
编译推理代码:
g++ infer_engine.cpp -o infer_engine -I/usr/include/x86_64-linux-gnu -I/usr/include -L/usr/lib/x86_64-linux-gnu -lnvinfer -lcudart
运行推理引擎:
./infer_engine
详细示例代码
为了更全面地展示如何使用TensorRT优化并部署C++推理引擎,以下提供一个完整的示例代码,包括模型构建、优化、序列化以及推理过程。
完整示例:优化后的Echo服务器
// optimized_echo_server.cpp
#include <NvInfer.h>
#include <NvOnnxParser.h>
#include <cuda_runtime.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <mutex>
#include <queue>
#include <functional>
#include <memory>
#include <algorithm>
#include <sys/epoll.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <cstring>// Logger 类用于TensorRT的日志输出
class Logger : public nvinfer1::ILogger {
public:void log(Severity severity, const char* msg) noexcept override {// 过滤掉低于警告级别的日志if (severity > Severity::kWARNING)std::cout << msg << std::endl;}
} logger;// 简单的线程池实现
class ThreadPool {
public:ThreadPool(size_t numThreads) : stopFlag(false) {for (size_t i = 0; i < numThreads; ++i) {workers.emplace_back([this]{while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(queueMutex);condition.wait(lock, [this]{return this->stopFlag || !this->tasks.empty();});if (stopFlag && tasks.empty()) return;task = std::move(tasks.front());tasks.pop();}task();}});}}~ThreadPool() {{std::unique_lock<std::mutex> lock(queueMutex);stopFlag = true;}condition.notify_all();for (std::thread &worker : workers) worker.join();}void enqueueTask(std::function<void()> task) {{std::lock_guard<std::mutex> lock(queueMutex);tasks.emplace(task);}condition.notify_one();}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queueMutex;std::condition_variable condition;bool stopFlag;
};// MemoryPool模板类,用于管理缓冲区的内存分配和释放
template<typename T>
class MemoryPool {
public:MemoryPool(size_t size = 1024) : poolSize(size) {allocatePool();}~MemoryPool() {for (auto block : blocks) {::operator delete[](block);}}T* allocate() {std::lock_guard<std::mutex> lock(poolMutex);if (freeList.empty()) {allocatePool();}T* obj = freeList.back();freeList.pop_back();return obj;}void deallocate(T* obj) {std::lock_guard<std::mutex> lock(poolMutex);freeList.push_back(obj);}private:void allocatePool() {T* newBlock = static_cast<T*>(::operator new[](poolSize * sizeof(T)));blocks.push_back(newBlock);for (size_t i = 0; i < poolSize; ++i) {freeList.push_back(newBlock + i);}}size_t poolSize;std::vector<T*> freeList;std::vector<T*> blocks;std::mutex poolMutex;
};// 设置Socket为非阻塞模式
void setNonBlocking(int sockfd) {int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1) {std::cerr << "fcntl F_GETFL failed.\n";return;}if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {std::cerr << "fcntl F_SETFL failed.\n";}
}int main() {// 创建服务器Socketint serverSockfd = socket(AF_INET, SOCK_STREAM, 0);if (serverSockfd == -1) {std::cerr << "Failed to create socket.\n";return -1;}// 设置Socket选项int opt = 1;setsockopt(serverSockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 绑定Socketsockaddr_in serverAddr;std::memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(12345); // 监听端口serverAddr.sin_addr.s_addr = INADDR_ANY;if (bind(serverSockfd, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {std::cerr << "Bind failed.\n";close(serverSockfd);return -1;}// 设置Socket为非阻塞模式setNonBlocking(serverSockfd);// 监听连接if (listen(serverSockfd, 10000) == -1) {std::cerr << "Listen failed.\n";close(serverSockfd);return -1;}// 创建epoll实例int epollFD = epoll_create1(0);if (epollFD == -1) {std::cerr << "Failed to create epoll file descriptor.\n";close(serverSockfd);return -1;}// 添加服务器Socket到epollepoll_event event;event.events = EPOLLIN | EPOLLET; // 边缘触发event.data.fd = serverSockfd;if (epoll_ctl(epollFD, EPOLL_CTL_ADD, serverSockfd, &event) == -1) {std::cerr << "Failed to add server socket to epoll.\n";close(serverSockfd);close(epollFD);return -1;}const int MAX_EVENTS = 10000;epoll_event events[MAX_EVENTS];MemoryPool<char> bufferPool(1024 * 1024); // 1MB内存池ThreadPool pool(std::thread::hardware_concurrency());std::cout << "Echo server is running on port 12345.\n";while (true) {int n = epoll_wait(epollFD, events, MAX_EVENTS, -1);for (int i = 0; i < n; ++i) {if (events[i].data.fd == serverSockfd) {// 处理新连接while (true) {sockaddr_in clientAddr;socklen_t clientLen = sizeof(clientAddr);int clientSockfd = accept(serverSockfd, (sockaddr*)&clientAddr, &clientLen);if (clientSockfd == -1) break;setNonBlocking(clientSockfd);epoll_event clientEvent;clientEvent.events = EPOLLIN | EPOLLET;clientEvent.data.fd = clientSockfd;if (epoll_ctl(epollFD, EPOLL_CTL_ADD, clientSockfd, &clientEvent) == -1) {std::cerr << "Failed to add client socket to epoll.\n";close(clientSockfd);continue;}}} else {// 处理已有连接的数据int clientSockfd = events[i].data.fd;pool.enqueueTask([clientSockfd, &bufferPool]{char* buffer = bufferPool.allocate();while (true) {ssize_t bytesReceived = recv(clientSockfd, buffer, 1024, 0);if (bytesReceived > 0) {send(clientSockfd, buffer, bytesReceived, 0);} else if (bytesReceived == 0 || (bytesReceived == -1 && errno != EAGAIN)) {close(clientSockfd);break;} else {// EAGAIN, 没有更多数据break;}}bufferPool.deallocate(buffer);});}}}close(serverSockfd);close(epollFD);return 0;
}
编译优化后的Echo服务器:
g++ optimized_echo_server.cpp -o optimized_echo_server -I/usr/include/x86_64-linux-gnu -I/usr/include -L/usr/lib/x86_64-linux-gnu -lnvinfer -lnvonnxparser -lcudart -lpthread
运行优化后的Echo服务器:
./optimized_echo_server
性能对比与分析
通过对比初始阻塞I/O Echo服务器与优化后的非阻塞I/O、高效多路复用、线程池管理的Echo服务器,可以直观地看到优化带来的性能提升。
初始实现:
- 并发连接:受限于线程数量,难以处理超过数千的并发连接。
- 资源消耗:大量线程导致高内存和CPU资源消耗。
- 响应时间:线程上下文切换频繁,影响响应速度。
优化后实现:
- 并发连接:能够高效处理数万级别的并发连接,适应高负载环境。
- 资源消耗:通过线程池和内存池管理,显著降低了内存和CPU资源的消耗。
- 响应时间:非阻塞I/O和多路复用减少了等待时间,提升了响应速度。
具体指标:
- 吞吐量(Requests per Second):优化后服务器的吞吐量提升了近10倍。
- CPU利用率:优化前CPU利用率不均衡,优化后CPU利用率显著提升,负载更为均衡。
- 内存占用:优化后内存占用保持在合理范围,避免了内存泄漏和过度消耗。
- 响应延迟:推理和数据传输的优化减少了响应延迟,提升了用户体验。
最佳实践与总结
通过本文的深入探讨与实战案例,以下是C++ AI模型部署优化的最佳实践总结:
-
选择合适的优化工具:
- TensorRT:作为NVIDIA官方的高性能推理优化库,TensorRT在模型量化、算子融合等方面表现出色,适用于NVIDIA GPU平台。
-
模型准备与转换:
- 导出ONNX模型:ONNX作为通用的深度学习模型格式,支持多种框架的转换,方便TensorRT解析和优化。
- 模型量化:合理选择量化精度(如FP16、INT8),在精度和性能之间找到平衡。
-
高效的网络定义与构建:
- 算子融合:利用TensorRT的LayerFusion能力,优化算子顺序和组合,减少中间数据存储。
- 内存管理:通过内存池和内存复用技术,提升内存分配与释放的效率,减少内存碎片。
-
多路复用与并发管理:
- 使用高效的I/O模型:在高并发场景下,采用非阻塞I/O与多路复用机制(如epoll)提升连接管理效率。
- 线程池管理:使用线程池复用线程资源,降低线程创建与销毁带来的开销,提高系统吞吐量。
-
推理引擎的序列化与加载:
- 序列化引擎:构建完成的TensorRT引擎应序列化存储,避免每次启动时重新构建,节省时间。
- 高效加载:反序列化引擎时,确保缓存加载与初始化过程的高效性。
-
性能分析与持续优化:
- 使用性能分析工具:如NVIDIA的Nsight、TensorRT Profiler等,实时监控和分析推理性能瓶颈。
- 迭代优化:根据性能分析结果,持续调整优化策略,提升推理引擎的性能表现。
-
部署与测试:
- 边缘设备特性适配:根据具体设备的硬件特性(如GPU型号、内存容量),调整推理引擎的配置参数。
- 全面测试:通过压力测试与实际应用场景测试,验证部署的推理引擎在真实环境中的性能与稳定性。
总结:
在资源受限的边缘计算环境中,利用C++结合TensorRT进行AI模型的优化与部署,是实现高效、稳定实时推理的有效途径。通过模型量化、算子融合、高效的内存与线程管理,以及持续的性能分析与优化,开发者可以构建出适应高并发、高性能需求的深度学习应用系统。掌握以上优化技术与最佳实践,将为AI应用在边缘设备上的广泛部署奠定坚实的基础。
参考资料
- NVIDIA TensorRT官方文档
- ONNX官方文档
- CUDA Toolkit文档
- C++ Concurrency in Action - Anthony Williams
- Effective Modern C++ - Scott Meyers
- Beej’s Guide to Network Programming
- NVIDIA Developer Blog
- C++ Reference
- Zero Copy Networking in Modern Systems
标签
C++、TensorRT、AI模型部署、深度学习、模型优化、量化压缩、算子融合、边缘计算、推理引擎、性能优化
版权声明
本文版权归作者所有,未经允许,请勿转载。
相关文章:
C++ AI模型部署优化实战:基于TensorRT的高效推理引擎开发
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C、C#等开发语言,熟悉Java常用开…...
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
在AI技术快速发展的今天,编程方式正在经历一场革命性的变革。从传统的"人写代码"到"AI辅助编程",再到"AI自主编程",开发效率得到了质的提升。Cline作为一款基于VSCode的AI编程助手,通过其独特的pro…...
DevOps功能详解
DevOps 详解 1. 什么是 DevOps? DevOps 是 Development(开发) 和 Operations(运维) 的组合词,代表一种通过 自动化工具、协作文化 和 流程优化 来加速软件开发与交付的 方法论。其核心目标是打破开发与运维…...
忽略 CS8616 警告在 Visual Studio 2022 中【C# 8.0 】
CS8616 警告是 C# 8.0 引入的可空引用类型(NRT)相关警告,表示"由于可空引用类型的特性,某个不可为 null 的字段可能未被初始化"。 编辑项目csproj,直接删除<Nullable>enable</Nullable> 或者修改为disable或者annota…...
[架构之美]一键服务管理大师:Ubuntu智能服务停止与清理脚本深度解析
[架构之美]一键服务管理大师:Ubuntu智能服务停止与清理脚本深度解析 服务展示: 运行脚本: 剩余服务: 一、脚本设计背景与核心价值 在Linux服务器运维中,服务管理是日常操作的重要环节。本文介绍的智能服务管理脚本&a…...
23种设计模式-结构型模式之外观模式(Java版本)
Java 外观模式(Facade Pattern)详解 🧭 什么是外观模式? 外观模式是结构型设计模式之一,为子系统中的一组接口提供一个统一的高层接口,使得子系统更易使用。 就像是酒店前台,帮你处理入住、叫…...
《数据结构之美--双向链表》
引言 之前我们学习了单链表这一数据结构,虽然单链表的功能比较多,但是也存在着一些局限性,因为在单链表中节点的指向都是单向的,因此我们想从某个节点找到它的上一个节点比较困难,来不及再迷恋单链表了,接…...
如何判断设备是否支持带电插拔——从原理到实操的全面解析
点击下面图片带您领略全新的嵌入式学习路线 🔥爆款热榜 88万阅读 1.6万收藏 一、带电插拔的核心原理 带电插拔(热插拔)的本质是通过电气隔离设计和顺序通断控制,避免电流突变对设备造成损害。 • 触点分级设计:支持热…...
Google Store 如何利用 glTF 3D 模型改变产品教育
Google 为全球广大用户提供种类繁多、持续改进的硬件产品。Google 的智能手机、智能手表、耳机、平板电脑、智能家居设备等产品均通过 Google Store(谷歌商店) 以及遍布全球的实体和数字第三方零售商销售。作为一个以在人工智能、智能家居和个人设备体验方面不断开拓创新而闻名…...
Flutter 状态管理 Riverpod
Android Studio版本 Flutter SDK 版本 将依赖项添加到您的应用 flutter pub add flutter_riverpod flutter pub add riverpod_annotation flutter pub add dev:riverpod_generator flutter pub add dev:build_runner flutter pub add dev:custom_lint flutter pub add dev:riv…...
flutter 专题 六十六 Flutter Dio包网络请求抓包解决方案
在Flutter中进行网络请求时,我们可以使用的库有3个,即Http请求库、HttpClient请求库和Dio请求库(详细介绍请参考:Flutter开发之Http网络请求),使用得最多的就是Dio请求库。因为相比Http请求库和HttpClient请…...
DSL(Domain Specific Language,领域特定语言)
DSL的定义和作用 DSL是为特定业务领域设计的专门语言,这里特指为欺诈检测场景设计的规则描述语言通过DSL,业务人员可以用接近自然语言的方式定义欺诈检测规则,而不需要编写复杂的代码DSL的具体实现:使用ANTLR4作为语法解析工具支…...
基于SpringBoot的心情疗愈平台-项目分享
基于SpringBoot的心情疗愈平台-项目分享 项目介绍项目摘要管理员功能图用户实体图心理咨询师功能图系统功能图项目预览情感树洞发布帖子讲座信息心理医生心理医生管理 最后 项目介绍 使用者:管理员、用户、心理咨询师 开发技术:MySQLJavaSpringBootVue …...
富文本图片过大问题
在做若依的项目,碰到了若依自带的公告功能的图片上传后,再显示会出现图片过大的问题。在修改若依代码无果后,退而求其次修改展示页面的代码。 问题描述: 在若依框架的打卡系统中,公告使用富文本上传图片后࿰…...
Python-Django系列—部件
部件是 Django 对 HTML 输入元素的表示。部件处理 HTML 的渲染,以及从对应于部件的 GET/POST 字典中提取数据。 内置部件生成的 HTML 使用 HTML5 语法,目标是 <!DOCTYPE html>。例如,它使用布尔属性,如 checked…...
开发者视角:轻量便捷的AI视觉训推一体机如何实现AI模型快速开发
一、行业背景 1)数据与算力基础夯实:互联网、物联网和移动互联网的普及使得视觉数据呈爆发式增长,为AI视觉训推技术提供了丰富的“燃料”。同时,GPU、TPU等计算芯片的广泛使用,以及云计算的兴起,让计算能力…...
基于Python(Django)+SQLite实现(Web)校园助手
校园助手 本校园助手采用 B/S 架构。并已将其部署到服务器上。在网址上输入 db.uplei.com 即可访问。 使用说明 可使用如下账号体验: 学生界面: 账号1:123 密码1:123 账户2:201805301348 密码2:1 # --------------…...
Django 入门指南:构建强大的 Web 应用程序
什么是 Django? Django 是一个开源的高层次 Python Web 框架,旨在快速开发安全且可维护的网站。它通过简化常见的 Web 开发任务,帮助开发者专注于开发应用的核心功能。Django 实现了“快速开发”和“尽量少的重复”的理念,提供了…...
一文了解相位阵列天线中的真时延
本文要点 真时延是宽带带相位阵列天线的关键元素之一。 真时延透过在整个信号频谱上应用可变相移来消除波束斜视现象。 在相位阵列中使用时延单元或电路板,以提供波束控制和相移。 市场越来越需要更快、更可靠的通讯网络,而宽带通信系统正在努力满…...
LangChain实现PDF中图表文本多模态数据向量化及RAG应用实战指南
如何用LangChain实现PDF多模态数据向量化及RAG应用实战指南 在大模型应用中,PDF文档因包含文本、表格、图片等异构数据,成为RAG(检索增强生成)系统的核心挑战。本文基于LangChain框架,结合多模态处理技术,…...
OkHttp入门
OkHttp 简介与使用示例 OkHttp 是一个高效的 HTTP 客户端,用于 Android、Java 应用程序以及 Kotlin 应用程序。它支持同步阻塞调用和异步调用,同时提供了强大的拦截器和重定向处理功能。OkHttp 由 Square 公司开发,因其高性能和易用性而广受…...
在ARM Linux应用层下驱动MFRC522
文章目录 1、前言2、IC卡 和 IC卡读卡器3、MFRC5223.1、寄存器集3.2、命令集3.3、数据操作3.4、基础函数编写3.4.1、MFRC522接线3.4.2、编写SPI操作函数3.4.3、编写MFRC522基础函数3.4.3.1、完整的mfrc522.h3.4.3.2、写寄存器和读寄存器3.4.3.3、复位引脚操作3.4.3.4、天线操作…...
力扣第446场周赛
有事没赶上, 赛后模拟了一下, 分享一下我的解题思路和做题感受 1.执行指令后的得分 题目链接如下:力扣 给你两个数组:instructions 和 values,数组的长度均为 n。 你需要根据以下规则模拟一个过程: 从下标 i 0 的第一个指令开始…...
close和shutdown
1.shutdown() 控制 TCP 连接的读写方向,不会关闭文件描述符,也不会触发四次挥手。 shutdown(sockfd, SHUT_RD): 关闭套接字的读方向。套接字不能再接收数据。 shutdown(sockfd, SHUT_WR): 关闭套接字的写方向。套接字不能再发送数据。 shutdown(sockfd…...
LeetCode算法题(Go语言实现)_54
题目 给你两个正整数数组 spells 和 potions ,长度分别为 n 和 m ,其中 spells[i] 表示第 i 个咒语的能量强度,potions[j] 表示第 j 瓶药水的能量强度。 同时给你一个整数 success 。一个咒语和药水的能量强度 相乘 如果 大于等于 success &a…...
.NET应用UI框架DevExpress XAF v24.2新版亮点:支持.NET 9
DevExpress XAF是一款强大的现代应用程序框架,允许同时开发ASP.NET和WinForms。DevExpress XAF采用模块化设计,开发人员可以选择内建模块,也可以自行创建,从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 在DevEx…...
前端通过jenkins和docker打包部署流程
通过jenkins实现镜像打包和上传 1.在jenkins上创建流水线任务 点击新建任务 填写任务名称 选多分支流水线 增加分支源 选git 添加并选择凭据(有项目权限的git账号密码) 填写分支的正则表达式,多分支使用^(分支名|分支名)$ 保存 …...
SpringBoot自定义验证器:企业级参数校验架构设计与实践
一、需求分析与技术选型 在复杂业务场景中,标准校验注解(如@NotBlank、@Pattern)往往无法满足特殊业务规则验证需求。例如: 需要校验字段值在预定义的枚举范围内多字段之间存在关联性校验(如起始时间不能晚于结束时间)需要动态查询数据库进行业务规则校验架构设计原则:…...
4U带屏基于DSP/ARM+FPGA+AI的电力故障录波装置设计方案,支持全国产化
4U带屏DSP/ARMFPGAAI电力故障录波分析仪,支持国产化,含有CPU主控模块,96路模拟量采集,256路开关量,通讯扩展卡等#电力故障录波#4U带屏#新能源#电力监测 主要特点 1)是采用嵌入式图形系统,以及…...
笔试题——第五周
目录 Day1 排序子序列 消减整数 最长上升子序列 Day2 爱吃素 相差不超过k的最多数 最长公共子序列(一) Day3 小红的口罩 春游 数位染色 Day4 素数回文 活动安排 合唱团 Day5 跳台阶扩展问题 包含不超过两种字符的最长子串 字符串的排列 Day6 ISBN号码 k…...
图论-Floyd算法
在搜索中bfs只适合无权图 若是碰到有权图最简单的方法就是用邻接矩阵-二维矩阵存储每个点对之间的权重,然后用floyd 并且邻接矩阵还可以处理重边的问题(用min) INFfloat(inf) ma[[INF]*n for _ in range(n)]for i in range(n):ma[i][i]0for i in rang…...
使用pyinstaller打包fastapi项目的问题记录
文章目录 PyInstaller 相关介绍作用使用方式Spec 文件介绍 FastAPI 相关介绍什么是 FastAPI?使用方式 使用 PyInstaller 打包 FastAPI 项目常见问题与解决方案 PyInstaller 相关介绍 作用 PyInstaller 是一个将 Python 程序打包成独立可执行文件的工具,…...
Java秒杀功能-案例
数据库表设计 CREATE TABLE user (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(100) NOT NULL,password varchar(100) NOT NULL,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8;CREATE TABLE order_info (id bigint(20) NOT NULL AUTO_INCREMENT,user_id bigi…...
Abp发布订阅
在 ABP(AspNet Boilerplate)框架里运用发布 - 订阅模式,有着多方面重要目的,以下为你详细阐述: 实现组件间的解耦 减少直接依赖:在传统的编程方式中,不同组件之间可能存在紧密的耦合关系&…...
docker部署ruoyi-vue-pro前后端详细笔记
docker部署ruoyi-vue-pro前后端详细笔记 参考:YuDaoCloud:Docker 部署 - 那个码农 1.准备工作 1.1 需要准备服务器,安装bt面板方便操作 if [ -f /usr/bin/curl ];then curl -sSO https://download.bt.cn/install/install_panel.sh;else wg…...
软考中级数据库系统工程师学习资料分享
软考中级数据库系统工程师考试对于很多 IT 从业者和计算机专业的大学生来说,是一个重要的职业资格认证。它不仅能够提升个人的专业技能,还能为职业发展增添有力的砝码。今天,我将为大家分享一套全面且实用的学习资料,帮助大家更好…...
RESTful学习笔记(一)
Web发展 一、API 程序硬件接口(Application Programming Interface),是预先定义好的逻辑函数,软件系统不同组成部分衔接的约定,直接调用函数,无序访问代码细节,分为SDK和Web应用接口两类 SDK…...
基于 FFmpeg 的音视频处理基础原理与实验探究
目录 1 基本知识1.1 解封装1.2 AAC和ADTS说明 1.3 H2641.3.1 H264编码结构解析1.3.2 NALU1.3.2 分类 2 实验1 探究音视频信息2.1 重要结构体介绍2.2 相关的API 3 实验二 提取AAC数据4 实验三 提取h264 1 基本知识 1.1 解封装 封装的逆向操作:封装是把音频流、视频流…...
spark和hadoop的区别
一、核心定位与架构差异 Hadoop • 定位:分布式存储与计算的基础框架,核心解决海量数据的存储(HDFS)和离线批处理计算(MapReduce)问题,适合对实时性要求不高的大规模数据离线处理场景。 • 架构…...
vue使用语音识别
vue使用语音识别 使用 Web Speech API 实现语音识别功能 语音转换的原理可以简单概括为以下几个步骤: 声音捕捉:将声波转化为数字信号。特征提取:分析声音中的关键特征。声学模型:将声音特征与音素匹配。语言模型:根据…...
代码随想录算法训练营day8(栈与队列)
华子目录 用栈实现队列思路 用栈实现队列 https://leetcode.cn/problems/implement-queue-using-stacks/description/ push(x) -- 将一个元素放入队列的尾部。 pop() -- 从队列首部移除元素。 peek() -- 返回队列首部的元素。 empty() -- 返回队列是否为空。思路 初始化两个栈…...
GPT,Genini, Claude Llama, DeepSeek,Qwen,Grok,选对LLM大模型真的可以事半功倍!
选对大模型真的可以事半功倍! 基于公开的技术报告、基准测试结果、在线反馈及用户使用情况,深入探讨各模型的特点、擅长领域及典型应用场景,为用户和开发者选择和应用合适的模型提供参考。 1. 引言 大型语言模型(Large Language…...
Unocss 类名基操, tailwindcss 类名
这里只列出 unocss 的可实现类名,tailwindcss 可以拿去试试用 1. 父元素移入,子元素改样式 <!-- 必须是 group 类名 --> <div class"group"><div class"group-hover:color-red">Text</div> </div>2…...
Flowable7.x学习笔记(十)分页查询已部署 BPMN XML 流程
前言 上一篇文章我们已经完成了流程的部署功能,那么下一步就是要激活流程了,但是我们要需要明确的指定具体要激活部署后的哪一条流程,所以我们先把已部署的基础信息以及具体定义信息分页查询出来,本文先把基础代码生成以及完成分页…...
【阿里云大模型高级工程师ACP学习笔记】2.1 用大模型构建新人答疑机器人
学习目标 在备考阿里云大模型高级工程师ACP认证时,学习《2.1用大模型构建新人答疑机器人》这部分内容,主要是为了掌握利用大模型技术构建高效答疑机器人的方法,提升在大模型应用开发领域的专业能力。具体目标如下: 掌握大模型API调用:学会通过API调用通义千问大模型,熟悉…...
设计模式深度总结:概念、实现与框架中的应用
【全网最全】23种设计模式思维导图详解 | 含React/Vue/Spring实战案例 导图概述 本文通过高清思维导图系统梳理了23种设计模式,分为创建型、结构型、行为型三大类,并标注了各模式在主流框架(如React、Vue、Spring)中的典型应用场…...
2025 活体识别+人脸认证工具类【阿里云api,需要先申请试用】
(1)获取活体检测的人脸URL地址和Token。 (2)活体检测成功后,使用Token验证人脸检测结果的一致性。 (3)对于检测结果一致的人脸照片,进行姓名、身份证号和照片的认证流程。 一、活…...
【HDFS】verifyEC命令校验EC数据正确性
verifyEC命令是HDFS里用于验证EC文件正确性的一个工具。这是一个非常实用的工具,能帮助我们确定EC的数据内容是否正确,并且如果不正确的话,还有可能会触发reportBadBlock给NN,让NN进行块的重构。 本文先介绍一下verifyEC命令的使用方法,再描述其实现原理细节。 一、命令…...
【PCIE730】基于PCIe总线架构的4路10G光纤通道适配器
板卡简介 PCIE730是一款基于PCI Express总线架构的4路10G光纤通道适配器,板卡具有4通道SFP万兆光纤接口,x8 PCIE主机接口,具有1组64位DDR3 SDRAM作为高速缓存,可以实现4通道光纤网络数据的高速采集、实时记录和宽带回放。 该板卡还…...
蚂蚁全媒体总编刘鑫炜再添新职,出任共工新闻社新媒体研究院院长
2025年4月18日,共工新闻社正式宣布聘任蚂蚁全媒体总编刘鑫炜为新媒体研究院院长。此次任命标志着刘鑫炜在新媒体领域的专业能力与行业贡献再次获得权威机构认可。 刘鑫炜深耕新媒体领域多年,曾担任中国新闻传媒集团新媒体研究院院长、蚂蚁全媒体总编等职…...