Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_multicore / modules / mega_mhc / graph.py: 95%

176 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 05:03 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""Pure AIC/AIV token-tile task graph for shifted Single-Pass HyperMegaMhc.""" 

16 

17from __future__ import annotations 

18 

19from dataclasses import dataclass 

20from typing import Callable 

21 

22from hyper_multicore.scheduler.config import ( 

23 ATOMIC_ADD_VALUE_LEN, 

24 EVENT_INVALID_ID, 

25 MIN_EVENT_CAPACITY, 

26 RuntimeConfigC, 

27 TaskAiCoreType, 

28 TaskDescC, 

29 TaskSplitValue, 

30 TaskType, 

31) 

32from hyper_multicore.scheduler.graph import ( 

33 ComputeGraph, 

34 OperatorNode, 

35 OpType, 

36 SplitSpec, 

37 TensorSpec, 

38) 

39from hyper_multicore.tasks.task_base import FillConfig 

40 

41 

42DEFAULT_TOKEN_TILE = 32 

43_STAGE1_BS_FACTOR = 32 

44POST_DONE_EVENT = 0 

45NORM_EVENT_BASE = 1 

46 

47 

48@dataclass(frozen=True) 

49class MegaMhcEventLayout: 

50 """Contiguous event ranges for one shape-bound pipeline.""" 

51 

52 post_done: int 

53 norm_base: int 

54 input_mix_base: int 

55 projection_base: int 

56 final: int 

57 event_count: int 

58 

59 

60def build_event_layout(tile_count: int) -> MegaMhcEventLayout: 

61 """Return event IDs for a six-stage, per-token-tile pipeline. 

62 

63 Args: 

64 tile_count: Number of logical token tiles. 

65 """ 

66 if tile_count <= 0: 

67 raise ValueError(f"tile_count must be positive, got {tile_count}.") 

68 input_mix_base = NORM_EVENT_BASE + tile_count 

69 projection_base = input_mix_base + tile_count 

70 final = projection_base + tile_count 

71 event_count = final + ATOMIC_ADD_VALUE_LEN 

72 if event_count > MIN_EVENT_CAPACITY: 

73 raise ValueError( 

74 f"HyperMegaMhc needs {event_count} events for {tile_count} token tiles, " 

75 f"but RuntimeConfig supports only {MIN_EVENT_CAPACITY}." 

76 ) 

77 return MegaMhcEventLayout( 

78 post_done=POST_DONE_EVENT, 

79 norm_base=NORM_EVENT_BASE, 

80 input_mix_base=input_mix_base, 

81 projection_base=projection_base, 

82 final=final, 

83 event_count=event_count, 

84 ) 

85 

86 

87def resolve_token_tile(token_count: int, requested_tile: int, num_cube_cores: int) -> int: 

88 """Choose an aligned tile that fits event and native Stage1 workspace limits. 

89 

90 Args: 

91 token_count: Total flattened token count. 

92 requested_tile: Preferred token count per task. 

93 num_cube_cores: Number of Cube cores. 

94 """ 

95 if token_count <= 0 or requested_tile <= 0 or num_cube_cores <= 0: 

96 raise ValueError( 

97 "token_count, requested_tile, and num_cube_cores must all be positive, " 

98 f"got {(token_count, requested_tile, num_cube_cores)}." 

99 ) 

100 max_tiles = (MIN_EVENT_CAPACITY - NORM_EVENT_BASE - ATOMIC_ADD_VALUE_LEN) // 3 

101 minimum_tile = (token_count + max_tiles - 1) // max_tiles 

102 minimum_tile = (minimum_tile + _STAGE1_BS_FACTOR - 1) // _STAGE1_BS_FACTOR * _STAGE1_BS_FACTOR 

103 token_tile = max(requested_tile, minimum_tile) 

104 token_tile = (token_tile + _STAGE1_BS_FACTOR - 1) // _STAGE1_BS_FACTOR * _STAGE1_BS_FACTOR 

105 stage1_workspace_rows = 4 * num_cube_cores * _STAGE1_BS_FACTOR 

106 if token_tile > stage1_workspace_rows: 

107 raise ValueError( 

108 f"token tile {token_tile} exceeds the native Stage1 X-cast workspace capacity " 

109 f"of {stage1_workspace_rows} rows." 

110 ) 

111 return token_tile 

112 

113 

114def _append_task( 

115 cfg: RuntimeConfigC, 

116 tsv: TaskSplitValue, 

117 *, 

118 task_type: TaskType, 

119 core_type: TaskAiCoreType, 

120 dependent_event: int, 

121 trigger_event: int, 

122 task_index: int, 

123 task_num: int, 

124 split_value: int, 

125) -> None: 

126 """Append one task to the matching static AIC or AIV schedule.""" 

127 task = TaskDescC() 

128 task.task_type = task_type 

129 task.task_aicore_type = core_type 

130 task.dependent_event = dependent_event 

131 task.trigger_event = trigger_event 

132 task.task_index = task_index 

133 task.task_split_num = task_num 

134 task.task_split_value = split_value 

135 task_id = tsv.pre_task_num 

136 cfg.all_tasks[task_id] = task 

137 if core_type == TaskAiCoreType.TASK_AICORE_CUBE: 

138 cfg.cube_task_indices[tsv.pre_cube_task_num] = task_id 

139 cfg.task_index_num[0] += 1 

140 tsv.pre_cube_task_num += 1 

141 elif core_type == TaskAiCoreType.TASK_AICORE_VECTOR: 

142 cfg.vector_task_indices[tsv.pre_vector_task_num] = task_id 

143 cfg.task_index_num[1] += 1 

144 tsv.pre_vector_task_num += 1 

145 else: 

146 raise ValueError(f"HyperMegaMhc task must be pure AIC or AIV, got {core_type!r}.") 

147 tsv.pre_task_num += 1 

148 

149 

150_AIV_PIPELINE_TYPES = ( 

151 TaskType.TASK_MHC_NORM_CAST, 

152 TaskType.TASK_MHC_INPUT_MIX, 

153 TaskType.TASK_RMS_NORM, 

154 TaskType.TASK_MHC_MAPPING, 

155) 

156 

157 

158def _reorder_vector_task_waves( 

159 cfg: RuntimeConfigC, 

160 tsv: TaskSplitValue, 

161 tile_count: int, 

162 wave_size: int, 

163) -> None: 

164 """Fill Projection gaps with NormCast/InputMix/RMSNorm waves before Mapping.""" 

165 if wave_size <= 0: 

166 raise ValueError(f"wave_size must be positive, got {wave_size}.") 

167 post_task_ids = [] 

168 pipeline_task_ids = {task_type: [] for task_type in _AIV_PIPELINE_TYPES} 

169 for schedule_index in range(tsv.pre_vector_task_num): 

170 task_id = cfg.vector_task_indices[schedule_index] 

171 task_type = TaskType(cfg.all_tasks[task_id].task_type) 

172 if task_type == TaskType.TASK_MHC_POST: 

173 post_task_ids.append(task_id) 

174 elif task_type in pipeline_task_ids: 

175 pipeline_task_ids[task_type].append(task_id) 

176 else: 

177 raise ValueError(f"Unexpected AIV task type in HyperMegaMhc schedule: {task_type!r}.") 

178 

179 if len(post_task_ids) != tile_count: 

180 raise ValueError(f"Expected {tile_count} MhcPost tasks, got {len(post_task_ids)}.") 

181 for task_type, task_ids in pipeline_task_ids.items(): 

182 if len(task_ids) != tile_count: 

183 raise ValueError(f"Expected {tile_count} {task_type.name} tasks, got {len(task_ids)}.") 

184 

185 reordered_task_ids = list(post_task_ids) 

186 for wave_start in range(0, tile_count, wave_size): 

187 wave_end = min(wave_start + wave_size, tile_count) 

188 for task_type in _AIV_PIPELINE_TYPES[:3]: 

189 reordered_task_ids.extend(pipeline_task_ids[task_type][wave_start:wave_end]) 

190 reordered_task_ids.extend(pipeline_task_ids[TaskType.TASK_MHC_MAPPING]) 

191 for schedule_index, task_id in enumerate(reordered_task_ids): 

192 cfg.vector_task_indices[schedule_index] = task_id 

193 

194 

195@dataclass(frozen=True) 

196class _PostFillConfig(FillConfig): 

197 """Emit token-local residual update tasks on AIV.""" 

198 

199 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

200 """Append the MhcPost task slice to the runtime configuration. 

201 

202 Args: 

203 cfg: Runtime configuration receiving tasks. 

204 op: Scheduled operator node. 

205 tsv: Mutable task split counters. 

206 """ 

207 layout = build_event_layout(op.task_num) 

208 cfg.all_event_num_triggers[layout.post_done] = op.task_num 

209 for task_index in range(op.task_num): 

210 _append_task( 

211 cfg, 

212 tsv, 

213 task_type=TaskType.TASK_MHC_POST, 

214 core_type=TaskAiCoreType.TASK_AICORE_VECTOR, 

215 dependent_event=EVENT_INVALID_ID, 

216 trigger_event=layout.post_done, 

217 task_index=task_index, 

218 task_num=op.task_num, 

219 split_value=op.split_value, 

220 ) 

221 

222 

223@dataclass(frozen=True) 

224class _NormCastFillConfig(FillConfig): 

225 """Emit AIV normalization/cast tasks with safe X-cast ring-slot reuse.""" 

226 

227 ring_slots: int 

228 

229 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

230 """Append NormCast tasks and ring-buffer reuse dependencies. 

231 

232 Args: 

233 cfg: Runtime configuration receiving tasks. 

234 op: Scheduled operator node. 

235 tsv: Mutable task split counters. 

236 """ 

237 layout = build_event_layout(op.task_num) 

238 for task_index in range(op.task_num): 

239 dependent_event = layout.post_done 

240 if task_index >= self.ring_slots: 

241 dependent_event = layout.projection_base + task_index - self.ring_slots 

242 trigger_event = layout.norm_base + task_index 

243 cfg.all_event_num_triggers[trigger_event] = 1 

244 _append_task( 

245 cfg, 

246 tsv, 

247 task_type=TaskType.TASK_MHC_NORM_CAST, 

248 core_type=TaskAiCoreType.TASK_AICORE_VECTOR, 

249 dependent_event=dependent_event, 

250 trigger_event=trigger_event, 

251 task_index=task_index, 

252 task_num=op.task_num, 

253 split_value=op.split_value, 

254 ) 

255 

256 

257@dataclass(frozen=True) 

258class _ProjectionFillConfig(FillConfig): 

259 """Emit projection tasks on AIC.""" 

260 

261 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

262 """Append token-tiled AIC projection tasks. 

263 

264 Args: 

265 cfg: Runtime configuration receiving tasks. 

266 op: Scheduled operator node. 

267 tsv: Mutable task split counters. 

268 """ 

269 layout = build_event_layout(op.task_num) 

270 for task_index in range(op.task_num): 

271 trigger_event = layout.projection_base + task_index 

272 cfg.all_event_num_triggers[trigger_event] = 1 

273 _append_task( 

274 cfg, 

275 tsv, 

276 task_type=TaskType.TASK_MHC_PROJECTION, 

277 core_type=TaskAiCoreType.TASK_AICORE_CUBE, 

278 dependent_event=layout.norm_base + task_index, 

279 trigger_event=trigger_event, 

280 task_index=task_index, 

281 task_num=op.task_num, 

282 split_value=op.split_value, 

283 ) 

284 

285 

286@dataclass(frozen=True) 

287class _MappingFillConfig(FillConfig): 

288 """Emit per-tile mapping/Sinkhorn tasks after the corresponding Projection.""" 

289 

290 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

291 """Append mapping and Sinkhorn tasks after their projection tiles. 

292 

293 Args: 

294 cfg: Runtime configuration receiving tasks. 

295 op: Scheduled operator node. 

296 tsv: Mutable task split counters. 

297 """ 

298 layout = build_event_layout(op.task_num) 

299 cfg.all_event_num_triggers[layout.final] = 2 * op.task_num 

300 for task_index in range(op.task_num): 

301 _append_task( 

302 cfg, 

303 tsv, 

304 task_type=TaskType.TASK_MHC_MAPPING, 

305 core_type=TaskAiCoreType.TASK_AICORE_VECTOR, 

306 dependent_event=layout.projection_base + task_index, 

307 trigger_event=layout.final, 

308 task_index=task_index, 

309 task_num=op.task_num, 

310 split_value=op.split_value, 

311 ) 

312 

313 

314@dataclass(frozen=True) 

315class _InputMixFillConfig(FillConfig): 

316 """Emit input-mix tasks after NormCast to fill the Projection AIV gap.""" 

317 

318 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

319 """Append shifted input-mix tasks after the matching NormCast tiles. 

320 

321 Args: 

322 cfg: Runtime configuration receiving tasks. 

323 op: Scheduled operator node. 

324 tsv: Mutable task split counters. 

325 """ 

326 layout = build_event_layout(op.task_num) 

327 for task_index in range(op.task_num): 

328 trigger_event = layout.input_mix_base + task_index 

329 cfg.all_event_num_triggers[trigger_event] = 1 

330 _append_task( 

331 cfg, 

332 tsv, 

333 task_type=TaskType.TASK_MHC_INPUT_MIX, 

334 core_type=TaskAiCoreType.TASK_AICORE_VECTOR, 

335 dependent_event=layout.norm_base + task_index, 

336 trigger_event=trigger_event, 

337 task_index=task_index, 

338 task_num=op.task_num, 

339 split_value=op.split_value, 

340 ) 

341 

342 

343@dataclass(frozen=True) 

344class _RmsNormFillConfig(FillConfig): 

345 """Emit shifted input RMSNorm tasks on AIV.""" 

346 

347 wave_size: int 

348 

349 def fill(self, cfg: RuntimeConfigC, op: OperatorNode, tsv: TaskSplitValue) -> None: 

350 """Append shifted RMSNorm tasks and arrange the AIV task waves. 

351 

352 Args: 

353 cfg: Runtime configuration receiving tasks. 

354 op: Scheduled operator node. 

355 tsv: Mutable task split counters. 

356 """ 

357 layout = build_event_layout(op.task_num) 

358 for task_index in range(op.task_num): 

359 _append_task( 

360 cfg, 

361 tsv, 

362 task_type=TaskType.TASK_RMS_NORM, 

363 core_type=TaskAiCoreType.TASK_AICORE_VECTOR, 

364 dependent_event=layout.input_mix_base + task_index, 

365 trigger_event=layout.final, 

366 task_index=task_index, 

367 task_num=op.task_num, 

368 split_value=op.split_value, 

369 ) 

370 _reorder_vector_task_waves(cfg, tsv, op.task_num, self.wave_size) 

371 

372 

373def _assemble_forward_graph( 

374 topology: TaskSplitValue, 

375 post: OperatorNode, 

376 norm_cast: OperatorNode, 

377 projection: OperatorNode, 

378 input_mix: OperatorNode, 

379 rms_norm: OperatorNode, 

380 mapping: OperatorNode, 

381) -> ComputeGraph: 

382 """Connect the forward stages and resolve their task splits.""" 

383 graph = ComputeGraph() 

384 for operator in (post, norm_cast, projection, input_mix, rms_norm, mapping): 

385 graph.add_op(operator) 

386 graph.add_edge(post, norm_cast).add_edge(norm_cast, projection).add_edge(projection, mapping) 

387 graph.add_edge(norm_cast, input_mix).add_edge(input_mix, rms_norm) 

388 graph.propagate_splits(topology) 

389 return graph 

390 

391 

392def _make_post_and_norm_cast( 

393 residual: TensorSpec, 

394 updated_residual: TensorSpec, 

395 normalized: TensorSpec, 

396 token_tile: int, 

397 task_num_fn: Callable[[TaskSplitValue], int], 

398 ring_slots: int, 

399) -> tuple[OperatorNode, OperatorNode]: 

400 """Create the two opening AIV stages.""" 

401 post = OperatorNode( 

402 name="mhc_post", 

403 op_type=OpType.MHC_POST, 

404 inputs=[residual], 

405 outputs=[updated_residual], 

406 param_positions=[1, 12], 

407 split_value=token_tile, 

408 split_spec=SplitSpec(None, task_num_fn, [0]), 

409 tiling_position=18, 

410 fill_config=_PostFillConfig(), 

411 diagnostic_name="MhcPost", 

412 ) 

413 norm_cast = OperatorNode( 

414 name="mhc_norm_cast", 

415 op_type=OpType.MHC_NORM_CAST, 

416 inputs=[updated_residual], 

417 outputs=[normalized], 

418 param_positions=[12, 17], 

419 split_value=token_tile, 

420 split_spec=SplitSpec(None, task_num_fn, [0]), 

421 tiling_position=18, 

422 fill_config=_NormCastFillConfig(ring_slots), 

423 diagnostic_name="MhcNormCast", 

424 ) 

425 return post, norm_cast 

426 

427 

428def build_mega_mhc_graph( 

429 token_count: int, 

430 hidden_size: int, 

431 num_cube_cores: int, 

432 token_tile: int = DEFAULT_TOKEN_TILE, 

433) -> tuple[ComputeGraph, TaskSplitValue]: 

434 """Build the overlapped Projection and shifted InputMix/RMSNorm token pipeline. 

435 

436 Args: 

437 token_count: Total flattened token count. 

438 hidden_size: Hidden dimension. 

439 num_cube_cores: Number of Cube cores. 

440 token_tile: Preferred token count per task. 

441 """ 

442 if token_count <= 0 or hidden_size <= 0 or num_cube_cores <= 0: 

443 raise ValueError( 

444 "token_count, hidden_size, and num_cube_cores must all be positive, " 

445 f"got {(token_count, hidden_size, num_cube_cores)}." 

446 ) 

447 token_tile = resolve_token_tile(token_count, token_tile, num_cube_cores) 

448 tile_count = (token_count + token_tile - 1) // token_tile 

449 build_event_layout(tile_count) 

450 stage1_workspace_rows = 4 * num_cube_cores * _STAGE1_BS_FACTOR 

451 ring_slots = min(tile_count, stage1_workspace_rows // token_tile) 

452 

453 topology = TaskSplitValue(tp=1, ep=1, seq_size=token_count, all_expert_num=1, top_k=1) 

454 residual = TensorSpec("residual", [token_count, 4, hidden_size], 1) 

455 updated_residual = TensorSpec("updated_residual", [token_count, 4, hidden_size], 12) 

456 normalized = TensorSpec("normalized", [token_tile, 4, hidden_size], 17, dtype_size=4) 

457 projection = TensorSpec("projection", [token_count, 24], 17, dtype_size=4) 

458 mappings = TensorSpec("mappings", [token_count, 24], 13, dtype_size=4) 

459 mixed_input = TensorSpec("mixed_input", [token_count, hidden_size], 17) 

460 block_input = TensorSpec("block_input", [token_count, hidden_size], 16) 

461 

462 def task_num_fn(tsv: TaskSplitValue) -> int: 

463 """Return the token tile count shared by all forward stages. 

464 

465 Args: 

466 tsv: Unused topology value required by the scheduler callback. 

467 """ 

468 del tsv 

469 return tile_count 

470 

471 post, norm_cast = _make_post_and_norm_cast( 

472 residual, 

473 updated_residual, 

474 normalized, 

475 token_tile, 

476 task_num_fn, 

477 ring_slots, 

478 ) 

479 projection_op = OperatorNode( 

480 name="mhc_projection", 

481 op_type=OpType.MHC_PROJECTION, 

482 inputs=[normalized], 

483 outputs=[projection], 

484 param_positions=[17], 

485 split_value=token_tile, 

486 split_spec=SplitSpec(None, task_num_fn, [0]), 

487 tiling_position=18, 

488 fill_config=_ProjectionFillConfig(), 

489 diagnostic_name="MhcProjection", 

490 ) 

491 input_mix = OperatorNode( 

492 name="mhc_input_mix", 

493 op_type=OpType.MHC_INPUT_MIX, 

494 inputs=[updated_residual], 

495 outputs=[mixed_input], 

496 param_positions=[12, 2, 17], 

497 split_value=token_tile, 

498 split_spec=SplitSpec(None, task_num_fn, [0]), 

499 tiling_position=18, 

500 fill_config=_InputMixFillConfig(), 

501 diagnostic_name="MhcInputMix", 

502 ) 

503 rms_norm = OperatorNode( 

504 name="mhc_rms_norm", 

505 op_type=OpType.RMS_NORM, 

506 inputs=[mixed_input], 

507 outputs=[block_input], 

508 param_positions=[16], 

509 split_value=token_tile, 

510 split_spec=SplitSpec(None, task_num_fn, [0]), 

511 tiling_position=18, 

512 fill_config=_RmsNormFillConfig(2 * num_cube_cores), 

513 diagnostic_name="ShiftedRmsNorm", 

514 ) 

515 mapping = OperatorNode( 

516 name="mhc_mapping", 

517 op_type=OpType.MHC_MAPPING, 

518 inputs=[projection, updated_residual], 

519 outputs=[mappings], 

520 param_positions=[17, 12, 13], 

521 split_value=token_tile, 

522 split_spec=SplitSpec(None, task_num_fn, [0]), 

523 tiling_position=18, 

524 fill_config=_MappingFillConfig(), 

525 diagnostic_name="MhcMapping/Sinkhorn", 

526 ) 

527 graph = _assemble_forward_graph( 

528 topology, post, norm_cast, projection_op, input_mix, rms_norm, mapping 

529 ) 

530 return graph, topology