Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / data / batching / runtime_input.py: 36%

100 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""Resolve sequence metadata and model-owned runtime inputs.""" 

16 

17from __future__ import annotations 

18 

19from abc import ABC, abstractmethod 

20from collections.abc import Mapping, Sequence 

21from dataclasses import dataclass 

22from typing import Any 

23 

24import torch 

25 

26 

27class OnlineBoundaryResolver: 

28 """Read global cumulative sequence boundaries emitted by Online packing.""" 

29 

30 @staticmethod 

31 def resolve(canonical_batch: Mapping[str, Any]) -> Any: 

32 """Read leading-zero ``cu_seq_lens`` from an Online batch.""" 

33 raw_cu_seq_lens = canonical_batch["cu_seq_lens"] 

34 cu_seq_lens = raw_cu_seq_lens.to(torch.int32) 

35 return cu_seq_lens 

36 

37 

38class IndexedBoundaryResolver: 

39 """Recover global cumulative sequence boundaries from Indexed input IDs.""" 

40 

41 def __init__(self, eod_token_id: int | None) -> None: 

42 """Store the token separating packed Indexed sequences.""" 

43 self.eod_token_id = eod_token_id 

44 

45 def resolve(self, canonical_batch: Mapping[str, Any]) -> Any: 

46 """Recover leading-zero ``cu_seq_lens`` from Indexed tokens.""" 

47 input_ids = canonical_batch["input_ids"] 

48 batch_size, seq_len = input_ids.shape 

49 token_indices = torch.arange(seq_len, dtype=input_ids.dtype, device=input_ids.device) 

50 

51 seq_ends = [] 

52 for batch_idx in range(batch_size): 

53 if self.eod_token_id is None: 

54 eod_indices = token_indices[:0] 

55 else: 

56 eod_indices = token_indices[input_ids[batch_idx] == self.eod_token_id] 

57 

58 prev_eod_idx = -1 

59 for eod_idx in eod_indices: 

60 eod_idx = int(eod_idx.item()) 

61 if eod_idx == prev_eod_idx: 

62 break 

63 

64 seq_end = batch_idx * seq_len + eod_idx + 1 

65 seq_ends.append(seq_end) 

66 prev_eod_idx = eod_idx + 1 

67 

68 row_end = (batch_idx + 1) * seq_len 

69 if not seq_ends or seq_ends[-1] != row_end: 

70 seq_ends.append(row_end) 

71 

72 cu_seq_lens = torch.tensor([0, *seq_ends], dtype=torch.int32) 

73 return cu_seq_lens 

74 

75 

76@dataclass(frozen=True) 

77class RuntimeInputContext: 

78 """Framework execution facts available to a model runtime adapter. 

79 

80 ``options`` deliberately carries feature-specific policy. The framework 

81 owns batch movement and parallel coordinates, while a model adapter may 

82 translate those facts into attention metadata, modality routing inputs, 

83 cache descriptors, or another forward-only contract. 

84 """ 

85 

86 local_input_shape: Sequence[int] 

87 parallel_ranks: Mapping[str, int] 

88 parallel_sizes: Mapping[str, int] 

89 options: Mapping[str, Any] 

90 

91 @classmethod 

92 def from_batch( 

93 cls, 

94 *, 

95 batch: Mapping[str, Any], 

96 parallel_context: Any, 

97 options: Mapping[str, Any] | None = None, 

98 ) -> RuntimeInputContext: 

99 """Build runtime context from one parallel-local batch.""" 

100 runtime_context = cls( 

101 local_input_shape=batch["input_ids"].shape, 

102 parallel_ranks={ 

103 "tp": parallel_context.tp_rank, 

104 "cp": parallel_context.cp_rank, 

105 }, 

106 parallel_sizes={ 

107 "tp": parallel_context.tp_world_size, 

108 "cp": parallel_context.cp_world_size, 

109 }, 

110 options={} if options is None else options, 

111 ) 

112 return runtime_context 

113 

114 

115class RuntimeInputAdapter(ABC): 

116 """Extend a generic batch with model-owned forward inputs.""" 

117 

118 def build( 

119 self, 

120 *, 

121 batch: Mapping[str, Any], 

122 parallel_context: Any, 

123 options: Mapping[str, Any] | None = None, 

124 ) -> dict[str, Any]: 

125 """Build runtime context and produce one runtime's model inputs.""" 

126 runtime_context = RuntimeInputContext.from_batch( 

127 batch=batch, 

128 parallel_context=parallel_context, 

129 options=options, 

130 ) 

131 runtime_inputs = self.build_runtime_inputs( 

132 batch=batch, 

133 context=runtime_context, 

134 ) 

135 if not isinstance(runtime_inputs, Mapping): 

136 raise TypeError( 

137 "RuntimeInputAdapter.build_runtime_inputs must return a mapping, " 

138 f"got {type(runtime_inputs).__name__}" 

139 ) 

140 return dict(runtime_inputs) 

141 

142 def runtime_input_fields(self) -> tuple[str, ...]: 

143 """Return the forward fields exclusively owned by this adapter. 

144 

145 Dynamic values may vary by batch, but field ownership must be stable so 

146 diagnostics can reject collisions before interpreting precision runs. 

147 """ 

148 return () 

149 

150 @abstractmethod 

151 def build_runtime_inputs( 

152 self, 

153 *, 

154 batch: Mapping[str, Any], 

155 context: RuntimeInputContext, 

156 ) -> Mapping[str, Any]: 

157 """Build additional model inputs without mutating ``batch``. 

158 

159 Args: 

160 batch: Device-resident, parallel-local batch. Global metadata such 

161 as ``cu_seq_lens`` remains available when required. 

162 context: Local geometry, parallel coordinates, and feature-specific 

163 recipe options. 

164 

165 Returns: 

166 Mapping merged into the model forward inputs. Keys must not replace 

167 fields already owned by the generic batch path. 

168 """ 

169 raise NotImplementedError 

170 

171 

172class AttentionRuntime(RuntimeInputAdapter): 

173 """Build dense attention fields from common runtime context.""" 

174 

175 def __init__( 

176 self, 

177 *, 

178 mode: str, 

179 create_mask: bool, 

180 reset_mask: bool, 

181 sliding_window: int | None, 

182 ) -> None: 

183 """Store the attention representation and mask policy.""" 

184 self.mode = mode 

185 self.create_mask = create_mask 

186 self.reset_mask = reset_mask 

187 self.sliding_window = sliding_window 

188 

189 def build_runtime_inputs( 

190 self, 

191 *, 

192 batch: Mapping[str, Any], 

193 context: RuntimeInputContext, 

194 ) -> Mapping[str, Any]: 

195 """Build attention fields without owning model-specific packed metadata.""" 

196 attention_inputs = {"attention_mask": None} 

197 if not self.create_mask or self.mode == "compressed": 

198 return attention_inputs 

199 

200 micro_batch_size, local_sequence_length = batch["input_ids"].shape 

201 cp_size = context.parallel_sizes["cp"] 

202 global_sequence_length = local_sequence_length * cp_size 

203 attention_mask, sliding_window_mask = self._build_dense_attention_masks( 

204 cu_seq_lens=batch["cu_seq_lens"], 

205 micro_batch_size=micro_batch_size, 

206 seq_length=global_sequence_length, 

207 device=batch["input_ids"].device, 

208 ) 

209 attention_inputs["attention_mask"] = attention_mask 

210 if sliding_window_mask is not None: 

211 attention_inputs["swa_mask"] = sliding_window_mask 

212 

213 return attention_inputs 

214 

215 def _build_dense_attention_masks( 

216 self, 

217 *, 

218 cu_seq_lens: Any, 

219 micro_batch_size: int, 

220 seq_length: int, 

221 device: Any, 

222 ) -> tuple[Any, Any | None]: 

223 """Build global dense attention and optional sliding-window masks.""" 

224 boundaries = [int(boundary) for boundary in cu_seq_lens.tolist()] 

225 if len(boundaries) < 2 or boundaries[0] != 0: 

226 raise ValueError("cu_seq_lens must contain a leading zero and at least one sequence") 

227 

228 if any(end <= start for start, end in zip(boundaries[:-1], boundaries[1:])): 

229 raise ValueError("cu_seq_lens must be strictly increasing") 

230 

231 physical_sequence_length = micro_batch_size * seq_length 

232 if boundaries[-1] != physical_sequence_length: 

233 raise ValueError( 

234 "cu_seq_lens must cover the physical batch length " 

235 f"({physical_sequence_length}), but got {boundaries[-1]}" 

236 ) 

237 

238 attention_batch_size = micro_batch_size if self.reset_mask else 1 

239 attention_mask = torch.ones( 

240 (attention_batch_size, seq_length, seq_length), 

241 dtype=torch.bool, 

242 device=device, 

243 ).tril() 

244 attention_mask = attention_mask.view(attention_batch_size, 1, seq_length, seq_length) 

245 

246 if self.reset_mask: 

247 for sequence_start in boundaries[:-1]: 

248 batch_index, row_sequence_start = divmod(sequence_start, seq_length) 

249 attention_mask[batch_index, 0, row_sequence_start:, :row_sequence_start] = False 

250 

251 sliding_window_mask = None 

252 if self.sliding_window is not None: 

253 positions = torch.arange(seq_length, dtype=torch.int64, device=device) 

254 outside_window = positions.unsqueeze(1) - positions.unsqueeze(0) > self.sliding_window 

255 sliding_window_mask = attention_mask & ~outside_window.unsqueeze(0).unsqueeze(0) 

256 

257 return attention_mask, sliding_window_mask 

258 

259 

260__all__ = [ 

261 "AttentionRuntime", 

262 "IndexedBoundaryResolver", 

263 "OnlineBoundaryResolver", 

264 "RuntimeInputAdapter", 

265 "RuntimeInputContext", 

266]