Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_multicore / diagnostics / dump / l0.py: 97%
95 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 05:03 +0800
« 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"""Whole-MegaKernel boundary snapshots and stage-only Tensor storage."""
17from __future__ import annotations
19from dataclasses import dataclass
20import os
21from pathlib import Path
22from typing import Any
24import torch
26from hyper_multicore.diagnostics.dump.layout import _SAFE_FILE_NAME, _stage_names
29@dataclass(frozen=True)
30class _TensorPort:
31 """One internal operator port referring to a logical graph Tensor."""
33 operator: str
34 stage: str
35 op_type: str
36 io: str
37 index: int
40@dataclass(frozen=True)
41class _GraphTensor:
42 """One logical graph Tensor and its L0 capture relationship."""
44 name: str
45 param_position: int
46 declared_shape: tuple[int, ...]
47 role: str
48 producers: tuple[str, ...]
49 consumers: tuple[str, ...]
50 ports: tuple[_TensorPort, ...]
53@dataclass
54class _TensorSnapshot:
55 """One Host-owned Tensor value plus export metadata."""
57 graph_tensor: _GraphTensor
58 phase: str
59 original_dtype: str
60 stored_dtype: str
61 shape: tuple[int, ...]
62 data: torch.Tensor
65def _operator_type_name(operator: Any) -> str:
66 op_type = operator.op_type
67 value = getattr(op_type, "value", op_type)
68 return str(value)
71def _describe_graph_tensors(graph: Any) -> tuple[_GraphTensor, ...]:
72 """Deduplicate ABI-bound graph tensors and classify their producers and consumers."""
73 relationships: dict[tuple[str, int], dict[str, Any]] = {}
74 operators = graph.topological_sort()
75 stage_names = _stage_names(operators)
76 for operator in operators:
77 op_type = _operator_type_name(operator)
78 for index, tensor in enumerate(operator.inputs):
79 key = (tensor.name, int(tensor.param_position))
80 relationship = relationships.setdefault(
81 key,
82 {
83 "shape": tuple(int(dim) for dim in tensor.shape),
84 "producers": [],
85 "consumers": [],
86 "ports": [],
87 },
88 )
89 relationship["consumers"].append(operator.name)
90 relationship["ports"].append(
91 _TensorPort(operator.name, stage_names[operator.name], op_type, "input", index)
92 )
93 for index, tensor in enumerate(operator.outputs):
94 key = (tensor.name, int(tensor.param_position))
95 relationship = relationships.setdefault(
96 key,
97 {
98 "shape": tuple(int(dim) for dim in tensor.shape),
99 "producers": [],
100 "consumers": [],
101 "ports": [],
102 },
103 )
104 relationship["producers"].append(operator.name)
105 relationship["ports"].append(
106 _TensorPort(operator.name, stage_names[operator.name], op_type, "output", index)
107 )
109 descriptions = []
110 for (name, param_position), relationship in relationships.items():
111 producers = tuple(relationship["producers"])
112 consumers = tuple(relationship["consumers"])
113 if not producers:
114 role = "external_input"
115 elif consumers:
116 role = "stage_output"
117 else:
118 role = "final_output"
119 descriptions.append(
120 _GraphTensor(
121 name=name,
122 param_position=param_position,
123 declared_shape=relationship["shape"],
124 role=role,
125 producers=producers,
126 consumers=consumers,
127 ports=tuple(relationship["ports"]),
128 )
129 )
130 return tuple(sorted(descriptions, key=lambda tensor: (tensor.param_position, tensor.name)))
133def _dtype_name(dtype: Any) -> str:
134 return str(dtype).removeprefix("torch.")
137def _snapshot_tensor(tensor: Any, graph_tensor: _GraphTensor, phase: str) -> _TensorSnapshot:
138 if not torch.is_tensor(tensor):
139 raise TypeError(
140 f"dump Tensor {graph_tensor.name!r} at param_position={graph_tensor.param_position} "
141 f"must map to torch.Tensor, got {type(tensor).__name__}"
142 )
143 host_tensor = tensor.detach().contiguous().cpu().clone()
144 dtype_name = _dtype_name(host_tensor.dtype)
145 return _TensorSnapshot(
146 graph_tensor=graph_tensor,
147 phase=phase,
148 original_dtype=dtype_name,
149 stored_dtype=dtype_name,
150 shape=tuple(int(dim) for dim in tensor.shape),
151 data=host_tensor,
152 )
155def _write_l0_stage_files(
156 snapshot: _TensorSnapshot,
157 invocation_dir: Path,
158 window_dir: Path,
159 safe_name: str,
160) -> tuple[Path, list[dict[str, Any]]]:
161 """Save one L0 Tensor under stages and hard-link its remaining ports."""
162 metadata = []
163 primary_path = None
164 for port in snapshot.graph_tensor.ports:
165 safe_stage = _SAFE_FILE_NAME.sub("_", port.stage)
166 stage_dir = invocation_dir / "stages" / safe_stage
167 stage_dir.mkdir(parents=True, exist_ok=True)
168 stage_file_name = f"{port.io}_{port.index:02d}_{safe_name}.pt"
169 stage_path = stage_dir / stage_file_name
170 if primary_path is None:
171 torch.save(snapshot.data, stage_path)
172 primary_path = stage_path
173 else:
174 try:
175 os.link(primary_path, stage_path)
176 except OSError as error:
177 raise OSError(
178 f"failed to create stage-file hard link {stage_path} for {primary_path}"
179 ) from error
180 metadata.append(
181 {
182 "operator": port.operator,
183 "stage": port.stage,
184 "opType": port.op_type,
185 "io": port.io,
186 "index": port.index,
187 "file": stage_path.relative_to(window_dir).as_posix(),
188 }
189 )
190 if primary_path is None:
191 boundary_dir = invocation_dir / "stages" / "__kernel_boundary__"
192 boundary_dir.mkdir(parents=True, exist_ok=True)
193 boundary_io = "input" if snapshot.phase == "before" else "output"
194 file_name = f"{boundary_io}_{snapshot.graph_tensor.param_position:02d}_{safe_name}.pt"
195 primary_path = boundary_dir / file_name
196 torch.save(snapshot.data, primary_path)
197 return primary_path, metadata