Go-内存分配器
内存分配器 内存空间包含两个重要区域:栈区和堆区。不同编程语言使用不同的方法管理堆区的内存,C++ 等编程语言会由工程师主动申请和释放内存,Go 以及 Java 等编程语言会由工程师和编译器共同管理,堆中的对象由内存分配器分配并由垃圾收集器回收。 设计原理 内存管理一般包含三个不同的组件,分别是用户程序(Mutator)、分配器(Allocator)和收集器(Collector);当用户程序申请内存时,它会通过内存分配器申请新内存,而分配器会负责从堆中初始化相应的内存区域。 分配方法 线性分配器 使用线性分配器时,只需要在内存中维护一个指向特定位置的指针;用户程序申请内存时,移动指针。然而,这不利于在内存被释放时重用内存。如下图中红色部分难以利用: 因为线性分配器需要与具有拷贝特性的垃圾回收算法配合,所以 C 和...
Go-调度器
Go-调度器 多个线程可以属于同一个进程并共享内存空间。因此它们也不需要内存管理单元处理上下文的切换,线程之间的通信是基于共享的内存进行的。 虽然线程比较轻量,但是在调度时也有比较大的额外开销。每个线程会都占用 1M 以上的内存空间,在切换线程时不止会消耗较多的内存,恢复寄存器中的内容还需要向操作系统申请或者销毁资源,每一次线程上下文的切换都需要消耗 ~1us 左右的时间1,但是 Go 调度器对 Goroutine 的上下文切换约为 ~0.2us,减少了 80% 的额外开销。 Go 语言的调度器通过使用与 CPU 数量相等的线程减少线程频繁切换的内存开销,同时在每一个线程上执行额外开销更低的 Goroutine 来降低操作系统和硬件的负载。 设计原理 包括以下几个版本: 单线程调度器:程序中只能存在一个活跃线程,由G-M模型组成; 单线程调度器 0.x...
并发系列:1. 自旋锁
首先从乐观锁和悲观锁的设计思想开始。 乐观锁和悲观锁 悲观锁 悲观锁总认为最坏的情况可能会出现,即数据很可能会被其他人所修改,所以悲观锁在持有数据的时候总会把资源或者数据锁住,其他线程想要请求这个资源时阻塞,直到悲观锁释放资源(读写操作均加锁)。传统的关系型数据库里边就用到了很多悲观锁机制,比如行锁,表锁等,读锁,写锁等,均在做操作之前先上锁。悲观锁的实现往往依靠数据库本身的锁功能实现。 Java 中的 Synchronized 和 ReentrantLock 等独占锁(排他锁)也是一种悲观锁思想的实现,因为 Synchronzied 和 ReetrantLock 不管是否持有资源,它都会尝试去加锁。 一个悲观锁的运用场景: select * from student where name="cxuan" for update 这条 sql 语句从 Student 表中选取 name =...
Go-channel
Go-Channel Channel是Go的核心数据结构和Goroutine之间的通信方式,支持Go的高性能并发编程模型。 设计原理 不通过共享内存的方式通信,而是通过通信的方式共享内存,采用的并发模式为:CSP(通信顺序进程)。Goroutine 和 Channel 分别对应 CSP 中的实体和传递信息的媒介,Goroutine 之间会通过 Channel 传递数据。 怎么理解“不通过共享内存的方式通信,而是通过通信的方式共享内存”这句话呢? 前半句指通过 sync 包里的一些组件进行并发编程;后半句指使用 channel 进行并发编程。实际上,channel 的底层就是通过 mutex 来控制并发的。只是 channel 是更高一层次的并发编程原语,封装了更多的功能。 上图中的两个 Goroutine,一个会向 Channel 中发送数据,另一个会从 Channel 中接收数据,它们两者能够独立运行并不存在直接关联,但是能通过 Channel 间接完成通信。 FIFO Channel的收发操作遵循“先进先出”: 先从 Channel 读取数据的 Goroutine...
Go-上下文Context
上下文context 上下文context是Go中较独特的设计,其他编程语言中较为少见,主要用于在Goroutine之间传递请求的截止时间、取消信号和其他跨API边界的值。 context.Context是Go在1.7版本中引入的标准库接口,定义了4个待实现的方法: Deadline:返回 context.Context 被取消的时间,也就是完成工作的截止日期(如果Context设置了截止时间,则返回ok=true,deadline返回该时间;如果没有设置截止时间,则返回ok=false,deadline为空) Done:返回一个Channel(在当前工作完成或者上下文被取消后关闭),作为对Context关联函数的取消信号。当Channel关闭时,关联函数终止工作并返回。 多次调用 Done...
Transformer系列:2. Attention机制,MHA,MQA和GQA
Scaled Dot-Product Attention 只使用一个注意力头计算权重。 假设有输入序列\(X=(x_1, x_2,..., x_n)\),对于每个词\(x_i\)(维度为\(d\)),计算其与所有其他词的相关性,并赋予不同的权重,最后对这些信息加权求和,得到新的表示。 输入矩阵:\(X\in\mathbb{R^{n\times d}}\). \[ Attention(Q, K, V)=softmax(\frac{QK^{T}}{\sqrt{d_k}})V \] 这里,\(Q\in\mathbb{R^{n\times d_k}}, K\in\mathbb{R^{m\times d_k}, V\in\mathbb{R^{m\times d_v}}}\)。实质上,一个Attention层是:将\([n, d_k]\)的序列\(Q\)编码成一个新的\(n\times d_v\)序列。 从向量角度看: \[ Attention(q_t, K, V)=\sum_{s=1}^m...
Transformer系列:1. 从RNN到Transformer
场景: 图像信息:任务为理解图像内容,采用卷积神经网络; 序列信息:任务为理解语音/文字/视频,采用循环神经网络。 对于序列信息,由于按时序输入的数据之间非独立,前后数据之间具备相关性,因此网络需要存储信息的能力。 RNN 网络结构 RNN通过使用带自反馈的神经元,能够处理任意长度的序列 时序sequence:RNN能建模序列数据,序列指的是前、后输入数据\((x^{(t)}, x^{(t+1)})\)不独立,相互影响; 循环recurrent:对每个输入的操作都是一样的,循环往复地重复这些相同操作,每时刻有相同参数W和U(参数共享); 记忆memory: 隐藏层\(h_{(t)}\)中捕捉了所有时刻t之前的信息,理论上\(h_{(t)}\)记忆的内容可以无限长,然而实际上记忆还是有限的; 正向计算 反向传播BPTT 梯度消失 / 梯度爆炸 循环神经网络的递归结构,导致梯度消失/梯度爆炸现象更明显 梯度爆炸:可采用梯度截断解决 由于梯度消失,RNN无法处理长期依赖关系。 比如,考虑一个语言模型,试图根据之前单词预测下一个; 如果要预测“The clouds...
Transformer的KV Cache
Problems 对于LLMs,每次矩阵乘法都由若干个浮点运算组成,因此其性能受限于GPU的FLOPS;随着输入的token长度增加,Transformer的自注意力机制与输入序列长度呈平方关系增长,产生最大的延迟开销。 为了解决推理延迟和吞吐量问题,当前的大模型服务系统通常采用KV Cache:通过缓存已计算的Key和Value矩阵,以避免在解码阶段重复计算键和值的投影(空间换时间)。然而在以下场景中KV Cache占用内存较大,影响推理性能: 处理长序列或多轮对话; 对于多个客户端请求,每个请求分别保留各自的KV Cache。 KV Cache的核心问题在于:占用大量内存和访存带宽;在生成阶段引入大量重复计算。本篇博客探讨KV Cache压缩技术。 Backgrounds 推理加速的衡量指标如下: 吞吐量:每生成一个token,服务商需要支付的算力成本。可以通过tokens per second(tps)衡量,即推理服务器单位时间内能处理针对所有用户和请求生成的输出token数。 延迟:包括两个指标: TTFT(Time To...
verl框架:2. 对比OpenRLHF+colocate思路解析
SPMD->MPMD SPMD设计范式:单程序多数据,所有进程/线程执行同一个程序的拷贝,通过环境变量差异自主确定行为模式,无需中心调度节点。主流并行框架(DDP/DeepSpeed/Megatron)均基于SPMD范式。 优点:SPMD由于没有controller,完全由worker自驱,在运行时更为高效; 缺点:由于各个worker上需要运行相同程序,灵活性不如single-controller模式;需要考虑各个rank之间的通信,增加编程复杂度。 经典代码如下: 123456import torchimport osprint(os.environ['RANK'], os.environ['WORLD_SIZE'], os.environ['MASTER_ADDR'],...
verl框架:1. Ray集群介绍+verl中基于Ray的执行流程解析
现代计算机体系结构 现代计算机体系结构如下: 多核:一台计算机上有多颗CPU,每个 CPU 有多个计算核心。CPU内部有缓存结构,外部有主存。 集群:多台计算机通过高速网络互联,每台计算机上配有至少一块高速网卡。使得不同节点之间互相访问数据就像在单个节点一样。 异构计算:CPU 和主存通常被称为主机(Host),各类专用的加速器被称为设备(Device)。当前基于 GPU 的异构计算是主流,GPU 有区别于 CPU 的芯片微架构和编译软件栈。 软件层面:GPU 提供了 CUDA编程接口; 硬件层面:GPU 有很多个专用计算核心,和 GPU...