# ORCA论文阅读




现阶段有什么问题：

之前的推理引擎在处理基于transform模型decode的时候存在局限

1. 由于语言模型的自回归特性，每次模型的输出作为模型的输入再次执行模型，导致输出多少个token，就需要运行多少次模型，并且这个运行多少次模型是不确定的
2. 之前的推理引擎只需要运行一次就可以了
3. 这导致了以下的问题

    1. 之前的推理引擎是以请求为粒度调度的，很多请求过来，我通过调度方法来确定那些请求，走一遍模型，然后所有引擎一起返回
    2. 但是对于有自回归特性的语言模型，每个请求不知道的自己输出多少token，运行多少次，这就导致了一个问题，如果调度器选择了一批请求，那么返回这一批的请求的时间取决于耗费时间最初的请求，之前的已经完成的请求无法直接返回

‍

之前的推理引擎这么做是有原因的：

1. 之前的模型确实一次的就可以运行完成
2. 批处理可以高效地利用GPU资源，多个请求的输入可以聚合成一个大张量，来和模型参数进行矩阵运算，提高效率。另一点就是这一批次在计算过程中可以重复使用模型参数，由于内存墙的存在，可以提高效率

一般推理服务系统包含两部分，一个是调度器，另一个是执行引擎，下图是一个简单的示例：

调度器的调度算法多种多样，为了满足各种需求也就是有各种算法，比如有的目标是吞吐，有的目标是延迟，

![推理服务.drawio](https://pic.bynshard.top/推理服务.drawio-20250509164834-7i0zt17.svg)​

现有的推理服务问题是，如果我一次调度了两个请求，一个是"I love"，另一个是“I think”，那么这两个请求会一直等到第二个请求结束第一个才能返回。

![image](https://pic.bynshard.top/20250509170359.png)​

所以改进方法也是容易想到的，不在请求调度，使用迭代调度，运行模型一次我们就返回到调度器，看看有没有结束的，然后还可以在把请求池里面请求在塞进去。可以解决我们上面提出的问题，

![image](https://pic.bynshard.top/20250509170753.png)

上面这个是原文的图示，请求的有阴影的块就是prompt，也就是请求的输入，$x_{ij}$代表的是第x个请求的第j个token。其中$x_1$这个请求已经进行了两次迭代，生成了$x_{13}x_{14} $。经过一次迭代之后，执行引擎返回了下一个的token。

这样做有一个问题，就是运行效率的问题。在上文中，讨论了之前推理引擎设计的核心就是批处理，效率高，迭代级调度效率低，这里做简单解释：

1. 可能有人会疑问为什么会下降，不是传入的批次大小是一样的吗？
2. 实际上批处理有个苛刻的要求就是说每个请求的形状要是一样的，如果不一样，那么就没有办法进行批处理。形状不一致意味着底层要做不同大小的循环/访存，无法在一次 kernel 调用里并行完成；但 kernel 调用本身开销很大，效率会急剧下降。
3. 其余人可能认为，我们在一个kernel中使用一个循环来处理，这就是一个kernel，但是问题时这样的话就不能共享权重参数，计算每个请求都需要重新加载权重，由于内存墙的存在，效率不够高

‍

比如$x_1$和$x_2$，他们都处于decode阶段，但是他们的长度不同，那么在计算注意力的时候，k的长度就是不同的，所以不能批处理。 

对于$x_3$和$x_4$，他俩长度不同，无法进行拼接。

对于$x_1$和$x_3$，他俩处于不同的阶段，对于使用了kv cache的推理引擎，$x_3$处于profill阶段，需要将这个2个token都放进去，然后根据最后一个token的输出隐藏维度计算下一个token，但是对于$x_1$，他只需要将一个token放进去，然后使用之前的kv cache进行计算，他俩的计算都不一样，所以更不能批处理了。

‍

为了解决这个问题，提出了选择批处理计算，这里的选择指的是将推理引擎中的多个算子选择出来进行批处理。

token被嵌入在加上位置信息，这个是token级别的操作。

qkv线性转换也是token级别的操作，

但是attention操作是请求级别的操作，

ffn中的线性变化也是token级别的操作

层归一化是也是token级别的操作，他是在token隐藏向量维度上进行归一化

gelu则是对单个数值进行操作，对每个标量进行操作

‍

所以根据上述，可以把这个token级别操作的算子进行批处理，对于注意力机制进行单独处理，这个就是选择批处理的含义。

‍

![image](https://pic.bynshard.top/20250509194845.png)​

可以看到，在CPU端，我们现将几个请求合并成token级别，进入里面进行操作，这个时候一起使用模型权重，进行批处理。在计算

