Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_multicore / modules / mega_moe / module.py: 89%

277 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"""Model-facing Torch MegaMoe expert module.""" 

16 

17from __future__ import annotations 

18 

19__all__ = ["MegaMoeExperts"] 

20 

21import math 

22import struct 

23from collections.abc import Mapping 

24from dataclasses import replace 

25from typing import Any, Literal 

26 

27import torch 

28import torch.distributed as dist 

29 

30from hyper_multicore import _collective, shmem 

31 

32from ..module import MulticoreModule 

33from .function import execute_mega_moe_with_permutation 

34from .heap_manager import _get_heap_manager, _workspace_layout 

35from .plan import build_mega_moe_plan 

36from .route import prepare_topk_route, restore_topk_output 

37from .spec import ( 

38 _COMMUNICATION_SPLIT, 

39 _resolve_capacity_factors, 

40 bind_mega_moe_spec, 

41) 

42from .workspace import MegaMoeWorkspace 

43 

44 

45ExpertWeightLayout = Literal["EIO", "EOI"] 

46_EXPERT_WEIGHT_LAYOUTS = frozenset(("EIO", "EOI")) 

47 

48 

49def _validate_swiglu_limit(swiglu_limit: float | None) -> None: 

50 """Validate a positive clamp value that survives float32 serialization.""" 

51 if swiglu_limit is None: 

52 return 

53 valid_type = isinstance(swiglu_limit, (int, float)) and not isinstance( 

54 swiglu_limit, bool 

55 ) 

56 try: 

57 encoded_limit = ( 

58 struct.unpack("<f", struct.pack("<f", float(swiglu_limit)))[0] 

59 if valid_type 

60 else 0.0 

61 ) 

62 valid_value = ( 

63 valid_type 

64 and math.isfinite(swiglu_limit) 

65 and math.isfinite(encoded_limit) 

66 and encoded_limit > 0 

67 ) 

68 except (OverflowError, TypeError, ValueError, struct.error): 

69 valid_value = False 

70 if not valid_value: 

71 raise ValueError( 

72 "swiglu_limit must be None or a finite positive float32-representable number, " 

73 f"got {swiglu_limit!r}." 

74 ) 

75 

76 

77def _create_mega_moe_parameters( 

78 local_experts: int, 

79 hidden_size: int, 

80 intermediate_size: int, 

81 expert_weight_layout: ExpertWeightLayout, 

82) -> tuple[torch.nn.Parameter, torch.nn.Parameter]: 

83 """Create independently initialized local expert parameters.""" 

84 if expert_weight_layout == "EOI": 

85 gate_up_shape = (local_experts, intermediate_size * 2, hidden_size) 

86 down_shape = (local_experts, hidden_size, intermediate_size) 

87 else: 

88 gate_up_shape = (local_experts, hidden_size, intermediate_size * 2) 

89 down_shape = (local_experts, intermediate_size, hidden_size) 

90 gate_up_weight = torch.nn.Parameter( 

91 torch.empty(gate_up_shape) 

92 ) 

93 down_weight = torch.nn.Parameter( 

94 torch.empty(down_shape) 

95 ) 

96 for expert_index in range(local_experts): 

97 torch.nn.init.xavier_uniform_(gate_up_weight[expert_index]) 

98 torch.nn.init.xavier_uniform_(down_weight[expert_index]) 

99 return gate_up_weight, down_weight 

100 

101 

102def _resolve_ep_size(ep_size: int | None, ep_group: Any | None) -> int: 

103 """Infer size from an explicit group without silently choosing WORLD.""" 

104 if ep_size is not None and (not isinstance(ep_size, int) or isinstance(ep_size, bool) or ep_size <= 0): 

105 raise ValueError("ep_size must be a positive integer or None") 

106 if ep_group is not None: 

107 if not dist.is_initialized(): 

108 raise ValueError("Initialize torch.distributed before passing ep_group") 

109 group_size = dist.get_world_size(ep_group) 

110 if ep_size is not None and ep_size != group_size: 

111 raise ValueError(f"ep_size={ep_size} does not match ep_group size={group_size}") 

112 return group_size 

113 if dist.is_initialized() and dist.get_world_size() > 1: 

114 raise ValueError("Pass ep_group explicitly for distributed MegaMoeExperts") 

115 return 1 if ep_size is None else ep_size 

116 

117 

118class _MegaMoeExecutionResources: 

119 """Own one shape-bound plan and workspace in the shared SHMEM lifecycle.""" 

120 

121 def __init__( 

122 self, 

123 specification: dict[str, Any], 

124 tensor: torch.Tensor, 

125 *, 

126 shared: bool, 

127 active_specifications: tuple[Mapping[str, Any], ...], 

128 ) -> None: 

129 """Bind resources once to the first NPU tensor.""" 

130 self.spec = bind_mega_moe_spec(specification, tensor) 

131 self._token_capacity = self.spec.local_num_tokens 

132 self._plans = {} 

133 if dist.is_initialized(): 

134 self._validate_distributed_configuration(active_specifications) 

135 reservations = tuple((item, _workspace_layout(item, tensor.dtype)) for item in active_specifications) 

136 self.heap_manager = _get_heap_manager(self.spec.ep_group, tensor) 

137 added_specifications = self.heap_manager.reserve( 

138 reservations, 

139 allow_resize=any(item.get("dispatch_mode", "push") == "push" or item.get("auto_tokens", False) 

140 for item in active_specifications), 

141 ) 

142 attached = False 

143 self.plan = None 

144 self.workspace = None 

145 try: 

146 attached = self.heap_manager.attach_runtime() 

147 

148 def construct() -> None: 

149 """Build and bind local resources before converging construction results.""" 

150 self.plan = (build_mega_moe_plan(self.spec, tensor.device) 

151 if not self.spec.dynamic_tokens else None) 

152 self.workspace = MegaMoeWorkspace(shared=shared) 

153 self.workspace.capacity_floor = self.heap_manager.bind(self.workspace, self.spec, specification) 

154 self.workspace.heap_manager = self.heap_manager 

155 

156 _collective.collective_call( 

157 construct, stage="MegaMoe resource construction", group=self.heap_manager.root_group, 

158 ) 

159 except Exception: 

160 self.heap_manager.rollback_reservations(added_specifications, self.workspace) 

161 if attached and not self.heap_manager.workspaces: 

162 self.heap_manager.detach_runtime() 

163 raise 

164 self._closed = False 

165 self._workspace_closed = False 

166 

167 def ensure_capacity(self, maximum_received_slots: int) -> None: 

168 """Submit a larger layout, falling back to minimum capacity within an explicit budget. 

169 

170 Args: 

171 maximum_received_slots: Maximum destination load from the root count exchange. 

172 """ 

173 self.heap_manager.ensure_capacity(self.workspace, maximum_received_slots) 

174 

175 def lifecycle_signature(self) -> tuple[Any, ...]: 

176 """Identify allocations independently of process-local group addresses.""" 

177 return ( 

178 "mega_moe", self.spec.local_num_tokens, self.spec.hidden_size, 

179 self.spec.intermediate_size, self.spec.num_experts, self.spec.top_k, 

180 self.spec.receive_capacity, self.spec.ep_size, self.spec.dispatch_mode, 

181 self.spec.expert_weight_layout, 

182 self.spec.initial_capacity_factor, self.spec.capacity_growth_factor, 

183 self.spec.dispatch_split, self.spec.combine_split, self.spec.max_local_num_tokens, self.spec.auto_tokens, 

184 ) 

185 

186 def prepare_auto_workspace(self, tokens: int, received: int, tensor: torch.Tensor) -> None: 

187 """Commit physical capacity before taking a pull lease and publishing source rows. 

188 

189 Args: 

190 tokens: Aligned group-maximum actual input length. 

191 received: Group-maximum received route rows. 

192 tensor: Input supplying device and dtype. 

193 """ 

194 physical_spec = self.heap_manager.ensure_token_capacity(self.workspace, tokens, received) 

195 self.workspace.ensure(physical_spec, tensor.dtype, tensor.device) 

196 self._token_capacity = physical_spec.local_num_tokens 

197 if self.spec.dispatch_mode == "pull": 

198 self.workspace.claim() 

199 

200 def can_close(self) -> bool: 

201 """Report whether neither an active call nor a backward graph needs buffers.""" 

202 return self._workspace_closed or self.workspace.can_close() 

203 

204 def _validate_distributed_configuration(self, specifications: tuple[Mapping[str, Any], ...]) -> None: 

205 """Reject different allocation manifests before acquiring symmetric memory.""" 

206 if self.spec.ep_size == 1: 

207 return 

208 fields = ("local_num_tokens", "max_local_num_tokens", "hidden_size", "intermediate_size", 

209 "num_experts", "top_k", "initial_capacity_factor", "capacity_growth_factor", "dispatch_mode", 

210 "dispatch_split", "combine_split", "swiglu_limit", "expert_weight_layout", "ep_size", "auto_tokens") 

211 # BF16 is the sole supported dtype; the literal tags also identify the protocol. 

212 manifest = ("dynamic_tokens_v3", "bfloat16", 

213 tuple(tuple(spec.get(key) for key in fields) for spec in specifications)) 

214 # Resource groups must have the same order as their symmetric allocations. 

215 manifests = [None] * self.spec.ep_size 

216 dist.all_gather_object(manifests, manifest, group=self.spec.ep_group) 

217 if any(value != manifests[0] for value in manifests): 

218 raise ValueError("MegaMoe resource configurations differ across EP ranks") 

219 

220 def plan_for_tokens(self, tokens: int, device: Any) -> Any: 

221 """Reuse one covering plan per geometric bucket within committed capacity. 

222 

223 Args: 

224 tokens: EP group maximum input rows, already aligned to 128. 

225 device: Execution device used for the parameters and workspace. 

226 

227 Returns: 

228 Immutable plan covering the requested rows without enlarging storage. 

229 """ 

230 if tokens > self._token_capacity: 

231 raise RuntimeError(f"MegaMoe schedule exceeds committed token capacity: " 

232 f"requested={tokens}, capacity={self._token_capacity}") 

233 bucket = 1 << (max(128, tokens) - 1).bit_length() 

234 plan = self._plans.get(bucket) 

235 if plan is None or plan.spec.plan_tokens < tokens: 

236 schedule_tokens = min(bucket, self._token_capacity) 

237 plan = build_mega_moe_plan(replace(self.spec, schedule_tokens=schedule_tokens), device) 

238 # INT32 route bounds allow at most 25 buckets starting at 128 rows. 

239 # A growing partial bucket replaces its plan; backward keeps the old object. 

240 self._plans[bucket] = plan 

241 return plan 

242 

243 def close(self) -> None: 

244 """Release the workspace and leave the shared SHMEM lifecycle.""" 

245 if self._closed: 

246 return 

247 if not self._workspace_closed: 

248 self.heap_manager.close_workspace(self.workspace) 

249 self._workspace_closed = True 

250 self._plans.clear() 

251 self._closed = True 

252 

253 

254class MegaMoeExperts(MulticoreModule): 

255 """Execute Router-selected local experts with the Torch MegaMoe kernel. 

256 

257 Serial model layers can call :meth:`share_execution_resources` before 

258 their first forward to share one automatically growing workspace while keeping 

259 independent parameters and optimizer state. 

260 """ 

261 

262 @classmethod 

263 def from_module( 

264 cls, module: torch.nn.Module, *, top_k: int, ep_group: Any | None = None, **execution_options: Any, 

265 ) -> MegaMoeExperts: 

266 """Convert materialized local GroupedExperts SwiGLU weights before optimizer construction. 

267 

268 Args: 

269 module: Module exposing w1/w3 [local_E, I, H], w2 [local_E, H, I]. 

270 DTensor local EP shards are accepted; FSDP weights must be materialized first. 

271 top_k: Experts selected by the existing router for each token. 

272 ep_group: Explicit EP group owning these local experts, or None for a standalone rank. 

273 **execution_options: Advanced MegaMoe options, including num_experts and expert_weight_layout. 

274 

275 Returns: 

276 Experts with copied, converted Parameters. Optimizer state is not migrated. 

277 """ 

278 values = [] 

279 for name in ("w1", "w3", "w2"): 

280 value = getattr(module, name, None) 

281 if hasattr(value, "to_local"): 

282 value = value.to_local() 

283 if not isinstance(value, torch.Tensor) or value.ndim != 3: 

284 raise ValueError(f"source {name} must be a materialized local rank-three expert tensor") 

285 if value.is_meta: 

286 raise ValueError(f"source {name} is meta; materialize and initialize/load weights before from_module") 

287 values.append(value) 

288 gate, up, down = values[0], values[1], values[2] 

289 if (up.shape != gate.shape or down.shape != (gate.shape[0], gate.shape[2], gate.shape[1]) 

290 or any(value.device != gate.device or value.dtype != gate.dtype for value in values) 

291 or gate.requires_grad != up.requires_grad): 

292 raise ValueError("source SwiGLU expert shapes, dtypes, devices and gate/up gradient policies must match") 

293 ep_size = _resolve_ep_size(execution_options.get("ep_size"), ep_group) 

294 experts = execution_options.pop("num_experts", getattr(module, "num_experts", gate.shape[0] * ep_size)) 

295 if experts != gate.shape[0] * ep_size: 

296 raise ValueError("source local expert count does not match num_experts and ep_group") 

297 result = cls(hidden_size=gate.shape[2], intermediate_size=gate.shape[1], num_experts=experts, 

298 top_k=top_k, ep_group=ep_group, create_parameters=False, **execution_options) 

299 gate_up = torch.cat((gate.detach(), up.detach()), dim=1) 

300 down_data = down.detach() 

301 if result.expert_weight_layout == "EIO": 

302 gate_up = gate_up.transpose(1, 2) 

303 down_data = down_data.transpose(1, 2) 

304 result.gate_up_weight = torch.nn.Parameter( 

305 gate_up.contiguous(), requires_grad=gate.requires_grad) 

306 result.down_weight = torch.nn.Parameter( 

307 down_data.clone(memory_format=torch.contiguous_format), 

308 requires_grad=down.requires_grad) 

309 return result 

310 

311 def _resource_signature(self, tensor: Any) -> Any: 

312 """Compare static configuration without process-local group identities.""" 

313 configuration = tuple(sorted((name, value) for name, value in self._resource_group.specification.items() 

314 if name != "ep_group")) 

315 return (super()._resource_signature(tensor), configuration, 

316 self._resource_group.shared) 

317 

318 def _register_runtime_release(self, manager: Any, resources: Any) -> None: 

319 """Register the Heap Manager's sole Runtime reference for final cleanup.""" 

320 manager.runtime_release = resources.heap_manager.detach_runtime 

321 

322 def __init__( 

323 self, 

324 *, 

325 hidden_size: int, 

326 intermediate_size: int, 

327 num_experts: int, 

328 top_k: int, 

329 swiglu_limit: float | None = None, 

330 ep_size: int | None = None, 

331 ep_group: Any | None = None, 

332 create_parameters: bool = True, 

333 expert_weight_layout: ExpertWeightLayout = "EIO", 

334 dispatch_mode: str = "push", 

335 initial_capacity_factor: float | None = None, 

336 capacity_growth_factor: float | None = None, 

337 dispatch_split: int = _COMMUNICATION_SPLIT, 

338 combine_split: int = _COMMUNICATION_SPLIT, 

339 ) -> None: 

340 """Initialize local expert parameters and a lazy execution owner. 

341 

342 Args: 

343 hidden_size: Input and output hidden dimension. 

344 intermediate_size: SwiGLU intermediate dimension per expert. 

345 num_experts: Global routed-expert count. 

346 top_k: Experts selected for every token. 

347 initial_capacity_factor: Push initial receive rows as a multiple of local routed rows. 

348 Defaults to 1.25 for push; omitted for pull. Must be finite and at least 1.0. 

349 capacity_growth_factor: Push capacity multiplier on overflow, defaulting to 1.25. 

350 Must be finite and at least 1.0; 1.0 grows only to the current route demand. 

351 The resulting capacity is capped by the lossless route bound. Pull rejects explicit factors. 

352 dispatch_mode: Dispatch transport, either "push" (default) or "pull". 

353 Construct separate modules to switch modes; sharing requires equal modes. 

354 dispatch_split: Dispatch tile rows in forward and backward, default 128. 

355 Must be a positive multiple of 128. 

356 combine_split: Combine tile rows in forward and backward, with the same 

357 constraints and default as dispatch_split. Sharing requires equal splits. 

358 swiglu_limit: Optional positive, finite float32-representable clamp 

359 limit for SwiGLU. The gate branch uses ``min(gate, limit)`` and 

360 the up branch is clamped to ``[-limit, limit]``. ``None`` 

361 preserves the legacy unclamped path. 

362 ep_size: Optional compatibility check; normally inferred from explicit ep_group. 

363 create_parameters: Allocate owned weights; False requires explicit expert_weights each forward. 

364 expert_weight_layout: Logical expert-weight axis order. ``"EIO"`` means 

365 ``[experts, in_features, out_features]`` and preserves the historical layout. ``"EOI"`` means 

366 ``[experts, out_features, in_features]`` and accepts Torch linear storage directly. ``EOI`` weights 

367 must use dense contiguous storage for zero-copy execution. 

368 ep_group: Explicit EP group; expert ownership follows group-local rank order. 

369 Disjoint PP/DP groups bootstrap independently. Only one ordered EP membership 

370 can be active in SHMEM per process. 

371 """ 

372 specification = { 

373 "local_num_tokens": _COMMUNICATION_SPLIT, 

374 "hidden_size": hidden_size, 

375 "intermediate_size": intermediate_size, 

376 "num_experts": num_experts, 

377 "top_k": top_k, 

378 "initial_capacity_factor": initial_capacity_factor, 

379 "swiglu_limit": swiglu_limit, 

380 "ep_size": _resolve_ep_size(ep_size, ep_group), 

381 "ep_group": ep_group, 

382 "expert_weight_layout": expert_weight_layout, 

383 "dispatch_mode": dispatch_mode, 

384 "capacity_growth_factor": capacity_growth_factor, 

385 "dispatch_split": dispatch_split, 

386 "combine_split": combine_split, 

387 } 

388 specification["auto_tokens"] = True 

389 specification = self._resolve_specification(specification) 

390 compatibility_key = tuple( 

391 id(value) if name == "ep_group" else value for name, value in specification.items()) 

392 super().__init__( 

393 resource_specification=specification, 

394 resource_compatibility_key=compatibility_key, 

395 resource_scope_key=("mega_moe", shmem.root_members(ep_group) if dist.is_initialized() else id(ep_group)), 

396 ) 

397 self._initialize_module_state(specification, create_parameters=create_parameters) 

398 

399 def _initialize_module_state( 

400 self, specification: Mapping[str, Any], *, create_parameters: bool, 

401 ) -> None: 

402 """Bind normalized configuration and initialize expert parameters.""" 

403 self.hidden_size = specification["hidden_size"] 

404 self.intermediate_size = specification["intermediate_size"] 

405 self.num_experts = specification["num_experts"] 

406 self.top_k = specification["top_k"] 

407 self.expert_weight_layout = specification["expert_weight_layout"] 

408 self.initial_capacity_factor = specification["initial_capacity_factor"] 

409 self.capacity_growth_factor = specification["capacity_growth_factor"] 

410 self.dispatch_mode = specification["dispatch_mode"] 

411 self.dispatch_split = specification["dispatch_split"] 

412 self.combine_split = specification["combine_split"] 

413 self.swiglu_limit = specification["swiglu_limit"] 

414 self.ep_size = specification["ep_size"] 

415 self.local_experts = self.num_experts // self.ep_size 

416 self._ep_group = specification["ep_group"] 

417 if create_parameters: 

418 self.gate_up_weight, self.down_weight = _create_mega_moe_parameters( 

419 self.local_experts, self.hidden_size, self.intermediate_size, self.expert_weight_layout, 

420 ) 

421 else: 

422 self.register_parameter("gate_up_weight", None) 

423 self.register_parameter("down_weight", None) 

424 

425 @classmethod 

426 def _resolve_specification(cls, specification: dict[str, Any]) -> dict[str, Any]: 

427 """Validate and normalize configuration before registering an execution owner.""" 

428 expert_weight_layout = specification["expert_weight_layout"] 

429 if expert_weight_layout not in _EXPERT_WEIGHT_LAYOUTS: 

430 raise ValueError( 

431 "expert_weight_layout must be 'EIO' ([experts, in_features, out_features]) " 

432 "or 'EOI' ([experts, out_features, in_features]), " 

433 f"got {expert_weight_layout!r}." 

434 ) 

435 initial_capacity_factor, capacity_growth_factor = _resolve_capacity_factors( 

436 specification["dispatch_mode"], 

437 specification["initial_capacity_factor"], 

438 specification["capacity_growth_factor"], 

439 ) 

440 cls._validate_topology( 

441 local_num_tokens=specification["local_num_tokens"], 

442 hidden_size=specification["hidden_size"], 

443 intermediate_size=specification["intermediate_size"], 

444 num_experts=specification["num_experts"], 

445 top_k=specification["top_k"], 

446 swiglu_limit=specification["swiglu_limit"], 

447 ep_size=specification["ep_size"], 

448 dispatch_split=specification["dispatch_split"], 

449 combine_split=specification["combine_split"], 

450 auto_tokens=specification.get("auto_tokens", False), 

451 ) 

452 swiglu_limit = specification["swiglu_limit"] 

453 return { 

454 **specification, 

455 "initial_capacity_factor": initial_capacity_factor, 

456 "capacity_growth_factor": capacity_growth_factor, 

457 "swiglu_limit": float(swiglu_limit) if swiglu_limit is not None else None, 

458 } 

459 

460 @staticmethod 

461 def _validate_topology( 

462 *, 

463 local_num_tokens: int, 

464 hidden_size: int, 

465 intermediate_size: int, 

466 num_experts: int, 

467 top_k: int, 

468 swiglu_limit: float | None, 

469 ep_size: int, 

470 dispatch_split: int, 

471 combine_split: int, 

472 auto_tokens: bool = False, 

473 ) -> None: 

474 """Validate static shape and topology values before allocation.""" 

475 values = { 

476 "local_num_tokens": local_num_tokens, 

477 "hidden_size": hidden_size, 

478 "intermediate_size": intermediate_size, 

479 "num_experts": num_experts, 

480 "top_k": top_k, 

481 "ep_size": ep_size, 

482 } 

483 for name, value in values.items(): 

484 if not isinstance(value, int) or isinstance(value, bool) or value <= 0: 

485 raise ValueError(f"{name} must be a positive integer, got {value!r}.") 

486 if top_k > num_experts: 

487 raise ValueError( 

488 f"top_k ({top_k}) cannot exceed num_experts ({num_experts})." 

489 ) 

490 if num_experts % ep_size: 

491 raise ValueError( 

492 f"num_experts ({num_experts}) must be divisible by ep_size ({ep_size})." 

493 ) 

494 _validate_swiglu_limit(swiglu_limit) 

495 for name, split in (("dispatch_split", dispatch_split), ("combine_split", combine_split)): 

496 if (not isinstance(split, int) or isinstance(split, bool) 

497 or split <= 0 or split % _COMMUNICATION_SPLIT): 

498 raise ValueError( 

499 f"{name} must be a positive integer multiple of {_COMMUNICATION_SPLIT}, got {split!r}.") 

500 if not auto_tokens and local_num_tokens % split: 

501 raise ValueError(f"local_num_tokens ({local_num_tokens}) must be divisible by {name} ({split}).") 

502 

503 def _root_group(self) -> Any: 

504 """Keep lifecycle coordination within this module's EP group.""" 

505 return self._ep_group 

506 

507 def _validate_tensors(self, hidden_states: torch.Tensor, expert_weights: tuple) -> None: 

508 """Validate activation and parameter metadata before acquiring resources.""" 

509 if hidden_states.dtype != torch.bfloat16 or not getattr(hidden_states, "is_npu", False): 

510 raise TypeError("MegaMoeExperts requires BF16 NPU hidden states.") 

511 weight1, weight2 = expert_weights 

512 if weight1 is None or weight2 is None: 

513 raise ValueError("Parameterless MegaMoe requires explicit expert_weights") 

514 if self.expert_weight_layout == "EOI": 

515 expected_weight1 = (self.local_experts, self.intermediate_size * 2, self.hidden_size) 

516 expected_weight2 = (self.local_experts, self.hidden_size, self.intermediate_size) 

517 else: 

518 expected_weight1 = (self.local_experts, self.hidden_size, self.intermediate_size * 2) 

519 expected_weight2 = (self.local_experts, self.intermediate_size, self.hidden_size) 

520 if tuple(weight1.shape) != expected_weight1: 

521 raise ValueError( 

522 f"gate_up_weight must have shape {expected_weight1}, got {tuple(weight1.shape)}." 

523 ) 

524 if tuple(weight2.shape) != expected_weight2: 

525 raise ValueError( 

526 f"down_weight must have shape {expected_weight2}, got {tuple(weight2.shape)}." 

527 ) 

528 if ( 

529 weight1.device != hidden_states.device 

530 or weight2.device != hidden_states.device 

531 ): 

532 raise ValueError( 

533 "expert weights and hidden states must be on the same NPU." 

534 ) 

535 if weight1.dtype != hidden_states.dtype or weight2.dtype != hidden_states.dtype: 

536 raise TypeError("expert weights and hidden states must all use BF16.") 

537 if self.expert_weight_layout == "EOI" and ( 

538 not weight1.is_contiguous() or not weight2.is_contiguous() 

539 ): 

540 raise ValueError("EOI expert weights must be contiguous for zero-copy execution.") 

541 

542 def _validate_forward_inputs( 

543 self, 

544 hidden_states: torch.Tensor, 

545 topk_ids: torch.Tensor, 

546 topk_weights: torch.Tensor, 

547 tokens_per_expert: torch.Tensor | None, 

548 expert_weights: tuple, 

549 ) -> torch.Tensor: 

550 """Validate input shapes and return flattened local token states.""" 

551 if hidden_states.ndim < 2 or hidden_states.shape[-1] != self.hidden_size: 

552 raise ValueError( 

553 "hidden_states must have at least two dimensions and configured " 

554 f"hidden size {self.hidden_size}, got {tuple(hidden_states.shape)}." 

555 ) 

556 hidden_flat = hidden_states.reshape(-1, self.hidden_size) 

557 route_shape = (hidden_flat.shape[0], self.top_k) 

558 if tuple(topk_ids.shape) != route_shape: 

559 raise ValueError( 

560 f"topk_ids must have shape {route_shape}, got {tuple(topk_ids.shape)}." 

561 ) 

562 if tuple(topk_weights.shape) != route_shape: 

563 raise ValueError( 

564 f"topk_weights must have shape {route_shape}, " 

565 f"got {tuple(topk_weights.shape)}." 

566 ) 

567 if tokens_per_expert is not None and tuple(tokens_per_expert.shape) != ( 

568 self.num_experts, 

569 ): 

570 raise ValueError( 

571 f"tokens_per_expert must have shape ({self.num_experts},), " 

572 f"got {tuple(tokens_per_expert.shape)}." 

573 ) 

574 self._validate_tensors(hidden_flat, expert_weights) 

575 return hidden_flat 

576 

577 def forward( 

578 self, 

579 hidden_states: torch.Tensor, 

580 topk_ids: torch.Tensor, 

581 topk_weights: torch.Tensor, 

582 *, 

583 tokens_per_expert: torch.Tensor | None = None, 

584 expert_weights: tuple[torch.Tensor, torch.Tensor] | None = None, 

585 ) -> torch.Tensor: 

586 """Route token states through the configured experts. 

587 

588 Args: 

589 hidden_states: Tensor ending in ``hidden_size``. 

590 topk_ids: Global expert IDs shaped ``[tokens, top_k]`` for the current input. 

591 topk_weights: Router weights with the same shape as ``topk_ids``. 

592 expert_weights: Current unsharded weights owned by an outer module. 

593 Noncontiguous EIO weights are copied with autograd tracking, never cached 

594 by this executor. EOI weights must be contiguous for zero-copy execution. 

595 tokens_per_expert: Optional trusted exact global-expert histogram. 

596 Supplying Router-produced counts skips histogram recomputation. 

597 

598 Returns: 

599 Tensor matching the shape and dtype of ``hidden_states``. 

600 

601 Raises: 

602 ValueError: EOI expert weights are noncontiguous. 

603 RuntimeError: The current NPU stream is being captured. 

604 

605 Note: 

606 Automatic token management requires eager execution for both push 

607 and pull. NPU graph capture is unsupported, even with stable token 

608 counts and capacity. 

609 

610 ``tokens_per_expert`` must describe the current ``topk_ids`` exactly. 

611 The steady-state path validates metadata but deliberately does not 

612 rebuild and compare the histogram. 

613 """ 

614 weights = ((self.gate_up_weight, self.down_weight) if expert_weights is None else expert_weights) 

615 hidden_flat = self._validate_forward_inputs( 

616 hidden_states, 

617 topk_ids, 

618 topk_weights, 

619 tokens_per_expert, 

620 weights, 

621 ) 

622 if torch.npu.is_current_stream_capturing(): 

623 raise RuntimeError("MegaMoe push heap growth and dynamic tokens do not support graph capture") 

624 resources = self._get_execution_resources(hidden_flat) 

625 route_options = {"autograd_mask": ( 

626 int(torch.is_grad_enabled()) | (int(hidden_flat.requires_grad) << 1) 

627 | (int(weights[0].requires_grad) << 2) | (int(weights[1].requires_grad) << 3) 

628 | (int(topk_weights.requires_grad) << 4) 

629 )} 

630 pull = self.dispatch_mode == "pull" 

631 automatic = resources.spec.auto_tokens 

632 claimed = False 

633 def prepare_workspace(tokens: int, received: int) -> None: 

634 """Grow before publishing pull source rows into symmetric memory. 

635 

636 Args: 

637 tokens: Current local source-token count. 

638 received: Current routed receive-row count. 

639 """ 

640 nonlocal claimed 

641 resources.prepare_auto_workspace(tokens, received, hidden_flat) 

642 claimed = pull 

643 

644 if automatic: 

645 route_options["prepare_workspace"] = prepare_workspace 

646 elif pull: 

647 resources.workspace.ensure(resources.spec, hidden_flat.dtype, hidden_flat.device) 

648 resources.workspace.claim() 

649 claimed = True 

650 try: 

651 # Pull source writes and execution must share one workspace lease. 

652 with torch.no_grad(): 

653 route = prepare_topk_route( 

654 hidden_flat, 

655 topk_ids, 

656 topk_weights, 

657 resources.spec, 

658 tokens_per_expert, 

659 workspace=resources.workspace, 

660 **route_options, 

661 ) 

662 if not pull and not automatic: 

663 resources.ensure_capacity(route.maximum_received_slots) 

664 plan = resources.plan_for_tokens(route.metadata.plan_tokens, hidden_flat.device) 

665 expert_output = execute_mega_moe_with_permutation( 

666 hidden_flat, 

667 topk_ids, 

668 weights[0], 

669 weights[1], 

670 route, 

671 plan, 

672 resources.workspace, 

673 topk_weights=topk_weights if pull else None, 

674 workspace_claimed=pull, 

675 ) 

676 finally: 

677 if claimed: 

678 resources.workspace.release() 

679 if pull: 

680 return expert_output.reshape_as(hidden_states) 

681 output = restore_topk_output( 

682 expert_output, 

683 route.unpermute_mapping, 

684 topk_weights, 

685 ) 

686 return output.reshape(hidden_states.shape) 

687 

688 def _create_execution_resources( 

689 self, 

690 tensor: torch.Tensor, 

691 *, 

692 shared: bool, 

693 active_specifications: tuple[Mapping[str, Any], ...], 

694 ) -> _MegaMoeExecutionResources: 

695 """Create the plan and workspace owned by this resource group.""" 

696 return _MegaMoeExecutionResources( 

697 self._resource_group.specification, 

698 tensor, 

699 shared=shared, 

700 active_specifications=active_specifications, 

701 )