STL,即Standard Template Library,标准模板库,是C++标准库的一个重要组成部分。它是一个可复用的封装好的组件库,同时也是一个包罗数据结构与算法的软件框架。STL为C++程序员们提供了一个可扩展的应用框架,高度体现了软件的封装性与可复用性。
STL包含了诸多在计算机科学领域里所常用的数据结构和基本算法,提供了许多通用的模板类和函数,用于实现常用的数据结构和算法。STL的一个重要特点是数据结构和算法的分离,它允许程序员编写通用的代码,即可适用于不同的数据类型,而不必为每种类型编写不同的代码。
STL的六大组件包括容器(Containers)、迭代器(Iterators)、算法(Algorithms)、仿函数(Functors)、适配器(Adapters)和空间配置器(Allocators)。
其中,
容器:存储数据的数据结构类,如vector、deque、list等,C++的string数据结构也是一个封装好的容器。
算法:对容器中的数据进行操作的函数模板,如排序、查找等。
迭代器:提供一种方法来访问容器中的元素。
仿函数:行为类似函数的对象,可以作为算法的参数来定制算法的行为。
适配器:用于修改容器或迭代器的接口,以提供不同的功能。
空间配置器:负责内存分配和释放的组件,通常不需要直接操作。
序列式容器中的元素按照插入顺序进行存储。
Vector容器:又叫动态数组,支持随机访问。
#include <vector> #include <iostream> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; for (const auto& element : vec) { std::cout << element << " "; } return 0; }
Deque容器:双端队列,支持在头部和尾部进行插入和删除操作。
#include <deque> #include <iostream> int main() { std::deque<int> deq = {1, 2, 3, 4, 5}; deq.push_front(0); // 在头部插入元素 deq.push_back(6); // 在尾部插入元素 for (const auto& element : deq) { std::cout << element << " "; } return 0; }
List容器:双向链表,支持在任意位置进行插入和删除操作。
#include <list> #include <iostream> int main() { std::list<int> lst = {1, 2, 3, 4, 5}; lst.push_front(0); // 在头部插入元素 lst.push_back(6); // 在尾部插入元素 for (const auto& element : lst) { std::cout << element << " "; } return 0; }
关联式容器中的元素通过关键字进行存储和访问。关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有维持元素置入容器时的逻辑顺序。
Set/multiset容器:集合,存储唯一或重复的元素。
#include <set> #include <iostream> int main() { std::set<int> s = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}; for (const auto& element : s) { std::cout << element << " "; } return 0; }
Map/multimap容器:键值对集合,存储不重复/重复的键及其对应的值。
#include <map> #include <iostream> int main() { std::map<std::string, int> m = {{"Alice", 25}, {"Bob", 30}, {"Charlie", 35}}; for (const auto& pair : m) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; }
质变算法 质变算法会改变容器内元素的内容。
拷贝算法:例如std::copy。
#include <vector> #include <algorithm> #include <iostream> #include <iterator> int main() { std::vector<int> src = {1, 2, 3, 4, 5}; std::vector<int> dst(src.size()); std::copy(src.begin(), src.end(), dst.begin()); for (const auto& element : dst) { std::cout << element << " "; } return 0; }
非质变算法 非质变算法不会改变容器内元素的内容。
查找算法:例如std::find。 继续上面的内容讲解,我们来看非质变算法的一个例子——std::find。
#include <vector> #include <algorithm> #include <iostream> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = std::find(vec.begin(), vec.end(), 3); // 查找值为3的元素 if (it != vec.end()) { std::cout << "Found: " << *it << std::endl; } else { std::cout << "Not found" << std::endl; } return 0; }
在这个例子中,std::find算法在vec容器中查找值为3的元素,并返回一个迭代器指向找到的元素。如果找不到,则返回end()迭代器。
迭代器提供了一种方法来遍历容器中的元素。上面的算法例子中已经展示了如何使用冒号遍历,当然数组可以用[]遍历,STL提供的新的遍历方法:使用迭代器。迭代器类似于指针,但提供了更安全的访问方式。STL提供了不同类型的迭代器,如输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器等,每种类型支持不同的操作集。它使得程序员能够遍历容器(如vector、map、set等)中的元素,同时隐藏了底层数据结构的实现细节。迭代器就像是指向容器中元素的指针或引用,但它比指针更加类型安全,并且可以处理不同类型的容器。 语法是T<类>::iterator 迭代器指针名字 =。 begin()这个接口返回一个指向容器第一个元素的迭代器。end()这个接口返回一个指向容器“尾后”位置的迭代器。注意,这不是容器的最后一个元素,而是最后一个元素再之后的位置。对于空容器,begin() 返回的迭代器与 end() 返回的迭代器是相等的。
下面是对vector、map和set使用迭代器进行遍历的示例代码:
#include <iostream> #include <vector> int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; // 使用迭代器遍历vector for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; // 使用基于范围的for循环遍历vector(C++11及以后) for (int val : vec) { std::cout << val << " "; } std::cout << std::endl; return 0; }
#include <iostream> #include <map> int main() { std::map<std::string, int> myMap = {{"apple", 1}, {"banana", 2}, {"cherry", 3}}; // 使用迭代器遍历map for (std::map<std::string, int>::iterator it = myMap.begin(); it != myMap.end(); ++it) { std::cout << it->first << ": " << it->second << std::endl; } // 使用基于范围的for循环遍历map(C++11及以后) for (const auto& kv : myMap) { std::cout << kv.first << ": " << kv.second << std::endl; } return 0; }
#include <iostream> #include <set> int main() { std::set<int> mySet = {1, 3, 5, 7, 9}; // 使用迭代器遍历set for (std::set<int>::iterator it = mySet.begin(); it != mySet.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; // 使用基于范围的for循环遍历set(C++11及以后) for (int val : mySet) { std::cout << val << " "; } std::cout << std::endl; return 0; }
在上面的代码中,可以看到对于vector、map和set,我们都可以使用传统的迭代器进行遍历,也可以使用C++11及以后版本的基于范围的for循环进行遍历。基于范围的for循环更加简洁,易于理解。但需要注意的是,基于范围的for循环在遍历map时,返回的是键值对(key-value pair),而不是单独的键或值。 注意map和set不支持用数组符号[]来遍历和索引。
仿函数是行为类似于函数的对象。它们可以像函数一样被调用,并可以作为算法的参数,以定制算法的行为,一般配合 algorithm
使用。例如,std::less<T>
是一个仿函数,用于比较两个对象是否一个小于另一个。
#include <vector> #include <algorithm> #include <iostream> struct IsEven { bool operator()(int n) const { return n % 2 == 0; } }; int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; vec.erase(std::remove_if(vec.begin(), vec.end(), IsEven()), vec.end()); for (const auto& element : vec) { std::cout << element << " "; } return 0; }
在这个例子中,我们定义了一个仿函数IsEven,它接受一个整数并检查它是否为偶数。然后,我们使用std::remove_if算法和IsEven仿函数来移除vec中所有的偶数。
适配器用于修改容器或迭代器的接口,以提供不同的功能。例如,std::stack和std::queue就是基于std::deque或std::list等容器的适配器,它们提供了栈和队列的接口。
#include <stack> #include <iostream> int main() { std::stack<int> stk; stk.push(1); stk.push(2); stk.push(3); while (!stk.empty()) { std::cout << stk.top() << " "; stk.pop(); } return 0; }
在这个例子中,我们使用了std::stack适配器来创建一个栈,并使用其提供的push、pop和top等方法来操作栈。
空间配置器负责内存的分配和释放。在STL中,它通常被封装起来,不需要直接操作。空间配置器允许STL库更高效地管理内存,特别是在大量小对象的情况下。
MLIR是一个多级中间表示框架,旨在解决当前深度学习领域中存在的IR(Intermediate Representation,中间表示)碎片化问题。它由LLVM团队开发和维护,强调工具链的可重用性和可扩展性。MLIR试图通过引入统一的表示形式——Dialect(方言),来克服不同IR之间转换难度大、优化Pass难以共享等问题。
MLIR它不仅仅局限于深度学习或某个特定的框架。MLIR旨在提供一个多级抽象的表示,支持从高层次算法到低层硬件指令的各种编程范式和优化技术。MLIR既可以用于深度学习模型的表示和优化,也可以用于传统软件编译领域,甚至是硬件设计的高级表示。
ONNX(Open Neural Network Exchange)是一种开放的IR,主要用于不同深度学习框架之间的模型转换和互操作性。ONNX通过定义一套标准化的算子和模型格式来实现这一点。
无论你使用何种训练框架训练模型(比如TensorFlow/Pytorch/OneFlow/Paddle),在训练完毕后你都可以将这些框架的模型统一转换为ONNX这种统一的格式进行存储。
然而,ONNX主要关注模型的表示和交换,而不涉及模型的优化过程。
TVM是一个端到端的深度学习编译框架,整体结构可以分为以下几个关键组成部分:
首先推理优化的原因主要是为了提高模型在实际应用中的性能,如快速响应。
其次,随着深度学习的广泛应用和以Transformer大模型为基座的深度算法的普及,tensorflow或者pytorch的推理性能远远跟不上了。
为了提高模型在实际应用中的性能,解决其在推理时面临的高计算复杂度、大内存需求、并行性限制等问题,常用一些办法总结如下:
推理性能优化的常用方法主要包括两大类,一类是模型的压缩技术,比如模型的剪枝、量化、蒸馏等,在较大的预训练模型下被广泛使用;另外是推理加速的技术,在CPU、GPU加速上分别有一些方法。
1. 网络剪枝
网络剪枝是从大型网络中筛选出不重要的神经元以及权重,将它们从网络中删除,同时尽可能地保留网络的性能。
2. 量化技术
量化技术是把高精度表示的网络权重和激活值,用低精度来近似表示,实现网络的轻量化。优势如下:
网络存储 :每个层权重量化后,32位的比特就可以压缩到8比特,就是浮点型到整形的量化,整个模型占的空间就会变小;
激活值 : 通过使用较少位的数值表示,在处理同样数据时需要读/写的内容就更短,内存带宽的压力就变得更小;
计算时间 :单位时间内处理定点运算指令就会比浮点运算的指令多。
3. 模型蒸馏
蒸馏是一种模型压缩常见方法,将复杂、学习能力强的网络学到的特征,表示“知识”蒸馏出来,传递给参数量小、学习能力弱的网络。
4. Caching
CPU频率远快于主存访问速度,在处理器时钟周期内,CPU常常需要等待主存,浪费计算资源。为了缓解CPU和内存之间速度的不匹配问题,增加了CPU cache 来解决。
cache 利用局部性原理来提高缓存命中率:
5. 多算子融合
算子融合是GPU上一个很重要的推理加速的一个优化手段,尤其是针对NLP这样的大模型,会带来比较显著的效果的提升。对于GPU异构编程,每一次op操作都会有一个内核的调用和多次的显存的读取;对于小op来说启动GPU kernel的时间会大于GPU计算时间,显存的读取开销也很大;op数目太多的话,效率会变低;所以将算子合并,可以有效地提高计算的性能。
6. 计算图优化
每个计算图中都包含许多计算节,图优化的目标很简单,就是简化计算图中计算节点的计算量。常用的方式分为以下几种:
首先,明确为什么需要自定义算子,了解TensorRT中自定义算子的基本概念,包括插件(Plugin)和插件工厂(Plugin Factory)。
其次,开始实现自定义算子,主要分为三步,定义插件类,实现算子逻辑和注册插件,下面分别展开说说。
再次,是构建使用自定义算子代码
最后,测试和验证
验证正确性和性能:通过与预期结果或其他框架的对比,验证自定义算子的实现是否正确、性能是否达标,并确保其在不同条件下的鲁棒性。
解释:常量折叠是一种编译时优化技术,它预计算图中那些在推理前就能确定结果的表达式。这意味着网络中的任何常量操作,如常量之间的算术运算,都会在模型编译期间被提前计算并简化,从而减少运行时的计算负担。
好处:通过减少不必要的运行时计算,可以显著提高模型的推理速度。
解释:算子融合是将多个操作合并为一个复合操作的过程,这可以减少内存访问次数并降低推理延迟。例如,卷积、批量归一化(Batch Normalization)、激活函数等可以融合成一个单一的高效操作。
好处:减少了内存访问和计算步骤,提高了数据吞吐率和运算效率。
解释:量化是指将模型中的权重和激活从浮点数转换为低精度的表示形式,如从32位的浮点数(FP32)转换为8位整数(INT8)。TensorRT提供了量化校准工具,以最小化量化带来的精度损失。
好处:量化可以显著减少模型的大小和推理时间,同时也减少了内存带宽的需求,使得模型更适合在资源受限的设备上运行。
解释:TensorRT会针对特定的硬件平台自动选择最优的算法来执行各种操作。这是通过在不同的算法实现之间运行基准测试来完成的,以确保选择的实现能够提供最佳性能。
好处:确保模型在特定硬件上达到最优的运行效率。
解释:TensorRT支持利用GPU的并行处理能力,通过多流执行来同时处理多个推理请求,从而提高吞吐量。
好处:在处理大量并发请求时,能够有效提升GPU利用率和总体吞吐量。
首先,算子融合通过将多个操作合并为一个单一复合操作来减少模型中的层次。这种优化减少了内存访问次数和数据传输量,因为它降低了中间结果的读写需求。同时,减少了CPU和GPU之间的同步点,提高了计算效率。
其次,TensorRT利用算子融合技术,例如将卷积、批量归一化(Batch Normalization)、激活函数(如ReLU)融合为一个单一操作。这种融合不仅减少了计算步骤,还减少了中间数据的存储需求。
在推理框架中,算子融合通常在图优化阶段进行。框架会分析计算图,识别可以融合的操作序列。然后,通过生成一个包含所有融合操作逻辑的新内核来实现融合。这个过程可能涉及到自动生成代码或者使用预先定义的高效内核。
然而,实现算子融合需要考虑操作之间的依赖关系和数据流动,确保融合后的操作不会引入错误。此外,还需要平衡融合的程度和实现的复杂性,以达到最佳性能。
首先,模型量化是指将模型中的权重和激活函数的数据类型从浮点数(如FP32)转换为低精度的格式(如INT8或FP16)。
这种转换减少了模型的内存占用,降低了计算所需的内存带宽,并且在一些硬件上可以利用专门的低精度计算单元,从而加速模型的推理过程。
量化可以显著减少模型大小,提高数据加载和处理速度。例如,在支持INT8指令集的硬件上,使用INT8量化的模型相比于FP32模型,理论上可以提高4倍的内存带宽效率和计算速度。
量化带来的精度损失及解决方案:
精度损失原因:量化过程中,由于将浮点数映射到有限的整数范围,会引入量化误差,导致模型的精度下降。
解决策略:
模型量化是一种有效的推理加速技术,虽然可能会带来一定的精度损失,但通过量化校准和量化感知训练等策略,可以显著减轻甚至克服这一问题。
首先,ONNX Runtime是一个用于优化和运行机器学习模型的性能引擎。它支持使用ONNX格式的模型,这是一个开放格式,用于表示机器学习模型,使得不同的AI框架训练的模型能够在不同的平台和设备上运行。
其次,多硬件支持的实现机制实现原理是,ONNX Runtime通过提供一系列的“执行提供程序”(Execution Providers,EPs)来支持不同的硬件。每个执行提供程序都是为特定的硬件或计算库优化的后端,它定义了如何在该硬件上执行ONNX模型中的操作。它会根据可用的执行提供程序和硬件资源自动选择最适合当前环境的执行路径。用户也可以手动指定使用哪个执行提供程序,以便更精细地控制模型的运行方式。
例如,NVIDIA GPU的系统上,ONNX Runtime可以利用CUDA执行提供程序来加速模型的推理。此外,对于需要进一步优化的场景,可以使用TensorRT执行提供程序,它利用NVIDIA TensorRT进行图优化和内核融合,以实现更高效的推理。
总之,ONNX Runtime通过灵活的执行提供程序机制,有效地支持了多种硬件平台,这种跨平台的能力大大降低了模型部署的复杂性,并为开发者提供了更多的灵活性和选择。
从TensorRT,ONNX Runtime推理框架来看,推理框架的核心组成部分应该分为以下几个部分:
在为硬件开发一套推理框架时,重点要关注两个方面,兼容和利用硬件特点优化性能和图优化技术。
推理框架需要根据目标硬件的特点进行专门的优化,通过利用硬件的并行计算能力、特殊的指令集、高效的内存访问,显著提高模型的推理速度,降低能耗,突出差异化和优势。
通过算子融合、优化数据传输路径等,减少不必要的计算和内存访问,提升模型执行的效率。这对于加速模型推理、减少资源消耗至关重要。
性能总结: 推理框架的性能受到多种因素影响,包括模型的复杂度、硬件的计算能力、框架的优化程度等。一般而言,专门为特定硬件优化的推理框架(如TensorRT、Core ML)能够提供最佳性能。然而,跨平台框架(如ONNX Runtime、TensorFlow Lite)提供了更广泛的适用性和灵活性,允许模型在多种设备上运行,虽然可能牺牲一定的性能。
分布式训练主要分为两种数据并行和模型并行。
数据并行适用于大规模训练数据集和相对较小的模型,能够提高训练速度。通常需要将训练数据集划分为多个子集,每个设备或节点负责处理一个子集,并在每个子集上独立训练模型。然后,通过梯度聚合和同步来更新模型参数。
它的缺点是需要大量的通信开销,因为设备之间需要传输梯度信息。同时,需要额外的内存来存储模型副本和梯度。
模型并行适用于大型模型或需要更高计算需求的任务,允许训练更大规模的模型。它指的是将模型分解为多个部分,在不同的设备或节点上并行处理。每个设备只负责处理模型的一部分,并与其他设备交换中间结果。
缺点是需要更复杂的编程和通信模式,以确保各个设备之间的协同工作。可能存在设备之间的通信瓶颈。
不过在实际应用中,一般是可以将数据并行和模型并行结合使用,以充分利用多个设备和节点的计算能力。
在分布式环境下,非常需要关注设备之间的通信开销和传输延迟。为了减少通信开销,可以采用压缩、稀疏化和量化等技术。同时,可以利用高速网络和专用硬件来减少传输延迟。
例如,可以使用混合精度训练(Mixed Precision Training)将参数和梯度从单精度浮点数压缩为半精度浮点数,从而减少传输数据的大小。在多机之间进行数据传输时,可以采用RDMA(Remote Direct Memory Access)网络或InfiniBand网络等减少传输延迟。
在分布式环境下,尤其是多机训练中,必须考虑的就是训练中的设备故障或网络异常。例如断点训练,冗余参数服务器等。
AI模型的训练通常需要处理大规模的数据集,而数据并行性可以将数据划分为多个部分,并将其分发到不同的计算节点上并行处理。MPI提供了消息传递的能力,可以在不同计算节点之间传输数据,实现数据的并行处理。
在分布式训练中,不同计算节点上的模型参数需要进行同步,以保持一致性和提高训练效果。MPI可以通过消息传递的方式,在计算节点之间进行参数的同步和更新,确保所有节点上的模型参数保持一致。
MPI可以应用于AI框架中的集群扩展,使得框架可以在大规模分布式环境下运行。通过MPI,可以实现节点之间的通信和数据传输,以及任务的分发和调度,从而实现在集群中进行规模化的训练和推理。
AI框架中的分布式训练通常需要考虑设备故障和恢复的情况。MPI提供了容错功能,可以在计算节点故障后重新分配任务和数据,并进行恢复,以保证训练过程的连续性和可靠性。
可以参考Horovod在TensorFlow、PyTorch深度学习训练中的使用。
损失函数求得的结果对权重和偏置求偏导,和原来的参数做差
深度神经网络的权重是如何精确调整的?它们就是通过 反向传播进行调整的。如果没有反向传播,深度神经网络将无法执行识别图像和解释自然语言等任务。
反向传播的目标 是为了减少损失或误差,通过调整网络的权重来实现的,使假设更像输入特征之间的真实关系。
前向传播是神经网络中的常规训练过程,通过对输入进行加权求和和激活函数运算得到输出,然后将该输出作为下一层的输入,一直传递到神经网络的末端。
反向传播过程接受模型训练过程的最终决策,然后确定这些决策中的错误。通过对比网络的输出/决策和网络的预期/期望输出来计算误差。
一旦计算出网络决策中的错误,该信息就会通过网络反向传播,并且网络参数会随之改变。用于更新网络权重的方法基于微积分,具体来说,它基于链式法则。
当神经元提供输出值时,会使用传递函数计算输出值的斜率,从而产生派生输出。进行反向传播时,特定神经元的误差根据以下公式计算 公式:
误差 = (预期输出 – 实际输出) * 神经元输出值的斜率
对输出层的神经元进行操作时,将类别值作为期望值。计算出误差后,该误差将用作隐藏层中神经元的输入,这意味着该隐藏层的误差是输出层中找到的神经元的加权误差。误差计算沿着权重网络向后传播。
计算出网络的误差后,必须更新网络中的权重。如前所述,计算误差涉及确定输出值的斜率。计算出斜率后,将进行一个称为 梯度下降 可用于调整网络中的权重。
梯度是一个斜率,其角度/陡度可以测量。斜率是通过在“运行”上绘制“y”或“上升”来计算的。在神经网络和错误率的情况下,“y”是计算的误差,而“x”是网络的参数。网络的参数与计算的误差值有关,并且随着网络权重的调整,误差会增加或减少。
“梯度下降”是更新权重以降低错误率的过程。反向传播用于预测神经网络参数与错误率之间的关系,从而建立梯度下降网络。使用梯度下降训练网络涉及通过前向传播计算权重、反向传播误差,然后更新网络的权重。
TensorFlow像是搞数据的人研发的,Pytorch才像是搞算法人研发的。
Tensorflow 和 pytorch 相比,前者占据先机,后者则势头完全盖过前者。
TensorFlow:由Google开发,设计时考虑了分布式计算、大规模数据处理和生产环境的需求。TensorFlow使用静态计算图,需要先定义后运行,适合于大型模型和复杂的神经网络。
PyTorch:由Facebook的人工智能研究团队开发,设计理念侧重于灵活性和直观性。PyTorch采用动态计算图,允许即时修改和执行,非常适合于快速原型开发和研究工作。
TensorFlow:虽然提供了强大的功能,但其静态图机制使得调试和理解模型相对复杂。TensorFlow 2.0及后续版本引入了Eager Execution,改善了易用性。但API得兼容性也是一个大问题。
PyTorch:动态图的特性使得PyTorch在模型构建和调试方面更加直观和用户友好。PyTorch的API设计更接近Python原生,易于理解和使用。
TensorFlow:在大规模数据集和复杂模型的训练上展现出较强的性能,尤其是在GPU加速和TPU支持方面。
PyTorch:虽然在早期版本中性能略逊于TensorFlow,但近年来通过优化和社区努力,其性能已大幅提升,尤其在某些特定任务上表现出色。
TensorFlow:由于其稳定性和规模化部署能力,非常适合用于商业产品和大型企业项目。TensorFlow Serving、TensorFlow Lite和TFX等工具支持了从研发到生产的全流程。
PyTorch:以其灵活性和友好的API,更受研究人员和数据科学家的喜爱,特别是在进行快速实验和研究原型开发时。不过随着大语言模型的爆火,很多大语言模型的商用都是基于PyTorch。
在TensorFlow和PyTorch中,可以通过以下步骤来添加自定义算子:
首先,是实现底层算子,可以使用C++或者CUDA,来写出你想要实现的功能,也就是自定义算子的代码。把这段代码编译成一个库文件,这样我们的TensorFlow才能认识它。
其次,需要在TensorFlow里面创建一个新的操作符(Op),并通过注册机制告诉TensorFlow,这时需要提供这个算子的名字,输入输出是什么类型的,形状长啥样等等。
最后,在Python环境下,你可以用TensorFlow提供的API来把这个自定义算子加入到你的计算图中。通过tf.load_op_library()函数加载你的库文件,然后用tf.custom_op()函数把它当作一个操作来使用。
对于PyTorch,其实步骤差不多,但是在PyTorch中你可以直接用Python或者C++来写你的自定义算子代码。 然后,在Python环境下,你利用torch.utils.cpp_extension来编译这段代码,生成一个扩展模块。这里同样需要告诉它算子的名字,数据类型和形状等信息。 编译好之后,就可以像导入普通模块一样,把这个扩展模块导入到PyTorch中了。
最后,使用这个自定义算子就像使用PyTorch中其他的操作一样简单。
无论是在TensorFlow还是PyTorch中添加自定义算子,过程都是:“写算子,编译,注册,使用”。
systemctl是一个管理系统服务的命令行工具,用于控制systemd系统和服务管理器。它可以启动、停止、重启、重新加载和查询系统服务的状态。以下是一些常用的systemctl命令及其作用:
systemctl start servicename
systemctl stop servicename
systemctl restart servicename
systemctl reload servicename
systemctl status servicename
systemctl show servicename
systemctl enable servicename
systemctl disable servicename
假设要运行的python程序为XXX.py,路径为/root/XXX.py。
打开终端并使用root权限创建一个名为/etc/systemd/system/XXX.service的文件,可以使用sudo命令:
sudo vim /etc/systemd/system/XXX.service
在打开的文件中输入以下内容,注意替换ExecStart中的路径为您的Python文件实际路径,并且User改为当前用户:
[Unit] Description=My XXX Python Script After=network.target [Service] User=root ExecStart=/usr/bin/python3 /root/XXX.py Restart=always RestartSec=1 [Install] WantedBy=multi-user.target
一旦修改配置文件,就要让 systemd 重新加载配置文件,然后重新启动,否则修改不会生效,例如:
sudo systemctl daemon-reload sudo systemctl restart httpd.service
1.git是通过ssh协议连接远程git服务器的,所以在登录ssh之前需要生成ssh密钥对将本地和远程git服务器连接起来:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
,其中your_email@example.com
替换为你的邮箱地址。2.接下来配置ssh密钥,即将上一步产生的密钥添加到git服务器上,配置完成后,现在你可以通过SSH登录到Git服务器上的仓库了。
3.配置git客户端,让其使用SSH登录进行一次登录。可以按照以下步骤进行操作:
git config --global user.name "Your Name"
,将Your Name
替换为你的用户名。git config --global user.email "your_email@example.com"
,将your_email@example.com
替换为你的邮箱地址。git config --global core.sshCommand "ssh -i ~/.ssh/id_rsa"
,将~/.ssh/id_rsa
替换为私钥的路径。4.验证配置:
ssh -T git@example.com
,将git@example.com
替换为你的Git服务器地址。通过以上配置,我们能够使用Git客户端连接到Git服务器上的仓库,进行诸如克隆、推送、拉取等操作。
1.创建仓库:
git init
:初始化一个git仓库git clone <url>
:clone一个git仓库2.git config
,我们可以通过git config
来配置用户名和邮箱地址,便于我们将代码提交到远程仓库,具体格式如下:
git config --global user.name '你的用户名' git config --global user.email '你的邮箱'
3.git add
,git add
命令可将文件添加到缓存,如新项目中,添加所有文件很普遍,可以使用如下命令:
git add .
当然我们也可以指定某一类文件,如将java文件添加到缓存中,可以使用如下命令:
git add *.java
4.git status
,我们可以使用 git status
命令来查看相关文件的状态,直接执行如下命令:
git status
5.git commit
,git commit
将缓存区内容添加到仓库中,可以在后面加-m选项,以在命令行中提供提交注释,格式如下:
git commit -m "第一次版本提交"
如果你觉得 每次 commit之前要add一下,想跳过add这一步,可以直接使用 -a选项,如:
git commit -am "第一次版本提交"
6.git branch
,git branch
可以查看分支,也可以创建分支,如果没有参数时,git branch会列出你在本地的分支;如果有参数时,git branch
就会创建改参数的分支。如果要查看分支,命令格式如下:
git branch
当我们想创建分支时,可以在后面加参数,命令格式如下:
git branch branchname
7.git checkout (branchname)
,git checkout
可以切换分支,命令格式如下:
git checkout branchname
8.git merge
,git merge
命令可以将任意分支合并到到当前分支中去,命令格式如下:
git merge branchname
9.git branch -d (branchname)
,git branch -d
可以删除分支,删除分支命令格式如下:
git branch -d (branchname)
10.git remote add
,git remote add
可以添加一个远程仓库,其命令格式如下:
git remote add [alias] [url]
参数[alias]为别名, [url]为远程仓库的地址,如:我们可以将https://github.com/qtqt/test.git
11.git remote
,git remote
可以查看当前有哪些远程仓库;
12.git fetch
可以提取远程仓库的数据,如果有多个远程仓库,我们可以在后面加仓库的别名,git pull
命令用于从另一个存储库或本地分支获取并集成(整合),在默认模式下,git pull
是git fetch
后跟git merge FETCH_HEAD
的缩写.
13.git push
,git push
推送你的新分支与数据到某个远端仓库命令,格式如下:
git push -u [alias] [branch]
参数[alias]为别名, [branch]为远程仓库项目的分支;
14.git remote rm
,git remote rm
删除远程仓库,格式如下:
git remote rm [别名]
1.error: failed to push some refs to 'https://github.com/zzwcreator/chatglm_caption.git' 问题原因:远程库与本地库不一致造成的,在hint中也有提示把远程库同步到本地库就可以了 解决办法:使用命令行:
git pull --rebase origin master