机器学习编译
一、机器学习概述
1.1 什么是机器学习编译
将机器学习算法从开发形态通过变换和优化算法使其变成部署形态。即将训练好的机器学习模型应用落地,部署在特定的系统环境之中的过程。
开发形态:开发机器学习模型时使用的形态。Pytorch,TensorFlow等通用框架编写的模型描述,以及与之相关的权重。
部署形态:指执行机器学习应用程序所需的形态。通常涉及机器学习模型的每个步骤的支持代码、管理资源的控制器以及应用程序开发环境的接口。
不用的AI应用对应的部署环境是互不相同的。
在进行机器学习模型部署时,不仅要考虑硬件系统的环境,与此同时也需要考虑软件环境(如操作系统环境、机器学习平台环境等)。
1.2 机器学习编译的目标
机器学习编译的直接目标包括两点:最小化依赖和利用硬件加速。最终目的是实现性能(时间复杂度、空间复杂度)优化。
最小化依赖可以认为是集成的一部分,提取出与应用相关的库(删除与应用无关的库),从而减少应用的大小,达到节省空间的目的。
利用硬件加速指的是利用硬件本身的特性进行加速。可以通过构建调用原生加速库的部署代码或生成利用原生指令的代码来实现。
1.3 为什么要学习机器学习编译
构建机器学习部署的解决方案。
对现有机器学习框架形成更加深刻的理解。
1.4 机器学习编译的核心要素
-
张量(Tensor):是执行中最重要的元素。张量是表示神经网络模型执行的输入、输出和中间结果的多维数组。
-
张量函数(Tensor Function):神经网络的知识被编码在权重和接收张量和输出张量的计算序列中。将这些计算称为张量函数。注意:张量函数不需要对应于神经网络计算的单个步骤。部分计算或者整个端到端的计算也可以看作张量函数。
不仅是单个函数可以认作张量函数,也可以把其中一部分(或者整个整体)看作是张量函数。
举个栗子:
第一个linear层和relu层计算被折叠成一个linear_relu函数(算子融合),这需要特定的linear_relu的详细实现。
def linear_relu(x,w,out):for i in range(1):for j in range(200):out[i,j]=0for k in range(3072):out[i,j]+=w[i,k]+w[j,k]out[i,j]=max(out[i,j],0)
1.5 抽象和实现
对同样的目标有不同颗粒度的表示。例如对linear_relu而言,既可以使用左边两个框图进行表示,也可以用右边的循环进行表示。
-
抽象:表示相同张量函数的方式。不同的抽象可能会指定一些细节,而忽略其他实现细节。例如,linear_relu可以使用另外一个不同的for循环来实现。
-
抽象和实现可能是所有计算机系统中最重要的关键字。抽象指定"做什么",实现提供”如何做“。没有具体的界限。根据我们的看法,for循环本身可以被视作一种抽象,可以使用python解释器实现或编译为本地汇编代码,
机器学习编译实际上是在相同或不同抽象下转换和组装张量函数的过程。我们将研究张量函数的不同抽象类型,以及他们如何协同工作以解决机器学习部署中 的挑战。
在后续文章中分别会涉及到四种类型的抽象:
计算图,张量程序,库(运行时),硬件专用指令
二、张量程序抽象
2.1 元张量函数(张量算子)
元张量函数指的是单个单元(最小颗粒度)的张量函数。
以pytorch中的加法为例:
import torch
C = torch.empty((128,), dtype=torch.float32)
a = torch.tensor(np.arange(128, dtype="float32"))
b = torch.tensor(np.ones(128, dtype="float32"))
torch.add(a,b,out=c) # torch.add can be viewed as a primitive tensor function
那么torch.add是如何实现的呢?或者在实际部署中,如何将不同的步骤(多个元张量函数)合并在一起?
抽象与实现是本课程的核心概念之一。对于同一个张量算子而言,可以使用不同的方式来实现:
第二种表示是对第一种表示的细化,第三种表示是使用低级语言(C语言,甚至是使用汇编语言)来进行更高效的实现。
许多框架提供的一个最常见的MLC过程是将基本函数的实现(或在运行时分派它们)转换为基于环境的更优化的实现。
例如当前环境可以四个四个读取程序数据,所以可以使用并行化来优化当前代码。
2.1.1 张量算子变换
常见的一种做法是直接把它映射到相应的算子库上。比如在cuda环境中使用cudaAdd。另外一种是更细力度的程序变换。前者直接用符号表示即可,而后者需要表示成循环的形式。
2.2 张量程序抽象
上文提到的张量算子变换包括两种常见的形式。一种是直接映射到对应的算子库上,另一种是进行更细粒度的程序变换。将后者称之为是张量函数抽象
。
下图是TVM中的一个样例:
张量函数抽象包括三大关键要素:
1.多维的输入、输出的数据。
2.循环
3.每个循环中进行相应的计算。
涉及到一个问题:为什么我们不直接使用c语言等低级语言进行编写程序。主要在于机器学习编译的核心不仅在于程序员能够完成代码的编写,更重要的事完成张量算子变换,即给定一个张量将它从原始形态变换到更加优化的形态(尝试不同的变换,最终找到一个最为优化的形态)。如果通过程序来探索可能得张量函数对应的等价空间就能够大大提升效率。
2.2.1为什么需要进行张量程序抽象、
在Program-based transformations 中进行了循环拆分,本质上是将单层循环变换为双层循环(循环拆分是比较常见的一种变换),而Transformed Program涉及到了并行化与向量化的操作,其中向量化在这里指的是每一步都以长度为4的单元(批处理)来进行计算。
2.2.2 常见张量变换
1.循环拆分
其中等价替换包括:
x=xo*4+xi
这样简化并行化处理,提高了内存访问效率,便于向量化操作,简化编程模型。
2.循环重排
将两个循环的顺序进行更换
3.线程绑定
如果在GPU上进行并行化,可以把xi和xo对应到GPU的两个线程上。通过绑定GPU的线程得到最终的结果(后续章节中会进行更加深入的讲解)。
在不同的硬件环境上尝试不同的变换组合,然后根据测试在张量函数对应的等价空间中得到最优的变换。需要注意的一点是,在这里进行的变换必须是等价变换。下面举个反例,本质上可以表示成C = A + C ,C会对其他index下的C的结果存在依赖(在xo = 4, xi = 0的情况会对 xo = 0, xi = 1的结果造成依赖, 而如果我们改变了循环顺序之后 xo = 0, xi = 1会先被循环到),如果只是单纯的reorder,循环的顺序会有变化导致依赖关系被打破,会出现等式左边的C计算时等式右边的C还没有结果的情况。所以在这里不能进行循环重排。 即注意变量是否依赖其他变量。
for xo in range(32):for xi in range(4):C[xo * 4 + xi] = A[xo * 4 + xi] + C[max(xo * 4 + xi – 1, 0)]
这里的xi需要依赖于xi-1所以不能进行线程绑定。
2.2.3 张量程序抽象中的其他结构
根据上文所述,我们不能任意地对程序进行变换(比如部分计算会依赖于循环之间的顺序)。但幸运的是我们所感兴趣的大多数元张量函数都具有良好的属性(例如循环迭代之间的独立性)。
张量程序可以将这些额外的信息合并为程序的一部分,可以使得程序更加便利。
其中vi = T.axis.spatial(128, i)不仅等价于vi=i,并且表明vi表示了一个迭代器,并且迭代器中所有的迭代都是独立的。这个信息对于执行这个程序而言并非必要,但会使得我们在变换这个程序时更加方便。在这个例子中,我们知道我们可以安全地并行或者重新排序所有与 vi 有关的循环,只要实际执行中 vi 的值按照从 0 到 128 的顺序变化。
2.3 张量程序变换实践
建议直接使用Colab,可以直接使用pip安装包来安装tvm
1.配置环境
由于 https://mlc.ai/wheels需要代理才能连通。
python3 -m pip install mlc-ai-nightly -f https://mlc.ai/wheels
2.构造张量程序
import numpy as np
import tvm
from tvm.ir.module import IRModule
from tvm.script import tir as T
@tvm.script.ir_module
class MyModule:@T.prim_funcdef main(A:T.Buffer[128,"float32"],B:T.Buffer[128,"float32"],C:T.Buffer[128,"float32"]):T.func_attr({"global_symbol":"main","tir.noalias":True})for i in range(128):with T.block("C"):vi=T.axis.spatial(128,i)C[vi]=A[vi]+B[vi]
TvmScript是一种我们能以python抽象语法树的形式来表示张量程序的方式。与python语法所对应,并在该语法的基础上增加了能够帮助程序分析与变换的额外结构。
print(type(MyModule))#tvm,ir.module.IRModule
MyModule是IRModule数据结构的一个实例,是一组张量函数的集合。
我们可以通过script函数得到这个IRModule的TVMScript表示,这个函数对于在一步步程序变换间检查IRModule而言很有帮助。
print(MyModule.script())
3.编译与运行
使用build函数将一个IRModule转换为可以执行的函数,其中llvm表示CPU作为硬件执行环境
rt_mod=tvm.build(MyModule,target="llvm")
type(rt_mod) #tvm.driver.build_module.OperatorModule
在编译后,mod包含了一组可以执行的函数,我们可以通过这些函数名字拿到对应的函数
func=rt_mod["main"]
func #<tvm.runtime.packed_func.PackedFunc>
先构建三个张量,其中c用来接收输出:
a = tvm.nd.array(np.arange(128, dtype="float32"))
b = tvm.nd.array(np.ones(128, dtype="float32"))
c = tvm.nd.empty((128,), dtype="float32")
func(a, b, c)
print(c)[1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118. 119. 120. 121. 122. 123. 124. 125. 126. 127. 128.]
4.张量程序变换
一个张量程序可以通过一个辅助的名为调度(schedule)的数据结构得到变换
sch=tvm.tir.Schedule(MyModule)
type(sch) #tvm.tir.schedule.schedule.Schedule
拆分循环
# Get block by its name
block_c = sch.get_block("C")
# Get loops surrounding the block,拿到block之外对应的循环
(i,) = sch.get_loops(block_c)
# Tile the loop nesting.
i_0, i_1, i_2 = sch.split(i, factors=[None, 4, 4])
print(sch.mod.script())
将for i in tir.serial(128)中的i变换成了i_0, i_1和i_2。另外需要注意的是factors=[None, 4, 4]和tir.grid(8, 4, 4)中之间一一的对应关系以及vi等价于tir.axis.spatial(128, i_0 * 16 + i_1 * 4 + i_2)。后者是通过三重循环来表示一重循环。
你可以通过这幅图片来了解这个拆分关系
# from tvm.script import ir as I
# from tvm.script import tir as T
@I.ir_module
class Module:@T.prim_funcdef main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32"), C: T.Buffer((128,), "float32")):T.func_attr({"tir.noalias": T.bool(True)})# with T.block("root"):for i_0, i_1, i_2 in T.grid(8, 4, 4):with T.block("C"):vi = T.axis.spatial(128, i_0 * 16 + i_1 * 4 + i_2)T.reads(A[vi], B[vi])T.writes(C[vi])C[vi] = A[vi] + B[vi]
循环重排
sch.reorder(i_0,i_2,i_1)
print(sch.mod.script())
# from tvm.script import ir as I
# from tvm.script import tir as T
@I.ir_module
class Module:@T.prim_funcdef main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32"), C: T.Buffer((128,), "float32")):T.func_attr({"tir.noalias": T.bool(True)})# with T.block("root"):for i_0, i_2, i_1 in T.grid(8, 4, 4):with T.block("C"):vi = T.axis.spatial(128, i_0 * 16 + i_1 * 4 + i_2)T.reads(A[vi], B[vi])T.writes(C[vi])C[vi] = A[vi] + B[vi]
并行化
对最外层循环进行并行操作
sch.parallel(i_0)
print(sch.mod.script())
# from tvm.script import ir as I
# from tvm.script import tir as T
@I.ir_module
class Module:@T.prim_funcdef main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32"), C: T.Buffer((128,), "float32")):T.func_attr({"tir.noalias": T.bool(True)})# with T.block("root"):for i_0 in T.parallel(8):for i_2, i_1 in T.grid(4, 4):with T.block("C"):vi = T.axis.spatial(128, i_0 * 16 + i_1 * 4 + i_2)T.reads(A[vi], B[vi])T.writes(C[vi])C[vi] = A[vi] + B[vi]
一下是张量程序创建->编译->运行
三、张量程序案例 TensorIR
使用张量程序抽象的目的是为了表示循环和相关硬件加速选择,如多线程、特殊硬件指令的使用和内存访问。
3.1 举个例子
使用张量程序抽象,我们可以在较高层的抽象指定一些与特定硬件无关的较通用的IR优化(计算优化)。
比如,对于两个大小为128*128的矩阵A和B,我们进行如下两步的张量计算:
即一个matmul操作和一个relu操作。
-
使用numpy
dtype="float32" a=np.random.rand(128,128).astype(dtype) b=np.random.rand(128,128).astype(dtype) c_mm_relu=np.maximum(a@b,0)dtype="float32" a=np.random.rand(128,128).astype(dtype) b=np.random.rand(128,128).astype(dtype) c_mm_relu=np.maximum(a@b,0)
-
在底层,Numpy调用库和它自己在低级C语言中的一些实现来执行这些计算。
def lnumpy_mm_relu(A: np.ndarray, B: np.ndarray, C: np.ndarray):Y = np.empty((128, 128), dtype="float32")for i in range(128):for j in range(128):Y[i, j] = 0 # 初始化 Y[i, j] 为 0for k in range(128):Y[i, j] += A[i, k] * B[k, j]for i in range(128):for j in range(128):C[i, j] = max(Y[i, j], 0)
-
主程序对比
# 主程序 dtype = "float32" a = np.random.rand(128, 128).astype(dtype) b = np.random.rand(128, 128).astype(dtype) # 使用 NumPy 的矩阵乘法和 ReLU 激活计算正确结果 c_mm_relu = np.maximum(a @ b, 0) # 初始化输出矩阵 c c = np.empty((128, 128), dtype=dtype) # 使用 lnumpy_mm_relu 计算结果 lnumpy_mm_relu(a, b, c) # 使用 np.testing.assert_allclose 验证结果是否一致 np.testing.assert_allclose(c_mm_relu, c, rtol=1e-5, atol=1e-8) print("Validation passed: The results are consistent.")
但第二种实现更接近实际的框架在目标硬件上的调度和计算。这里的抽象展示了我们在实际实现中需要的元素:
-
多维缓冲区(数组)。
-
在数组维度上的循环。
-
在循环下执行的计算语句.
3.2 Tensor IR
Tensor IR是TVM里的Tensor抽象,是最接近硬件的IR
在TVM中可以用一种叫做TVMScript DSL来用python写一些IR优化,比如上面的例子可以写成:
import numpy as np
from tvm.script import tir as T
import tvm
@tvm.script.ir_module
class Module:@T.prim_funcdef mm_relu(A: T.Buffer((128, 128), "float32"),B: T.Buffer((128, 128), "float32"),C: T.Buffer((128, 128), "float32")):T.func_attr({"global_symbol": "mm_relu", "tir.noalias": True})Y = T.alloc_buffer((128, 128), dtype="float32")for i, j, k in T.grid(128, 128, 128):with T.block("Y"):vi = T.axis.spatial(128, i)vj = T.axis.spatial(128, j)vk = T.axis.reduce(128, k)with T.init():Y[vi, vj] = T.float32(0)Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]for i, j in T.grid(128, 128):with T.block("C"):vi = T.axis.spatial(128, i)vj = T.axis.spatial(128, j)C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
import numpy as np
from tvm.script import tir as T
import tvm@tvm.script.ir_module
class Module:@T.prim_funcdef mm_relu(A: T.Buffer((128, 128), "float32"),B: T.Buffer((128, 128), "float32"),C: T.Buffer((128, 128), "float32")):T.func_attr({"global_symbol": "mm_relu", "tir.noalias": True})Y = T.alloc_buffer((128, 128), dtype="float32")for i, j, k in T.grid(128, 128, 128):with T.block("Y"):vi = T.axis.spatial(128, i)vj = T.axis.spatial(128, j)vk = T.axis.reduce(128, k)with T.init():Y[vi, vj] = T.float32(0)Y[vi, vj] = Y[vi, vj] + A[vi, vk] * B[vk, vj]for i, j in T.grid(128, 128):with T.block("C"):vi = T.axis.spatial(128, i)vj = T.axis.spatial(128, j)C[vi, vj] = T.max(Y[vi, vj], T.float32(0))
与numpy指定优化对比:
计算块:
TensorIR 是 TVM 中用于表示张量计算的低级中间表示,它不仅描述了计算的数据流,还描述了计算之间的依赖关系。这些依赖关系对于理解数据如何在计算图中流动以及如何正确地执行计算至关重要。在 TensorIR 中,数据依赖关系体现在各个计算块(block)之间的相互作用上。
对于循环中的变量需要映射到一个块上做计算。
块是TensorIR中的基本计算单位,该块比numpy包含的信息多。一个块包含一组块轴(vi、vj、vk)和围绕他们定义的计算。
块轴绑定的语法糖:在每个块轴直接映射到外部循环迭代器的情况下我们可以使用T.axis.remap
在一行中声明所有块轴
# SSR means the properties of each axes are "spatial", "spatial", "reduce" vi, vj, vk = T.axis.remap("SSR", [i, j, k])
这里就相当于块 vi, vj, vk 分别和循环迭代器 i, j, k 绑定上了,类似 T.grid,只需要一行代码就可以完成多个绑定。
于是上述代码可以写成
3.3 变换
3.3.1 循环拆分
sch=tvm.tir.Schedule(Module)
block_T=sch.get_block("matmul",func_name="mm_relu")
i,j,k=sch.get_loops(block_T)
j0,j1=sch.split(j,factors=[None,4])
print(sch.mod.script())
3.3.2 循环重排
sch.reorder(j0,k,j1)
print(sch.mod.script())
3.3.3 算子融合
将 ReLU 块(block_C
)的计算移动到矩阵乘法块(block_T
)的 j0
循环中。
作用:
(1)减少内存访问:将Relu的计算移动到矩阵乘法的内层循环中,可以减少对中间缓冲区Y的访问次数,从而提高内存访问效率。
(2)提高计算效率:减少了循环嵌套的层数,降低了循环开销。可以利用CPU或GPU的缓存,减少数据在内存和缓存之间的传输。
3.3.4 规约分解
将规约操作(如矩阵乘法中的累加)分解为多个阶段,从而优化计算过程。
sch.decompose_reduction(block_T,k)
print(sch.mod.script())
3.3.5 优化对比
import numpy as np
# 主程序
dtype = "float32"
a = np.random.rand(128, 128).astype(dtype)
b = np.random.rand(128, 128).astype(dtype)
# 使用 NumPy 的矩阵乘法和 ReLU 激活计算正确结果
c_mm_relu = np.maximum(a @ b, 0)
mod=tvm.build(Module,target="llvm")
func=mod["mm_relu"]
# 初始化输出矩阵 c
c = np.empty((128, 128), dtype="float32")
a_tvm = tvm.nd.array(a)
b_tvm = tvm.nd.array(b)
c_tvm = tvm.nd.array(c)
# 使用 lnumpy_mm_relu 计算结果
func(a_tvm, b_tvm, c_tvm)
np.testing.assert_allclose(c_mm_relu, c_tvm.numpy(), rtol=1e-5, atol=1e-8)
print("Validation passed: The results are consistent.")
Validation passed: The results are consistent.
对比优化前后时间差,帮助评估调度优化(如循环分块、规约分解等)对性能的影响。
# 比较计算内存优化前后的运行时间
rt_lib = tvm.build(Module, target='llvm')
a_nd = tvm.nd.array(a)
b_nd = tvm.nd.array(b)
c_nd = tvm.nd.empty((128, 128), dtype='float32')rt_lib_after = tvm.build(sch.mod, target='llvm')
f_time_before = rt_lib.time_evaluator("mm_relu", tvm.cpu())
print(f"Time cost of Mymodule:{f_time_before(a_nd, b_nd, c_nd).mean}")
f_time_after = rt_lib_after.time_evaluator("mm_relu", tvm.cpu())
print(f"Time cost after transform:{f_time_after(a_nd, b_nd, c_nd).mean}")
3.4 分析优化原因
如下图,现代CPU带有多级缓存,需要先将数据提取到缓存当中,然后CPU才能访问它。
数据访问流程
当 CPU 需要访问数据时,它会按照以下顺序查找数据:
-
检查寄存器:首先在最快的寄存器中查找数据。
-
检查 L1 缓存:如果数据不在寄存器中,CPU 会检查 L1 缓存。
-
检查 L2 缓存:如果数据不在 L1 缓存中,CPU 会检查 L2 缓存。
-
访问主内存:如果数据不在任何缓存中,CPU 最后会从主内存(DRAM)中加载数据。
这种层次化的存储结构允许计算机在处理大量数据时保持较高的效率,因为经常使用的数据可以存储在快速的缓存中,从而减少访问较慢的主内存的次数。
缓存一致性和替换策略
-
缓存一致性:确保所有级别的缓存中的数据与主内存中的数据保持一致。
-
替换策略:当缓存满时,决定哪些数据应该被替换出去。常见的策略包括最近最少使用(LRU)等。
访问已经在缓存中的数据要快的多。CPU采用一种策略是获取彼此更接近的数据(局部性原理)。当我们读取内存中的一个元素时,它会尝试将附近的元素(“缓存行”)获取到缓存中。因此,当你读取下一个元素时,它已经在缓存中了。因此具有连续内存访问的代码通常比随机访问内存不同部分的代码更快。
现在让我们看看上面的迭代可视化,分析一下是怎么回事。 在这个分析中,让我们关注最里面的两个循环:k 和 j1。高亮的地方显示了当我们针对 k 的一个特定实例迭代 j1 时迭代触及的 Y、A 和 B 中的相应区域。
我们可以发现,j1 这一迭代产生了对 B 元素的连续访问。具体来说,它意味着在 j1=0 和 j1=1 时我们读取的值彼此相邻。这可以让我们拥有更好的缓存访问行为。此外,我们使 C 的计算更接近 Y,从而实现更好的缓存行为。
改变计算的循环迭代使之更接近 CPU 执行策略是编译器的常见优化手段之一。
相关文章:
机器学习编译
一、机器学习概述 1.1 什么是机器学习编译 将机器学习算法从开发形态通过变换和优化算法使其变成部署形态。即将训练好的机器学习模型应用落地,部署在特定的系统环境之中的过程。 开发形态:开发机器学习模型时使用的形态。Pytorch,TensorFlow等通用框…...
mysql中什么机制保证宕机数据恢复
MySQL 通过多种机制来保证在宕机或意外崩溃时数据的完整性和可恢复性。这些机制主要包括 事务日志、崩溃恢复 和 数据持久化 等。以下是 MySQL 中保证数据恢复的核心机制: 1. 事务日志(Transaction Log) 事务日志是 MySQL 实现数据恢复的核心机制之一,主要包括 Redo Log(…...
使用 display: flex 实现动态布局:每行两个 item,单数时最后一个占满整行
文章目录 使用 display: flex 实现动态布局:每行两个 item,单数时最后一个占满整行 🎯一、需求分析二、实现思路三、代码实现1. HTML 结构2. CSS 样式关键点解析: 四、效果演示HTML 示例:效果: 五、完整代码…...
android 支持自定义布局、线程安全、避免内存泄漏的 Toast 工具类
支持自定义布局:可以灵活地显示自定义样式的 Toast。 线程安全:确保在主线程中显示 Toast,避免崩溃。 避免内存泄漏:使用 ApplicationContext 和取消机制,防止内存泄漏问题。 工具类:作为一个通用的工具…...
密码学系列 - 利用CPU指令加速
CPU擅长的操作: AES 指令, SHA 指令为了充分利用流水线带来的好处,出现了一种叫做RISC的CPU架构。RISC是Reduced Instruction Set Computer(精简指令集)的缩写 SHA加速 Filecoin系列 - 源码分析 - CPU SHA扩展 当前的构造在某些阶段确实涉…...
什么是美颜SDK?从几何变换到深度学习驱动的美颜算法详解
美颜SDK是一种用于处理图像与视频的开发工具,能够提供磨皮、美白、瘦脸、五官优化、动态贴纸等美颜特效。它广泛应用于直播、短视频、社交、在线会议、电商等行业,帮助用户在视频或图片中实现更好的视觉呈现。 一、从几何变换到深度学习:美颜…...
用Deepseek写一个五子棋微信小程序
在当今快节奏的生活中,休闲小游戏成为了许多人放松心情的好选择。五子棋作为一款经典的策略游戏,不仅规则简单,还能锻炼思维。最近,我借助 DeepSeek 的帮助,开发了一款五子棋微信小程序。在这篇文章中,我将…...
FPGA 实验报告:四位全加器与三八译码器仿真实现
目录 安装Quartus软件 四位全加器 全加器、半加器 半加器: 全加器: 四位全加器电路图 创建项目 半加器 全加器 四位全加器 代码实现 半加器 全加器 四位全加器 三八译码器 创建项目 代码展示 modelsim仿真波形图 四位全加器 三八译码…...
Vercel Serverless
1. 引言 现代应用程序是为适应当前技术环境需求而设计的软件,采用现代开发工具和实践,针对云部署和可扩展性优化。它们由多个模块化小组件组成,便于集成和缩放,具有高度的敏捷性和适应性,能快速响应用户或业务需求变化…...
胜软科技冲刺北交所一年多转港股:由盈转亏,毛利率大幅下滑
《港湾商业观察》施子夫 近期,山东胜软科技股份有限公司(以下简称,胜软科技)递表港交所获受理,独家保荐机构为广发证券(香港)。 在赴港上市之前,胜软科技还曾谋求过A股上市&#x…...
JJJ:linux sysfs相关
文章目录 1.sysfs(属性)文件的创建、读、写1.1 创建流程1.2 open流程1.3 read流程 2.补充2.1 sysfs下常见目录介绍2.2 属性相关2.2.1 简介2.2.2 attribute文件的创建 2.3 sysfs目录如何创建的 1.sysfs(属性)文件的创建、读、写 1…...
vue3 遇到babel问题(exports is not defined) 解决方案
由于我在引用ant-design-vue插件,于是产生了下图的问题。 1.问题分析 Babel 是一个 JavaScript 编译器,主要用于:将 ES6 代码转译为 ES5 代码,以兼容旧版浏览器。处理模块化语法(如 import/export)。 2.解…...
《原型链的故事:JavaScript 对象模型的秘密》
原型链(Prototype Chain) 是 JavaScript 中实现继承的核心机制。每个对象都有一个内部属性 [[Prototype]](可以通过 __proto__ 访问),指向其原型对象。每个对象都有一个原型, 原型本身也是一个对象…...
Python怎样安装,Windows/Mac/Linux系统安装教程
Python的安装步骤如下,结合不同操作系统和关键注意事项进行说明: 一、Windows系统安装 下载安装包 访问Python官网,点击“Downloads”选择适合的版本(推荐稳定版,如3.9或3.10,避免最新版可能的不兼容问题&a…...
03.08
1. 数字三角形 题目描述 上图给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。 路径上的每一步只能从一个数走到下一层和它最近的左边的那个数或者右 边的那…...
LeetCode 解题思路 11(Hot 100)
解题思路: 若相等: 直接返回 true。若当前元素大于目标值: 由于列递增,当前列下方所有元素均大于目标值,故排除该列(向左移动)。若当前元素小于目标值: 由于行递增,当前…...
使用websocket,注入依赖service的bean为null
问题:依赖注入失败,service获取不到,提示null 这是参考代码 package com.shier.ws;import cn.hutool.core.date.DateUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.google.gson.Gson; import com.s…...
【数据结构初阶】---堆的实现、堆排序以及文件中的TopK问题
1.树的概念及结构 1.1树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 有一个特殊的结点&…...
【量化策略】均值回归策略
【量化策略】均值回归策略 🚀量化软件开通 🚀量化实战教程 技术背景和应用场景 均值回归策略是一种基于统计学原理的量化交易策略,它假设资产价格会围绕其历史平均水平波动。当价格偏离这个平均水平时,就有可能会回到平均值附…...
设计模式 - 工厂模式 精准梳理精准记忆
1、代码片段 - 带入理解 一、核心模式分类 简单工厂模式(编程习惯,非 GoF 设计模式)工厂方法模式(GoF 创建型模式)抽象工厂模式(GoF 创建型模式) 二、演变过程:咖啡店案例 初始实现…...
WIFI ESP8266以及基础功能介绍
芯片一旦烧写了程序就不可以使用AT指令集,需要重新刷回AT指令库才可以使用 wifi的通信频段是2.4G免费频段。 AT指令 AT(attention)command set.AT指令集或命令集,一般称为AT指令 海斯命令集:Hayes command set 默认…...
面试java做了一道逻辑题,人麻了
题目:给你一个5升水壶,一个6升水壶,去池塘中取水,如何保证最后取出的水是3升? 思考了很久终于想出来了,这里用X5代表5升的桶,X6代表6升的桶: ① 6升桶装满,X50ÿ…...
go语言因为前端跨域导致无法访问到后端解决方案
前端服务8080访问后端8081这端口显示跨域了 ERROR Network Error AxiosError: Network Error at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:116:14) at Axios.request (webpack-internal:///./node_modules/axios/lib/core/A…...
svn删除所有隐藏.svn文件,文件夹脱离svn控制
新建一个文件,取名remove-svn-folders.reg,输入如下内容: Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN] "Delete SVN Folders" [HKEY_LOCAL_MACHINE\SOFTWARE\Class…...
Greenplum6.19集群搭建
一,安装说明 1.1环境说明 1、首先确定部署的环境,确定下服务器的端口,一般默认是22的端口; 2、当前这份文档是服务器处于10022端口下部署的(现场生产环境要求,22端口在生产环境存在安全隐患)&…...
C/C++蓝桥杯算法真题打卡(Day4)
一、P11041 [蓝桥杯 2024 省 Java B] 报数游戏 - 洛谷 算法代码: #include<bits/stdc.h> using namespace std;// 计算第 n 个满足条件的数 long long findNthNumber(long long n) {long long low 1, high 1e18; // 二分查找范围while (low < high) {lo…...
TinyWebServer项目笔记——01 线程同步机制封装类
目录 1.基础知识 (1)RALL (2)信号量 (3)互斥量 (4)条件变量 2.功能 1.基础知识 (1)RALL RALL全称“Resource Acquisition is Initialization”…...
如何在Ubuntu上直接编译Apache Doris
以下是在 Ubuntu 22.04 上直接编译 Apache Doris 的完整流程,综合多个版本和环境的最佳实践: 注意:Ubuntu的数据盘VMware默认是20G,编译不够用,给到50G以上吧 一、环境准备 1. 安装系统依赖 # 基础构建工具链 apt i…...
算法006——和为S 的两个数
力扣——查找总价格为目标值的两个商品点击跳转 注意题目中的关键信息升序 我们利用双指针,不管 target 是多少,让一个指针指向最小值,让一个指针指向最大 那么,共有三种情况 我们首先遇到的是第二种情况 sum < target left …...
物联网设备接入系统后如何查看硬件实时数据?
要在软件中实时查看硬件设备的信息,通常需要结合前后端技术来实现。以下是设计思路和实现步骤: 1. 系统架构设计 实时查看硬件设备信息的系统通常采用以下架构: 数据采集层: 硬件设备通过传感器采集数据,发送到InfluxDB。数据存…...
CSS—属性继承与预处理器:2分钟掌握预处理器
个人博客:haichenyi.com。感谢关注 1. 目录 1–目录2–属性继承3–预处理器 2. 属性继承 像Android里面继承extends,类继承,子类可以使用父类的public和protected的属性和方法。子类可以直接用。 在CSS里面也是类似的。CSS里面是布局里面…...
前端知识点---http.createHttp()的理解(arkts)
通俗易懂的例子:点外卖 🍔🥤 想象一下,你在家里点外卖,HTTP 请求就像是你和餐厅之间的沟通方式。 1️⃣ 没有 http.createHttp():每次点餐都重新拨电话 📞 如果你每次点餐都重新拨打餐厅的电话…...
信息安全访问控制、抗攻击技术、安全体系和评估(高软42)
系列文章目录 信息安全访问控制、抗攻击技术、安全体系和评估 文章目录 系列文章目录前言一、信息安全技术1.访问控制2.抗攻击技术 二、欺骗技术1.ARP欺骗2.DNS欺骗3.IP欺骗 三、抗攻击技术1.端口扫描2.强化TCP/IP堆栈 四、保证体系和评估1.保证体系2.安全风险管理 五、真题在…...
【深度学习】宠物品种分类Pet Breeds Classifier
文章目录 宠物品种数据集制作宠物品种标签图像预处理Presizing 损失函数loss观察模型的性能提升模型的性能learning rate finder使用CLR算法训练选择学习率的策略重新训练 迁移学习微调fine_tunefit_one_cycle有判别力的学习率 选择epoch的数量更深的网络架构 宠物品种数据集 …...
PyQt组件间的通信方式
PyQt组件间的通信方式 PyQt组件间的通信方式 1. 组件介绍 1.1 组件的定义1.2 组件的分类 2. 组件的通信方式 2.1 信号与槽(Signal & Slot) 1. 组件介绍 在 Qt 框架中,组件(Component)是构建图形用户界面&am…...
基于编译器特性浅析C++程序性能优化
最近在恶补计算机基础知识,学到CSAPP第五章的内容,在这里总结并且展开一下C程序性能优化相关的内容。 衡量程序性能的方式 一般而言,程序的性能可以用CPE(Cycles Per Element)来衡量,其指的是处理每个元素…...
在 Docker 中搭建GBase 8s主备集群环境
本文介绍了如何在同一台机器上使用 Docker 容器搭建GBase 8s主备集群环境。 拉取镜像 拉取GBase 8s的最新镜像 docker pull liaosnet/gbase8s或者docker pull liaosnet/gbase8s:v8.8_3513x25_csdk_x64注:在tag为v8.8_3513x25_csdk_x64及之后的版本中,…...
hadoop集群环境配置
目录 VMware虚拟机安装 Xshell安装 网络问题 centos7下载 ---------参考以下视频步骤进行生态搭建---------- 搭建好hadoop01 克隆出hadoop02、hadoop03 启动三台虚拟机 打开终端 输入 记录下各个ip 打开Xshell,新建会话 修改主机名 配置静态IP 主机名称…...
Hive-优化(参数优化篇)
map 数和reduce数 控制hive任务中的map数 合适的map数,会让资源分配的更平均,让我们的代码运行更快,通常情况下,作业会通过input的目录产生一个或者多个map任务。我们可以通过调整参数来控制运行过程中的map数。 Hive Map的数量…...
深度学习|MAE技术全景图:自监督学习的“掩码魔法“如何重塑AI基础
一、引言:深度学习的困境与自监督的曙光 深度学习(Deep Learning)无疑是当今人工智能领域基础中的基础。从图像识别到自然语言处理(NLP),它在无数任务中展现了卓越性能。例如,在安防监控中&…...
学习threejs,使用LineBasicMaterial基础线材质
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.LineBasicMaterial1.…...
第本章:go 切片
注意: 切片必须要初始化 才能使用 ,切片是引用类型 a :[]int{} // 这上叫始化 此时并没有申请内存 // 如果要追加值的话: append ints : append(a, 1, 2, 3)a : make([]int,5) // 声明切片类型var a []string //声明一…...
dify + ollama + deepseek-r1+ stable-diffusion 构建绘画智能体
故事背景 stable-diffusion 集成进 dify 后,我们搭建一个小智能体,验证下文生图功能 业务流程 #mermaid-svg-6nSwwp69eMizP6bt {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6nSwwp69eMiz…...
Java基础面试题全集
1. Java语言基础 1.1 Java是什么? • Java是一种广泛使用的编程语言,最初由Sun Microsystems(现为Oracle公司的一部分)于1995年发布。它是一种面向对象的、基于类的、通用型的编程语言,旨在让应用程序“编写一次&…...
基于multisim的自动干手器设计与仿真
1 设计的任务与要求 设计一个输出 5V 的直流稳压电源。用开关的闭合模拟手挡住光线的功能。用灯的亮灭模拟烘干吹风功能。 2 方案论证与选择 2.1 自动干手器的系统方案 本设计由5V直流电源、红外发射电路、红外接收电路、灯模拟电路构成。 1. 5V直流电源系统 这一部分是整…...
three.js 在 webGL 添加纹理
在我们生成了3D设计之后,我们可以添加纹理使其更加吸引人。在 webGL 和 p5.js中,可以使用 gl.texImage2D() 和 texture() API来为形状应用纹理。 使用 webGL 在 webGL 中,gl.texImage2D() 函数用于从图像文件生成2D纹理。该函数接受许多参…...
Docker 部署 MongoDB 并持久化数据
Docker 部署 MongoDB 并持久化数据 在现代开发中,MongoDB 作为 NoSQL 数据库广泛应用,而 Docker 则提供了高效的容器化方案。本教程将介绍如何使用 Docker 快速部署 MongoDB,并实现数据持久化,确保数据不会因容器重启或删除而丢失…...
SpringBoot优雅关机,监听关机事件,docker配置
Spring Boot 提供了多种方法来实现优雅停机(Graceful Shutdown),这意味着在关闭应用程序之前,它会等待当前正在处理的请求完成,并且不再接受新的请求。 一、优雅停机的基本概念 优雅停机的主要步骤如下: …...
网络基础(一)【网络发展/认识协议/网络 VS 系统/以太网通信原理/重谈协议/网络中的地址管理】
网络基础(一) 1. 网络的发展2. 认识协议3. 网络 VS 系统4. 以太网通信原理5. 重谈协议6. 网络中的地址管理 1. 网络的发展 最开始时,计算机之间相互独立。 但是为了协作完成一些任务,就产生了计算机之间相互通讯的需求,…...
PostgreSQL、SQL Server和MySQL数据库性能调优与故障排除技术
通过结合具体技术特性与工具链的深度使用,可系统化提升数据库性能和稳定性。建议根据实际负载特征制定监控-分析-优化的闭环管理流程。 数据库技术: PostgreSQL 13:逻辑复制、分区表、并行查询、监控工具(如pg_stat_statements、…...