# Memory Parser 开发者指南 本文面向 Memory Parser 的开发者,介绍其设计思路、内部接口约定以及扩展开发指南。用户侧快速入门见 [Memory Parser Quickstart](../overview/memory_parser_quickstart.md)。 ## 1. 设计概述 ### 1.1 背景与目标 RL-Insight 已有时序分析 Parser(`mstx` / `torch`),基于 `EventRow` 数据模型输出算子级时间线事件。Memory Parser 新增**内存分析**能力,从 Ascend Profiler 输出的内存相关文件中提取内存分配信息,为 RL 训练的内存瓶颈分析提供数据支撑。 **核心目标**:在每条内存申请记录中补全以下信息: - 调用栈(Call Stack)—— 定位内存申请源头 - 内存申请大小(Size)—— 量化内存消耗 - 内存申请时间(Allocation Time)—— 时序关联 - 内存占用时长(Duration)—— 生命周期分析 ### 1.2 数据流 ``` InputData (ASCEND_MEMORY) │ ├── DataChecker 校验 │ ▼ MemoryClusterParser │ ├── allocate_prof_data() ← 扫描目录,构建 DataMap │ ├── mapper_func() ← 多进程并行调度 │ └── parse_analysis_data() │ ├── _build_call_stack_index() ← 流式解析 trace_view.json │ └── _parse_operator_memory() ← 解析 operator_memory.csv │ └── _match_call_stack() ← 二分查找调用栈 │ ├── reducer_func() ← 汇总排序 │ ▼ OutputData (SUMMARY_MEMORY_EVENT) → pd.DataFrame ``` ### 1.3 类结构 ``` BaseClusterParser (rl_insight/parser/parser.py) └── MemoryClusterParser (rl_insight/parser/memory_parser.py) ├── input_type = DataEnum.ASCEND_MEMORY ├── allocate_prof_data() → 扫描目录,构建 DataMap ├── parse_analysis_data() → 主解析流程 ├── _build_call_stack_index() → 流式解析 trace_view.json,构建调用栈索引 ├── _parse_operator_memory() → 解析 operator_memory.csv,输出 dict[str, Any] ├── _match_call_stack() → name + ts 匹配调用栈 ├── _get_data_map() → 构建 (role, rank_id) → [path] 映射 ├── _get_rank_path_with_role() → 生成 DataMap 列表 ├── _get_profiler_data_path() → 拼接 ASCEND_PROFILER_OUTPUT 路径 ├── _get_rank_id() → 从 profiler_info_*.json 提取 rank_id ├── _get_task_role() → 从 profiler_metadata.json 提取 role └── _extract_timestamp_key() → 提取目录名中的时间戳排序键 ``` --- ## 2. 内部接口 ### 2.1 注册与数据类型 ```python @register_cluster_parser("memory") class MemoryClusterParser(BaseClusterParser): input_type: DataEnum = DataEnum.ASCEND_MEMORY ``` | 属性 | 值 | 说明 | |------|-----|------| | 注册名 | `"memory"` | CLI `--profiler-type memory` | | `input_type` | `DataEnum.ASCEND_MEMORY` | 输入数据类型 | | 输出类型 | `DataEnum.SUMMARY_MEMORY_EVENT` | Parser 输出 / Visualizer 输入 | `DataEnum` 新增值(定义在 `rl_insight/data/data_checker.py`): ```python class DataEnum(Enum): ASCEND_MEMORY = "ascend_memory" # Memory Parser 输入 SUMMARY_MEMORY_EVENT = "summary_memory_event" # Memory Parser 输出 ``` ### 2.2 MemoryEventRow 定义在 `rl_insight/utils/schema.py`: ```python class MemoryEventRow(TypedDict): name: str # 算子名称 role: str # RL 角色 rank_id: int # 进程 rank call_stack: str # 完整调用栈(";\r\n" 分隔) call_stack_top: str # 调用栈顶层入口 size_kb: float # 内存大小(KB),正数=申请,负数=释放 start_time_ms: float # 内存申请时间(ms),与 BaseClusterParser.reducer_func 的排序键对齐 duration_ms: float # 内存占用时长(ms),0 表示未释放 total_allocated_mb: float # 申请时刻累计已分配内存(MB) total_reserved_mb: float # 申请时刻累计预留内存(MB) total_active_mb: float # 申请时刻累计活跃内存(MB) device_type: str # 设备类型 ``` **设计决策**: - `size_kb`:正数表示内存申请,负数表示内存释放,与 `operator_memory.csv` 中的 `Size(KB)` 语义一致 - `duration_ms`:若 `Duration(us)` 有值则转换,无值则为 `0.0`(表示内存尚未释放) - `call_stack_top`:取调用栈第一行(用户代码入口),便于快速定位,无需解析完整调用栈 - 时间单位统一为毫秒(ms),与现有 `EventRow` 保持一致 ### 2.3 allocate_prof_data() 复用 MstxClusterParser 的目录扫描逻辑: 1. 遍历 `input_path`,找到所有 `_