0%

Train

深度学习的训练过程是通过设定某种数据方式, 并设计合适的网络模型结构,以及损失函数和优化算法, 在此基础上将数据集以小批量反复进行 前向计算并计算损失,然后反向计算梯度利用特定的优化函数来更新模型,来使得损失函数达到最优的结果。训练过程最重要的就是梯度的计算和反向传播。

Infer

推理就是在训练好的模型结构和参数基础上,做一次前向传播得到模型输出的过程。相对于训练而言,推理不涉及梯度和损失优化。推理的最终目标是将训练好的模型部署生产环境中。真正让 AI 能够运用起来。推理引擎可以将深度学习模型部署到云(Cloud)端或者边缘(Edge)端,并服务用户的请求。

image-20230313144448981

链接:https://www.zhihu.com/question/411393222/answer/2919454954

1. Fabless、Foundry、IDM

半导体行业根据生产设计以及制造能力分为不同的公司种类,分别是Fabless, Foundry和IDM。

  • Fabless

Fabless 指的只从事芯片设计与销售,不从事生产的公司,这样的企业被成为“无厂化企业”,手机厂商中的华为、苹果和小米,还有高通和联发科,都属于 Fabless。

目前大多数的芯片公司基本都是Fabless,也就是大家只负责芯片的开发设计,然后生产可以找专业的代工厂进行生产。芯片的开发设计是一个需要大量人力进行创新的领域,相对于生产设计投入成本相对较小,但是收益周期会更快。因此更多的公司只会选择开发设计这一块来做。

  • Foundry

Foundry 是能够自行完成芯片制造,但是没有设计能力的厂商,就是我们所熟知的代工厂。台积电就是典型的 Foundry,专注芯片制造,发展相关的工艺和制程,所以 Foundry 厂商其实就是 Fabless 厂商的代工方。

  • IDM

IDM 就是指既能够自行设计、也能够自行生产的芯片厂商,世界上有这种能力的不多,三星和英特尔。

2. 半导体集成电路和晶圆有何关系?

半导体集成电路是将很多元件集成到一个芯片内, 以处理和储存各种功能的电子部件。

晶圆是指将硅(Si)、砷化镓(GaAs)等生成的单 晶柱切成薄片的圆盘。大部分晶圆都是由沙子中提 取的硅制成的。地球上有大量的硅,可以稳定供应, 并且硅具有无毒、环保的特点。

由于半导体集成电路是通过在晶圆的薄基板上制造多个相同电路而产生的, 因此晶圆是半导体的基础, 就像盖房子打地基一样。

3. 半导体晶圆制造工艺

image-20230309135720606

阅读全文 »

image-20221230153835324

BiSeNet出自旷世,于ECCV2018发表,实时语义分割的双向网络。

paper name : BiSeNet: Bilateral Segmentation Network for Real-time Semantic Segmentation

1. 要解决的问题

  • 解决之前一些轻量级语义分割算法在做算法加速时损失空间信息或者感受野缩小的问题。

2. 采用的方法

  • 提出双向(两条路)网络, 一条Spatial Path(SP), 使用Conv(stride = 2)3次,获得原图1/8尺寸的特征图,保留较为丰富的空间特征信息(较多的channel, 较浅的网络)。另一条Context Path(CP),译为上下文路径, 可以使用任意的轻量级网络,如Xception、ShuffleNet、MobileNet, 文中在 Xception 尾部附加一个全局平均池化层,其中感受野是 backbone 网络的最大值。

  • 两条网络提取的特征通过特征融合模块(Feature Fusion Module)来快速合并。

效果

  • 在 Cityscapes,CamVid 和 COCO-Stuff 数据集上达到了准确率和速度的平衡。

image-20221230163614657

阅读全文 »

RocketMQ

RocketMQ 是阿里捐赠给Apache的一款低延迟、高并发、高可用、高可靠的分布式消息中间件, 经历过淘宝多次双十一的考验,RocketMQ既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性。

核心概念

  • Topic: 消息主题, 一级消息类型, 生产者向其发送消息。
  • Message: 生产者向Topic发送并最终传送给消费者的数据消息的载体。
  • 消息属性:生产者可以为消息定义的属性,包含Message Key和Tag。
  • Message Key:消息的业务标识,由消息生产者(Producer)设置,唯一标识某个业务逻辑。
  • Message ID:消息的全局唯一标识,由消息队列RocketMQ系统自动生成,唯一标识某条消息。
  • Tag:消息标签,二级消息类型,用来进一步区分某个Topic下的消息分类

  • Producer:也称为消息发布者,负责生产并发送消息至Topic。

  • Consumer:也称为消息订阅者,负责从Topic接收并消费消息。
  • Group:一类生产者或消费者,这类生产者或消费者通常生产或消费同一类消息,且消息发布或订阅的逻辑一致。
  • Group ID:Group的标识。
  • 队列:Topic下会由一到多个队列来存储消息。
  • Exactly-Once投递语义:Exactly-Once投递语义是指发送到消息系统的消息只能被Consumer处理且仅处理一次,即使Producer重试消息发送导致某消息重复投递,该消息在Consumer也只被消费一次。
  • 集群消费:一个Group ID所标识的所有Consumer平均分摊消费消息。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在集群消费模式下每个实例平均分摊,只消费其中的3条消息。
  • 广播消费:一个Group ID所标识的所有Consumer都会各自消费某条消息一次。例如某个Topic有9条消息,一个Group ID有3个Consumer实例,那么在广播消费模式下每个实例都会各自消费9条消息。
  • 定时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是推迟到在当前时间点之后的某一个时间投递到Consumer进行消费,该消息即定时消息。
  • 延时消息:Producer将消息发送到消息队列RocketMQ服务端,但并不期望这条消息立马投递,而是延迟一定时间后才投递到Consumer进行消费,该消息即延时消息。
  • 顺序消息:RocketMQ提供的一种按照顺序进行发布和消费的消息类型,分为全局顺序消息和分区顺序消息。

  • 消息堆积:Producer已经将消息发送到消息队列RocketMQ的服务端,但由于Consumer消费能力有限,未能在短时间内将所有消息正确消费掉,此时在消息队列RocketMQ的服务端保存着未被消费的消息,该状态即消息堆积。

  • 消息过滤:Consumer可以根据消息标签(Tag)对消息进行过滤,确保Consumer最终只接收被过滤后的消息类型。消息过滤在消息队列RocketMQ的服务端完成。
  • 消息轨迹:在一条消息从Producer发出到Consumer消费处理过程中,由各个相关节点的时间、地点等数据汇聚而成的完整链路信息。通过消息轨迹,您能清晰定位消息从Producer发出,经由消息队列RocketMQ服务端,投递给Consumer的完整链路,方便定位排查问题。

消息收发模型

消息队列RocketMQ支持发布和订阅模型,消息生产者应用创建Topic并将消息发送到Topic。消费者应用创建对Topic的订阅以便从其接收消息。通信可以是一对多(扇出)、多对一(扇入)和多对多。具体通信如下图所示。

image-20221228150138473

  • 生产者集群:用来表示发送消息应用,一个生产者集群下包含多个生产者实例,可以是多台机器,也可以是一台机器的多个进程,或者一个进程的多个生产者对象。
    一个生产者集群可以发送多个Topic消息。发送分布式事务消息时,如果生产者中途意外宕机,消息队列RocketMQ服务端会主动回调生产者集群的任意一台机器来确认事务状态。

  • 消费者集群:用来表示消费消息应用,一个消费者集群下包含多个消费者实例,可以是多台机器,也可以是多个进程,或者是一个进程的多个消费者对象。
    一个消费者集群下的多个消费者以均摊方式消费消息。如果设置的是广播方式,那么这个消费者集群下的每个实例都消费全量数据。

    一个消费者集群对应一个Group ID,一个Group ID可以订阅多个Topic,如上图中的Group 2所示。Group和Topic的订阅关系可以通过直接在程序中设置即可。

阅读全文 »

前段时间,在做深度学习训练平台时有用到RocketMQ这个分布式消息中间件, 自己对中间件(Middleware)基础概念还不是很清晰, 这里记录学习下。

百度百科上的描述:

中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能), 衔接网络上应用系统的各个部分或不同的应用, 能够达到资源共享,功能共享的目的。

定义: 中间件是一种独立的系统软件服务程序, 分布式应用软件借助这种软件在不同的技术之间共享资源, 中间件位于客户机服务器的操作系统之上管理计算资源和网络通信。

中间件 = 平台 + 通信

中间件(基本功能)提供 1. 通信支持, 2. 应用支持, 3.公共服务

中间件(基本分类)有 1. 事务式中间件 2,过程式中间件 3,面向消息的中间件 4,面向对象的中间件 5, Web应用服务器 6,其他

知乎Gocy大佬的通俗性解释

个人理解:
将具体业务和底层逻辑解耦的组件。

大致的效果是:
需要利用服务的人(前端写业务的),不需要知道底层逻辑(提供服务的)的具体实现,只要拿着中间件结果来用就好了。

举个例子:
我开了一家炸鸡店(业务端),然而周边有太多屠鸡场(底层),为了成本我肯定想一个个比价,再综合质量挑选一家屠鸡场合作(适配不同底层逻辑)。由于市场变化,合作一段时间后,或许性价比最高的屠鸡场就不是我最开始选的了,我又要重新和另一家屠鸡场合作,进货方式、交易方式等等全都要重来一套(重新适配)。

然而我只想好好做炸鸡,有性价比高的肉送来就行。于是我找到了一个专门整合屠鸡场资源的第三方代理(中间件),跟他谈好价格和质量后(统一接口),从今天开始,我就只需要给代理钱,然后拿肉就行。代理负责保证肉的质量,至于如何根据实际性价比,选择不同的屠鸡场,那就是代理做的事了。

链接:https://www.zhihu.com/question/19730582/answer/140527549

知乎小傅哥的解答

中间件用于解决共性凝练和复用

Reference

  1. https://www.redhat.com/zh/topics/middleware/what-is-middleware
  2. https://baike.baidu.com/item/%E4%B8%AD%E9%97%B4%E4%BB%B6/452240
  3. https://www.zhihu.com/question/19730582

1. 生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。

生产者和消费者之间不直接通讯, 而是通过阻塞队列来进行通讯。

生产者生产完数据之后不用等待消费者处理, 直接扔给阻塞队列, 同理, 消费者也不找生产者要数据, 而是直接从阻塞队列中获取。

这里, 阻塞队列可以看做是一个缓冲区, 平衡了生产者和消费者的处理能力。

image-20221222173718064

2. 主要作用

  • 解耦合
  • 异步执行,提高程序的运行效率。

3. 结构剖析

在生产者-消费者模式中,通常有两类线程,一类是生产者线程一类是消费者线程。生产者线程负责提交用户请求,消费者线程则负责处理生产者提交的任务。

​ 最简单粗暴的做法就是生产者每提交一个任务,消费者就立即处理,直到所有任务处理完。但是这样直接通信很容易出现性能上的问题,消费者必须等待它的生产者提交到任务才能执行,就不能达到真正的并行。同时生产者和消费者之间存在依赖关系,在设计上耦合度非常高,这是不可取的。那么最好的做法就是加一个中间层作为通信桥梁

生产者和消费者之间通过共享内存缓存区进行通信。多个生产者线程将任务提交给共享内存缓存区,消费者线程并不直接与生产者线程通信,而在共享内存缓冲区获取任务,并行地处理。其中内存缓冲区的主要功能是数据再多线程间的共享,同时还可以通过该缓存区,缓解生产者和消费者间的性能差。它是生产者消费者模式的核心组件,既能作为通信的桥梁,又能避免两者直接通信,从而将生产者和消费者进行解耦。生产者不需要消费者的存在,消费者也不需要知道生产者的存在。

例如在CV中, 取图线程(进程)通常比较快速, 计算线程(进程)通常较慢, 此时可以维护一个队列(共享内存缓冲区),设计为生产者消费者模式进行处理, 一个取图线程当做生产者, 多个计算线程当做消费者。

4. code

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
import threading
import queue
import time
#定义一个生产者
def producer():
count = 0
for i in range(5):
print('生产第%s个产品......',count)
q.put(count)
count+=1
time.sleep(1)
q.join()

#定义一个消费者
def consumer(name):
while True:
print("%s 消费第%s个产品" % (name,q.get()))
q.task_done()

print('消费者执行完了所有任务')
#定义一个队列
q = queue.Queue(maxsize=4)

t1 = threading.Thread(target=producer)
t2 = threading.Thread(target=consumer,args=('Consumer',))
t1.start()
t2.start()

'''
生产第%s个产品...... 0
Consumer 消费第0个产品
消费者执行完了所有任务
生产第%s个产品...... 1
Consumer 消费第1个产品
消费者执行完了所有任务
生产第%s个产品...... 2
Consumer 消费第2个产品
消费者执行完了所有任务
生产第%s个产品...... 3
Consumer 消费第3个产品
消费者执行完了所有任务
生产第%s个产品...... 4
Consumer 消费第4个产品
消费者执行完了所有任务
'''

map 与 reduce函数

  1. map()函数接收两个参数, 第一个是函数, 第二个是Iterable, map将传入的函数依次作用到序列的每个元素, 并将结果作为新的Iterator返回

    1
    2
    3
    4
    5
    6
    7
    8
    def square(x):
    return x ** 2
    r = map(square, [1,2,3,4,5])
    squareed_list = list(r)
    print(squreed_list) # [1,4,9,16,25]
    #使用lambda匿名函数简化为一行代码
    list(map(lambda x: x*x, [1,2,3,4,5]))
    list(map(str, [1,2,3])) # ['1', '2', '3']

    注意map函数返回的是一个Iterator(惰性序列), 要通过list函数转化为常用列表结构。

  2. reduce()函数接收两个参数, 一个是函数(两个参数), 一个是序列, 与map不同的是,reduce把结果和序列的下一个元素做累积计算。

    1
    reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from functools import reduce
    CHAR_TO_INT = {
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9
    }
    def str2int(str):
    ints = map(lambda x: CHAR_TO_INT[x], str)
    return reduce(lambda x,y: 10*x + y, ints)
    print(str2int('0012345')) # 0012345

python 实现对函数参数做类型检查

python自带的函数一般都会有对函数参数类型做检查,自定义的函数参数类型检查可以用函数isinstance()实现:

1
2
3
4
5
6
7
8
9
10
11
12
def my_abs(x):
"""
自定义的绝对值函数
:param x: int or float
:return: positive number, int or float
"""
if not isinstance(x, (int, float)):
raise TypeError('bad operated type')
if x > 0:
return x
else:
return -x

添加了函数参数检查后,如果传入错误的参数类型, 函数就可以抛出一个TypeError错误。

  • 将可变对象(mutable):列表list,字典dict,Numpy数组ndarray和用户自定义类型(类class),作为参数传递给函数, 函数内部将其改变后, 函数外部这个变量也会改变。
  • 将不可改变对象(immutable):字符串string,元祖tuple,数值numbers,作为参数传递给函数,函数内部将其改变后,函数外部这个变量不会改变。
1
2
3
4
5
a = 1
def fun(a):
a = 2
fun(a)
print a # 1
1
2
3
4
5
a = []
def fun(a):
a.append(1)
fun(a)
print a # [1]

使用id来看引用a的内存地址辅助理解一下

1
2
3
4
5
6
7
8
a = 1
def fun(a):
print "func_in",id(a) # func_in 41322472
a = 2
print "re-point",id(a), id(2) # re-point 41322448 41322448
print "func_out",id(a), id(1) # func_out 41322472 41322472
fun(a)
print a # 1

1
2
3
4
5
6
7
a = []
def fun(a):
print "func_in",id(a) # func_in 53629256
a.append(1)
print "func_out",id(a) # func_out 53629256
fun(a)
print a # [1]

前言

记得刚工作的时候做摄像头端的灯语识别, 主要是未涂红状态下的各种信号灯的识别, 当时用一个resnet-50跑了下实验觉得还不错, 结果导师讲用不了这么“大”的模型, 让把这个大模型砍成一个小的, 似懂非懂,一顿操作, 换backbone,各种轻量级网络,shuffleNet,mobilenet一顿尝试,还不如直接把resnet砍几层好使, 在此学习记录下田子宸@知乎的文章。

一 、模型”大小”评价指标

| 计算量 | 参数量 |访存量 |内存占用|

  1. 计算量

    计算量是模型所需的计算次数,反映了模型对硬件计算单元的需求。计算量一般用 OPs (Operations) ,即计算次数来表示。由于最常用的数据格式为 float32,因此也常常被写作 FLOPs (Floating Point Operations),即浮点计算次数。
    之前总结过卷积中的计算量 计算量

  2. 参数量

    参数量是模型中的参数的总和,跟模型在磁盘中所需的空间大小直接相关。对于 CNN 来说参数主要由 Conv/FC 层的 Weight 构成。

    参数量往往是被算作访存量的一部分,因此参数量不直接影响模型推理性能。但是参数量一方面会影响内存占用,另一方面也会影响程序初始化的时间。

  3. 访存量 (MACs memory access cost)

    访存量往往是最容易忽视的评价指标,但其实是现在的计算架构中对性能影响极大的指标。

    访存量是指模型计算时所需访问存储单元的字节大小,反映了模型对存储单元带宽的需求。访存量一般用 Bytes(或者 KB/MB/GB)来表示,即模型计算到底需要存/取多少 Bytes 的数据。

    和计算量一样,模型整体访存量等于模型各个算子的访存量之和。对于 Eltwise Sum 来讲,两个大小均为 (N, C, H, W) 的 Tensor 相加,访存量是 (2 + 1) x N x C x H x W x sizeof(data_type),其中 2 代表读两个 Tensor,1 代表写一个 Tensor;而对于卷积来说,访存量公式为:

image-20221201155159400

  1. 内存占用

    内存占用是指模型运行时,所占用的内存/显存大小。一般有工程意义的是最大内存占用,当然有的场景下会使用平均内存占用。这里要注意的是,内存占用 ≠ 访存量。

    和参数量一样,内存占用不会直接影响推理速度,往往算作访存量的一部分。但在同一平台上有多个任务并发的环境下,如推理服务器、车载平台、手机 APP,往往要求内存占用可控。可控一方面是指内存/显存占用量,如果占用太多,其他任务就无法在平台上运行;另一方面是指内存/显存的占用量不会大幅波动,影响其他任务的可用性。

二、计算量越小,模型推理越快吗?

答案是否定的。

实际上计算量和实际的推理速度之间没有直接的因果关系。计算量仅能作为模型推理速度的一个参考依据。

模型在特定硬件上的推理速度,除了受计算量影响外,还会受访存量、硬件特性、软件实现、系统环境等诸多因素影响,呈现出复杂的特性。因此,在手头有硬件且测试方便的情况下,实测是最准确的性能评估方式

在设计网络结构时,如果有实测的条件,建议在模型迭代早期对性能也进行测试。一些 NAS 的方法也会对搜索出来的网络结构进行测速,或者干脆对硬件速度进行了建模,也作为初期搜索的重要参数。这种方法设计出来的网络在后期部署时,会极大减少因性能问题迭代优化的时间和人力开销。

这里作者讨论影响模型在硬件上推理速度的一些因素,一方面希望可以帮助手动/自动设计网络结构的同学更快的设计更高效的网络结构,另一方面希望当模型部署时性能出现问题时能够为大家提供分析原因的思路。

  • 计算密度与 RoofLine 模型
  • 计算密集型算子与访存密集型算子
  • 推理时间
  1. 计算密度与 RoofLine 模型

计算密度是指一个程序在单位访存量下所需的计算量, 单位是FLOPs/Byte, 也称计算访存比,用于反映一个程序相对于访存来说计算的密集程度:

image-20221201175419410

image-20221202104733290

RoofLine 模型是一个用于评估程序在硬件上能达到的性能上界的模型,可用上图表示:

RoofLine 模型

用公式描述:

image-20221202104836499

当程序的计算密度I较小时, 程序访存多而计算少,性能受到内存贷款限制,称为访存密集型程序。即上图的橙色区域,。在此区域上的程序性能上界 = 计算密度 * 内存带宽, 表现为图中的斜线,其中斜率为内存带宽的大小。计算密度越大, 程序所能达到的速度上界越高,但使用的内存带宽始终为最大值。

阅读全文 »