自定义工作负载配置的推荐步骤#
第一步:决定需要使用的仿真模式#
NPU-SIM 支持多种仿真模式。
仿真模式 |
适用场景 |
|---|---|
dataflow |
不规则或自定义的数据流图、通信原语;单批次负载。不支持可变数据流图。 |
gpu |
SIMT架构仿真,仅支持单卡。不支持可变执行流。 |
sched_pd |
LLM Serving场景下的PD融合负载,支持可变数据流图。所有请求计算流相同。 |
sched_pds |
LLM Serving场景下的PD分离负载,支持可变数据流图。所有请求计算流相同。 |
在决定需要使用的仿真模式之后,填入配置文件的 mode 字段。
第二步:决定模型结构和参数大小#
无论采用何种仿真模式,都需要通过特定的方式配置模型参数和大小。在dataflow和gpu模式下,虽然模型的层数、隐藏层维度、中间层维度由后续定义的数据流图决定,无需显式指定,但需提前在 var 字段中定义后续原语所用到的所有变量。在pd与pds模式下,模型的参数与大小则需要通过 model 字段显式指定。
第三步:以原语为单位组织数据流图#
仿真器支持的所有原语种类可在 llm/include/prims/ 下找到定义,书写的过程中需参考各原语的头文件,为每一个原语指定所有的参数与输入输出标签。常见的参数名及含义见下表。
参数名 |
含义 |
|---|---|
B |
batch size |
T |
sequence length (token count, 矩阵乘MN×NK中的M) |
C |
channels (NH * DH, 矩阵乘MN×NK中的N) |
OC |
矩阵乘MN×NK中的K |
NH |
number of heads |
DH |
dimention of heads |
KVH |
number of kv heads (GQA) |
HS |
hidden size (在自动生成脚本中标记为P) |
IS |
intermediate size (在自动生成脚本中标记为J) |
R |
数值上等于 NH / KVH |
K |
MoE算子中的Top-K数量 |
E_N |
MoE算子中的专家数量 |
N |
通常用于单一参数的算子的计算维度 |
dim |
矩阵融合的方式。1代表累加,2代表拼接 |
slice |
参与融合的矩阵个数,通常等同于TP大小 |
C |
channels (NH * DH, 矩阵乘MN×NK中的N) |
job_type |
PD与PDS模式专属算子的工作方式。0代表仅Prefill,1代表仅Decode,2代表混合 |
slice_x |
GPU专属算子在x方向上的分片数 |
slice_y |
GPU专属算子在y方向上的分片数 |
need_choose |
MoE算子中进行专家预取的策略 |
配置字段与书写规范#
完整的工作负载配置以 JSON 字典的形式呈现,以下对各个字段的含义与书写规范进行说明。
支持的值:[“dataflow”, “pd”, “pds”, “gpu”, “gpu_pd”]
指定当前配置文件的仿真模式。对于不同的仿真模式,配置文件的书写方法略有不同。
dataflow: 众核数据流模式。
pd: LLM Serving模式,且采用 PD-aggregation 。
pds: LLM Serving模式,且采用 PD-split 。
gpu: GPU模式。
gpu_pd: GPU模式,且采用 PD-aggregation 。
备注
一个配置文件仅支持一种仿真模式。
在本页面其余字段的说明下方,会标记出该字段所适用的仿真模式。
适用模式:dataflow, gpu
定义在配置文件中所使用到的变量键值对。键代表变量名,值代表变量值。在配置文件中,若使用字符串作为任意字段的值,优先为其赋予 vars 中该变量名所对应的值。
示例
在配置文件中,vars定义如下:
{
"vars": {
"foo": 123.456,
"bar": 7000
}
}
随后在配置文件的其他位置,使用如下写法,将 some_random_field 的值设置为123.456:
{
"some_random_field": "foo"
}
备注
变量值仅支持整型与浮点型。
不能对同一变量名重复定义,且不能在配置文件中使用未定义的变量。
使用变量时,不支持对变量名进行运算。例如”foo+1”、”foo / bar”、”2foo”等,在仅定义了
foo变量的情况下,均是错误写法。
适用模式:dataflow
在简单数据流模式下,表示 总请求个数 。该模式无法指定请求到达时间分布,因此所有请求视为在仿真开始时刻(0s)同时到达。
适用模式:pd, pds
描述所有请求的元数据。包括总请求个数、请求到达时刻、请求token长度(input token)。
count : number
所有请求的总个数。
seq_len : number
所有请求的平均token数,代表模型推理的平均input token长度。
eof_chance : double
所有请求所生成的平均token数,代表模型推理的平均output token长度。实际数值等同于
2 / eof_chance。arrival : number[]
所有请求的到达时刻分布。数组中的每一个元素代表请求到达的时间戳(单位:ns)。 若数组长度小于总请求个数,则剩余请求的到达时刻统一为数组中的最后一个元素。
备注
请求的到达时刻分布必须为非递减序列。
数组长度至少为1。
示例
定义一次LLM Serving流程,共10个请求。所有请求的平均input token长度为64。前三个请求的到达时刻分别为1、10、100ns,从第4个请求开始,所有请求的到达时刻均为1000ns。
{
"requests": {
"count": 10,
"seq_len": 64,
"arrival": [1, 10, 100, 1000]
}
}
适用模式:pd, pds
在LLM Serving仿真模式下,定义有关模型与仿真流程的元信息。
heads : number
模型注意力头个数。
stage : number
适用模式:pd
模型进行流水线并行(PP)的大小。
prefill_stage : number
适用模式:pds
Prefill工作核进行流水线并行(PP)的大小。
decode_stage : number
适用模式:pds
Decode工作核进行流水线并行(PP)的大小。
prefill_cores : number
适用模式:pds
进行Prefill的工作核个数,需保证为
prefill_stage的倍数。decode_cores : number
适用模式:pds
进行Decode的工作核个数,需保证为
decode_stage的倍数。kv_heads : number
模型KV头个数,用于包含GQA算子的模型。
head_size : number
模型注意力头维度。
hidden_size : number
模型隐藏层维度。
intermediate_size
模型中间层维度。
prefill_iters : number
使用Chunked Prefill优化,将Prefill工作拆分为均等chunk的个数。如果希望关闭Chunked Prefill,则设置为1。
示例1
在LLM Serving PD aggregation 模式中,模型的注意力头数为24,KV头数为6,模型头维度为128,流水线并行大小为12(每一拍流水线执行的模型层数等于 模型总层数 / PP大小 )。不使用Chunked Prefill。
此时使用的核心总数量至少为12个,实际数量由硬件配置文件决定(可参阅硬件配置文件 硬件配置 )。
{
"model": {
"heads": 24,
"stage": 12,
"kv_heads": 6,
"head_size": 128,
"prefill_iters": 1
}
}
示例2
在LLM Serving PD Split 模式中,模型的注意力头数为24,KV头数为6,模型头维度为128。使用Chunked Prefill将每一个Prefill任务分解为均等的2个小chunk。Prefill与Decode的流水线并行大小均为7,其中进行Prefill的总核数为42,进行Decode的总核数为21。这意味着Prefill工作核的数据并行(DP)大小为 42 / 7 = 6 ,Decode工作核的数据并行(DP)大小为 21 / 7 = 3 。
此时使用核心总数量为 42 + 21 = 63 个,在指定硬件配置文件时需满足这一点。
{
"model": {
"heads": 24,
"prefill_stage": 7,
"decode_stage": 7,
"prefill_cores": 42,
"decode_cores": 21,
"kv_heads": 6,
"head_size": 128,
"prefill_iters": 2
}
}
工作核的负载由原语按顺序排列而成,记录在 cores 字段中。为了仿真平台的可扩展性,我们要求在书写 cores 字段时,必须将其包含在 chips 字段中,具体写法如下:
{
"chips": [
{
"chip_id": 0,
"cores": [
{ "contents": "..." }
]
}
]
}
备注
除了 cores 字段以外,其余内容为固定结构,不允许修改。
适用模式:dataflow, pd, pds, gpu, gpu_pd
记录每一个工作核的负载信息。
id : number
工作核编号。
loop : string/number
循环执行
worklist中所记录原语的次数。prim_copy : number, optional
为了书写便利,使用此字段复制其他工作核的所有worklist及其中的所有原语。需注意被复制的核不能是未定义的核、或是另一个指定了
prim_copy字段的核。在主动复制原语之后,仍需填写所有worklist中的cast字段,无需填写worklist中的prims字段。worklist : dict[]
按顺序执行的原语列表。每一个worklist中记录了一系列 计算 原语(COMP_PRIM)。NPU-SIM会自动在每一个worklist执行前加上一个 接收 原语(RECV_PRIM),在worklist执行结束后加上一个 发送 原语(SEND_PRIM),因此工作核间的通信流程由
worklist数组对工作流程的切割隐式决定。具体原语通信范式可参阅 原语与工作核通信范式 。recv_cnt : number
在执行此
worklist之前,需要等待从其他工作核传来的数据份数。若为0,则表示无需等待。recv_tag : number, optional
接收数据时,过滤指定的标签号。只有与发送核侧指定了相同的标签号时,才能成功接收。与
cast中的tag配合使用。若未指定此字段,则默认为本核编号。cast : dict[]
在此worklist执行完毕后,将数据发送至其他工作核的相关信息。
dest : number
目标核编号。
tag : number, optional
发送数据时,为数据添加的标签号。只有与接收核侧指定了相同的标签号时,才能成功发送。与
worklist中的recv_tag配合使用。若未指定此字段,则默认为目标核编号。weight : number, optional
发送数据占总数据的比例(
1 / weight,均匀划分)。默认值为1。prims : dict[]
此worklist的原语序列,由前至后依次执行。
type : string
原语类型(需填写指定字符串,见下)。
{{var}} : string/number
原语的参数列表。不同原语所需参数及参数名均不相同,可参考以下目录中的头文件定义(其中包含原语类型名):
${NPU_SIM_ROOT}/llm/include/primssram_address : dict
指定计算原语在SRAM中的输入输出标签,具体内存读写与标签管理范式可参考 内存访问与标签管理范式 。
indata : string
输入标签。此字段不会被NPU-SIM理解为
vars中的变量。outdata : string
输出标签。此字段不会被NPU-SIM理解为
vars中的变量。prefill : dict[]
适用模式:pds
标记Prefill工作核的
worklist。使用方法见下。decode : dict[]
适用模式:pds
标记Decode工作核的
worklist。使用方法见下。{ "cores": { "prefill": [ { "id": 0, "worklist": "some_worklist..." } ], "decode": [ { "id": 1, "worklist": "some_worklist..." } ] } }
备注
在 pd 与 pds 模式中,只需在配置文件中指定单个张量并行(TP)组中所有核心的配置即可。例如在 pds 模式下,Prefill任务与Decode任务的TP大小均为2,此时配置文件中 prefill 和 decode 字段中都只应包含核0与核1的配置。对于其中的收发目标和编号与数据标签号,按照该TP组内填写(例如在核0与核1组成的TP组中,核0的发送目的地即为核1)。
在 pd 模式下,所需求的最少核心总数量等于 stage * TP大小 。在 pds 模式下,核心总数量等于 (prefill_cores + decode_cores) * TP大小 。
示例1
对于一组收发核,发送方的数据标签需等同于接收方的数据标签。在示例中,需要等待核0与核1 worklist 中的原语执行完毕,发送给核2,使其接收到两份数据后,方可执行核2的第一个 worklist 中的原语。
{
"cores": [
{
"id": 0,
"worklist": [
"recv_cnt": 0,
"cast": [
{
"dest": 2,
"tag": 200
}
],
"prims": "some_comp_prims..."
]
},
{
"id": 1,
"worklist": [
"recv_cnt": 0,
"cast": [
{
"dest": 2,
"tag": 200
}
],
"prims": "some_comp_prims..."
]
},
{
"id": 2,
"worklist": [
"recv_cnt": 2,
"recv_tag": 200,
"cast": []
"prims": "some_comp_prims..."
]
}
]
}
示例2
对于使用 prim_copy 的原语,需要显式指定所有 worklist 及其中的 cast 字段。
{
{
"id": 0,
"worklist": [
"recv_cnt": 0,
"cast": [
{
"dest": 1
},
{
"dest": 2,
"tag": 300
}
],
"prims": "some_comp_prims..."
]
},
{
"id": 10,
"prim_copy": 0,
"worklist": [
"recv_cnt": 0,
"cast": [
{
"dest": 11,
"tag": 500
},
{
"dest": 12,
"tag": 600
}
]
]
}
}
示例3
需确保每一个计算原语的输入标签都曾经作为某个其他计算原语的输出标签出现过(相关内存读取与标签管理范式可参阅 内存访问与标签管理范式 )。在示例中, layernorm1_in 标签就曾作为 parse_input 原语的输出标签出现过。
{
"prims": [
{
"type": "parse_input",
"size": "BTC",
"sram_address": {
"indata": "input_label",
"outdata": "layernorm1_in"
}
},
{
"type": "Layernorm_f",
"B": "B",
"T": "T",
"C": "C",
"sram_address": {
"indata": "_layernorm1_in",
"outdata": "layernorm1_out"
}
}
]
}