深度学习模型部署(十一)TensorRT写Plugin

什么是plugin & 有什么用?

TensorRT的一种机制,以.so的形式插入到网络中实现某些算子。
作用:

  • 实现TensorRT不支持的层
  • 替换性能不好的层
  • 手动进行图优化算子融合

写Plugin就是自己写算子的CUDA kernel实现。
Plugin与其他layer之间无法自动进行算子融合,可能会在plugin前后加入reformating节点,增加开销。
建议先进行原生layer组合保证计算正确性,再尝试官方自带的Plugin是否满足要求,都不行再自己写plugin。

创建Plugin工作流程:

实现一个算子,对输入的张量每个元素加上一个常量

  • 继承 IPluginV2DynamicExt 类实现一个Plugin 类
  • 继承 IPluginCreator 类实现一个 PluginCreator 类
  • 实现用于计算的 CUDA C++ kernel
  • 将 Plugin 编译为 .so 保存
  • 在 TenorRT 中加载和使用 Plugin

实现Plugin类

继承IPluginV2DynamicExt类

Plugin有V1和V2两个版本,V1已经弃用,V2分为:IPluginV2,IPluginV2Ext,IPluginV2IOExt,IPluginV2DynamicExt四种,第三种第四种最常用
在这里插入图片描述

class AddScalarPlugin : public IPluginV2DynamicExt // 定义AddScalarPlugin类,继承IPluginV2DynamicExt类
{
private:const std::string name_; //算子名称std::string       namespace_; //算子所属的域struct{float scalar;} m_;public:AddScalarPlugin() = delete; //禁止默认构造函数AddScalarPlugin(const std::string &name, float scalar);AddScalarPlugin(const std::string &name, const void *buffer, size_t length); //构造函数~AddScalarPlugin();// Method inherited from IPluginV2const char *getPluginType() const noexcept override; //获取插件类型,noexcept表示该函数不会抛出异常,override表示该函数是虚函数const char *getPluginVersion() const noexcept override; //获取插件版本int32_t     getNbOutputs() const noexcept override; //获取输出张量的数量int32_t     initialize() noexcept override; //初始化插件void        terminate() noexcept override; //终止插件,释放资源size_t      getSerializationSize() const noexcept override; //获取序列化后的大小void        serialize(void *buffer) const noexcept override; //序列化void        destroy() noexcept override; //销毁插件,当context或engine被销毁时,插件也会被销毁void        setPluginNamespace(const char *pluginNamespace) noexcept override; //设置插件的命名空间const char *getPluginNamespace() const noexcept override; //获取插件的命名空间//当我们的模型来自onnx的时候,命名空间,版本等信息会被保存在onnx模型中,这个函数就是用来获取这些信息的//一般不用我们自己设置,而是由onnx模型中的信息来设置//如果这些信息设置不对,会导致onnxparser解析模型的时候出错,无法识别插件// Method inherited from IPluginV2ExtDataType getOutputDataType(int32_t index, DataType const *inputTypes, int32_t nbInputs) const noexcept override;void     attachToContext(cudnnContext *contextCudnn, cublasContext *contextCublas, IGpuAllocator *gpuAllocator) noexcept override;void     detachFromContext() noexcept override;// Method inherited from IPluginV2DynamicExtIPluginV2DynamicExt *clone() const noexcept override;DimsExprs            getOutputDimensions(int32_t outputIndex, const DimsExprs *inputs, int32_t nbInputs, IExprBuilder &exprBuilder) noexcept override;// getOutputDimensions,向TensorRT报告输出张量的形状,outputIndex是指输出张量的索引bool                 supportsFormatCombination(int32_t pos, const PluginTensorDesc *inOut, int32_t nbInputs, int32_t nbOutputs) noexcept override;// supportsFormatCombination,检查输入和输出张量的格式是否支持,pos是指输入张量的索引,inOut是指输入和输出张量的描述符, nbInputs是指输入张量的数量,nbOutputs是指输出张量的数量// 尽量多的支持格式组合,以便TensorRT可以选择最佳的格式组合void                 configurePlugin(const DynamicPluginTensorDesc *in, int32_t nbInputs, const DynamicPluginTensorDesc *out, int32_t nbOutputs) noexcept override;// configurePlugin,配置插件,in是指输入张量的描述符,nbInputs是指输入张量的数量,out是指输出张量的描述符,nbOutputs是指输出张量的数量// 在推理期前调用该函数,用于将插件中的动态维度转换为静态维度size_t               getWorkspaceSize(const PluginTensorDesc *inputs, int32_t nbInputs, const PluginTensorDesc *outputs, int32_t nbOutputs) const noexcept override;// getWorkspaceSize,获取插件所需的工作空间大小,inputs是指输入张量的描述符,nbInputs是指输入张量的数量,outputs是指输出张量的描述符,nbOutputs是指输出张量的数量// 在推理期前调用该函数,用于计算插件所需的工作空间大小,向TensorRT报告工作空间的大小int32_t              enqueue(const PluginTensorDesc *inputDesc, const PluginTensorDesc *outputDesc, const void *const *inputs, void *const *outputs, void *workspace, cudaStream_t stream) noexcept override;// enqueue,执行插件的推理,inputDesc是指输入张量的描述符,outputDesc是指输出张量的描述符,inputs是指输入张量的数据,outputs是指输出张量的数据,workspace是指工作空间,stream是指CUDA流// 在推理期间调用该函数,用于执行插件的推理。不要在enqueue中调用cudaMalloc或cudaFree等CUDA API,会造成性能下降// 原因我猜是因为前面getworkspaceSize已经分配了空间,如果这里再进行分配,会使之前针对内存分配做的优化失效
protected:// To prevent compiler warnings,使用using声明,将基类的成员函数引入到子类中,避免编译器警告using nvinfer1::IPluginV2::enqueue;using nvinfer1::IPluginV2::getOutputDimensions;using nvinfer1::IPluginV2::getWorkspaceSize;using nvinfer1::IPluginV2Ext::configurePlugin;
};

实现PluginCreator类

继承IPluginCreator类


class AddScalarPluginCreator : public IPluginCreator 
// 定义一个AddScalarPluginCreator类,继承于IPluginCreator,PluginCreator是一个工厂类,用于创建Plugin
{
private:static PluginFieldCollection    fc_;static std::vector<PluginField> attr_;std::string                     namespace_;public:AddScalarPluginCreator();~AddScalarPluginCreator();const char                  *getPluginName() const noexcept override;const char                  *getPluginVersion() const noexcept override;const PluginFieldCollection *getFieldNames() noexcept override;IPluginV2DynamicExt         *createPlugin(const char *name, const PluginFieldCollection *fc) noexcept override;// 接受一个插件名称和插件属性集合,返回一个新的插件实例IPluginV2DynamicExt         *deserializePlugin(const char *name, const void *serialData, size_t serialLength) noexcept override;// 接受一个插件名称和序列化数据,返回一个新的插件实例void                         setPluginNamespace(const char *pluginNamespace) noexcept override;// 设置插件的命名空间const char                  *getPluginNamespace() const noexcept override;// 获取插件的命名空间
};

实现kernel函数

// kernel for GPU
__global__ void addScalarKernel(const float *input, float *output, const float scalar, const int nElement)
// cuda中global关键字修饰函数表示该函数必须由CPU调用,GPU运行
{const int index = blockIdx.x * blockDim.x + threadIdx.x;//cuda中kernel函数内置变量blockIdx表示目前执行该kernel的block信息,threadIdx表示执行该kernel的thread信息if (index >= nElement) // 如果越界就返回,否则会出现内存访问错误return; //cuda中kernel不允许返回值,但是return可以用来提前结束函数float _1      = input[index];float _2      = _1 + scalar;output[index] = _2;
}int32_t AddScalarPlugin::enqueue(const PluginTensorDesc *inputDesc, const PluginTensorDesc *outputDesc, const void *const *inputs, void *const *outputs, void *workspace, cudaStream_t stream) noexcept
{WHERE_AM_I();int nElement = 1;for (int i = 0; i < inputDesc[0].dims.nbDims; ++i){nElement *= inputDesc[0].dims.d[i];}dim3 grid(CEIL_DIVIDE(nElement, 256), 1, 1), block(256, 1, 1);addScalarKernel<<<grid, block, 0, stream>>>(reinterpret_cast<const float *>(inputs[0]), reinterpret_cast<float *>(outputs[0]), m_.scalar, nElement);return 0;
}

编译

include ../include/Makefile.incSOURCE_CU   = $(shell find . -name '*.cu' 2>/dev/null)
SOURCE_PY   = $(shell find . -name '*.py' 2>/dev/null)
OBJ         = $(shell find . -name *.o 2>/dev/null)
DEP         = $(OBJ:.o=.d)
TARGET_SO   = $(SOURCE_CU:.cu=.so)-include $(DEP)all: $(TARGET_SO)%.so: %.o$(NVCC) $(SOFLAG) $(LDFLAG) -o $@ $+
# nvcc是指定编译器,-shared是指定生成动态链接库,-o是指定生成的动态链接库的名字,$+是指定生成动态链接库的目标文件%.o: %.cu$(NVCC) $(CUFLAG) $(INCLUDE) -M -MT $@ -o $(@:.o=.d) $<$(NVCC) $(CUFLAG) $(INCLUDE) -o $@ -c $<.PHONY: test
# PHONY是一个伪目标,它表示不管是否存在这个文件,只要执行这个目标,就会执行后面的命令
# 伪目标是指不生成任何文件,只是执行一些特定的命令
test:make cleanmakepython3 $(SOURCE_PY).PHONY: clean
clean:rm -rf ./*.d ./*.o ./*.so ./*.exe ./*.plan

加载使用

import ctypes
import osimport numpy as np
import tensorrt as trt
from cuda import cudartsoFile = "./AddScalarPlugin.so"
logger = trt.Logger(trt.Logger.ERROR)
trt.init_libnvinfer_plugins(logger, '')
# trt.init_libnvinfer_plugins函数的作用是初始化TensorRT库中的插件,其中的两个参数分别是日志级别和插件库的路径。
ctypes.cdll.LoadLibrary(soFile)
# ctypes.cdll.LoadLibrary函数的作用是加载指定的动态链接库,其中的参数是动态链接库的路径。
构建期
  • TensorRT 向 Plugin 传输参数和权重
  • Plugin 向 TensorRT 报告其输入输出张量信息,包括数量、形状(Shape)、数据类型(DataType)和数据排布(Layout)组合
  • Plugin 向 TensorRT 报告其需要的 workspace 大小
  • TensorRT 尝试各种允许的组合,选择性能最佳的输入输出组合(可能在 Plugin 前后插入 reformat 节点)
  • Plugin 不参与层 fusing
def getAddScalarPlugin(scalar):for c in trt.get_plugin_registry().plugin_creator_list:#print(c.name)if c.name == "AddScalar":parameterList = []parameterList.append(trt.PluginField("scalar", np.float32(scalar), trt.PluginFieldType.FLOAT32))# PluginField类的作用是定义插件的属性,其中的三个参数分别是属性的名称、属性的值和属性的数据类型。return c.create_plugin(c.name, trt.PluginFieldCollection(parameterList))# create_plugin函数的作用是创建一个插件,其中的两个参数分别是插件的名称和插件的属性集合。return Nonebuilder = trt.Builder(logger)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
profile = builder.create_optimization_profile()
config = builder.create_builder_config()inputT0 = network.add_input("inputT0", trt.float32, [-1 for i in shape])
profile.set_shape(inputT0.name, [1 for i in shape], [8 for i in shape], [32 for i in shape])
config.add_optimization_profile(profile)pluginLayer = network.add_plugin_v2([inputT0], getAddScalarPlugin(scalar))
# add_plugin_v2函数的作用是向网络中添加一个插件层,其中的两个参数分别是输入张量列表和插件。
network.mark_output(pluginLayer.get_output(0))
# mark_output函数的作用是标记网络的输出张量,其中的参数是张量。
engineString = builder.build_serialized_network(network, config)
# build_serialized_network函数的作用是构建序列化的网络,其中的两个参数分别是网络和配置。
运行期
  • TensorRT 为 Plugin 提供输入输出张量的地址,workspace 的地址,以及所在的 stream

完整代码

import ctypes
import osimport numpy as np
import tensorrt as trt
from cuda import cudartsoFile = "./AddScalarPlugin.so"
np.set_printoptions(precision=3, linewidth=200, suppress=True)
# np.set_printoptions函数的作用是设置打印时的精度、行宽、是否使用科学计数法等。其中的三个
# 参数含义分别是:precision:设置浮点数的精度,即小数点后的位数;linewidth:设置输出的行宽;suppress:当suppress=True时,表示不输出小数点后面的数字,即将小数部分四舍五入
np.random.seed(31193)
cudart.cudaDeviceSynchronize()def printArrayInformation(x, info="", n=5):if 0 in x.shape:print('%s:%s' % (info, str(x.shape)))returnx = x.astype(np.float32)print( '%s:%s,SumAbs=%.5e,Var=%.5f,Max=%.5f,Min=%.5f,SAD=%.5f'%( \info,str(x.shape),np.sum(abs(x)),np.var(x),np.max(x),np.min(x),np.sum(np.abs(np.diff(x.reshape(-1)))) ))print('\t', x.reshape(-1)[:n], x.reshape(-1)[-n:])returndef check(a, b, weak=False, checkEpsilon=1e-5, info=""):if a.shape != b.shape:print("Error shape: A%s : B%s" % (str(a.shape), str(b.shape)))returnif weak:a = a.astype(np.float32)b = b.astype(np.float32)res = np.all(np.abs(a - b) < checkEpsilon)else:res = np.all(a == b)maxAbsDiff = np.max(np.abs(a - b))meanAbsDiff = np.mean(np.abs(a - b))maxRelDiff = np.max(np.abs(a - b) / (np.abs(b) + checkEpsilon))meanRelDiff = np.mean(np.abs(a - b) / (np.abs(b) + checkEpsilon))res = "%s:%s,MaxAbsDiff=%.2e,MeanAbsDiff=%.2e,MaxRelDiff=%.2e,MeanRelDiff=%.2e," % (info, res, maxAbsDiff, meanAbsDiff, maxRelDiff, meanRelDiff)index = np.argmax(np.abs(a - b))valueA, valueB= a.flatten()[index], b.flatten()[index]shape = a.shapeindexD = []for i in range(len(shape) - 1, -1, -1):x = index % shape[i]indexD = [x] + indexDindex = index // shape[i]res += "WorstPair=(%f:%f)at%s" %(valueA, valueB, str(indexD))print(res)returndef addScalarCPU(inputH, scalar):return [inputH[0] + scalar]def getAddScalarPlugin(scalar):for c in trt.get_plugin_registry().plugin_creator_list:#print(c.name)if c.name == "AddScalar":parameterList = []parameterList.append(trt.PluginField("scalar", np.float32(scalar), trt.PluginFieldType.FLOAT32))# PluginField类的作用是定义插件的属性,其中的三个参数分别是属性的名称、属性的值和属性的数据类型。return c.create_plugin(c.name, trt.PluginFieldCollection(parameterList))# create_plugin函数的作用是创建一个插件,其中的两个参数分别是插件的名称和插件的属性集合。return Nonedef run(shape, scalar):testCase = "<shape=%s,scalar=%f>" % (shape, scalar)trtFile = "./model-Dim%s.plan" % str(len(shape))print("Test %s" % testCase)logger = trt.Logger(trt.Logger.ERROR)trt.init_libnvinfer_plugins(logger, '')# trt.init_libnvinfer_plugins函数的作用是初始化TensorRT库中的插件,其中的两个参数分别是日志级别和插件库的路径。ctypes.cdll.LoadLibrary(soFile)# ctypes.cdll.LoadLibrary函数的作用是加载指定的动态链接库,其中的参数是动态链接库的路径。if os.path.isfile(trtFile):with open(trtFile, "rb") as f:engine = trt.Runtime(logger).deserialize_cuda_engine(f.read())if engine == None:print("Failed loading engine!")returnprint("Succeeded loading engine!")else:builder = trt.Builder(logger)network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))profile = builder.create_optimization_profile()config = builder.create_builder_config()inputT0 = network.add_input("inputT0", trt.float32, [-1 for i in shape])profile.set_shape(inputT0.name, [1 for i in shape], [8 for i in shape], [32 for i in shape])config.add_optimization_profile(profile)pluginLayer = network.add_plugin_v2([inputT0], getAddScalarPlugin(scalar))# add_plugin_v2函数的作用是向网络中添加一个插件层,其中的两个参数分别是输入张量列表和插件。network.mark_output(pluginLayer.get_output(0))# mark_output函数的作用是标记网络的输出张量,其中的参数是张量。engineString = builder.build_serialized_network(network, config)# build_serialized_network函数的作用是构建序列化的网络,其中的两个参数分别是网络和配置。if engineString == None:print("Failed building engine!")returnprint("Succeeded building engine!")with open(trtFile, "wb") as f:f.write(engineString)engine = trt.Runtime(logger).deserialize_cuda_engine(engineString)# deserialize_cuda_engine函数的作用是反序列化一个CUDA引擎,其中的参数是序列化的引擎。nIO = engine.num_io_tensors# num_io_tensors属性的作用是获取引擎的输入输出张量的数量。lTensorName = [engine.get_tensor_name(i) for i in range(nIO)]# get_tensor_name函数的作用是获取引擎的输入输出张量的名称。nInput = [engine.get_tensor_mode(lTensorName[i]) for i in range(nIO)].count(trt.TensorIOMode.INPUT)# get_tensor_mode函数的作用是获取引擎的输入输出张量的模式,其中的参数是张量的名称。context = engine.create_execution_context()context.set_input_shape(lTensorName[0], shape)#for i in range(nIO):#    print("[%2d]%s->" % (i, "Input " if i < nInput else "Output"), engine.get_tensor_dtype(lTensorName[i]), engine.get_tensor_shape(lTensorName[i]), context.get_tensor_shape(lTensorName[i]), lTensorName[i])bufferH = []bufferH.append(np.arange(np.prod(shape), dtype=np.float32).reshape(shape))# np.arange函数的作用是创建一个等差数组,其中的参数是数组的大小。np.prod函数的作用是计算数组的元素个数。for i in range(nInput, nIO):bufferH.append(np.empty(context.get_tensor_shape(lTensorName[i]), dtype=trt.nptype(engine.get_tensor_dtype(lTensorName[i]))))# 初始化一个空数组,数组的形状是引擎的输入输出张量的形状,数组的数据类型是引擎的输出张量的数据类型。bufferD = []for i in range(nIO):bufferD.append(cudart.cudaMalloc(bufferH[i].nbytes)[1])# cudart.cudaMalloc函数的作用是在GPU上分配一块内存,其中的参数是内存的大小。# 为推理输入输出张量分配内存。for i in range(nInput):cudart.cudaMemcpy(bufferD[i], bufferH[i].ctypes.data, bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyHostToDevice)# cudart.cudaMemcpy函数的作用是在GPU之间复制内存,其中的四个参数分别是目标内存、源内存、内存的大小和复制的方向。# 将模型的输入张量从CPU复制到GPU。for i in range(nIO):context.set_tensor_address(lTensorName[i], int(bufferD[i]))# set_tensor_address函数的作用是设置张量的地址,其中的两个参数分别是张量的名称和地址。context.execute_async_v3(0)# execute_async_v3函数的作用是异步执行推理,其中的参数是批次大小。for i in range(nInput, nIO):cudart.cudaMemcpy(bufferH[i].ctypes.data, bufferD[i], bufferH[i].nbytes, cudart.cudaMemcpyKind.cudaMemcpyDeviceToHost)# 将模型的输出张量从GPU复制到CPU。outputCPU = addScalarCPU(bufferH[:nInput], scalar)"""for i in range(nInput):printArrayInformation(bufferH[i])for i in range(nInput, nIO):printArrayInformation(bufferH[i])for i in range(nInput, nIO):printArrayInformation(outputCPU[i - nInput])"""check(bufferH[nInput:][0], outputCPU[0], True)for b in bufferD:cudart.cudaFree(b)# 释放GPU上的内存。print("Test %s finish!\n" % testCase)if __name__ == "__main__":os.system("rm -rf ./*.plan")run([32], 1)run([32, 32], 1)run([16, 16, 16], 1)run([8, 8, 8, 8], 1)run([32], 1)run([32, 32], 1)run([16, 16, 16], 1)run([8, 8, 8, 8], 1)print("Test all finish!")

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

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

相关文章

【数据结构】顺序表和链表详解顺序表和链表的实现

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;数据结构_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.线性表 1.1 顺序表 1.1.1 概念及结构 1.1.2 静态顺序表 1.1.3 动态顺序表 1.2 链表 1.2.1 链表的概念及结构 1.2.2 链表…

馆室一体化查档平台制度有哪些

馆室一体化查档平台制度是指图书馆或档案馆在数字化和信息化的背景下&#xff0c;建立起的集查阅、借阅、咨询、文献传递等多项功能于一体的平台制度。下面是一些常见的馆室一体化查档平台制度&#xff1a; 1. 馆藏管理制度&#xff1a;包括图书和档案的采购、编目、分类、整理…

详解rtklib中main函数如何配置文件(下)

目录 一、main函数流程总结 二、分析识别 -k 后如何配置 三、最后传参的数据文件处理方式 一、main函数流程总结 详解rtklib中main函数如何配置文件&#xff08;上&#xff09;-CSDN博客 在这片文章中讲解了rtklib中main函数的整个流程。 &#xff08;1&#xff09;通过…

最长上升子序列

一、题目描述 B3637 最长上升子序列 二、问题简析 2.1 法一&#xff1a; O ( N 2 ) O(N^2) O(N2) 令 d p [ i ] dp[i] dp[i] 以 a i a_i ai​ 结尾的上升子序列的最大长度。 以 a i a_i ai​ 结尾的上升子序列有两种可能&#xff1a; 1、仅有 a i a_i ai​ 一个元素2…

保研复习概率论2

1.什么是随机变量的数学期望&#xff08;expected value&#xff09;&#xff1f; 如果X是离散型随机变量&#xff0c;其分布列为piP{Xxi}(i1,2...)&#xff0c;若级数绝对收敛&#xff0c;则称随机变量X的数学期望存在&#xff0c;并将的和称为随机变量X的数学期望&#xff0…

5.5.7、【AI技术新纪元:Spring AI解码】Redis

Redis Redis 是一款开源(BSD 许可)的内存数据结构存储系统,可用作数据库、缓存、消息代理以及流处理引擎。Redis 提供了诸如字符串、哈希表、列表、集合、带范围查询的有序集合、位图、HyperLogLogs、地理空间索引和流等多种数据结构。 Redis 向量搜索 Redis 向量搜索与查…

【C++】每日一题 位1的个数

编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 ‘1’ 的个数&#xff08;也被称为汉明重量&#xff09;。 #include <iostream> #include <cstdint>int hammingWeight(uint3…

汇编语言中的MVC

一 MVC指令 1&#xff0e;移动字符串指令MVC 移动字符串指令MVC的格式为&#xff1a; MVC D1(L,B1),D2(B2) (移动字符串) 功能&#xff1a;&#xff08;D1&#xff08;B1&#xff09;&#xff09; ← &#xff08;D2&#xff08;B2&#xff09;&#xff09; L个字符 指令的执行…

React面试总结

React中JSX转换为真实Dom的过程 可以从几个大体过程来看 jsx语法解析 jsx语法解析成js代表的对象&#xff0c;即把jsx语法转换为基于js的React.createElement(args1,args2,args3)&#xff0c;args1,args2,args3分别是标签类型、属性和标签内容&#xff0c;主要利用的是babel等…

leetcode 316.去除重复字母

思路&#xff1a;贪心单调栈。 这道题和前几天做的那道“删除k位数字”那道题很像。 这里由于是按照字典序进行输出的&#xff0c;而且删除的地方我们也不知道&#xff0c;是随机的&#xff0c;这个时候其实就应该想到用单调栈进行解答&#xff0c;其实这样才能进行很好的存储…

COPY requires at least two arguments, docker COPY 报错

COPY requires at least two arguments # 使用 Node.js 12.16.0FROM node:12.16.0WORKDIR /appCOPY ..原因&#xff1a;Dockerfile文件COPY后的两个. 要加空格 本内容来源于小豆包&#xff0c;想要更多内容请跳转小豆包 》

【Frida】【Android】01_手把手教你环境搭建

▒ 目录 ▒ &#x1f6eb; 导读开发环境 1️⃣ 环境搭建安装Android模拟器安装Frida CLI安装Frida Server端口重定向&#xff1a;adb forward 2️⃣ 运行测试spwan模式attach模式直接加载脚本 &#x1f4d6; 参考资料 &#x1f6eb; 导读 开发环境 版本号描述文章日期2024-03…

C++按位运算

6.3 按位运算 6.3.1 位运算概述 符号描述运算规则实例&与两个位都为1时&#xff0c;结果才为1。0001&00011,0001&00000,0000&00000000|或两个位都为0时&#xff0c;结果才为0。0001^异或两个位相同为0&#xff0c;相异为1。0001∧00010000,0001∧00001,0000∧…

matlab空间曲线图形

说明&#xff1a;问题来自CSDN-问答板块&#xff0c;题主提问。 需求&#xff1a;如何用子图命令画出平面y2z&#xff0c;z2y与球面x^2y^2z^25相交的空间曲线图形。需要完整代码和结果的图片。 一、先看效果图 二、代码 % 创建figure figure% 创建二维网格&#xff0c;用于定…

element ui实践bug

文章目录 el-table的默认全部展开属性default-expand-all el-table的默认全部展开属性default-expand-all 该属性只有table 初始化时才会生效&#xff0c;后续动态更改无效。 如果想要动态修改default-expand-all 属性&#xff0c;则需要控制table 的重新渲染&#xff0c;可以…

day01_mysql数据类型和运算符_课后练习 - 参考答案

文章目录 day01_mysql_课后练习第1题第2题第3题第4题第5题 day01_mysql_课后练习 第1题 案例&#xff1a; 1、创建数据库day01_test01_library 2、创建表格books 字段名字段说明数据类型允许为空唯一b_id书编号int(11)否是b_name书名varchar&#xff08;50&#xff09;否否…

性能调优专题并发编程专题(持续更新)

一、性能调优专题 MySQL相关 一、深入理解MySQL索引底层数据结构与算法 索引概念&#xff1a;索引是帮助MySQL高效获取数据的排好序的数据结构 索引数据结构&#xff1a; 1、二叉树 缺点&#xff1a;当索引字段有序的时候&#xff0c;不会自动平衡二叉树&#xff0c;数据…

JavaScript单元测试jasmine学习(一)

介绍&#xff1a; jasmine是用于测试JavaScript的一种测试框架,BDD(Behavior Driven Development)行为驱动开发。不依赖于任何其他JavaScript框架&#xff0c;也不需要DOM 准备工作&#xff1a; 1. 首先添加jasmine到自己的项目中 npm install --save-dev jasmine 2. 在项目…

【热门话题】ECMAScript vs JavaScript:理解两者间的联系与区别

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 ECMAScript vs JavaScript&#xff1a;理解两者间的联系与区别1. ECMAScript&am…

小目标检测常见解决策略总结

1. 引言 尽管目标检测算法取得了长足的发展&#xff0c;例如 Faster RCNN、YOLO、SSD、RetinaNet、EfficientDet 等。通常&#xff0c;这些模型是在 COCO数据集上训练的。它是一个包含各种对象类别和标注的大规模数据集&#xff0c;因此在训练对象检测器方面很受欢迎。然而&am…