跳到主要内容

10. G5 仿真器统计框架设计

概述

目标

为 G5 指令级仿真器设计一套层次化的统计信息收集框架,支持:

  • 性能调优/瓶颈定位: 每条指令的 stall 原因、流水线利用率、带宽争用等细粒度信息
  • 架构探索/对比: 不同芯片配置下的全局统计指标 (IPC、利用率、带宽效率)
  • 前端可视化: 统计数据存入数据库,通过图表展示

设计原则

  • 借鉴 gem5 Stats 体系: 层次化 StatGroup + 类型化统计量 (Scalar/Vector/Distribution/Formula)
  • 生产者-消费者解耦: 模块只管往自己的 StatGroup 写数据,框架负责收集和输出
  • 零框架修改扩展: 新增模块/统计量不需要改框架、adapter、数据库 schema
  • 按需实现: 先实现 Scalar + Vector 覆盖 90% 需求,Distribution 和 Formula 后续按需加

参考

仿真器统计机制特点
gem5Stats::Scalar/Vector/Distribution/Histogram/Formula, Stats::Group 层次化容器, ADD_STAT 宏, dump/reset最完善,工业标准
TPUPerf自定义 Profiler, per-instruction record (start_cycle, end_cycle, stall_cycle, bank_conflict_ratio)轻量,面向 TPU
SystemCsc_trace (VCD 波形), sc_report (诊断)无内建性能统计

统计类型体系

ScalarStat — 标量统计

单个数值:计数器、累加器、峰值、最终状态值。

@dataclass
class ScalarStat:
"""标量统计量"""
name: str
desc: str
value: float = 0.0

def inc(self, delta: float = 1.0) -> None:
"""累加"""
self.value += delta

def set_max(self, v: float) -> None:
"""取最大值"""
self.value = max(self.value, v)

使用示例:

total_cycles = ScalarStat("total_cycles", "TIU 总计算周期")
total_cycles.inc(14961)

cmd_count = ScalarStat("cmd_count", "执行的指令总数")
cmd_count.inc() # +1

peak_queue_depth = ScalarStat("peak_queue_depth", "事件队列最大深度")
peak_queue_depth.set_max(128)

VectorStat — 向量统计

按标签分组的一组数值,等价于 dict[str, float]

@dataclass
class VectorStat:
"""向量统计量 (按标签分组)"""
name: str
desc: str
bins: dict[str, float] = field(default_factory=dict)

def inc(self, label: str, delta: float = 1.0) -> None:
"""指定标签累加"""
self.bins[label] = self.bins.get(label, 0.0) + delta

使用示例:

# TIU 按精度分的周期数
cycles_by_precision = VectorStat("cycles_by_precision", "按精度分的计算周期")
cycles_by_precision.inc("BF16", 12000)
cycles_by_precision.inc("INT8", 3200)
# dump → {"BF16": 12000, "INT8": 3200}

# DMA 按方向分的数据量
bytes_by_direction = VectorStat("bytes_by_direction", "按方向分的搬运字节数")
bytes_by_direction.inc("DDR_TO_LMEM", 262144)
bytes_by_direction.inc("LMEM_TO_DDR", 131072)

# SDMA 按操作类型分的次数
cmd_by_type = VectorStat("cmd_by_type", "按操作类型分的指令数")
cmd_by_type.inc("TENSOR", 1)

DistributionStat — 分布统计 (Phase 2)

记录一个指标多次采样后的分布:min/max/mean + 分桶计数,用于画直方图。

@dataclass
class DistributionStat:
"""分布统计量 (直方图)"""
name: str
desc: str
bucket_edges: list[float] # 分桶边界
_buckets: list[int] = field(...) # 各桶计数
_min: float = float('inf')
_max: float = float('-inf')
_sum: float = 0.0
_count: int = 0

def sample(self, value: float) -> None:
"""记录一次采样"""
self._min = min(self._min, value)
self._max = max(self._max, value)
self._sum += value
self._count += 1
# 放入对应桶
...

使用示例:

# DMA 每次搬运的数据量分布
dma_transfer_size = DistributionStat(
"transfer_size", "DMA 单次搬运数据量分布",
bucket_edges=[1024, 4096, 65536, 262144] # 1KB, 4KB, 64KB, 256KB
)
dma_transfer_size.sample(32768) # 一次 32KB 搬运
dma_transfer_size.sample(256) # 一次 256B 搬运
# dump → {"min": 256, "max": 32768, "mean": 16512, "count": 2, "buckets": [1, 0, 1, 0]}

FormulaStat — 公式统计 (Phase 2)

引用其他统计量通过公式计算,不直接采集。

@dataclass
class FormulaStat:
"""公式统计量 (从其他统计量推导)"""
name: str
desc: str
formula: Callable[[], float] # 无参函数,引用闭包中的其他 Stat

@property
def value(self) -> float:
return self.formula()

使用示例:

cmd_count = ScalarStat("cmd_count", "指令总数")
total_cycles = ScalarStat("total_cycles", "总周期数")

ipc = FormulaStat(
"ipc", "Instructions Per Cycle",
formula=lambda: cmd_count.value / total_cycles.value if total_cycles.value > 0 else 0
)

类型总结

类型数据形态用途前端图表优先级
Scalar一个数计数/累加/峰值KPI 卡片、表格Phase 1
Vector一组数 (按标签)分类统计堆叠柱状图、饼图Phase 1
Distribution数值分布采样直方图直方图Phase 2
Formula推导值引用其他统计同 ScalarPhase 2

StatGroup — 层次化容器

设计

class StatGroup:
"""统计组 — 每个模块 (SimObject) 持有一个

支持层次化嵌套,dump() 递归收集所有子节点统计。
"""

def __init__(self, name: str, parent: StatGroup | None = None):
self.name = name
self._stats: dict[str, ScalarStat | VectorStat | DistributionStat | FormulaStat] = {}
self._children: dict[str, StatGroup] = {}
if parent is not None:
parent._add_child(self)

def scalar(self, name: str, desc: str) -> ScalarStat:
"""创建并注册一个 ScalarStat"""
stat = ScalarStat(name, desc)
self._stats[name] = stat
return stat

def vector(self, name: str, desc: str) -> VectorStat:
"""创建并注册一个 VectorStat"""
stat = VectorStat(name, desc)
self._stats[name] = stat
return stat

def _add_child(self, child: "StatGroup") -> None:
self._children[child.name] = child

def dump(self) -> dict[str, Any]:
"""递归收集所有统计 -> 扁平化 dict

key 格式: "parent.child.stat_name"
"""
result: dict[str, Any] = {}
for name, stat in self._stats.items():
if isinstance(stat, VectorStat):
result[f"{self.name}.{name}"] = dict(stat.bins)
elif isinstance(stat, FormulaStat):
result[f"{self.name}.{name}"] = stat.value
else:
result[f"{self.name}.{name}"] = stat.value
for child in self._children.values():
for key, value in child.dump().items():
result[f"{self.name}.{key}"] = value
return result

def reset(self) -> None:
"""重置所有统计 (用于分阶段统计,如 prefill/decode)"""
for stat in self._stats.values():
if isinstance(stat, ScalarStat):
stat.value = 0.0
elif isinstance(stat, VectorStat):
stat.bins.clear()
for child in self._children.values():
child.reset()

层次结构

kernel (SimKernel)
├── kernel.total_sim_time_ns Scalar 仿真总时长
├── kernel.total_events Scalar 事件总数

├── bus (BusModel)
│ ├── bus.total_transfers Scalar 总传输次数
│ ├── bus.total_bytes Scalar 总传输数据量
│ └── bus.avg_hop_count Formula 平均跳数

├── core0 (CoreSubsys)
│ ├── core0.total_instructions Scalar 指令总数
│ ├── core0.busy_ns Scalar 至少一个引擎忙的时间
│ ├── core0.idle_ns Scalar 全部引擎空闲的时间
│ │
│ ├── tiu (TIU Engine)
│ │ ├── core0.tiu.cmd_count Scalar TIU 指令数
│ │ ├── core0.tiu.compute_cycles Scalar 纯计算周期
│ │ ├── core0.tiu.init_cycles Scalar 流水线填充周期
│ │ ├── core0.tiu.total_flops Scalar 总浮点运算量
│ │ └── core0.tiu.cycles_by_prec Vector 按精度分的周期 {BF16: N, INT8: M}
│ │
│ ├── dma (DMA Engine)
│ │ ├── core0.dma.cmd_count Scalar DMA 指令数
│ │ ├── core0.dma.bytes_read Scalar DDR->LMEM 读取量
│ │ ├── core0.dma.bytes_write Scalar LMEM->DDR 写入量
│ │ ├── core0.dma.startup_ns Scalar 启动延迟总和
│ │ ├── core0.dma.transfer_ns Scalar 数据传输时间总和
│ │ └── core0.dma.bytes_by_dir Vector 按方向分的字节数
│ │
│ ├── sdma (SDMA Engine)
│ │ ├── core0.sdma.cmd_count Scalar SDMA 指令数
│ │ ├── core0.sdma.total_bytes Scalar 总传输字节数
│ │ ├── core0.sdma.bus_latency_ns Scalar Bus 路由延迟总和
│ │ ├── core0.sdma.transfer_ns Scalar 数据传输时间总和
│ │ ├── core0.sdma.hop_total Scalar 总跳数
│ │ └── core0.sdma.cmd_by_type Vector 按类型分 {TENSOR: N, GATHER: M}
│ │
│ └── hau (HAU Engine)
│ ├── core0.hau.cmd_count Scalar HAU 指令数
│ ├── core0.hau.total_elements Scalar 处理的总元素数
│ ├── core0.hau.total_cycles Scalar 总排序周期
│ └── core0.hau.cmd_by_op Vector 按操作分 {SORT: N, TOP_K: M}

├── core1 (CoreSubsys)
│ └── ... (同 core0 结构)

└── core63 (CoreSubsys)
└── ...

dump() 输出示例

仿真结束后调用 kernel.stats.dump():

{
# 全局
"kernel.total_sim_time_ns": 14961.10,
"kernel.total_events": 128,

# Bus
"kernel.bus.total_transfers": 4,
"kernel.bus.total_bytes": 524288,

# Core 0
"kernel.core0.total_instructions": 4,
"kernel.core0.busy_ns": 14500.0,
"kernel.core0.idle_ns": 461.1,

# Core 0 - TIU
"kernel.core0.tiu.cmd_count": 4,
"kernel.core0.tiu.compute_cycles": 12000,
"kernel.core0.tiu.init_cycles": 176,
"kernel.core0.tiu.total_flops": 67108864,
"kernel.core0.tiu.cycles_by_prec": {"BF16": 12000},

# Core 0 - DMA
"kernel.core0.dma.cmd_count": 3,
"kernel.core0.dma.bytes_read": 262144,
"kernel.core0.dma.bytes_write": 131072,
"kernel.core0.dma.startup_ns": 300.0,
"kernel.core0.dma.transfer_ns": 2900.0,
"kernel.core0.dma.bytes_by_dir": {"DDR_TO_LMEM": 262144, "LMEM_TO_DDR": 131072},

# Core 0 - SDMA
"kernel.core0.sdma.cmd_count": 1,
"kernel.core0.sdma.total_bytes": 32768,
"kernel.core0.sdma.bus_latency_ns": 45.0,
"kernel.core0.sdma.transfer_ns": 120.0,
"kernel.core0.sdma.hop_total": 1,
"kernel.core0.sdma.cmd_by_type": {"TENSOR": 1},

# Core 0 - HAU
"kernel.core0.hau.cmd_count": 1,
"kernel.core0.hau.total_elements": 256,
"kernel.core0.hau.total_cycles": 180,
"kernel.core0.hau.cmd_by_op": {"TOP_K": 1},

# Core 1 ... Core 63 同理
}

各模块统计指标详细定义

SimKernel 全局统计

统计量类型含义
total_sim_time_nsScalar仿真总时长 (最后事件时间 - 首事件时间)
total_eventsScalar事件队列处理的事件总数

BusModel (NoC) 统计

统计量类型含义
total_transfersScalar总传输次数
total_bytesScalar总传输数据量 (bytes)
hop_totalScalar所有传输的跳数之和
hop_distributionDistribution (Phase 2)跳数分布直方图

CoreSubsys 核级统计

统计量类型含义
total_instructionsScalar该核执行的指令总数 (TIU+DMA+SDMA+HAU)
busy_nsScalar至少一个引擎在工作的时间
idle_nsScalar所有引擎空闲的时间
tiu_busy_nsScalarTIU 引擎忙碌时间
dma_busy_nsScalarDMA 引擎忙碌时间
sdma_busy_nsScalarSDMA 引擎忙碌时间
hau_busy_nsScalarHAU 引擎忙碌时间
dependency_wait_nsScalar等待指令依赖满足的总时间
cmd_count_by_engineVector按引擎分的指令数 {TIU: N, DMA: M, ...}

Phase 2 扩展:

统计量类型含义
tiu_utilizationFormulaTIU 利用率 = tiu_busy_ns / total_sim_time
dma_utilizationFormulaDMA 利用率
ipcFormula指令吞吐 = total_instructions / total_cycles
stall_by_reasonVector按原因分的 stall 时间 {dependency, resource, sync}

TIU 引擎统计

统计量类型含义
cmd_countScalarTIU 指令数
compute_cyclesScalar纯计算周期总和
init_cyclesScalar流水线填充周期总和
total_flopsScalar总浮点运算量
cycles_by_precVector按精度分的周期 {BF16: N, INT8: M, FP32: K}
cmd_by_opVector按操作分的指令数 {MM2_NN: N, CONV: M, SFU: K}

计算方式 (对标 TPUPerf tiuImpl.cc):

MM2_NN:
init_cycles = 44
ch_per_cyc = cube_k (INT8) | cube_k/2 (BF16) | cube_k/4 (FP32)
compute_cycles = ceil(M/lane_num) * ceil(N/eu_num) * ceil(K/ch_per_cyc)
flops = 2 * M * N * K

Phase 2 扩展:

统计量类型含义
bank_conflictsScalarLMEM Bank 冲突次数
init_overhead_ratioFormula流水线填充占比 = init_cycles / total_cycles
achieved_flops_per_cycleFormula实际算力/周期

DMA 引擎统计

统计量类型含义
cmd_countScalarDMA 指令数
bytes_readScalarDDR -> LMEM 读取字节数
bytes_writeScalarLMEM -> DDR 写入字节数
startup_nsScalar启动延迟总和 (每次搬运的固定开销)
transfer_nsScalar数据传输时间总和
bytes_by_dirVector按方向分的字节数 {DDR_TO_LMEM, LMEM_TO_DDR}

Phase 2 扩展:

统计量类型含义
transfer_size_distDistribution单次搬运数据量分布
startup_overhead_ratioFormula启动开销占比
effective_bandwidthFormula有效带宽 = total_bytes / total_transfer_ns

SDMA 引擎统计

统计量类型含义
cmd_countScalarSDMA 指令数
total_bytesScalar总传输字节数
bus_latency_nsScalarBus 路由延迟总和 (Manhattan 距离 * hop 延迟)
transfer_nsScalar数据传输时间总和
hop_totalScalar总跳数
cmd_by_typeVector按操作类型分 {TENSOR, GATHER, SCATTER}

HAU 引擎统计

统计量类型含义
cmd_countScalarHAU 指令数
total_elementsScalar处理的总元素数
total_cyclesScalar总排序/Top-K 周期
cmd_by_opVector按操作分 {SORT, TOP_K, UNIQUE}

集成方式

模块集成 (各引擎如何使用)

# CoreSubsys.__init__() 中:
class CoreSubsys:
def __init__(self, kernel: SimKernel, chip: ChipSpecImpl, core_id: int, ...):
self.stats = StatGroup(f"core{core_id}", parent=kernel.stats)
self._tiu_stats = StatGroup("tiu", parent=self.stats)
self._dma_stats = StatGroup("dma", parent=self.stats)
self._sdma_stats = StatGroup("sdma", parent=self.stats)
self._hau_stats = StatGroup("hau", parent=self.stats)

# 核级统计
self._stat_total_instructions = self.stats.scalar("total_instructions", "指令总数")
self._stat_busy_ns = self.stats.scalar("busy_ns", "引擎忙碌时间")

# TIU 统计
self._stat_tiu_cmd = self._tiu_stats.scalar("cmd_count", "TIU 指令数")
self._stat_tiu_compute = self._tiu_stats.scalar("compute_cycles", "计算周期")
self._stat_tiu_init = self._tiu_stats.scalar("init_cycles", "初始化周期")
self._stat_tiu_flops = self._tiu_stats.scalar("total_flops", "总 FLOPs")
self._stat_tiu_by_prec = self._tiu_stats.vector("cycles_by_prec", "按精度分的周期")
# ... DMA, SDMA, HAU 类似
# 引擎执行时累加统计:
def _execute_tiu(self, cmd: TIUCommand) -> None:
result = calc_tiu_latency(cmd, self._chip)

# 记录 SimRecord (不变)
record = SimRecord(engine="TIU", cmd_id=cmd.cmd_id, ...)
self._records.append(record)

# 累加统计 (新增)
self._stat_tiu_cmd.inc()
self._stat_tiu_compute.inc(result.compute_cycles)
self._stat_tiu_init.inc(result.init_cycles)
self._stat_tiu_flops.inc(result.flops)
self._stat_tiu_by_prec.inc(cmd.precision, result.total_cycles)
self._stat_total_instructions.inc()

SingleChipSim 收集

class SingleChipSim:
def simulate(self, program: CoreProgram) -> list[SimRecord]:
kernel = SimKernel()
# SimKernel 自动创建顶层 StatGroup
# ... 创建 cores, 运行仿真 ...

kernel.run()

# 收集统计
self._last_stats = kernel.stats.dump()
return all_records

def get_stats(self) -> dict[str, Any]:
"""获取最近一次仿真的统计数据"""
return self._last_stats

数据流: 仿真 -> 数据库 -> 前端

SingleChipSim.simulate()

kernel.stats.dump() → dict[str, Any]

G5ResultAdapter.convert()
↓ 将 stats dict 存入 EngineResult.trace_meta["stats"]
EngineResult

ReportingEngine → PerformanceReport

存入数据库 EvaluationResult.full_result["stats"]

前端 API 读取 → 图表组件渲染

EngineResult 集成

# G5ResultAdapter.convert() 中:
def convert(self, records: list[SimRecord], stats: dict[str, Any] | None = None) -> EngineResult:
step_metrics = self._build_step_metrics(records)
aggregates = self._build_aggregates(step_metrics)
return EngineResult(
step_metrics=step_metrics,
aggregates=aggregates,
granularity="instruction",
trace_meta={
"engine": "g5",
"stats": stats or {}, # 统计数据存入 trace_meta
},
)

扩展性分析

新增模块统计

以未来的 IFE (指令调度引擎) 为例,只需在模块内部创建 StatGroup:

class IFEEngine:
def __init__(self, core_stats: StatGroup):
self.stats = StatGroup("ife", parent=core_stats)
self.stats.scalar("fifo_full_stalls", "FIFO 满停顿次数")
self.stats.scalar("fifo_peak_depth", "FIFO 最大深度")
self.stats.scalar("issue_count", "发射指令数")

不需要修改框架、adapter、数据库 schema。dump() 自动包含新字段。

新增统计量

在任意模块中调用 stats.scalar()stats.vector() 即可:

# 一行代码新增一个统计量
self._stat_new_metric = self._tiu_stats.scalar("new_metric", "新增指标描述")

改动对比

操作gem5 式 StatGroupSimRecord 扩展方案
新增模块统计模块内 3-5 行改 4 个文件
新增一个统计量1 行 add_stat()改 SimRecord + Adapter
删除统计量删 1 行改 4 个文件
数据库 schema不变 (JSON)可能需要迁移
前端适配按需读 key每次都要改类型定义

前端展示规划

图表类型

图表数据来源用途
引擎利用率柱状图core{N}.{engine}_busy_ns / total_sim_time各引擎资源使用率对比
Cycle 分解饼图tiu.init_cycles / tiu.compute_cycles流水线效率分析
多核热力图core{0..63}.tiu_busy_ns各核负载均衡度一览
通信分析sdma.bus_latency_ns / sdma.transfer_nsBus 路由 vs 传输时间占比
DMA 方向堆叠图dma.bytes_by_dir读写带宽对比
精度分布饼图tiu.cycles_by_prec不同精度的计算量占比

数据库存储

统计数据作为 JSON 存入 EvaluationResult.full_result["stats"],无需修改数据库 schema。

前端通过 /api/evaluation/experiments/{id}/results 获取完整结果,从 full_result.stats 读取统计数据。


实现计划

Phase 1: 基础框架 + Scalar/Vector (优先)

步骤内容文件
1实现 ScalarStat, VectorStat, StatGroupkernel/stats.py (新增)
2SimKernel 集成顶层 StatGroupkernel/sim_kernel.py
3CoreSubsys 注册核级 + 引擎级统计chip/core_subsys.py
4各引擎执行逻辑中累加统计chip/core_subsys.py
5BusModel 注册 Bus 统计chip/bus.py
6SingleChipSim 收集 dump 结果top/single_chip.py
7G5ResultAdapter 透传 statsadapter.py
8单元测试tests/test_g5_stats.py

Phase 2: Distribution + Formula + 前端 (后续)

步骤内容
1实现 DistributionStat, FormulaStat
2各引擎添加分布统计 (DMA transfer size 等)
3CoreSubsys 添加公式统计 (utilization, IPC)
4前端统计图表组件
5Stall 分类统计 (dependency / resource / sync)

文件结构

backend/perf_model/L4_evaluation/g5/
├── kernel/
│ ├── sim_kernel.py # 集成 StatGroup 根节点
│ ├── sim_record.py # 不变
│ └── stats.py # [新增] ScalarStat, VectorStat, StatGroup
├── chip/
│ ├── core_subsys.py # 注册核级 + 引擎级统计,执行时累加
│ └── bus.py # 注册 Bus 统计
├── top/
│ └── single_chip.py # 收集 dump 结果
├── adapter.py # 透传 stats 到 EngineResult
├── tiu.py # 延迟计算返回 cycles 分解 (不变)
├── dma.py # 同上
├── sdma.py # 同上
└── hau.py # 同上