ONNX初探

ONNX初探

转载自:https://blog.csdn.net/just_sort/article/details/112912272

0x0. 背景

最近看了一些ONNX的资料,一个最大的感受就是这些资料太凌乱了。大多数都是在介绍ONNX模型转换中碰到的坑点以及解决办法。很少有文章可以系统的介绍ONNX的背景,分析ONNX格式,ONNX简化方法等。所以,综合了相当多资料之后我准备写一篇ONNX相关的文章,希望对大家有用。

0x1. 什么是ONNX?

简单描述一下官方介绍,开放神经网络交换(Open Neural Network Exchange)简称ONNX是微软和Facebook提出用来表示深度学习模型的开放格式。所谓开放就是ONNX定义了一组和环境,平台均无关的标准格式,来增强各种AI模型的可交互性。

换句话说,无论你使用何种训练框架训练模型(比如TensorFlow/Pytorch/OneFlow/Paddle),在训练完毕后你都可以将这些框架的模型统一转换ONNX这种统一的格式进行存储。注意ONNX文件不仅仅存储了神经网络模型的权重,同时也存储了模型的结构信息以及网络中每一层的输入输出和一些其它的辅助信息。我们直接从onnx的官方模型仓库拉一个yolov3-tiny的onnx模型(地址为:https://github.com/onnx/models/tree/master/vision/object_detection_segmentation/tiny-yolov3/model)用Netron可视化一下看看ONNX模型长什么样子。

在这里插入图片描述

这里我们可以看到ONNX的版本信息,这个ONNX模型是由Keras导出来的,以及模型的输入输出等信息,如果你对模型的输入输出有疑问可以直接看:https://github.com/onnx/models/blob/master/vision/object_detection_segmentation/tiny-yolov3/README.md。

在获得ONNX模型之后,模型部署人员自然就可以将这个模型部署到兼容ONNX的运行环境中去。这里一般还会设计到额外的模型转换工作,典型的比如在Android段利用NCNN部署模型,那么就需要将ONNX利用NCNN的转换工具转换到NCNN所支持的bin和param格式。

但在实际使用ONNX的过程中,大多数人对ONNX了解得并不多,仅仅认为它只是一个模型转换工具人而已,可以利用它完成模型转换和部署。正是因为对ONNX的不了解,在模型转换过程中出现的各种不兼容或者不支持让很多人浪费了大量时间。这篇文章将从理论和实践2个方面谈一谈ONNX。

0x2. ProtoBuf简介

在分析ONNX组织格式前我们需要了解ProtoBuf, 如果你非常了解ProtoBuf可以略过此节。

ONNX作为一个文件格式,我们自然需要一定的规则去读取我们想要的信息或者是写入我们需要保存信息。ONNX使用的是ProtoBuf这个序列化数据结构去存储神经网络的权重信息。熟悉Caffe或者Caffe2的同学应该知道,它们的模型存储数据结构协议也是Protobuf。这个从安装ONNX包的时候也可以看到:

在这里插入图片描述

Protobuf是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API(摘自官方介绍)。

Protobuf协议是一个以***.proto后缀文件为基础的,这个文件描述了用户自定义的数据结构。如果需要了解更多细节请参考后面的资料3,这里只是想表达ONNX是基于Protobuf来做数据存储和传输,那么自然onnx.proto**就是ONNX格式文件了,接下来我们就分析一下ONNX格式。

0x3. ONNX格式分析

这一节我们来分析一下ONNX的组织格式,上面提到ONNX中最核心的部分就是onnx.proto(https://github.com/onnx/onnx/blob/master/onnx/onnx.proto)这个文件了,它定义了ONNX这个数据协议的规则和一些其它信息。现在是2020年1月,这个文件有700多行,我们没有必要把这个文件里面的每一行都贴出来,我们只要搞清楚里面的核心部分即可。在这个文件里面以message关键字开头的对象是我们需要关心的。我们列一下最核心的几个对象并解释一下它们之间的关系。

  • ModelProto
  • GraphProto
  • NodeProto
  • ValueInfoProto
  • TensorProto
  • AttributeProto

当我们加载了一个ONNX之后,我们获得的就是一个ModelProto,它包含了一些版本信息,生产者信息和一个GraphProto。在GraphProto里面又包含了四个repeated数组,它们分别是node(NodeProto类型),input(ValueInfoProto类型),output(ValueInfoProto类型)和initializer(TensorProto类型),其中node中存放了模型中所有的计算节点,input存放了模型的输入节点,output存放了模型中所有的输出节点,initializer存放了模型的所有权重参数。

我们知道要完整的表达一个神经网络,不仅仅要知道网络的各个节点信息,还要知道它们的拓扑关系。这个拓扑关系在ONNX中是如何表示的呢?ONNX的每个计算节点都会有input和output两个数组,这两个数组是string类型,通过input和output的指向关系,我们就可以利用上述信息快速构建出一个深度学习模型的拓扑图。这里要注意一下,GraphProto中的input数组不仅包含我们一般理解中的图片输入的那个节点,还包含了模型中所有的权重。例如,Conv层里面的W权重实体是保存在initializer中的,那么相应的会有一个同名的输入在input中,其背后的逻辑应该是把权重也看成模型的输入,并通过initializer中的权重实体来对这个输入做初始化,即一个赋值的过程。

最后,每个计算节点中还包含了一个AttributeProto数组,用来描述该节点的属性,比如Conv节点或者说卷积层的属性包含group,pad,strides等等,每一个计算节点的属性,输入输出信息都详细记录在https://github.com/onnx/onnx/blob/master/docs/Operators.md。

0x4. onnx.helper

现在我们知道ONNX是把一个网络的每一层或者说一个算子当成节点node,使用这些Node去构建一个Graph,即一个网络。最后将Graph和其它的生产者信息,版本信息等合并在一起生成一个model,也即是最终的ONNX模型文件。
在构建ONNX模型的时候,https://github.com/onnx/onnx/blob/master/onnx/helper.py这个文件非常重要,我们可以利用它提供的make_node,make_graph,make_tensor等等接口完成一个ONNX模型的构建,一个示例如下:

import onnx
from onnx import helper
from onnx import AttributeProto, TensorProto, GraphProto# The protobuf definition can be found here:
# https://github.com/onnx/onnx/blob/master/onnx/onnx.proto# Create one input (ValueInfoProto)
X = helper.make_tensor_value_info('X', TensorProto.FLOAT, [3, 2])
pads = helper.make_tensor_value_info('pads', TensorProto.FLOAT, [1, 4])value = helper.make_tensor_value_info('value', AttributeProto.FLOAT, [1])# Create one output (ValueInfoProto)
Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, [3, 4])# Create a node (NodeProto) - This is based on Pad-11
node_def = helper.make_node('Pad', # node name['X', 'pads', 'value'], # inputs['Y'], # outputsmode='constant', # attributes
)# Create the graph (GraphProto)
graph_def = helper.make_graph([node_def],'test-model',[X, pads, value],[Y],
)# Create the model (ModelProto)
model_def = helper.make_model(graph_def, producer_name='onnx-example')print('The model is:\n{}'.format(model_def))
onnx.checker.check_model(model_def)
print('The model is checked!')

这个官方示例为我们演示了如何使用onnx.helper的make_tensor,make_tensor_value_info,make_attribute,make_node,make_graph,make_node等方法来完整构建了一个ONNX模型。需要注意的是在上面的例子中,输入数据是一个一维Tensor,初始维度为[2],这也是为什么经过维度为[1,4]的Pad操作之后获得的输出Tensor维度为[3,4]。另外由于Pad操作是没有带任何权重信息的,所以当你打印ONNX模型时,ModelProto的GraphProto是没有initializer这个属性的。

0x5. onnx-simplifier

原本这里是要总结一些使用ONNX进行模型部署经常碰到一些因为版本兼容性,或者各种框架OP没有对齐等原因导致的各种BUG。但是这样的话显得篇幅会很长,所以这里以一个经典的Pytorch转ONNX的reshape问题为例子,来尝试讲解一下大老师的onnx-simplifier,个人认为这个问题是基于ONNX模型部署最经典的问题,希望解决这个问题的过程中大家能有所收获。

问题发生在当我们想把下面这段代码导出ONNX模型时:

import torchclass JustReshape(torch.nn.Module):def __init__(self):super(JustReshape, self).__init__()def forward(self, x):return x.view((x.shape[0], x.shape[1], x.shape[3], x.shape[2]))net = JustReshape()
model_name = 'just_reshape.onnx'
dummy_input = torch.randn(2, 3, 4, 5)
torch.onnx.export(net, dummy_input, model_name, input_names=['input'], output_names=['output'])

由于这个模型输入维度是固定的,所以我们期望模型是这样的:

在这里插入图片描述

但是,即使使用了ONNX的polished工具也只能获得下面的模型:

在这里插入图片描述

要解决这个问题,有两种方法,第一种是做一个强制类型转换,将x.shape[0]类似的变量强制转换为常量即int(x.shape[0]),或者使用大老师的onnx-simplifer来解决这一问题。之前一直好奇onnx-simplifer是怎么做的,最近对ONNX有了一些理解之后也能逐步看懂做法了。我来尝试解释一下。onnx-simplifer的核心思路就是利用onnxruntime推断一遍ONNX的计算图,然后使用常量输出替代冗余的运算OP。

def simplify(model: Union[str, onnx.ModelProto], check_n: int = 0, perform_optimization: bool = True,skip_fuse_bn: bool = False, input_shapes: Optional[TensorShapes] = None, skipped_optimizers: Optional[Sequence[str]] = None, skip_shape_inference=False) \-> Tuple[onnx.ModelProto, bool]:if input_shapes is None:input_shapes = {}if type(model) == str:# 加载ONNX模型model = onnx.load(model)# 检查ONNX模型格式是否正确,图结构是否完整,节点是否正确等onnx.checker.check_model(model)# 深拷贝一份原始ONNX模型model_ori = copy.deepcopy(model)if not skip_shape_inference:# 获取ONNX模型中特征图的尺寸model = infer_shapes(model)input_shapes = check_and_update_input_shapes(model, input_shapes)if perform_optimization:model = optimize(model, skip_fuse_bn, skipped_optimizers)const_nodes = get_constant_nodes(model)res = forward_for_node_outputs(model, const_nodes, input_shapes=input_shapes)const_nodes = clean_constant_nodes(const_nodes, res)model = eliminate_const_nodes(model, const_nodes, res)onnx.checker.check_model(model)if not skip_shape_inference:model = infer_shapes(model)if perform_optimization:model = optimize(model, skip_fuse_bn, skipped_optimizers)check_ok = check(model_ori, model, check_n, input_shapes=input_shapes)return model, check_ok

上面有一行:model = infer_shapes(model) 是获取ONNX模型中特征图的尺寸,它的具体实现如下:

def infer_shapes(model: onnx.ModelProto) -> onnx.ModelProto:try:model = onnx.shape_inference.infer_shapes(model)except:passreturn model

我们保存一下调用了这个接口之后的ONNX模型,并将其可视化看一下:

在这里插入图片描述

相对于原始的ONNX模型,现在每一条线都新增了一个shape信息,代表它的前一个特征图的shape是怎样的。

接着,程序使用到了check_and_update_input_shapes接口,这个接口的代码示例如下,它可以用来判断输入的格式是否正确以及输入模型是否有所有的指定输入节点。

def check_and_update_input_shapes(model: onnx.ModelProto, input_shapes: TensorShapes) -> TensorShapes:input_names = get_input_names(model)if None in input_shapes:if len(input_names) == 1:input_shapes[input_names[0]] = input_shapes[None]del input_shapes[None]else:raise RuntimeError('The model has more than 1 inputs, please use the format "input_name:dim0,dim1,...,dimN" in --input-shape')for x in input_shapes:if x not in input_names:raise RuntimeError('The model doesn\'t have input named "{}"'.format(x))return input_shapes

在这个例子中,如果我们指定input_shapes为:{‘input’: [2, 3, 4, 5]},那么这个函数的输出也为{‘input’: [2, 3, 4, 5]}。如果不指定,输出就是{}。验证这个函数的调用代码如下所示:

在这里插入图片描述

确定了输入没有问题之后,程序会根据用户指定是否优化ONNX模型进入优化函数,函数定义如下:

def optimize(model: onnx.ModelProto, skip_fuse_bn: bool, skipped_optimizers: Optional[Sequence[str]]) -> onnx.ModelProto:""":model参数: 待优化的ONXX模型.:return: 优化之后的ONNX模型.简化之前, 使用这个方法产生会在'forward_all'用到的ValueInfo简化之后,使用这个方法去折叠前一步产生的常量到initializer中并且消除没被使用的常量"""onnx.checker.check_model(model)onnx.helper.strip_doc_string(model)optimizers_list = ['eliminate_deadend','eliminate_nop_dropout','eliminate_nop_cast','eliminate_nop_monotone_argmax', 'eliminate_nop_pad','extract_constant_to_initializer', 'eliminate_unused_initializer','eliminate_nop_transpose','eliminate_nop_flatten', 'eliminate_identity','fuse_add_bias_into_conv','fuse_consecutive_concats','fuse_consecutive_log_softmax','fuse_consecutive_reduce_unsqueeze', 'fuse_consecutive_squeezes','fuse_consecutive_transposes', 'fuse_matmul_add_bias_into_gemm','fuse_pad_into_conv', 'fuse_transpose_into_gemm', 'eliminate_duplicate_initializer']if not skip_fuse_bn:optimizers_list.append('fuse_bn_into_conv')if skipped_optimizers is not None:for opt in skipped_optimizers:try:optimizers_list.remove(opt)except ValueError:passmodel = onnxoptimizer.optimize(model, optimizers_list,fixed_point=True)onnx.checker.check_model(model)return model

这个函数的功能是对原始的ONNX模型做一些图优化工作,比如merge_bn,fuse_add_bias_into_conv等等。我们使用onnx.save保存一下这个例子中图优化后的模型,可以发现它和优化前的可视化效果是一样的,如下图所示:

在这里插入图片描述

这是因为在这个模型中是没有上面列举到的那些可以做图优化的情况,但是当我们打印一下ONNX模型我们会发现optimize过后的ONNX模型多出一些initializer数组:

在这里插入图片描述

这些数组存储的就是这个图中那些常量OP的具体值,通过这个处理我们就可以调用get_constant_nodes函数来获取ONNX模型的常量OP了,这个函数的详细解释如下:

def get_constant_nodes(m: onnx.ModelProto) -> List[onnx.NodeProto]:const_nodes = []# 如果节点的name在ONNX的GraphProto的initizlizer数组里面,它就是静态的tensorconst_tensors = [x.name for x in m.graph.initializer]# 显示的常量OP也加进来const_tensors.extend([node.output[0]for node in m.graph.node if node.op_type == 'Constant'])# 一些节点的输出shape是由输入节点决定的,我们认为这个节点的输出shape并不是常量,# 所以我们不需要简化这种节点dynamic_tensors = []# 判断是否为动态OPdef is_dynamic(node):if node.op_type in ['NonMaxSuppression', 'NonZero', 'Unique'] and node.input[0] not in const_tensors:return Trueif node.op_type in ['Reshape', 'Expand', 'Upsample', 'ConstantOfShape'] and len(node.input) > 1 and node.input[1] not in const_tensors:return Trueif node.op_type in ['Resize'] and ((len(node.input) > 2 and node.input[2] not in const_tensors) or (len(node.input) > 3 and node.input[3] not in const_tensors)):return Truereturn Falsefor node in m.graph.node:if any(x in dynamic_tensors for x in node.input):dynamic_tensors.extend(node.output)elif node.op_type == 'Shape':const_nodes.append(node)const_tensors.extend(node.output)elif is_dynamic(node):dynamic_tensors.extend(node.output)elif all([x in const_tensors for x in node.input]):const_nodes.append(node)const_tensors.extend(node.output)# 深拷贝return copy.deepcopy(const_nodes)

在这个例子中,我们打印一下通过这个获取常量OP二点函数之后,Graph中有哪些节点被看成了常量节点。

在这里插入图片描述

获取了模型中所有的常量OP之后,我们需要把所有的静态节点扩展到ONNX Graph的输出节点列表中,然后利用onnxruntme执行一次forward:

def forward_for_node_outputs(model: onnx.ModelProto, nodes: List[onnx.NodeProto],input_shapes: Optional[TensorShapes] = None) -> Dict[str, np.ndarray]:if input_shapes is None:input_shapes = {}model = copy.deepcopy(model)# nodes 是Graph中所有的静态OPadd_features_to_output(model, nodes)res = forward(model, input_shapes=input_shapes)return res

其中add_features_to_output的定义如下:

def add_features_to_output(m: onnx.ModelProto, nodes: List[onnx.NodeProto]) -> None:"""Add features to output in pb, so that ONNX Runtime will output them.:param m: the model that will be run in ONNX Runtime:param nodes: nodes whose outputs will be added into the graph outputs"""# ONNX模型的graph扩展输出节点,获取所有静态OP的输出和原始输出节点的输出for node in nodes:for output in node.output:m.graph.output.extend([onnx.ValueInfoProto(name=output)])

最后的forward函数就是利用onnxruntime推理获得我们指定的输出节点的值。这个函数这里不进行解释。推理完成之后,进入下一个函数clean_constant_nodes,这个函数的定义如下:

def clean_constant_nodes(const_nodes: List[onnx.NodeProto], res: Dict[str, np.ndarray]):"""It seems not needed since commit 6f2a72, but maybe it still prevents some unknown bug:param const_nodes: const nodes detected by `get_constant_nodes`:param res: The dict containing all tensors, got by `forward_all`:return: The constant nodes which have an output in res"""return [node for node in const_nodes if node.output[0] in res]

这个函数是用来清洗那些没有被onnxruntime推理的静态节点,但通过上面的optimize逻辑,我们的graph中其实已经不存在这个情况了(没有被onnxruntime推理的静态节点在图优化阶段会被优化掉),因此这个函数理论上是可以删除的。这个地方是为了避免删除掉有可能引发其它问题就保留了。

不过从一些实际经验来看,还是保留吧,毕竟不能保证ONNX的图优化就完全正确,前段时间刚发现了TensorRT图优化出了一个BUG。保留这个函数可以提升一些程序的稳定性。

接下来就是这个onnx-simplifier最核心的步骤了,即将常量节点从原始的ONNX删除,函数接口为eliminate_const_nodes:

def eliminate_const_nodes(model: onnx.ModelProto, const_nodes: List[onnx.NodeProto],res: Dict[str, np.ndarray]) -> onnx.ModelProto:""":model参数: 原始ONNX模型:const_nodes参数: 使用`get_constant_nodes`获得的静态OP:res参数: 包含所有输出Tensor的字典:return: 简化后的模型. 所有冗余操作都已删除."""for i, node in enumerate(model.graph.node):if node in const_nodes:for output in node.output:new_node = copy.deepcopy(node)new_node.name = "node_" + outputnew_node.op_type = 'Constant'new_attr = onnx.helper.make_attribute('value',onnx.numpy_helper.from_array(res[output], name=output))del new_node.input[:]del new_node.attribute[:]del new_node.output[:]new_node.output.extend([output])new_node.attribute.extend([new_attr])insert_elem(model.graph.node, i + 1, new_node)del model.graph.node[i]return model

运行这个函数之后我们获得的ONNX模型可视化结果是这样子的:

在这里插入图片描述

注意,这里获得的ONNX模型中虽然常量节点已经从Graph中断开了,即相当于这个DAG里面多了一些单独的点,但是这些点还是存在的。因此,我们最后还需要执行一次optimize就可以获得最终简化后的ONNX模型了。最终简化后的ONNX模型如下图所示:

在这里插入图片描述

0x6. 总结

好了,介于篇幅原因,介绍ONNX的第一篇文章就介绍到这里了,后续可能会结合更多实践的经验来谈谈ONNX了,例如OneFlow模型导出ONNX进行部署?。总之,文章很长,谢谢你的观看,希望这篇文章有帮助到你。最后欢迎star大老师的onnx-simplifier。

0x7. 参考资料

  • 【1】https://zhuanlan.zhihu.com/p/86867138
  • 【2】https://oldpan.me/archives/talk-about-onnx
  • 【3】https://blog.csdn.net/chengzi_comm/article/details/53199278
  • 【4】https://www.jianshu.com/p/a24c88c0526a
  • 【5】https://bindog.github.io/blog/2020/03/13/deep-learning-model-convert-and-depoly/
  • 【6】 https://github.com/daquexian/onnx-simplifier

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

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

相关文章

服务器修改地址,服务器修改管理地址

服务器修改管理地址 内容精选换一换在弹性云服务器上安装完成后输入公网IP,无法连接目的虚拟机,端口无法访问工具。源端网络未连通目的端。目的端安全组未开放8084端口。目的端网络ACL禁用了8084端口。登录源端服务器后,在源端服务器中ping 目…

ONNX再探

ONNX再探 本文转自:https://blog.csdn.net/just_sort/article/details/113802330 这篇文章从多个角度探索了ONNX,从ONNX的导出到ONNX和Caffe的对比,以及使用ONNX遭遇的困难以及一些解决办法,另外还介绍了ONNXRuntime以及如何基于…

图解自监督学习(CV)

图解自监督学习(CV) 译自:https://amitness.com/2020/02/illustrated-self-supervised-learning/ 作者:Amit Chaudhary 注:译者在某些地方对原文的表述做了调整,使其适合汉语的阅读习惯,并在…

机器学习中的归纳偏置

机器学习中的归纳偏置 带着偏见看世界,否则你根本没有看待世界的方式。 本文主要参考整理自知乎问题:如何理解Inductive bias? No-Free-Lunch(NLF)定理指出学习是不可能的,除非有先验知识。通常情况下&…

【c语言数据结构笔记】1.2 数据结构

1.2数据结构 数据元素并独立 结构实体关系 形式定义&#xff08;D&#xff0c;S&#xff09; 其中D是数据元素的有限集&#xff0c;S是D上关系的有限集 eg&#xff1a;12位数&#xff1a;132423451233 分成三组四位数 次序关系<a1,a2><a2,a3> 遵守次序关系 eg&…

使用Apex进行混合精度训练

使用Apex进行混合精度训练 转自&#xff1a;https://fyubang.com/2019/08/26/fp16/ 你想获得双倍训练速度的快感吗&#xff1f; 你想让你的显存空间瞬间翻倍吗&#xff1f; 如果我告诉你只需要三行代码即可实现&#xff0c;你信不&#xff1f; 在这篇博客里&#xff0c;瓦砾…

【数据结构1.3笔记】研究内容

1.3研究内容 数据结构&#xff08;D&#xff0c;S&#xff09; {逻辑结构&#xff1a; {物理结构&#xff08;存储结构&#xff09; {数据的运算 1.逻辑结构 1 集合&#xff1a;集合&#xff0c;没有逻辑关系 2 线性结构 “一对一” 3树形结构 层次关系 4图形结构 练习&…

2019年蓝桥杯第一题

第一题 标题&#xff1a;组队&#xff08;本题总分&#xff1a;5 分&#xff09; 作为篮球队教练&#xff0c;你需要从以下名单中选出 1 号位至 5 号位各一名球员&#xff0c; 组成球队的首发阵容。 每位球员担任 1 号位至 5 号位时的评分如下表所示。请你计算首发阵容 1 号位…

深度学习编译:MLIR初步

深度学习编译MLIR初步 深度模型的推理引擎 目前深度模型的推理引擎按照实现方式大体分为两类&#xff1a;解释型推理引擎和编译型推理引擎。 解释型推理引擎 一般包含模型解析器&#xff0c;模型解释器&#xff0c;模型优化器。 模型解析器负责读取和解析模型文件&#xff…

深入浅出LLVM

深入浅出LLVM 转自&#xff1a;https://www.jianshu.com/p/1367dad95445 什么是LLVM&#xff1f; LLVM项目是模块化、可重用的编译器以及工具链技术的集合。 美国计算机协会 (ACM) 将其2012 年软件系统奖项颁给了LLVM&#xff0c;之前曾经获得此奖项的软件和技术包括:Java、A…

一分钟系列:什么是虚拟内存?

一分钟系列&#xff1a;什么是虚拟内存&#xff1f; 转自&#xff1a;https://mp.weixin.qq.com/s/opMgZrXV-lfgOWrNUMKweg 注&#xff1a;一分钟系列的篇幅都不长&#xff0c;适合吃饭蹲坑、地铁公交上食用&#xff5e; 内存对于用户来说就是一个字节数组&#xff0c;我们可…

11-Kafka

1 Kafka Kafka是一个分布式流式数据平台&#xff0c;它具有三个关键特性 Message System: Pub-Sub消息系统Availability & Reliability&#xff1a;以容错及持久化的方式存储数据记录流Scalable & Real time 1.1 Kafka架构体系 Kafka系统中存在5个关键组件 Producer…

虚拟内存精粹

虚拟内存精粹 标题&#xff1a;虚拟内存精粹 作者&#xff1a;潘建锋 原文&#xff1a;HTTPS://strikefreedom.top/memory-management–virtual-memory 导言 虚拟内存是当今计算机系统中最重要的抽象概念之一&#xff0c;它的提出是为了更加有效地管理内存并且降低内存出错的概…

深度学习自动编译和优化技术调研

深度学习自动编译和优化技术调研 转自&#xff1a;https://moqi.com.cn/blog/deeplearning/ 作者&#xff1a;墨奇科技全栈开发 在墨奇科技&#xff0c;我们需要将一些包含深度神经网络&#xff08;DNN&#xff09;的 AI 算法移植到边缘端的设备&#xff0c; 这些设备往往使用 …

Copy-On-Write COW机制

Copy-On-Write COW机制 转自&#xff1a;https://zhuanlan.zhihu.com/p/48147304 作者&#xff1a;Java3y 前言 只有光头才能变强 在读《Redis设计与实现》关于哈希表扩容的时候&#xff0c;发现这么一段话&#xff1a; 执行BGSAVE命令或者BGREWRITEAOF命令的过程中&#xff0c…

第2章线性表的基本使用及其cpp示例(第二章汇总,线性表都在这里)

2.1线性表的定义和特点 【类型定义&#xff1a; *是n个元素的有限序列 *除了第一个元素没有直接前驱和最后一个没有直接后驱之外&#xff0c;其余的每个元素只有一个直接前驱和直接后驱&#xff1b; &#xff08;a1,a2…an&#xff09; 【特征&#xff1a; *有穷性&#xff1…

2.3单链表的基本使用及其cpp示例

2.3线性表的链式表现与实现 2.3.1.1单链表 【特点&#xff1a; *用一组任意的存储单元存储线性表的数据元素 *利用指针实现用不同相邻的存储单元存放逻辑上相邻的元素 *每个元素ai&#xff0c;除存储本身信息外&#xff0c;还存储其直接后继的元素&#xff08;后一个元素的地址…

TVM:简介

TVM&#xff1a;简介概述 Apache TVM 是一个用于 CPU、GPU 和机器学习加速器的开源机器学习编译器框架。它旨在使机器学习工程师能够在任何硬件后端上高效地优化和运行计算。本教程的目的是通过定义和演示关键概念&#xff0c;引导您了解 TVM 的所有主要功能。新用户应该能够从…

2.3.3单链表的双向链表

2.3.3双向链表 插入、删除 指在前驱和后驱方向都能游历&#xff08;遍历&#xff09;的线性链表 双向链表的每个结点有两个指针域 【结构】&#xff1a;prior data next 双链表通常采用带头结点的循环链表形式 可理解为首位相接的数据“圈”&#xff0c;每个结点都可以向前…

nvidia-smi 命令详解

nvidia-smi 命令详解 简介 nvidia-smi - NVIDIA System Management Interface program nvidia smi&#xff08;也称为NVSMI&#xff09;为来自 Fermi 和更高体系结构系列的 nvidia Tesla、Quadro、GRID 和 GeForce 设备提供监控和管理功能。GeForce Titan系列设备支持大多数…