Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / distributed / context_parallel / gated_delta_net.py: 26%

762 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +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"""Context parallel execution for Qwen3.5-style Gated DeltaNet layers.""" 

16 

17# pylint: disable=forbidden-backend-import,missing-public-type-hints 

18# pylint: disable=missing-public-docstring,not-callable 

19# PyTorch autograd.Function intentionally defines framework-specific signatures. 

20# pylint: disable=abstract-method,arguments-differ 

21from __future__ import annotations 

22 

23__all__ = ["GatedDeltaNetP2PCP", "GatedDeltaNetUlyssesCP"] 

24 

25import functools 

26import inspect 

27from typing import Any, Callable, NamedTuple, Optional 

28 

29import torch 

30 

31import torch.distributed as dist 

32from torch import nn 

33from torch.nn import functional as F 

34from torch.utils.checkpoint import checkpoint 

35 

36from hyper_parallel.components.functional.linear_attention_convolution import ( 

37 fused_causal_conv1d, 

38) 

39from hyper_parallel.components.functional.linear_attention_packed import ( 

40 PackedSequenceInfo, packed_causal_conv1d, resolve_packed_sequence, 

41) 

42 

43from hyper_parallel.core.context_parallel.context_parallel import ( 

44 _ensure_1d, 

45) 

46from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

47from hyper_parallel.core.dtensor.dtensor import DTensor 

48from hyper_parallel.components.modules.gated_delta_net import ( 

49 chunk_gated_delta_rule, 

50 is_triton_gdn_available, 

51 torch_chunk_gated_delta_rule, 

52) 

53from hyper_parallel.distributed.context_parallel.collectives import ulysses_head_to_seq, ulysses_seq_to_head 

54from hyper_parallel.core.utils import communication 

55 

56 

57_GDN_BACKENDS = frozenset({"eager", "triton", "ascendc"}) 

58 

59 

60def _global_peer_rank(cp_mesh: DeviceMesh, local_rank: int) -> int: 

61 """Map a CP-local rank index to its global distributed rank.""" 

62 return int(cp_mesh.rank_list[local_rank]) 

63 

64 

65def _slice_local_cp( 

66 tensor: torch.Tensor, 

67 dim: int, 

68 cp_rank: int, 

69 cp_size: int, 

70) -> torch.Tensor: 

71 """Return this CP rank's contiguous slice along ``dim``.""" 

72 dim_size = tensor.shape[dim] 

73 if dim_size % cp_size != 0: 

74 raise ValueError( 

75 f"linear attention CP expects dim size {dim_size} " 

76 f"to be divisible by cp_size {cp_size}." 

77 ) 

78 chunk = dim_size // cp_size 

79 return tensor.narrow(dim, cp_rank * chunk, chunk) 

80 

81 

82def _slice_qkv_local_cp( 

83 tensor: torch.Tensor, 

84 *, 

85 key_dim: int, 

86 value_dim: int, 

87 dim: int, 

88 cp_rank: int, 

89 cp_size: int, 

90) -> torch.Tensor: 

91 """Slice a fused ``[Q, K, V]`` tensor on the Q/K/V channel dimension.""" 

92 q, k, v = torch.split(tensor, [key_dim, key_dim, value_dim], dim=dim) 

93 return torch.cat( 

94 ( 

95 _slice_local_cp(q, dim, cp_rank, cp_size), 

96 _slice_local_cp(k, dim, cp_rank, cp_size), 

97 _slice_local_cp(v, dim, cp_rank, cp_size), 

98 ), 

99 dim=dim, 

100 ) 

101 

102 

103def _local_tensor_at_cp_boundary(tensor: torch.Tensor) -> torch.Tensor: 

104 """Return the local tensor carried by a CP-boundary input. 

105 

106 The first supported Qwen3.5 linear-attention CP path keeps decoder-layer 

107 activations as local sequence shards. If an upstream wrapper passes that 

108 shard as a DTensor, use its local tensor and continue with the same 

109 ``[B, S_local, H]`` boundary contract. 

110 """ 

111 if isinstance(tensor, DTensor): 

112 return tensor.to_local() 

113 return tensor 

114 

115 

116def _kv_groups(module: nn.Module) -> int: 

117 """Return the Q/K replication factor used by the official HF GDN layer.""" 

118 return int(module.num_v_heads // module.num_k_heads) 

119 

120 

121def _apply_local_padding_mask( 

122 hidden_states: torch.Tensor, 

123 attention_mask: Optional[torch.Tensor], 

124) -> torch.Tensor: 

125 """Apply the supported local 2-D padding mask without silent fallback.""" 

126 if attention_mask is None: 

127 return hidden_states 

128 if attention_mask.ndim != 2: 

129 raise NotImplementedError( 

130 "Qwen3.5 GDN Context Parallel supports only a local 2-D padding mask." 

131 ) 

132 if tuple(attention_mask.shape) != tuple(hidden_states.shape[:2]): 

133 raise ValueError( 

134 "Qwen3.5 GDN Context Parallel expects attention_mask shape " 

135 f"{tuple(hidden_states.shape[:2])}, got {tuple(attention_mask.shape)}." 

136 ) 

137 return hidden_states * attention_mask[:, :, None].to(hidden_states.dtype) 

138 

139 

140def _validate_gdn_activation(module: nn.Module) -> None: 

141 """Reject model variants whose Conv activation is not implemented here.""" 

142 activation = getattr(module, "activation", "silu") 

143 if activation not in ("silu", "swish"): 

144 raise NotImplementedError( 

145 "Qwen3.5 GDN Context Parallel currently supports only SiLU/Swish " 

146 f"Conv activation, got {activation!r}." 

147 ) 

148 

149 

150def _all_to_all_previous_rank_halo( 

151 tail: torch.Tensor, 

152 cp_mesh: DeviceMesh, 

153 cp_rank: int, 

154 cp_size: int, 

155) -> torch.Tensor: 

156 """Send a convolution halo only to the next rank using differentiable A2AV.""" 

157 if cp_size == 1: 

158 return torch.zeros_like(tail) 

159 

160 cp_group = cp_mesh.get_group() 

161 group_ranks = tuple(int(rank) for rank in dist.get_process_group_ranks(cp_group)) 

162 rank_list = tuple(int(rank) for rank in cp_mesh.rank_list) 

163 rank_to_group_index = {rank: index for index, rank in enumerate(group_ranks)} 

164 halo_width = tail.shape[1] 

165 

166 input_splits = [0] * cp_size 

167 exchange_input = tail.permute(1, 0, 2).contiguous() 

168 if cp_rank < cp_size - 1: 

169 input_splits[rank_to_group_index[rank_list[cp_rank + 1]]] = halo_width 

170 else: 

171 exchange_input = exchange_input[:0] 

172 

173 output_splits = [0] * cp_size 

174 if cp_rank > 0: 

175 output_splits[rank_to_group_index[rank_list[cp_rank - 1]]] = halo_width 

176 

177 exchange_output = communication.differentiable_all_to_all_single( 

178 exchange_input, 

179 input_splits, 

180 output_splits, 

181 group=cp_group, 

182 ) 

183 if cp_rank == 0: 

184 return torch.zeros_like(tail) + exchange_output.sum().to(tail.dtype) * 0 

185 return exchange_output.permute(1, 0, 2).contiguous() 

186 

187 

188def _causal_conv1d_with_cp_halo( 

189 mixed_qkv: torch.Tensor, 

190 conv1d: nn.Conv1d, 

191 cp_mesh: DeviceMesh, 

192 cp_rank: int, 

193 cp_size: int, 

194 sequence_info: Optional[PackedSequenceInfo] = None, 

195) -> torch.Tensor: 

196 """Run causal depthwise Conv1d with only the previous rank's boundary.""" 

197 kernel_size = conv1d.kernel_size[0] 

198 dilation = conv1d.dilation[0] 

199 halo_width = (kernel_size - 1) * dilation 

200 if sequence_info is not None: 

201 halo = None 

202 if halo_width and cp_size > 1: 

203 if mixed_qkv.shape[1] < halo_width: 

204 raise ValueError("Packed CP requires a local interval at least as long as its convolution halo.") 

205 halo = _all_to_all_previous_rank_halo( 

206 mixed_qkv[:, -halo_width:].contiguous(), cp_mesh, cp_rank, cp_size) 

207 return packed_causal_conv1d(mixed_qkv, conv1d, sequence_info, halo) 

208 if halo_width == 0 or cp_size == 1: 

209 conv_out = conv1d(mixed_qkv.transpose(1, 2)) 

210 return F.silu(conv_out[:, :, : mixed_qkv.shape[1]]).transpose(1, 2) 

211 

212 if mixed_qkv.shape[1] < halo_width: 

213 raise ValueError( 

214 "linear attention CP conv halo requires local_seq_len >= " 

215 f"{halo_width}, got {mixed_qkv.shape[1]}." 

216 ) 

217 

218 halo = _all_to_all_previous_rank_halo( 

219 mixed_qkv[:, -halo_width:, :].contiguous(), 

220 cp_mesh, 

221 cp_rank, 

222 cp_size, 

223 ) 

224 conv_input = torch.cat((halo, mixed_qkv), dim=1).transpose(1, 2) 

225 conv_out = F.conv1d( 

226 input=conv_input, 

227 weight=conv1d.weight, 

228 bias=conv1d.bias, 

229 stride=conv1d.stride, 

230 padding=0, 

231 dilation=conv1d.dilation, 

232 groups=conv1d.groups, 

233 ) 

234 return F.silu(conv_out).transpose(1, 2) 

235 

236 

237def _l2norm_torch(x: torch.Tensor, dim: int = -1, eps: float = 1e-6) -> torch.Tensor: 

238 """Match the pure torch GDN reference l2norm helper.""" 

239 return x * torch.rsqrt((x * x).sum(dim=dim, keepdim=True) + eps) 

240 

241 

242class _GDNPreparedChunks(NamedTuple): 

243 """Reusable chunk intermediates shared by state-summary CP modes.""" 

244 

245 initial_dtype: torch.dtype 

246 query: torch.Tensor 

247 key: torch.Tensor 

248 chunk_value: torch.Tensor 

249 g: torch.Tensor 

250 decay_mask: torch.Tensor 

251 k_cumdecay: torch.Tensor 

252 sequence_length: int 

253 total_sequence_length: int 

254 chunk_size: int 

255 

256 

257def _prepare_gdn_chunks_for_summary( 

258 query: torch.Tensor, 

259 key: torch.Tensor, 

260 value: torch.Tensor, 

261 g: torch.Tensor, 

262 beta: torch.Tensor, 

263 *, 

264 chunk_size: int = 64, 

265 use_qk_l2norm_in_kernel: bool = False, 

266) -> _GDNPreparedChunks: 

267 """Prepare GDN chunk intermediates shared by summary and local output.""" 

268 initial_dtype = query.dtype 

269 if use_qk_l2norm_in_kernel: 

270 query = _l2norm_torch(query, dim=-1, eps=1e-6) 

271 key = _l2norm_torch(key, dim=-1, eps=1e-6) 

272 

273 query, key, value, beta, g = [ 

274 x.transpose(1, 2).contiguous().to(torch.float32) 

275 for x in (query, key, value, beta, g) 

276 ] 

277 

278 sequence_length = key.shape[2] 

279 pad_size = (chunk_size - sequence_length % chunk_size) % chunk_size 

280 query = F.pad(query, (0, 0, 0, pad_size)) 

281 key = F.pad(key, (0, 0, 0, pad_size)) 

282 value = F.pad(value, (0, 0, 0, pad_size)) 

283 beta = F.pad(beta, (0, pad_size)) 

284 g = F.pad(g, (0, pad_size)) 

285 total_sequence_length = sequence_length + pad_size 

286 

287 query = query * (1 / (query.shape[-1] ** 0.5)) 

288 v_beta = value * beta.unsqueeze(-1) 

289 k_beta = key * beta.unsqueeze(-1) 

290 query, key, k_beta, v_beta = [ 

291 x.reshape(x.shape[0], x.shape[1], -1, chunk_size, x.shape[-1]) 

292 for x in (query, key, k_beta, v_beta) 

293 ] 

294 g = g.reshape(g.shape[0], g.shape[1], -1, chunk_size) 

295 

296 mask = torch.triu( 

297 torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=query.device), 

298 diagonal=0, 

299 ) 

300 g = g.cumsum(dim=-1) 

301 decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril() 

302 attn = -((k_beta @ key.transpose(-1, -2)) * decay_mask).masked_fill(mask, 0) 

303 for row_idx in range(1, chunk_size): 

304 row = attn[..., row_idx, :row_idx].clone() 

305 sub = attn[..., :row_idx, :row_idx].clone() 

306 attn[..., row_idx, :row_idx] = row + (row.unsqueeze(-1) * sub).sum(-2) 

307 attn = attn + torch.eye(chunk_size, dtype=attn.dtype, device=attn.device) 

308 

309 chunk_value = attn @ v_beta 

310 k_cumdecay = attn @ (k_beta * g.exp().unsqueeze(-1)) 

311 return _GDNPreparedChunks( 

312 initial_dtype=initial_dtype, 

313 query=query, 

314 key=key, 

315 chunk_value=chunk_value, 

316 g=g, 

317 decay_mask=decay_mask, 

318 k_cumdecay=k_cumdecay, 

319 sequence_length=sequence_length, 

320 total_sequence_length=total_sequence_length, 

321 chunk_size=chunk_size, 

322 ) 

323 

324 

325def _compute_gdn_state_summary_from_prepared( 

326 prepared: _GDNPreparedChunks, 

327) -> tuple[torch.Tensor, torch.Tensor]: 

328 """Compute ``state_out = M @ state_in + S`` from prepared GDN chunks.""" 

329 key = prepared.key 

330 batch_size, num_heads, _, _, k_head_dim = key.shape 

331 v_head_dim = prepared.chunk_value.shape[-1] 

332 eye = torch.eye(k_head_dim, device=key.device, dtype=torch.float32).reshape( 

333 1, 1, k_head_dim, k_head_dim 

334 ) 

335 state_ext = torch.zeros( 

336 batch_size, 

337 num_heads, 

338 k_head_dim, 

339 v_head_dim, 

340 device=key.device, 

341 dtype=torch.float32, 

342 ) 

343 transition = eye.expand(batch_size, num_heads, -1, -1).clone() 

344 

345 for chunk_idx in range(key.shape[2]): 

346 key_i = key[:, :, chunk_idx] 

347 value_i = prepared.chunk_value[:, :, chunk_idx] 

348 w_i = prepared.k_cumdecay[:, :, chunk_idx] 

349 g_i = prepared.g[:, :, chunk_idx] 

350 decay = g_i[:, :, -1].exp() 

351 key_decay = key_i * (g_i[:, :, -1, None] - g_i).exp()[..., None] 

352 

353 transition_i = ( 

354 decay[:, :, None, None] * eye 

355 - key_decay.transpose(-1, -2) @ w_i 

356 ) 

357 state_ext_i = key_decay.transpose(-1, -2) @ value_i 

358 state_ext = transition_i @ state_ext + state_ext_i 

359 transition = transition_i @ transition 

360 

361 return state_ext, transition 

362 

363 

364def _checkpoint_gdn_state_summary( 

365 prepared: _GDNPreparedChunks, 

366) -> tuple[torch.Tensor, torch.Tensor]: 

367 """Compute a state summary without retaining its per-chunk autograd graph.""" 

368 if not torch.is_grad_enabled(): 

369 return _compute_gdn_state_summary_from_prepared(prepared) 

370 

371 def recompute( 

372 key: torch.Tensor, 

373 chunk_value: torch.Tensor, 

374 g: torch.Tensor, 

375 k_cumdecay: torch.Tensor, 

376 ) -> tuple[torch.Tensor, torch.Tensor]: 

377 """Rebuild a prepared view from explicit checkpoint inputs.""" 

378 checkpoint_prepared = prepared._replace( 

379 key=key, 

380 chunk_value=chunk_value, 

381 g=g, 

382 k_cumdecay=k_cumdecay, 

383 ) 

384 return _compute_gdn_state_summary_from_prepared(checkpoint_prepared) 

385 

386 return checkpoint( 

387 recompute, 

388 prepared.key, 

389 prepared.chunk_value, 

390 prepared.g, 

391 prepared.k_cumdecay, 

392 use_reentrant=False, 

393 preserve_rng_state=False, 

394 ) 

395 

396 

397def _run_prepared_gdn_chunks( 

398 prepared: _GDNPreparedChunks, 

399 initial_state: Optional[torch.Tensor], 

400) -> torch.Tensor: 

401 """Run local GDN output using already prepared chunk intermediates.""" 

402 query = prepared.query 

403 key = prepared.key 

404 chunk_value = prepared.chunk_value 

405 batch_size, num_heads, _, _, k_head_dim = key.shape 

406 v_head_dim = chunk_value.shape[-1] 

407 recurrent_state = ( 

408 torch.zeros( 

409 batch_size, 

410 num_heads, 

411 k_head_dim, 

412 v_head_dim, 

413 device=chunk_value.device, 

414 dtype=chunk_value.dtype, 

415 ) 

416 if initial_state is None 

417 else initial_state.to(chunk_value) 

418 ) 

419 core_attn_out = torch.zeros_like(chunk_value) 

420 

421 for chunk_idx in range(0, prepared.total_sequence_length // prepared.chunk_size): 

422 q_i = query[:, :, chunk_idx] 

423 k_i = key[:, :, chunk_idx] 

424 v_i = chunk_value[:, :, chunk_idx] 

425 attn = q_i @ k_i.transpose(-1, -2) * prepared.decay_mask[:, :, chunk_idx] 

426 v_prime = prepared.k_cumdecay[:, :, chunk_idx] @ recurrent_state 

427 v_new = v_i - v_prime 

428 attn_inter = ( 

429 q_i * prepared.g[:, :, chunk_idx, :, None].exp() 

430 ) @ recurrent_state 

431 core_attn_out[:, :, chunk_idx] = attn_inter + attn @ v_new 

432 recurrent_state = ( 

433 recurrent_state * prepared.g[:, :, chunk_idx, -1, None, None].exp() 

434 + ( 

435 k_i 

436 * ( 

437 prepared.g[:, :, chunk_idx, -1, None] 

438 - prepared.g[:, :, chunk_idx] 

439 ).exp()[..., None] 

440 ).transpose(-1, -2) @ v_new 

441 ) 

442 

443 core_attn_out = core_attn_out.reshape( 

444 core_attn_out.shape[0], 

445 core_attn_out.shape[1], 

446 -1, 

447 core_attn_out.shape[-1], 

448 ) 

449 core_attn_out = core_attn_out[:, :, :prepared.sequence_length] 

450 return core_attn_out.transpose(1, 2).contiguous().to(prepared.initial_dtype) 

451 

452 

453class _RecvInitialStateP2PFunction(torch.autograd.Function): 

454 """Receive the recurrent initial state; send its gradient in backward.""" 

455 

456 @staticmethod 

457 def forward( # pylint: disable=arguments-differ 

458 ctx, 

459 anchor: torch.Tensor, 

460 cp_group, 

461 prev_rank: int, 

462 state_shape: tuple[int, ...], 

463 ) -> torch.Tensor: 

464 """Receive the initial state from the preceding CP rank.""" 

465 state = torch.empty(state_shape, device=anchor.device, dtype=torch.float32) 

466 dist.recv(state, src=prev_rank, group=cp_group) 

467 ctx.cp_group = cp_group 

468 ctx.prev_rank = prev_rank 

469 return state 

470 

471 @staticmethod 

472 def backward(ctx, grad_state: Optional[torch.Tensor]): 

473 """Return the incoming state gradient to the preceding CP rank.""" 

474 if grad_state is None: 

475 raise RuntimeError("linear attention P2P backward missing initial-state grad.") 

476 dist.send(grad_state.contiguous(), dst=ctx.prev_rank, group=ctx.cp_group) 

477 return None, None, None, None 

478 

479 

480class _SendFinalStateP2PFunction(torch.autograd.Function): 

481 """Send the recurrent final state; receive its gradient in backward.""" 

482 

483 @staticmethod 

484 def forward( # pylint: disable=arguments-differ 

485 ctx, 

486 final_state: torch.Tensor, 

487 cp_group, 

488 next_rank: int, 

489 ) -> torch.Tensor: 

490 """Send the final state to the succeeding CP rank.""" 

491 dist.send(final_state.contiguous(), dst=next_rank, group=cp_group) 

492 ctx.cp_group = cp_group 

493 ctx.next_rank = next_rank 

494 ctx.state_shape = tuple(final_state.shape) 

495 ctx.state_dtype = final_state.dtype 

496 return final_state.new_zeros(()) 

497 

498 @staticmethod 

499 def backward(ctx, grad_token: torch.Tensor): 

500 """Receive the final-state gradient from the succeeding CP rank.""" 

501 grad_state = torch.empty( 

502 ctx.state_shape, 

503 device=grad_token.device, 

504 dtype=ctx.state_dtype, 

505 ) 

506 dist.recv(grad_state, src=ctx.next_rank, group=ctx.cp_group) 

507 return grad_state, None, None 

508 

509 

510def _apply_gdn_state_summary( 

511 state_ext: torch.Tensor, 

512 transition: torch.Tensor, 

513 initial_state: Optional[torch.Tensor], 

514) -> torch.Tensor: 

515 """Apply ``state_out = M @ state_in + S`` to an incoming GDN state.""" 

516 if initial_state is None: 

517 return state_ext 

518 return transition @ initial_state.to(transition) + state_ext 

519 

520 

521def _gdn_state_p2p_summary( 

522 query: torch.Tensor, 

523 key: torch.Tensor, 

524 value: torch.Tensor, 

525 g: torch.Tensor, 

526 beta: torch.Tensor, 

527 cp_mesh: DeviceMesh, 

528 cp_rank: int, 

529 cp_size: int, 

530 *, 

531 chunk_size: int, 

532 use_qk_l2norm_in_kernel: bool, 

533 sequence_info: Optional[PackedSequenceInfo] = None, 

534) -> torch.Tensor: 

535 """Run local GDN with an affine-summary state wavefront. 

536 

537 Every rank prepares its local chunks and state transition in parallel. 

538 The rank-ordered critical path then contains only ``M @ state + S`` and 

539 the small state transfer. Token outputs retain the ordinary PyTorch graph, 

540 while the two custom autograd boundaries reverse the state communication. 

541 """ 

542 if sequence_info is not None: 

543 cp_group = None if cp_mesh is None else cp_mesh.get_group() 

544 incoming = None 

545 if cp_rank > 0: 

546 incoming = _RecvInitialStateP2PFunction.apply( 

547 query, cp_group, _global_peer_rank(cp_mesh, cp_rank - 1), 

548 (1, value.shape[2], query.shape[-1], value.shape[-1])) 

549 output, finals = torch_chunk_gated_delta_rule( 

550 query, key, value, g, beta, chunk_size=chunk_size, 

551 initial_state=sequence_info.first_state(incoming), output_final_state=True, 

552 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, cu_seqlens=sequence_info.local_offsets) 

553 if incoming is not None: 

554 output = output + incoming.sum().to(output.dtype) * 0 

555 if cp_rank < cp_size - 1: 

556 token = _SendFinalStateP2PFunction.apply( 

557 finals[-1:].contiguous(), cp_group, _global_peer_rank(cp_mesh, cp_rank + 1)) 

558 output = output + token.to(output.dtype) * 0 

559 return output 

560 if cp_size == 1: 

561 core_attn_out, _ = torch_chunk_gated_delta_rule( 

562 query, 

563 key, 

564 value, 

565 g=g, 

566 beta=beta, 

567 initial_state=None, 

568 output_final_state=False, 

569 chunk_size=chunk_size, 

570 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

571 ) 

572 return core_attn_out 

573 

574 cp_group = cp_mesh.get_group() 

575 prev_rank = _global_peer_rank(cp_mesh, cp_rank - 1) if cp_rank > 0 else -1 

576 next_rank = _global_peer_rank(cp_mesh, cp_rank + 1) if cp_rank < cp_size - 1 else -1 

577 state_shape = (query.shape[0], value.shape[2], query.shape[3], value.shape[3]) 

578 

579 prepared = _prepare_gdn_chunks_for_summary( 

580 query, 

581 key, 

582 value, 

583 g, 

584 beta, 

585 chunk_size=chunk_size, 

586 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

587 ) 

588 state_ext = None 

589 transition = None 

590 if cp_rank < cp_size - 1: 

591 state_ext, transition = _checkpoint_gdn_state_summary(prepared) 

592 

593 initial_state = None 

594 if cp_rank > 0: 

595 initial_state = _RecvInitialStateP2PFunction.apply( 

596 query, 

597 cp_group, 

598 prev_rank, 

599 state_shape, 

600 ) 

601 

602 send_token = None 

603 if cp_rank < cp_size - 1: 

604 final_state = _apply_gdn_state_summary( 

605 state_ext, 

606 transition, 

607 initial_state, 

608 ) 

609 send_token = _SendFinalStateP2PFunction.apply(final_state, cp_group, next_rank) 

610 

611 core_attn_out = _run_prepared_gdn_chunks(prepared, initial_state) 

612 if send_token is not None: 

613 core_attn_out = core_attn_out + send_token.to(core_attn_out.dtype) * 0 

614 

615 return core_attn_out 

616 

617 

618class _GDNStateP2PTritonFunction(torch.autograd.Function): 

619 """Pipeline fused affine GDN states over sequence-sharded CP ranks.""" 

620 

621 @staticmethod 

622 def forward( # pylint: disable=arguments-differ,too-many-locals 

623 ctx, 

624 query: torch.Tensor, 

625 key: torch.Tensor, 

626 value: torch.Tensor, 

627 g: torch.Tensor, 

628 beta: torch.Tensor, 

629 cp_rank: int, 

630 cp_size: int, 

631 cp_group, 

632 prev_rank: int, 

633 next_rank: int, 

634 sequence_info: Optional[PackedSequenceInfo], 

635 ) -> torch.Tensor: 

636 """Run fused local GDN and forward its affine state across CP ranks.""" 

637 from hyper_parallel.components.functional.gated_delta_net import ( # pylint: disable=import-outside-toplevel 

638 chunk_gated_delta_rule_fwd_apply_state_saved, 

639 chunk_gated_delta_rule_fwd_output_saved, 

640 chunk_gated_delta_rule_fwd_prepare_saved, 

641 ) 

642 from hyper_parallel.components.functional.gated_delta_net_state_summary import ( # pylint: disable=import-outside-toplevel 

643 apply_gdn_state_summary, 

644 chunk_gated_delta_rule_state_summary_fwd, 

645 ) 

646 

647 receive_previous = cp_rank > 0 and (sequence_info is None or sequence_info.continues_previous) 

648 send_next = cp_rank < cp_size - 1 and (sequence_info is None or sequence_info.continues_next) 

649 initial_state = None 

650 recv_buffer = None 

651 recv_handle = None 

652 if receive_previous: 

653 recv_buffer = torch.empty( 

654 (query.shape[0], query.shape[2], query.shape[3], value.shape[3]), 

655 device=query.device, 

656 dtype=torch.float32, 

657 ) 

658 recv_handle = dist.irecv(recv_buffer, src=prev_rank, group=cp_group) 

659 

660 cu_seqlens = None if sequence_info is None else sequence_info.materialize(query.device, 64).cu_seqlens 

661 ( 

662 query_norm, 

663 key_norm, 

664 _, 

665 _, 

666 g_cumsum, 

667 matrix_a, 

668 w, 

669 u, 

670 scale, 

671 ) = chunk_gated_delta_rule_fwd_prepare_saved( 

672 query, 

673 key, 

674 value, 

675 g, 

676 beta, 

677 use_qk_l2norm_in_kernel=False, 

678 cu_seqlens=cu_seqlens, 

679 ) 

680 

681 state_ext = None 

682 transition = None 

683 if send_next: 

684 tail = slice(None) if sequence_info is None else sequence_info.tail 

685 state_ext, transition = chunk_gated_delta_rule_state_summary_fwd( 

686 key_norm[:, tail], w[:, tail], u[:, tail], g_cumsum[:, tail], 

687 output_transition=sequence_info is None or sequence_info.transfer_active, 

688 ) 

689 

690 if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active): 

691 recv_handle.wait() 

692 initial_state = recv_buffer 

693 recv_handle = None 

694 

695 send_buffer = None 

696 send_handle = None 

697 if send_next: 

698 send_buffer = apply_gdn_state_summary( 

699 state_ext, 

700 transition, 

701 initial_state if transition is not None else None, 

702 ).contiguous() 

703 send_handle = dist.isend(send_buffer, dst=next_rank, group=cp_group) 

704 

705 if recv_handle is not None: 

706 recv_handle.wait() 

707 initial_state = recv_buffer 

708 h, v_new, _ = chunk_gated_delta_rule_fwd_apply_state_saved( 

709 key_norm, 

710 g_cumsum, 

711 w, 

712 u, 

713 initial_state=initial_state if sequence_info is None else sequence_info.first_state(initial_state), 

714 output_final_state=False, 

715 cu_seqlens=cu_seqlens, 

716 ) 

717 output = chunk_gated_delta_rule_fwd_output_saved( 

718 query_norm, 

719 key_norm, 

720 g_cumsum, 

721 h, 

722 v_new, 

723 scale, 

724 cu_seqlens=cu_seqlens, 

725 ).to(query.dtype) 

726 

727 if send_handle is not None: 

728 send_handle.wait() 

729 del send_buffer 

730 

731 empty = query.new_empty(0) 

732 ctx.save_for_backward( 

733 query_norm, 

734 key_norm, 

735 value, 

736 g_cumsum, 

737 beta, 

738 matrix_a, 

739 initial_state if initial_state is not None else empty, 

740 transition if transition is not None else empty, 

741 cu_seqlens if cu_seqlens is not None else query.new_empty(0, dtype=torch.int64), 

742 ) 

743 ctx.sequence_info = sequence_info 

744 ctx.has_initial_state = initial_state is not None 

745 ctx.has_transition = transition is not None 

746 ctx.receive_previous = receive_previous 

747 ctx.send_next = send_next 

748 ctx.cp_rank = cp_rank 

749 ctx.cp_size = cp_size 

750 ctx.cp_group = cp_group 

751 ctx.prev_rank = prev_rank 

752 ctx.next_rank = next_rank 

753 ctx.scale = scale 

754 return output 

755 

756 @staticmethod 

757 def backward(ctx, grad_output: torch.Tensor): # pylint: disable=too-many-locals 

758 """Backpropagate local GDN tensors and the state gradient wavefront.""" 

759 from hyper_parallel.components.functional.gated_delta_net import ( # pylint: disable=import-outside-toplevel 

760 chunk_gated_delta_rule_bwd_finish_saved, 

761 chunk_gated_delta_rule_bwd_prepare_saved, 

762 chunk_gated_delta_rule_bwd_state_saved, 

763 ) 

764 from hyper_parallel.components.functional.gated_delta_net_state_summary import ( # pylint: disable=import-outside-toplevel 

765 apply_gdn_state_gradient_summary, 

766 chunk_gated_delta_rule_state_gradient_summary_bwd, 

767 ) 

768 

769 ( 

770 query, 

771 key, 

772 value, 

773 g_cumsum, 

774 beta, 

775 matrix_a, 

776 initial_state, 

777 transition, 

778 saved_cu_seqlens, 

779 ) = ctx.saved_tensors 

780 sequence_info = ctx.sequence_info 

781 cu_seqlens = None if sequence_info is None else saved_cu_seqlens 

782 if not ctx.has_initial_state: 

783 initial_state = None 

784 

785 grad_final_state = None 

786 recv_handle = None 

787 if ctx.send_next: 

788 recv_buffer = torch.empty( 

789 (query.shape[0], query.shape[2], query.shape[3], value.shape[3]), 

790 device=grad_output.device, 

791 dtype=torch.float32, 

792 ) 

793 recv_handle = dist.irecv( 

794 recv_buffer, 

795 src=ctx.next_rank, 

796 group=ctx.cp_group, 

797 ) 

798 

799 kernel_initial = initial_state if sequence_info is None else sequence_info.first_state(initial_state) 

800 w, h, v_new, dv = chunk_gated_delta_rule_bwd_prepare_saved( 

801 query, 

802 key, 

803 value, 

804 g_cumsum, 

805 beta, 

806 matrix_a, 

807 kernel_initial, 

808 grad_output, 

809 ctx.scale, 

810 cu_seqlens=cu_seqlens, 

811 ) 

812 

813 grad_state_ext = None 

814 if ctx.receive_previous: 

815 head = slice(None) if sequence_info is None else sequence_info.head 

816 grad_state_ext = chunk_gated_delta_rule_state_gradient_summary_bwd( 

817 query[:, head], key[:, head], w[:, head], g_cumsum[:, head], 

818 grad_output[:, head], dv[:, head], ctx.scale) 

819 

820 if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active): 

821 recv_handle.wait() 

822 grad_final_state = recv_buffer 

823 recv_handle = None 

824 

825 send_buffer = None 

826 send_handle = None 

827 if ctx.receive_previous: 

828 send_buffer = apply_gdn_state_gradient_summary( 

829 grad_state_ext, 

830 transition, 

831 grad_final_state if ctx.has_transition else None, 

832 ).contiguous() 

833 send_handle = dist.isend( 

834 send_buffer, 

835 dst=ctx.prev_rank, 

836 group=ctx.cp_group, 

837 ) 

838 

839 if recv_handle is not None: 

840 recv_handle.wait() 

841 grad_final_state = recv_buffer 

842 kernel_grad_final = grad_final_state if sequence_info is None else sequence_info.last_state(grad_final_state) 

843 dh, _, dv = chunk_gated_delta_rule_bwd_state_saved( 

844 query, 

845 key, 

846 g_cumsum, 

847 w, 

848 kernel_initial, 

849 kernel_grad_final, 

850 grad_output, 

851 dv, 

852 ctx.scale, 

853 cu_seqlens=cu_seqlens, 

854 ) 

855 empty = query.new_empty(0) 

856 dq, dk, dv, dg, dbeta = chunk_gated_delta_rule_bwd_finish_saved( 

857 query, 

858 key, 

859 query, 

860 key, 

861 value, 

862 g_cumsum, 

863 beta, 

864 matrix_a, 

865 w, 

866 h, 

867 v_new, 

868 dv, 

869 grad_output, 

870 dh, 

871 empty, 

872 empty, 

873 ctx.scale, 

874 use_qk_l2norm_in_kernel=False, 

875 cu_seqlens=cu_seqlens, 

876 ) 

877 

878 if send_handle is not None: 

879 send_handle.wait() 

880 return dq, dk, dv, dg, dbeta, None, None, None, None, None, None 

881 

882 

883def _gdn_state_p2p_triton( 

884 query: torch.Tensor, 

885 key: torch.Tensor, 

886 value: torch.Tensor, 

887 g: torch.Tensor, 

888 beta: torch.Tensor, 

889 cp_mesh: DeviceMesh, 

890 cp_rank: int, 

891 cp_size: int, 

892 sequence_info: Optional[PackedSequenceInfo] = None, 

893) -> torch.Tensor: 

894 """Run fused local GDN with an affine state wavefront.""" 

895 if cp_size == 1: 

896 output, _ = chunk_gated_delta_rule( 

897 query, 

898 key, 

899 value, 

900 g=g, 

901 beta=beta, 

902 output_final_state=False, 

903 use_qk_l2norm_in_kernel=True, 

904 backend="triton", 

905 cu_seqlens=None if sequence_info is None else sequence_info.materialize(query.device, 64).cu_seqlens, 

906 ) 

907 return output 

908 

909 query = _l2norm_torch(query) 

910 key = _l2norm_torch(key) 

911 prev_rank = _global_peer_rank(cp_mesh, cp_rank - 1) if cp_rank > 0 else -1 

912 next_rank = ( 

913 _global_peer_rank(cp_mesh, cp_rank + 1) if cp_rank < cp_size - 1 else -1 

914 ) 

915 return _GDNStateP2PTritonFunction.apply( 

916 query, 

917 key, 

918 value, 

919 g, 

920 beta, 

921 cp_rank, 

922 cp_size, 

923 cp_mesh.get_group(), 

924 prev_rank, 

925 next_rank, 

926 sequence_info, 

927 ) 

928 

929 

930def _differentiable_all_to_all_shard( 

931 tensor: torch.Tensor, 

932 device_mesh: DeviceMesh, 

933 *, 

934 split_dim: int, 

935 concat_dim: int, 

936) -> torch.Tensor: 

937 """Split local data on ``split_dim`` and concatenate peers on ``concat_dim``. 

938 

939 This is the local-tensor equivalent of DTensor ``Shard(concat_dim) -> 

940 Shard(split_dim)`` redistribution for a 1-D mesh. It uses a 

941 differentiable all-to-all directly to avoid wrapping each activation in a 

942 temporary DTensor. 

943 """ 

944 split_count = device_mesh.size() 

945 if split_count == 1: 

946 return tensor 

947 

948 original_shape = tuple(tensor.shape) 

949 dim_size = original_shape[split_dim] 

950 if dim_size % split_count != 0: 

951 raise ValueError( 

952 f"linear attention all-to-all split dim {split_dim} with size " 

953 f"{dim_size} must be divisible by cp_size {split_count}." 

954 ) 

955 

956 split_size = dim_size // split_count 

957 final_shape = list(original_shape) 

958 if split_dim != concat_dim: 

959 final_shape[split_dim] = split_size 

960 final_shape[concat_dim] = final_shape[concat_dim] * split_count 

961 final_shape = tuple(final_shape) 

962 

963 reshape_dims = list(original_shape) 

964 reshape_dims[split_dim] = split_count 

965 reshape_dims.insert(split_dim + 1, split_size) 

966 

967 trans_dims = list(range(len(reshape_dims))) 

968 trans_dims.remove(split_dim) 

969 trans_dims.insert(0, split_dim) 

970 

971 a2a_input = tensor.reshape(reshape_dims).permute(trans_dims).contiguous() 

972 reshape_shape = list(a2a_input.shape) 

973 reshape_shape[0] = reshape_shape[0] * reshape_shape[1] 

974 reshape_shape.pop(1) 

975 a2a_input = a2a_input.reshape(reshape_shape) 

976 

977 a2a_input = a2a_input.contiguous() 

978 split_len = a2a_input.shape[0] // split_count 

979 input_splits = [split_len] * split_count 

980 output_splits = [split_len] * split_count 

981 output = communication.differentiable_all_to_all_single( 

982 a2a_input, 

983 input_splits, 

984 output_splits, 

985 group=device_mesh.get_group(), 

986 ) 

987 

988 output_reshape = list(output.shape) 

989 output_reshape[0] = split_count 

990 output_reshape.insert(1, output.shape[0] // split_count) 

991 

992 out_trans_dims = list(range(len(output_reshape))) 

993 first_dim = out_trans_dims.pop(0) 

994 if concat_dim >= len(out_trans_dims): 

995 out_trans_dims.append(first_dim) 

996 else: 

997 out_trans_dims.insert(concat_dim, first_dim) 

998 

999 final_output = output.reshape(output_reshape).permute(out_trans_dims).contiguous() 

1000 final_reshape = list(final_output.shape) 

1001 if concat_dim < len(final_reshape) - 1: 

1002 final_reshape[concat_dim] = ( 

1003 final_reshape[concat_dim] * final_reshape[concat_dim + 1] 

1004 ) 

1005 final_reshape.pop(concat_dim + 1) 

1006 

1007 return final_output.reshape(final_reshape).view(final_shape) 

1008 

1009 

1010class GatedDeltaNetUlyssesCP(nn.Module): 

1011 """Pure-Ulysses CP execution wrapper for a Qwen3.5 Gated DeltaNet module. 

1012 

1013 Parameters stay owned by the original module. The wrapper only changes the 

1014 execution layout: 

1015 

1016 ``[B, S_local, full_heads] -> [B, S_full, local_heads] -> 

1017 [B, S_local, full_heads]``. 

1018 

1019 ``backend`` selects both the local attention core and convolution for dense 

1020 and packed inputs. 

1021 """ 

1022 

1023 def __init__( 

1024 self, 

1025 module: nn.Module, 

1026 device_mesh: Optional[DeviceMesh], 

1027 *, 

1028 backend: str = "eager", 

1029 chunk_size: int = 64, 

1030 ) -> None: 

1031 """Initialize the shared attention and convolution backend.""" 

1032 super().__init__() 

1033 # The target owns this executor, so keep its back-reference outside the module tree. 

1034 object.__setattr__(self, "module", module) 

1035 backend = backend.lower() 

1036 if backend not in _GDN_BACKENDS: 

1037 raise ValueError( 

1038 f"unsupported GDN backend {backend!r}; " 

1039 f"expected one of {sorted(_GDN_BACKENDS)}." 

1040 ) 

1041 if chunk_size <= 0: 

1042 raise ValueError(f"chunk_size must be positive, got {chunk_size}.") 

1043 if backend in ("triton", "ascendc") and chunk_size != 64: 

1044 raise NotImplementedError("Fused GDN backends require chunk_size=64.") 

1045 self.gdn_backend = backend 

1046 self.chunk_size = chunk_size 

1047 self.cp_mesh = None if device_mesh is None else _ensure_1d(device_mesh) 

1048 self.cp_size = 1 if self.cp_mesh is None else self.cp_mesh.size() 

1049 self.cp_rank = 0 if self.cp_mesh is None else self.cp_mesh.get_local_rank() 

1050 self.seq_dim = 1 

1051 self.head_dim = 2 

1052 self._validate_module() 

1053 if self.gdn_backend != "eager": 

1054 _validate_gdn_activation(self.module) 

1055 self._validate_fused_conv() 

1056 

1057 def _validate_module(self) -> None: 

1058 """Validate the wrapped GDN module and Ulysses layout constraints.""" 

1059 required = ( 

1060 "in_proj_qkv", "in_proj_z", "in_proj_b", "in_proj_a", 

1061 "conv1d", "A_log", "dt_bias", "norm", "out_proj", 

1062 "num_k_heads", "num_v_heads", "head_k_dim", "head_v_dim", 

1063 "key_dim", "value_dim", "conv_dim", 

1064 ) 

1065 missing = [name for name in required if not hasattr(self.module, name)] 

1066 if missing: 

1067 raise TypeError( 

1068 "Qwen3.5 GDN layer is missing required attributes: " 

1069 + ", ".join(missing) 

1070 ) 

1071 if self.module.num_k_heads % self.cp_size != 0: 

1072 raise ValueError( 

1073 f"linear attention num_k_heads ({self.module.num_k_heads}) must be " 

1074 f"divisible by cp_size ({self.cp_size}) for Ulysses CP." 

1075 ) 

1076 if self.module.num_v_heads % self.cp_size != 0: 

1077 raise ValueError( 

1078 f"linear attention num_v_heads ({self.module.num_v_heads}) must be " 

1079 f"divisible by cp_size ({self.cp_size}) for Ulysses CP." 

1080 ) 

1081 if self.module.num_v_heads % self.module.num_k_heads: 

1082 raise ValueError("GDN num_v_heads must be divisible by num_k_heads.") 

1083 _validate_gdn_activation(self.module) 

1084 

1085 def _validate_fused_conv(self) -> None: 

1086 """Reject source convolutions whose semantics differ from a causal depthwise kernel.""" 

1087 convolution = self.module.conv1d 

1088 if convolution.stride != (1,) or convolution.dilation != (1,): 

1089 raise ValueError("GDN fused convolution requires stride=1 and dilation=1.") 

1090 if convolution.groups != convolution.in_channels or convolution.out_channels != convolution.in_channels: 

1091 raise ValueError("GDN fused convolution requires equal input/output channels and depthwise groups.") 

1092 if convolution.padding != (convolution.kernel_size[0] - 1,) or convolution.padding_mode != "zeros": 

1093 raise ValueError("GDN fused convolution requires causal zero padding.") 

1094 

1095 def _seq_to_head(self, tensor: torch.Tensor) -> torch.Tensor: 

1096 if self.cp_size == 1: 

1097 return tensor 

1098 return _differentiable_all_to_all_shard( 

1099 tensor, 

1100 self.cp_mesh, 

1101 split_dim=self.head_dim, 

1102 concat_dim=self.seq_dim, 

1103 ) 

1104 

1105 def _head_to_seq(self, tensor: torch.Tensor) -> torch.Tensor: 

1106 if self.cp_size == 1: 

1107 return tensor 

1108 return _differentiable_all_to_all_shard( 

1109 tensor, 

1110 self.cp_mesh, 

1111 split_dim=self.seq_dim, 

1112 concat_dim=self.head_dim, 

1113 ) 

1114 

1115 def _seq_to_head_qkvba( 

1116 self, 

1117 q_proj: torch.Tensor, 

1118 k_proj: torch.Tensor, 

1119 v_proj: torch.Tensor, 

1120 b: torch.Tensor, 

1121 a: torch.Tensor, 

1122 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: 

1123 """Pack Q/K/V/B/A by CP rank and run a single seq-to-head all-to-all.""" 

1124 if self.cp_size == 1: 

1125 return q_proj, k_proj, v_proj, b, a 

1126 

1127 base = self.module 

1128 local_key_dim = base.key_dim // self.cp_size 

1129 local_value_dim = base.value_dim // self.cp_size 

1130 local_num_v_heads = base.num_v_heads // self.cp_size 

1131 

1132 q_chunks = torch.split(q_proj, local_key_dim, dim=-1) 

1133 k_chunks = torch.split(k_proj, local_key_dim, dim=-1) 

1134 v_chunks = torch.split(v_proj, local_value_dim, dim=-1) 

1135 b_chunks = torch.split(b, local_num_v_heads, dim=-1) 

1136 a_chunks = torch.split(a, local_num_v_heads, dim=-1) 

1137 rank_major_chunks = [ 

1138 torch.cat(chunks, dim=-1) 

1139 for chunks in zip(q_chunks, k_chunks, v_chunks, b_chunks, a_chunks) 

1140 ] 

1141 packed = torch.cat(rank_major_chunks, dim=-1).contiguous() 

1142 packed = self._seq_to_head(packed) 

1143 return torch.split( 

1144 packed, 

1145 [ 

1146 local_key_dim, 

1147 local_key_dim, 

1148 local_value_dim, 

1149 local_num_v_heads, 

1150 local_num_v_heads, 

1151 ], 

1152 dim=-1, 

1153 ) 

1154 

1155 def _local_conv_weight(self) -> torch.Tensor: 

1156 return _slice_qkv_local_cp( 

1157 self.module.conv1d.weight, 

1158 key_dim=self.module.key_dim, 

1159 value_dim=self.module.value_dim, 

1160 dim=0, 

1161 cp_rank=self.cp_rank, 

1162 cp_size=self.cp_size, 

1163 ) 

1164 

1165 def _local_conv_bias(self) -> Optional[torch.Tensor]: 

1166 bias = self.module.conv1d.bias 

1167 if bias is None: 

1168 return None 

1169 return _slice_qkv_local_cp( 

1170 bias, 

1171 key_dim=self.module.key_dim, 

1172 value_dim=self.module.value_dim, 

1173 dim=0, 

1174 cp_rank=self.cp_rank, 

1175 cp_size=self.cp_size, 

1176 ) 

1177 

1178 def _convolve_exchanged_qkv(self, q_proj, k_proj, v_proj, exchanged_info, full_seq_len, local_conv_dim): 

1179 """Apply head-local ShortConv with the restored document boundaries.""" 

1180 base = self.module 

1181 mixed_qkv = torch.cat((q_proj, k_proj, v_proj), dim=-1) 

1182 weight, bias = self._local_conv_weight(), self._local_conv_bias() 

1183 if exchanged_info is not None: 

1184 return packed_causal_conv1d( 

1185 mixed_qkv, base.conv1d, exchanged_info, 

1186 weight=weight, bias=bias, backend=self.gdn_backend, 

1187 ) 

1188 if self.gdn_backend != "eager": 

1189 return fused_causal_conv1d( 

1190 mixed_qkv, weight, bias, backend=self.gdn_backend, 

1191 dilation=base.conv1d.dilation[0], 

1192 ) 

1193 conv_out = F.conv1d( 

1194 input=mixed_qkv.transpose(1, 2), 

1195 weight=weight, 

1196 bias=bias, 

1197 stride=base.conv1d.stride, 

1198 padding=base.conv1d.padding, 

1199 dilation=base.conv1d.dilation, 

1200 groups=local_conv_dim, 

1201 ) 

1202 return F.silu(conv_out[:, :, :full_seq_len]).transpose(1, 2) 

1203 

1204 def forward( 

1205 self, 

1206 hidden_states: torch.Tensor, 

1207 cache_params: Optional[object] = None, 

1208 attention_mask: Optional[torch.Tensor] = None, 

1209 **kwargs, 

1210 ) -> torch.Tensor: 

1211 """Run Gated DeltaNet with pure Ulysses context parallel.""" 

1212 if cache_params is not None: 

1213 raise NotImplementedError("GDN Ulysses CP currently supports training without cache only.") 

1214 hidden_states = _local_tensor_at_cp_boundary(hidden_states) 

1215 is_flat_input = hidden_states.ndim == 2 

1216 if is_flat_input: 

1217 hidden_states = hidden_states.unsqueeze(0) 

1218 sequence_info = resolve_packed_sequence( 

1219 kwargs, hidden_states.shape[1], cp_rank=self.cp_rank, cp_size=self.cp_size) 

1220 if sequence_info is not None and hidden_states.shape[0] != 1: 

1221 raise ValueError("Packed GDN requires batch dimension one.") 

1222 

1223 base = self.module 

1224 hidden_states = _apply_local_padding_mask(hidden_states, attention_mask) 

1225 

1226 bsz, local_seq_len, _ = hidden_states.shape 

1227 mixed_qkv = base.in_proj_qkv(hidden_states) 

1228 z = base.in_proj_z(hidden_states).reshape( 

1229 bsz, 

1230 local_seq_len, 

1231 base.num_v_heads, 

1232 base.head_v_dim, 

1233 ) 

1234 b = base.in_proj_b(hidden_states) 

1235 a = base.in_proj_a(hidden_states) 

1236 

1237 q_proj, k_proj, v_proj = torch.split( 

1238 mixed_qkv, 

1239 [base.key_dim, base.key_dim, base.value_dim], 

1240 dim=-1, 

1241 ) 

1242 q_proj, k_proj, v_proj, b, a = self._seq_to_head_qkvba( 

1243 q_proj, k_proj, v_proj, b, a 

1244 ) 

1245 

1246 full_seq_len = q_proj.shape[1] 

1247 local_key_dim = base.key_dim // self.cp_size 

1248 local_value_dim = base.value_dim // self.cp_size 

1249 local_num_k_heads = base.num_k_heads // self.cp_size 

1250 local_num_v_heads = base.num_v_heads // self.cp_size 

1251 local_conv_dim = local_key_dim * 2 + local_value_dim 

1252 

1253 exchanged_info = None if sequence_info is None else sequence_info.ulysses(self.cp_size, self.cp_rank) 

1254 mixed_qkv = self._convolve_exchanged_qkv( 

1255 q_proj, k_proj, v_proj, exchanged_info, full_seq_len, local_conv_dim, 

1256 ) 

1257 

1258 query, key, value = torch.split( 

1259 mixed_qkv, 

1260 [local_key_dim, local_key_dim, local_value_dim], 

1261 dim=-1, 

1262 ) 

1263 query = query.reshape(bsz, full_seq_len, local_num_k_heads, base.head_k_dim) 

1264 key = key.reshape(bsz, full_seq_len, local_num_k_heads, base.head_k_dim) 

1265 value = value.reshape(bsz, full_seq_len, local_num_v_heads, base.head_v_dim) 

1266 

1267 a_log = _slice_local_cp(base.A_log, 0, self.cp_rank, self.cp_size) 

1268 dt_bias = _slice_local_cp(base.dt_bias, 0, self.cp_rank, self.cp_size) 

1269 beta = b.sigmoid() 

1270 g = -a_log.float().exp() * F.softplus(a.float() + dt_bias) 

1271 

1272 kv_groups = _kv_groups(base) 

1273 if kv_groups > 1: 

1274 query = query.repeat_interleave(kv_groups, dim=2) 

1275 key = key.repeat_interleave(kv_groups, dim=2) 

1276 

1277 cu_seqlens = None 

1278 if exchanged_info is not None: 

1279 cu_seqlens = (exchanged_info.local_offsets if self.gdn_backend == "ascendc" else 

1280 exchanged_info.materialize(query.device, self.chunk_size).cu_seqlens) 

1281 core_attn_out, _ = chunk_gated_delta_rule( 

1282 query, 

1283 key, 

1284 value, 

1285 g=g, 

1286 beta=beta, 

1287 initial_state=None, 

1288 output_final_state=False, 

1289 chunk_size=self.chunk_size, 

1290 use_qk_l2norm_in_kernel=True, 

1291 backend=self.gdn_backend, 

1292 cu_seqlens=cu_seqlens, 

1293 ) 

1294 

1295 core_attn_out = self._head_to_seq(core_attn_out) 

1296 core_attn_out = core_attn_out.reshape(-1, base.head_v_dim) 

1297 z_flat = z.reshape(-1, base.head_v_dim) 

1298 core_attn_out = base.norm(core_attn_out, z_flat) 

1299 core_attn_out = core_attn_out.reshape(bsz, local_seq_len, base.value_dim) 

1300 if hasattr(base, "out_proj_input"): 

1301 core_attn_out = base.out_proj_input(core_attn_out) 

1302 output = base.out_proj(core_attn_out) 

1303 return output.squeeze(0) if is_flat_input else output 

1304 

1305 

1306class GatedDeltaNetP2PCP(nn.Module): 

1307 """Sequence-sharded GDN CP with an affine-summary state wavefront.""" 

1308 

1309 def __init__( 

1310 self, 

1311 module: nn.Module, 

1312 device_mesh: DeviceMesh, 

1313 *, 

1314 backend: str = "eager", 

1315 chunk_size: int = 64, 

1316 ): 

1317 super().__init__() 

1318 # The target owns this executor, so keep its back-reference outside the module tree. 

1319 object.__setattr__(self, "module", module) 

1320 backend = backend.lower() 

1321 if backend == "ascendc": 

1322 raise NotImplementedError("GDN Ascend C state P2P is not supported; select the pure Ulysses wrapper.") 

1323 if backend not in _GDN_BACKENDS: 

1324 raise ValueError( 

1325 f"unsupported GDN backend {backend!r}; " 

1326 f"expected one of {sorted(_GDN_BACKENDS)}." 

1327 ) 

1328 if chunk_size <= 0: 

1329 raise ValueError(f"chunk_size must be positive, got {chunk_size}.") 

1330 if backend == "triton" and chunk_size != 64: 

1331 raise NotImplementedError("GDN Triton backend requires chunk_size=64.") 

1332 self.gdn_backend = backend 

1333 self.chunk_size = chunk_size 

1334 self.cp_mesh = None if device_mesh is None else _ensure_1d(device_mesh) 

1335 self.cp_size = 1 if self.cp_mesh is None else self.cp_mesh.size() 

1336 self.cp_rank = 0 if self.cp_mesh is None else self.cp_mesh.get_local_rank() 

1337 self._validate_module() 

1338 

1339 def _validate_module(self) -> None: 

1340 """Validate the Conv1d requirements of the P2P CP path.""" 

1341 required = ( 

1342 "in_proj_qkv", "in_proj_z", "in_proj_b", "in_proj_a", 

1343 "conv1d", "A_log", "dt_bias", "norm", "out_proj", 

1344 "num_k_heads", "num_v_heads", "head_k_dim", "head_v_dim", 

1345 "key_dim", "value_dim", "conv_dim", 

1346 ) 

1347 missing = [name for name in required if not hasattr(self.module, name)] 

1348 if missing: 

1349 raise TypeError( 

1350 "Qwen3.5 GDN layer is missing required attributes: " 

1351 + ", ".join(missing) 

1352 ) 

1353 if self.gdn_backend == "triton" and ( 

1354 self.module.head_k_dim != 128 or self.module.head_v_dim != 128 

1355 ): 

1356 raise NotImplementedError( 

1357 "linear attention P2P Triton backend requires " 

1358 "head_k_dim=head_v_dim=128." 

1359 ) 

1360 if self.module.num_v_heads % self.module.num_k_heads: 

1361 raise ValueError("GDN num_v_heads must be divisible by num_k_heads.") 

1362 _validate_gdn_activation(self.module) 

1363 conv = self.module.conv1d 

1364 if conv.stride != (1,): 

1365 raise ValueError( 

1366 "linear attention P2P CP currently supports only conv1d stride=1." 

1367 ) 

1368 if conv.groups != self.module.conv_dim: 

1369 raise ValueError( 

1370 "linear attention P2P CP expects depthwise conv1d groups=conv_dim." 

1371 ) 

1372 if ( 

1373 conv.in_channels != self.module.conv_dim 

1374 or conv.out_channels != self.module.conv_dim 

1375 ): 

1376 raise ValueError( 

1377 "linear attention P2P CP expects conv1d channels to match conv_dim." 

1378 ) 

1379 

1380 def _conv1d_with_halo( 

1381 self, mixed_qkv: torch.Tensor, sequence_info: Optional[PackedSequenceInfo] = None, 

1382 ) -> torch.Tensor: 

1383 """Run local Conv1d after exchanging only the previous-rank halo.""" 

1384 return _causal_conv1d_with_cp_halo( 

1385 mixed_qkv, 

1386 self.module.conv1d, 

1387 self.cp_mesh, 

1388 self.cp_rank, 

1389 self.cp_size, 

1390 sequence_info=sequence_info, 

1391 ) 

1392 

1393 def forward( 

1394 self, 

1395 hidden_states: torch.Tensor, 

1396 cache_params: Optional[object] = None, 

1397 attention_mask: Optional[torch.Tensor] = None, 

1398 **kwargs, 

1399 ) -> torch.Tensor: 

1400 """Run Gated DeltaNet on local sequence shards with recurrent-state P2P.""" 

1401 if cache_params is not None: 

1402 raise NotImplementedError("GDN P2P CP currently supports training without cache only.") 

1403 hidden_states = _local_tensor_at_cp_boundary(hidden_states) 

1404 is_flat_input = hidden_states.ndim == 2 

1405 if is_flat_input: 

1406 hidden_states = hidden_states.unsqueeze(0) 

1407 sequence_info = resolve_packed_sequence( 

1408 kwargs, hidden_states.shape[1], cp_rank=self.cp_rank, cp_size=self.cp_size) 

1409 if sequence_info is not None and hidden_states.shape[0] != 1: 

1410 raise ValueError("Packed GDN requires batch dimension one.") 

1411 

1412 base = self.module 

1413 hidden_states = _apply_local_padding_mask(hidden_states, attention_mask) 

1414 

1415 bsz, local_seq_len, _ = hidden_states.shape 

1416 mixed_qkv = base.in_proj_qkv(hidden_states) 

1417 z = base.in_proj_z(hidden_states).reshape( 

1418 bsz, 

1419 local_seq_len, 

1420 base.num_v_heads, 

1421 base.head_v_dim, 

1422 ) 

1423 b = base.in_proj_b(hidden_states) 

1424 a = base.in_proj_a(hidden_states) 

1425 

1426 mixed_qkv = self._conv1d_with_halo(mixed_qkv, sequence_info=sequence_info) 

1427 query, key, value = torch.split( 

1428 mixed_qkv, 

1429 [base.key_dim, base.key_dim, base.value_dim], 

1430 dim=-1, 

1431 ) 

1432 query = query.reshape(bsz, local_seq_len, base.num_k_heads, base.head_k_dim) 

1433 key = key.reshape(bsz, local_seq_len, base.num_k_heads, base.head_k_dim) 

1434 value = value.reshape(bsz, local_seq_len, base.num_v_heads, base.head_v_dim) 

1435 

1436 beta = b.sigmoid() 

1437 g = -base.A_log.float().exp() * F.softplus(a.float() + base.dt_bias) 

1438 

1439 kv_groups = _kv_groups(base) 

1440 if kv_groups > 1: 

1441 query = query.repeat_interleave(kv_groups, dim=2) 

1442 key = key.repeat_interleave(kv_groups, dim=2) 

1443 

1444 if self.gdn_backend == "triton": 

1445 if not is_triton_gdn_available(query, key, value, g, beta): 

1446 raise RuntimeError( 

1447 "linear attention P2P Triton backend requires an NPU " 

1448 "input satisfying the fixed GDN contract and a validated " 

1449 "triton-ascend >= 3.2.1 installation." 

1450 ) 

1451 core_attn_out = _gdn_state_p2p_triton( 

1452 query, 

1453 key, 

1454 value, 

1455 g, 

1456 beta, 

1457 self.cp_mesh, 

1458 self.cp_rank, 

1459 self.cp_size, 

1460 sequence_info=sequence_info, 

1461 ) 

1462 else: 

1463 core_attn_out = _gdn_state_p2p_summary( 

1464 query, 

1465 key, 

1466 value, 

1467 g, 

1468 beta, 

1469 self.cp_mesh, 

1470 self.cp_rank, 

1471 self.cp_size, 

1472 chunk_size=self.chunk_size, 

1473 use_qk_l2norm_in_kernel=True, 

1474 sequence_info=sequence_info, 

1475 ) 

1476 

1477 core_attn_out = core_attn_out.reshape(-1, base.head_v_dim) 

1478 z_flat = z.reshape(-1, base.head_v_dim) 

1479 core_attn_out = base.norm(core_attn_out, z_flat) 

1480 core_attn_out = core_attn_out.reshape(bsz, local_seq_len, base.value_dim) 

1481 if hasattr(base, "out_proj_input"): 

1482 core_attn_out = base.out_proj_input(core_attn_out) 

1483 output = base.out_proj(core_attn_out) 

1484 return output.squeeze(0) if is_flat_input else output 

1485 

1486 

1487def _exchange_previous_cp_halo(tail: torch.Tensor, cp_mesh: Any) -> torch.Tensor: 

1488 """Adapt channel-first convolution boundaries to the shared halo exchange.""" 

1489 halo = _all_to_all_previous_rank_halo( 

1490 tail.transpose(1, 2).contiguous(), cp_mesh, cp_mesh.get_local_rank(), cp_mesh.size()) 

1491 return halo.transpose(1, 2).contiguous() 

1492 

1493 

1494def _gdn_cp_causal_conv1d( 

1495 original_conv: Callable[..., Any], cp_mesh: Any, 

1496 hidden_states: torch.Tensor, weight: torch.Tensor, 

1497 bias: Any = None, activation: Any = None, **kwargs: Any) -> Any: 

1498 """Run the original causal Conv1d with the previous CP rank's halo.""" 

1499 halo_width = weight.shape[-1] - 1 

1500 if halo_width <= 0 or cp_mesh.size() <= 1: 

1501 return original_conv( 

1502 hidden_states, weight, bias, activation, **kwargs) 

1503 if hidden_states.shape[-1] < halo_width: 

1504 raise ValueError( 

1505 "GDN CP requires local sequence length " 

1506 f"({hidden_states.shape[-1]}) to be at least Conv1d halo width " 

1507 f"({halo_width})" 

1508 ) 

1509 tail = hidden_states[..., -halo_width:].contiguous() 

1510 halo = _exchange_previous_cp_halo(tail, cp_mesh) 

1511 conv_input = torch.cat((halo, hidden_states), dim=-1) 

1512 output = original_conv( 

1513 conv_input, weight, bias, activation, **kwargs) 

1514 return output[..., halo_width:].contiguous() 

1515 

1516 

1517def _gdn_cp_conv1d_module( 

1518 original_forward: Callable[..., Any], conv_module: Any, 

1519 cp_mesh: Any, hidden_states: torch.Tensor) -> torch.Tensor: 

1520 """Run the Transformers Conv1d fallback with the previous CP halo.""" 

1521 kernel_size = conv_module.kernel_size[0] 

1522 dilation = conv_module.dilation[0] 

1523 halo_width = (kernel_size - 1) * dilation 

1524 if halo_width <= 0 or cp_mesh.size() <= 1: 

1525 return original_forward(hidden_states) 

1526 if hidden_states.shape[-1] < halo_width: 

1527 raise ValueError( 

1528 "GDN CP requires local sequence length " 

1529 f"({hidden_states.shape[-1]}) to be at least Conv1d halo width " 

1530 f"({halo_width})" 

1531 ) 

1532 tail = hidden_states[..., -halo_width:].contiguous() 

1533 halo = _exchange_previous_cp_halo(tail, cp_mesh) 

1534 conv_input = torch.cat((halo, hidden_states), dim=-1) 

1535 output = original_forward(conv_input) 

1536 local_length = hidden_states.shape[-1] 

1537 return output[..., halo_width:halo_width + local_length].contiguous() 

1538 

1539 

1540def _gdn_rule_cp_to_hp( 

1541 query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, 

1542 decay: torch.Tensor, beta: torch.Tensor, 

1543 cp_mesh: Any) -> tuple[torch.Tensor, ...]: 

1544 """Convert local-sequence GDN rule inputs into local-head layouts.""" 

1545 tensors = (query, key, value, decay, beta) 

1546 expected_dims = (4, 4, 4, 3, 3) 

1547 if any( 

1548 tensor.dim() != expected 

1549 for tensor, expected in zip(tensors, expected_dims)): 

1550 shapes = [tuple(tensor.shape) for tensor in tensors] 

1551 raise ValueError( 

1552 "GDN CP expects 4-D Q/K/V and 3-D g/beta, got " 

1553 f"{shapes}" 

1554 ) 

1555 if any(tensor.shape[1] != query.shape[1] for tensor in tensors[1:]): 

1556 raise ValueError("GDN CP inputs must share the sequence length") 

1557 return tuple( 

1558 ulysses_seq_to_head( 

1559 tensor, seq_dim=1, head_dim=2, cp_mesh=cp_mesh) 

1560 for tensor in tensors 

1561 ) 

1562 

1563 

1564def _gdn_rule_hp_to_cp( 

1565 output: torch.Tensor, cp_mesh: Any) -> torch.Tensor: 

1566 """Restore one GDN rule output to the local-sequence layout.""" 

1567 if output.dim() != 4: 

1568 raise ValueError( 

1569 "GDN CP expects a 4-D GDN rule output, got " 

1570 f"shape {tuple(output.shape)}" 

1571 ) 

1572 return ulysses_head_to_seq( 

1573 output, seq_dim=1, head_dim=2, cp_mesh=cp_mesh) 

1574 

1575 

1576def _validate_gdn_cp(target_module: Any, cp_mesh: Any) -> None: 

1577 """Validate the GDN interface required by CP interception.""" 

1578 if cp_mesh is None or cp_mesh.size() <= 1: 

1579 raise ValueError("GDN CP requires an active CP mesh") 

1580 module_name = type(target_module).__name__ 

1581 if "GatedDeltaNet" not in module_name: 

1582 raise TypeError( 

1583 "GDN CP wrapper requires a GatedDeltaNet module, got " 

1584 f"{module_name}" 

1585 ) 

1586 num_value_heads = getattr(target_module, "num_v_heads", None) 

1587 if not isinstance(num_value_heads, int) or num_value_heads <= 0: 

1588 raise ValueError( 

1589 "GDN CP requires target_module.num_v_heads to be a " 

1590 "positive integer" 

1591 ) 

1592 if num_value_heads % cp_mesh.size(): 

1593 raise ValueError( 

1594 f"GDN value heads ({num_value_heads}) must be divisible " 

1595 f"by CP size ({cp_mesh.size()})" 

1596 ) 

1597 

1598 

1599def _resolve_gdn_forward_implementation( 

1600 original_forward: Callable[..., Any]) -> Callable[..., Any]: 

1601 """Resolve the GDN implementation beneath Transformers hook wrappers.""" 

1602 supported_rule_names = { 

1603 "torch_chunk_gated_delta_rule", 

1604 "chunk_gated_delta_rule", 

1605 } 

1606 implementation = inspect.unwrap(original_forward) 

1607 visited = set() 

1608 while id(implementation) not in visited: 

1609 visited.add(id(implementation)) 

1610 referenced_names = set(implementation.__code__.co_names) 

1611 if supported_rule_names & referenced_names: 

1612 return implementation 

1613 closure = inspect.getclosurevars(implementation).nonlocals 

1614 wrapped = closure.get("forward_func") 

1615 if not callable(wrapped): 

1616 break 

1617 implementation = inspect.unwrap(wrapped) 

1618 raise RuntimeError( 

1619 "GDN CP could not resolve the Transformers forward " 

1620 "implementation" 

1621 ) 

1622 

1623 

1624def install_gdn_ulysses_cp( 

1625 target_module: Any, 

1626 cp_mesh: Any, 

1627) -> None: 

1628 """Install primitive-level Ulysses CP around a GDN forward.""" 

1629 _validate_gdn_cp(target_module, cp_mesh) 

1630 original_forward = target_module.forward 

1631 forward_impl = _resolve_gdn_forward_implementation(original_forward) 

1632 forward_globals = forward_impl.__globals__ 

1633 referenced_names = set(forward_impl.__code__.co_names) 

1634 

1635 conv_name = "causal_conv1d_fn" 

1636 conv_owner = target_module if hasattr(target_module, conv_name) else forward_globals 

1637 original_conv = ( 

1638 getattr(conv_owner, conv_name) 

1639 if conv_owner is target_module else conv_owner.get(conv_name) 

1640 ) 

1641 conv_module = getattr(target_module, "conv1d", None) 

1642 original_conv_forward = getattr(conv_module, "forward", None) 

1643 use_fused_conv = conv_name in referenced_names and callable(original_conv) 

1644 use_module_conv = ( 

1645 not use_fused_conv 

1646 and "conv1d" in referenced_names 

1647 and callable(original_conv_forward) 

1648 ) 

1649 if not use_fused_conv and not use_module_conv: 

1650 raise RuntimeError( 

1651 "GDN CP requires a fused or module causal Conv1d path" 

1652 ) 

1653 

1654 rule_names = tuple( 

1655 name for name in ( 

1656 "torch_chunk_gated_delta_rule", 

1657 "chunk_gated_delta_rule", 

1658 ) 

1659 if name in referenced_names and ( 

1660 callable(getattr(target_module, name, None)) 

1661 or callable(forward_globals.get(name)) 

1662 ) 

1663 ) 

1664 if not rule_names: 

1665 raise RuntimeError( 

1666 "GDN CP could not find a supported chunk Gated Delta " 

1667 "Rule call in the Transformers forward" 

1668 ) 

1669 rule_owners = { 

1670 name: target_module if callable(getattr(target_module, name, None)) 

1671 else forward_globals 

1672 for name in rule_names 

1673 } 

1674 original_rules = { 

1675 name: getattr(owner, name) if owner is target_module else owner[name] 

1676 for name, owner in rule_owners.items() 

1677 } 

1678 

1679 def replace_primitive(owner: Any, name: str, value: Callable[..., Any]) -> None: 

1680 """Replace one module attribute or forward global primitive.""" 

1681 if owner is target_module: 

1682 setattr(owner, name, value) 

1683 else: 

1684 owner[name] = value 

1685 

1686 def make_cp_rule(original_rule: Callable[..., Any]) -> Callable[..., Any]: 

1687 """Build one CP-aware wrapper for a referenced GDN rule primitive.""" 

1688 @functools.wraps(original_rule) 

1689 def cp_rule( 

1690 query: torch.Tensor, 

1691 key: torch.Tensor, 

1692 value: torch.Tensor, 

1693 *args: Any, 

1694 **kwargs: Any, 

1695 ) -> Any: 

1696 if args: 

1697 raise TypeError( 

1698 "GDN CP expects g and beta to be passed by keyword" 

1699 ) 

1700 decay = kwargs.get("g") 

1701 beta = kwargs.get("beta") 

1702 if not isinstance(decay, torch.Tensor) or not isinstance(beta, torch.Tensor): 

1703 raise TypeError( 

1704 "GDN CP requires Tensor keyword arguments g and beta" 

1705 ) 

1706 if kwargs.get("initial_state") is not None or kwargs.get( 

1707 "output_final_state", False): 

1708 raise NotImplementedError( 

1709 "GDN CP does not support recurrent cache state" 

1710 ) 

1711 query, key, value, decay, beta = _gdn_rule_cp_to_hp( 

1712 query, key, value, decay, beta, cp_mesh) 

1713 call_kwargs = dict(kwargs) 

1714 call_kwargs.update(g=decay, beta=beta) 

1715 output = original_rule(query, key, value, **call_kwargs) 

1716 if not isinstance(output, tuple) or len(output) != 2: 

1717 raise TypeError( 

1718 "GDN rule must return (output, recurrent_state)" 

1719 ) 

1720 core_output, recurrent_state = output 

1721 if recurrent_state is not None: 

1722 raise NotImplementedError( 

1723 "GDN CP does not support a returned recurrent state" 

1724 ) 

1725 return _gdn_rule_hp_to_cp(core_output, cp_mesh), recurrent_state 

1726 

1727 return cp_rule 

1728 

1729 @functools.wraps(original_forward) 

1730 def cp_forward(*args: Any, **kwargs: Any) -> Any: 

1731 """Run the original GDN forward with CP-aware primitive interfaces.""" 

1732 cache_params = kwargs.get("cache_params") 

1733 if cache_params is None and len(args) > 1: 

1734 cache_params = args[1] 

1735 if cache_params is not None: 

1736 raise NotImplementedError( 

1737 "GDN CP currently supports training with cache_params=None" 

1738 ) 

1739 if kwargs.get("cu_seq_lens_q") is not None: 

1740 raise NotImplementedError( 

1741 "GDN CP does not support packed sequences" 

1742 ) 

1743 

1744 fired = {"conv": 0, "rule": 0} 

1745 

1746 if use_fused_conv: 

1747 @functools.wraps(original_conv) 

1748 def cp_conv(*conv_args: Any, **conv_kwargs: Any) -> Any: 

1749 fired["conv"] += 1 

1750 if not conv_args and "x" in conv_kwargs: 

1751 conv_kwargs = dict(conv_kwargs) 

1752 conv_args = (conv_kwargs.pop("x"),) 

1753 return _gdn_cp_causal_conv1d( 

1754 original_conv, cp_mesh, *conv_args, **conv_kwargs) 

1755 else: 

1756 @functools.wraps(original_conv_forward) 

1757 def cp_conv(hidden_states: torch.Tensor) -> torch.Tensor: 

1758 fired["conv"] += 1 

1759 return _gdn_cp_conv1d_module( 

1760 original_conv_forward, conv_module, cp_mesh, hidden_states) 

1761 

1762 def count_rule(wrapped_rule): 

1763 @functools.wraps(wrapped_rule) 

1764 def counted_rule(*rule_args: Any, **rule_kwargs: Any) -> Any: 

1765 fired["rule"] += 1 

1766 return wrapped_rule(*rule_args, **rule_kwargs) 

1767 

1768 return counted_rule 

1769 

1770 wrapped_rules = { 

1771 name: count_rule(make_cp_rule(original_rule)) 

1772 for name, original_rule in original_rules.items() 

1773 } 

1774 

1775 if use_fused_conv: 

1776 replace_primitive(conv_owner, conv_name, cp_conv) 

1777 else: 

1778 conv_module.forward = cp_conv 

1779 for name, wrapped_rule in wrapped_rules.items(): 

1780 replace_primitive(rule_owners[name], name, wrapped_rule) 

1781 try: 

1782 output = original_forward(*args, **kwargs) 

1783 finally: 

1784 if use_fused_conv: 

1785 replace_primitive(conv_owner, conv_name, original_conv) 

1786 else: 

1787 conv_module.forward = original_conv_forward 

1788 for name, original_rule in original_rules.items(): 

1789 replace_primitive(rule_owners[name], name, original_rule) 

1790 if fired["conv"] != 1 or fired["rule"] != 1: 

1791 raise RuntimeError( 

1792 "GDN CP wrapper expected one causal Conv1d and one " 

1793 "Gated Delta Rule call, got " 

1794 f"conv={fired['conv']}, rule={fired['rule']}" 

1795 ) 

1796 return output 

1797 

1798 target_module.forward = cp_forward