Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / context_parallel / linear_attention_context_parallel.py: 19%

580 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-08-21 04:29 +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# This module is the Torch implementation of the public CP style. 

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

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

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

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

22from __future__ import annotations 

23 

24from typing import NamedTuple, Optional 

25 

26import torch 

27import torch.distributed as dist 

28from torch import nn 

29from torch.nn import functional as F 

30from torch.utils.checkpoint import checkpoint 

31 

32from hyper_parallel.core.context_parallel.context_parallel import ( 

33 _ensure_1d, 

34) 

35from hyper_parallel.core.dtensor.device_mesh import DeviceMesh 

36from hyper_parallel.core.dtensor.dtensor import DTensor 

37from hyper_parallel.core.tensor_parallel.style import ParallelStyle 

38from hyper_parallel.models.modules.linear_attention import ( 

39 chunk_gated_delta_rule, 

40 is_triton_gdn_available, 

41 torch_chunk_gated_delta_rule, 

42) 

43from hyper_parallel.platform import get_platform 

44 

45 

46platform = get_platform() 

47 

48 

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

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

51 return int(cp_mesh.rank_list[local_rank]) 

52 

53 

54def _slice_local_cp( 

55 tensor: torch.Tensor, 

56 dim: int, 

57 cp_rank: int, 

58 cp_size: int, 

59) -> torch.Tensor: 

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

61 dim_size = tensor.shape[dim] 

62 if dim_size % cp_size != 0: 

63 raise ValueError( 

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

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

66 ) 

67 chunk = dim_size // cp_size 

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

69 

70 

71def _slice_qkv_local_cp( 

72 tensor: torch.Tensor, 

73 *, 

74 key_dim: int, 

75 value_dim: int, 

76 dim: int, 

77 cp_rank: int, 

78 cp_size: int, 

79) -> torch.Tensor: 

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

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

82 return torch.cat( 

83 ( 

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

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

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

87 ), 

88 dim=dim, 

89 ) 

90 

91 

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

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

94 

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

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

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

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

99 """ 

100 if isinstance(tensor, DTensor): 

101 return tensor.to_local() 

102 return tensor 

103 

104 

105def _all_to_all_previous_rank_halo( 

106 tail: torch.Tensor, 

107 cp_mesh: DeviceMesh, 

108 cp_rank: int, 

109 cp_size: int, 

110) -> torch.Tensor: 

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

112 if cp_size == 1: 

113 return torch.zeros_like(tail) 

114 

115 cp_group = cp_mesh.get_group() 

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

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

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

119 halo_width = tail.shape[1] 

120 

121 input_splits = [0] * cp_size 

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

123 if cp_rank < cp_size - 1: 

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

125 else: 

126 exchange_input = exchange_input[:0] 

127 

128 output_splits = [0] * cp_size 

129 if cp_rank > 0: 

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

131 

132 exchange_output = platform.differentiable_all_to_all_single( 

133 exchange_input, 

134 input_splits, 

135 output_splits, 

136 group=cp_group, 

137 ) 

138 if cp_rank == 0: 

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

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

141 

142 

143def _causal_conv1d_with_cp_halo( 

144 mixed_qkv: torch.Tensor, 

145 conv1d: nn.Conv1d, 

146 cp_mesh: DeviceMesh, 

147 cp_rank: int, 

148 cp_size: int, 

149) -> torch.Tensor: 

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

151 kernel_size = conv1d.kernel_size[0] 

152 dilation = conv1d.dilation[0] 

153 halo_width = (kernel_size - 1) * dilation 

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

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

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

157 

158 if mixed_qkv.shape[1] < halo_width: 

159 raise ValueError( 

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

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

162 ) 

163 

164 halo = _all_to_all_previous_rank_halo( 

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

166 cp_mesh, 

167 cp_rank, 

168 cp_size, 

169 ) 

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

171 conv_out = F.conv1d( 

172 input=conv_input, 

173 weight=conv1d.weight, 

174 bias=conv1d.bias, 

175 stride=conv1d.stride, 

176 padding=0, 

177 dilation=conv1d.dilation, 

178 groups=conv1d.groups, 

179 ) 

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

181 

182 

183def _all_gather_stack( 

184 tensor: torch.Tensor, 

185 cp_mesh: DeviceMesh, 

186 cp_size: int, 

187) -> torch.Tensor: 

188 """All-gather equal-shaped tensors and stack them on a leading rank dim.""" 

189 if cp_size == 1: 

190 return tensor.unsqueeze(0) 

191 return platform.differentiable_all_gather_concat( 

192 tensor.unsqueeze(0), 

193 cp_mesh.get_group(), 

194 cp_size, 

195 0, 

196 tuple(int(rank) for rank in cp_mesh.rank_list), 

197 ) 

198 

199 

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

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

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

203 

204 

205class _GDNPreparedChunks(NamedTuple): 

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

207 

208 initial_dtype: torch.dtype 

209 query: torch.Tensor 

210 key: torch.Tensor 

211 chunk_value: torch.Tensor 

212 g: torch.Tensor 

213 decay_mask: torch.Tensor 

214 k_cumdecay: torch.Tensor 

215 sequence_length: int 

216 total_sequence_length: int 

217 chunk_size: int 

218 

219 

220def _prepare_gdn_chunks_for_summary( 

221 query: torch.Tensor, 

222 key: torch.Tensor, 

223 value: torch.Tensor, 

224 g: torch.Tensor, 

225 beta: torch.Tensor, 

226 *, 

227 chunk_size: int = 64, 

228 use_qk_l2norm_in_kernel: bool = False, 

229) -> _GDNPreparedChunks: 

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

231 initial_dtype = query.dtype 

232 if use_qk_l2norm_in_kernel: 

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

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

235 

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

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

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

239 ] 

240 

241 sequence_length = key.shape[2] 

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

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

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

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

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

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

248 total_sequence_length = sequence_length + pad_size 

249 

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

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

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

253 query, key, k_beta, v_beta = [ 

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

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

256 ] 

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

258 

259 mask = torch.triu( 

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

261 diagonal=0, 

262 ) 

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

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

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

266 for row_idx in range(1, chunk_size): 

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

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

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

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

271 

272 chunk_value = attn @ v_beta 

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

274 return _GDNPreparedChunks( 

275 initial_dtype=initial_dtype, 

276 query=query, 

277 key=key, 

278 chunk_value=chunk_value, 

279 g=g, 

280 decay_mask=decay_mask, 

281 k_cumdecay=k_cumdecay, 

282 sequence_length=sequence_length, 

283 total_sequence_length=total_sequence_length, 

284 chunk_size=chunk_size, 

285 ) 

286 

287 

288def _compute_gdn_state_summary_from_prepared( 

289 prepared: _GDNPreparedChunks, 

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

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

292 key = prepared.key 

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

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

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

296 1, 1, k_head_dim, k_head_dim 

297 ) 

298 state_ext = torch.zeros( 

299 batch_size, 

300 num_heads, 

301 k_head_dim, 

302 v_head_dim, 

303 device=key.device, 

304 dtype=torch.float32, 

305 ) 

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

307 

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

309 key_i = key[:, :, chunk_idx] 

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

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

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

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

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

315 

316 transition_i = ( 

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

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

319 ) 

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

321 state_ext = transition_i @ state_ext + state_ext_i 

322 transition = transition_i @ transition 

323 

324 return state_ext, transition 

325 

326 

327def _checkpoint_gdn_state_summary( 

328 prepared: _GDNPreparedChunks, 

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

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

331 if not torch.is_grad_enabled(): 

332 return _compute_gdn_state_summary_from_prepared(prepared) 

333 

334 def recompute( 

335 key: torch.Tensor, 

336 chunk_value: torch.Tensor, 

337 g: torch.Tensor, 

338 k_cumdecay: torch.Tensor, 

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

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

341 checkpoint_prepared = prepared._replace( 

342 key=key, 

343 chunk_value=chunk_value, 

344 g=g, 

345 k_cumdecay=k_cumdecay, 

346 ) 

347 return _compute_gdn_state_summary_from_prepared(checkpoint_prepared) 

348 

349 return checkpoint( 

350 recompute, 

351 prepared.key, 

352 prepared.chunk_value, 

353 prepared.g, 

354 prepared.k_cumdecay, 

355 use_reentrant=False, 

356 preserve_rng_state=False, 

357 ) 

358 

359 

360def _run_prepared_gdn_chunks( 

361 prepared: _GDNPreparedChunks, 

362 initial_state: Optional[torch.Tensor], 

363) -> torch.Tensor: 

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

365 query = prepared.query 

366 key = prepared.key 

367 chunk_value = prepared.chunk_value 

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

369 v_head_dim = chunk_value.shape[-1] 

370 recurrent_state = ( 

371 torch.zeros( 

372 batch_size, 

373 num_heads, 

374 k_head_dim, 

375 v_head_dim, 

376 device=chunk_value.device, 

377 dtype=chunk_value.dtype, 

378 ) 

379 if initial_state is None 

380 else initial_state.to(chunk_value) 

381 ) 

382 core_attn_out = torch.zeros_like(chunk_value) 

383 

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

385 q_i = query[:, :, chunk_idx] 

386 k_i = key[:, :, chunk_idx] 

387 v_i = chunk_value[:, :, chunk_idx] 

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

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

390 v_new = v_i - v_prime 

391 attn_inter = ( 

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

393 ) @ recurrent_state 

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

395 recurrent_state = ( 

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

397 + ( 

398 k_i 

399 * ( 

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

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

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

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

404 ) 

405 

406 core_attn_out = core_attn_out.reshape( 

407 core_attn_out.shape[0], 

408 core_attn_out.shape[1], 

409 -1, 

410 core_attn_out.shape[-1], 

411 ) 

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

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

414 

415 

416def _pack_gdn_state_summary( 

417 state_ext: torch.Tensor, 

418 transition: torch.Tensor, 

419) -> torch.Tensor: 

420 """Pack ``S`` and ``M`` summaries into one all-gather payload.""" 

421 if state_ext.shape[:-1] != transition.shape[:-1]: 

422 raise ValueError( 

423 "state_ext and transition must share [B,H,K] dimensions, got " 

424 f"{tuple(state_ext.shape)} and {tuple(transition.shape)}." 

425 ) 

426 return torch.cat((state_ext, transition), dim=-1) 

427 

428 

429def _unpack_gdn_state_summary( 

430 packed: torch.Tensor, 

431 v_head_dim: int, 

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

433 """Unpack a gathered ``[S, M]`` payload.""" 

434 if packed.shape[-1] <= v_head_dim: 

435 raise ValueError( 

436 f"packed state summary last dim must be > v_head_dim={v_head_dim}, " 

437 f"got {packed.shape[-1]}." 

438 ) 

439 state_ext = packed[..., :v_head_dim] 

440 transition = packed[..., v_head_dim:] 

441 return state_ext, transition 

442 

443 

444def _merge_gdn_prefix_state_summaries_torch( 

445 state_ext: torch.Tensor, 

446 transition: torch.Tensor, 

447 rank: int, 

448) -> torch.Tensor: 

449 """Merge gathered GDN summaries before ``rank`` into its initial state.""" 

450 if state_ext.dim() != 5 or transition.dim() != 5: 

451 raise ValueError( 

452 "state summary merge expects state_ext [R,B,H,K,V] and " 

453 "transition [R,B,H,K,K]." 

454 ) 

455 if state_ext.shape[0] != transition.shape[0]: 

456 raise ValueError("state_ext and transition must have the same rank dimension.") 

457 if rank < 0 or rank > state_ext.shape[0]: 

458 raise ValueError(f"rank must be in [0, {state_ext.shape[0]}], got {rank}.") 

459 

460 state = torch.zeros_like(state_ext[0]) 

461 for prev_rank in range(rank): 

462 state = transition[prev_rank] @ state + state_ext[prev_rank] 

463 return state 

464 

465 

466def _gdn_state_all_gather( 

467 query: torch.Tensor, 

468 key: torch.Tensor, 

469 value: torch.Tensor, 

470 g: torch.Tensor, 

471 beta: torch.Tensor, 

472 cp_mesh: DeviceMesh, 

473 cp_rank: int, 

474 cp_size: int, 

475 *, 

476 use_qk_l2norm_in_kernel: bool, 

477) -> torch.Tensor: 

478 """Apply local GDN with all-gathered recurrent-state summaries.""" 

479 if cp_size == 1: 

480 core_attn_out, _ = torch_chunk_gated_delta_rule( 

481 query, 

482 key, 

483 value, 

484 g=g, 

485 beta=beta, 

486 initial_state=None, 

487 output_final_state=False, 

488 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

489 ) 

490 return core_attn_out 

491 

492 prepared = _prepare_gdn_chunks_for_summary( 

493 query, 

494 key, 

495 value, 

496 g, 

497 beta, 

498 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

499 ) 

500 state_ext, transition = _checkpoint_gdn_state_summary(prepared) 

501 packed_summary = _pack_gdn_state_summary(state_ext, transition) 

502 gathered_summary = _all_gather_stack(packed_summary, cp_mesh, cp_size) 

503 gathered_state_ext, gathered_transition = _unpack_gdn_state_summary( 

504 gathered_summary, 

505 state_ext.shape[-1], 

506 ) 

507 

508 initial_state = _merge_gdn_prefix_state_summaries_torch( 

509 gathered_state_ext, 

510 gathered_transition, 

511 cp_rank, 

512 ) 

513 all_gather_tie = gathered_summary.sum() 

514 initial_state = initial_state + all_gather_tie.to(initial_state.dtype) * 0 

515 return _run_prepared_gdn_chunks(prepared, initial_state) 

516 

517 

518class _RecvInitialStateP2PFunction(torch.autograd.Function): 

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

520 

521 @staticmethod 

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

523 ctx, 

524 anchor: torch.Tensor, 

525 cp_group, 

526 prev_rank: int, 

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

528 ) -> torch.Tensor: 

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

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

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

532 ctx.cp_group = cp_group 

533 ctx.prev_rank = prev_rank 

534 return state 

535 

536 @staticmethod 

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

538 if grad_state is None: 

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

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

541 return None, None, None, None 

542 

543 

544class _SendFinalStateP2PFunction(torch.autograd.Function): 

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

546 

547 @staticmethod 

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

549 ctx, 

550 final_state: torch.Tensor, 

551 cp_group, 

552 next_rank: int, 

553 ) -> torch.Tensor: 

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

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

556 ctx.cp_group = cp_group 

557 ctx.next_rank = next_rank 

558 ctx.state_shape = tuple(final_state.shape) 

559 ctx.state_dtype = final_state.dtype 

560 return final_state.new_zeros(()) 

561 

562 @staticmethod 

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

564 grad_state = torch.empty( 

565 ctx.state_shape, 

566 device=grad_token.device, 

567 dtype=ctx.state_dtype, 

568 ) 

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

570 return grad_state, None, None 

571 

572 

573def _apply_gdn_state_summary( 

574 state_ext: torch.Tensor, 

575 transition: torch.Tensor, 

576 initial_state: Optional[torch.Tensor], 

577) -> torch.Tensor: 

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

579 if initial_state is None: 

580 return state_ext 

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

582 

583 

584def _gdn_state_p2p_summary( 

585 query: torch.Tensor, 

586 key: torch.Tensor, 

587 value: torch.Tensor, 

588 g: torch.Tensor, 

589 beta: torch.Tensor, 

590 cp_mesh: DeviceMesh, 

591 cp_rank: int, 

592 cp_size: int, 

593 *, 

594 use_qk_l2norm_in_kernel: bool, 

595) -> torch.Tensor: 

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

597 

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

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

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

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

602 """ 

603 if cp_size == 1: 

604 core_attn_out, _ = torch_chunk_gated_delta_rule( 

605 query, 

606 key, 

607 value, 

608 g=g, 

609 beta=beta, 

610 initial_state=None, 

611 output_final_state=False, 

612 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

613 ) 

614 return core_attn_out 

615 

616 cp_group = cp_mesh.get_group() 

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

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

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

620 

621 prepared = _prepare_gdn_chunks_for_summary( 

622 query, 

623 key, 

624 value, 

625 g, 

626 beta, 

627 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

628 ) 

629 state_ext = None 

630 transition = None 

631 if cp_rank < cp_size - 1: 

632 state_ext, transition = _checkpoint_gdn_state_summary(prepared) 

633 

634 initial_state = None 

635 if cp_rank > 0: 

636 initial_state = _RecvInitialStateP2PFunction.apply( 

637 query, 

638 cp_group, 

639 prev_rank, 

640 state_shape, 

641 ) 

642 

643 send_token = None 

644 if cp_rank < cp_size - 1: 

645 final_state = _apply_gdn_state_summary( 

646 state_ext, 

647 transition, 

648 initial_state, 

649 ) 

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

651 

652 core_attn_out = _run_prepared_gdn_chunks(prepared, initial_state) 

653 if send_token is not None: 

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

655 

656 return core_attn_out 

657 

658 

659class _GDNStateP2PTritonFunction(torch.autograd.Function): 

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

661 

662 @staticmethod 

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

664 ctx, 

665 query: torch.Tensor, 

666 key: torch.Tensor, 

667 value: torch.Tensor, 

668 g: torch.Tensor, 

669 beta: torch.Tensor, 

670 cp_rank: int, 

671 cp_size: int, 

672 cp_group, 

673 prev_rank: int, 

674 next_rank: int, 

675 ) -> torch.Tensor: 

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

677 from hyper_parallel.platform.torch.custom_ops.gdn.chunk_gated_delta_rule import ( # pylint: disable=import-outside-toplevel 

678 chunk_gated_delta_rule_fwd_apply_state_saved, 

679 chunk_gated_delta_rule_fwd_output_saved, 

680 chunk_gated_delta_rule_fwd_prepare_saved, 

681 ) 

682 from hyper_parallel.platform.torch.custom_ops.gdn.state_summary import ( # pylint: disable=import-outside-toplevel 

683 apply_gdn_state_summary, 

684 chunk_gated_delta_rule_state_summary_fwd, 

685 ) 

686 

687 ( 

688 query_norm, 

689 key_norm, 

690 _, 

691 _, 

692 g_cumsum, 

693 matrix_a, 

694 w, 

695 u, 

696 scale, 

697 ) = chunk_gated_delta_rule_fwd_prepare_saved( 

698 query, 

699 key, 

700 value, 

701 g, 

702 beta, 

703 use_qk_l2norm_in_kernel=False, 

704 ) 

705 initial_state = None 

706 recv_buffer = None 

707 recv_work = None 

708 if cp_rank > 0: 

709 recv_buffer = torch.empty( 

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

711 device=query.device, 

712 dtype=torch.float32, 

713 ) 

714 recv_work = dist.irecv(recv_buffer, src=prev_rank, group=cp_group) 

715 

716 state_ext = None 

717 transition = None 

718 if cp_rank < cp_size - 1: 

719 state_ext, transition = chunk_gated_delta_rule_state_summary_fwd( 

720 key_norm, 

721 w, 

722 u, 

723 g_cumsum, 

724 ) 

725 

726 if recv_work is not None: 

727 recv_work.wait() 

728 initial_state = recv_buffer 

729 

730 send_buffer = None 

731 send_work = None 

732 if cp_rank < cp_size - 1: 

733 send_buffer = apply_gdn_state_summary( 

734 state_ext, 

735 transition, 

736 initial_state, 

737 ).contiguous() 

738 send_work = dist.isend(send_buffer, dst=next_rank, group=cp_group) 

739 

740 h, v_new, _ = chunk_gated_delta_rule_fwd_apply_state_saved( 

741 key_norm, 

742 g_cumsum, 

743 w, 

744 u, 

745 initial_state=initial_state, 

746 output_final_state=False, 

747 ) 

748 output = chunk_gated_delta_rule_fwd_output_saved( 

749 query_norm, 

750 key_norm, 

751 g_cumsum, 

752 h, 

753 v_new, 

754 scale, 

755 ).to(query.dtype) 

756 

757 if send_work is not None: 

758 send_work.wait() 

759 

760 empty = query.new_empty(0) 

761 ctx.save_for_backward( 

762 query_norm, 

763 key_norm, 

764 value, 

765 g_cumsum, 

766 beta, 

767 matrix_a, 

768 initial_state if initial_state is not None else empty, 

769 transition if transition is not None else empty, 

770 ) 

771 ctx.has_initial_state = initial_state is not None 

772 ctx.cp_rank = cp_rank 

773 ctx.cp_size = cp_size 

774 ctx.cp_group = cp_group 

775 ctx.prev_rank = prev_rank 

776 ctx.next_rank = next_rank 

777 ctx.scale = scale 

778 return output 

779 

780 @staticmethod 

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

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

783 from hyper_parallel.platform.torch.custom_ops.gdn.chunk_gated_delta_rule import ( # pylint: disable=import-outside-toplevel 

784 chunk_gated_delta_rule_bwd_finish_saved, 

785 chunk_gated_delta_rule_bwd_prepare_saved, 

786 chunk_gated_delta_rule_bwd_state_saved, 

787 ) 

788 from hyper_parallel.platform.torch.custom_ops.gdn.state_summary import ( # pylint: disable=import-outside-toplevel 

789 apply_gdn_state_gradient_summary, 

790 chunk_gated_delta_rule_state_gradient_summary_bwd, 

791 ) 

792 

793 ( 

794 query, 

795 key, 

796 value, 

797 g_cumsum, 

798 beta, 

799 matrix_a, 

800 initial_state, 

801 transition, 

802 ) = ctx.saved_tensors 

803 if not ctx.has_initial_state: 

804 initial_state = None 

805 

806 w, h, v_new, dv = chunk_gated_delta_rule_bwd_prepare_saved( 

807 query, 

808 key, 

809 value, 

810 g_cumsum, 

811 beta, 

812 matrix_a, 

813 initial_state, 

814 grad_output, 

815 ctx.scale, 

816 ) 

817 

818 grad_state_ext = None 

819 if ctx.cp_rank > 0: 

820 grad_state_ext = chunk_gated_delta_rule_state_gradient_summary_bwd( 

821 query, 

822 key, 

823 w, 

824 g_cumsum, 

825 grad_output, 

826 dv, 

827 ctx.scale, 

828 ) 

829 

830 grad_final_state = None 

831 recv_work = None 

832 if ctx.cp_rank < ctx.cp_size - 1: 

833 recv_buffer = torch.empty( 

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

835 device=grad_output.device, 

836 dtype=torch.float32, 

837 ) 

838 recv_work = dist.irecv( 

839 recv_buffer, 

840 src=ctx.next_rank, 

841 group=ctx.cp_group, 

842 ) 

843 if recv_work is not None: 

844 recv_work.wait() 

845 grad_final_state = recv_buffer 

846 

847 send_buffer = None 

848 send_work = None 

849 if ctx.cp_rank > 0: 

850 send_buffer = apply_gdn_state_gradient_summary( 

851 grad_state_ext, 

852 transition, 

853 grad_final_state, 

854 ).contiguous() 

855 send_work = dist.isend( 

856 send_buffer, 

857 dst=ctx.prev_rank, 

858 group=ctx.cp_group, 

859 ) 

860 

861 dh, _, dv = chunk_gated_delta_rule_bwd_state_saved( 

862 query, 

863 key, 

864 g_cumsum, 

865 w, 

866 initial_state, 

867 grad_final_state, 

868 grad_output, 

869 dv, 

870 ctx.scale, 

871 ) 

872 empty = query.new_empty(0) 

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

874 query, 

875 key, 

876 query, 

877 key, 

878 value, 

879 g_cumsum, 

880 beta, 

881 matrix_a, 

882 w, 

883 h, 

884 v_new, 

885 dv, 

886 grad_output, 

887 dh, 

888 empty, 

889 empty, 

890 ctx.scale, 

891 use_qk_l2norm_in_kernel=False, 

892 ) 

893 

894 if send_work is not None: 

895 send_work.wait() 

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

897 

898 

899def _gdn_state_p2p_triton( 

900 query: torch.Tensor, 

901 key: torch.Tensor, 

902 value: torch.Tensor, 

903 g: torch.Tensor, 

904 beta: torch.Tensor, 

905 cp_mesh: DeviceMesh, 

906 cp_rank: int, 

907 cp_size: int, 

908) -> torch.Tensor: 

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

910 if cp_size == 1: 

911 output, _ = chunk_gated_delta_rule( 

912 query, 

913 key, 

914 value, 

915 g=g, 

916 beta=beta, 

917 output_final_state=False, 

918 use_qk_l2norm_in_kernel=True, 

919 backend="triton", 

920 ) 

921 return output 

922 

923 query = _l2norm_torch(query) 

924 key = _l2norm_torch(key) 

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

926 next_rank = ( 

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

928 ) 

929 return _GDNStateP2PTritonFunction.apply( 

930 query, 

931 key, 

932 value, 

933 g, 

934 beta, 

935 cp_rank, 

936 cp_size, 

937 cp_mesh.get_group(), 

938 prev_rank, 

939 next_rank, 

940 ) 

941 

942 

943def _differentiable_all_to_all_shard( 

944 tensor: torch.Tensor, 

945 device_mesh: DeviceMesh, 

946 *, 

947 split_dim: int, 

948 concat_dim: int, 

949) -> torch.Tensor: 

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

951 

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

953 Shard(split_dim)`` redistribution for a 1-D mesh. It uses platform-level 

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

955 temporary DTensor. 

956 """ 

957 split_count = device_mesh.size() 

958 if split_count == 1: 

959 return tensor 

960 

961 original_shape = tuple(tensor.shape) 

962 dim_size = original_shape[split_dim] 

963 if dim_size % split_count != 0: 

964 raise ValueError( 

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

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

967 ) 

968 

969 split_size = dim_size // split_count 

970 final_shape = list(original_shape) 

971 if split_dim != concat_dim: 

972 final_shape[split_dim] = split_size 

973 final_shape[concat_dim] = final_shape[concat_dim] * split_count 

974 final_shape = tuple(final_shape) 

975 

976 reshape_dims = list(original_shape) 

977 reshape_dims[split_dim] = split_count 

978 reshape_dims.insert(split_dim + 1, split_size) 

979 

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

981 trans_dims.remove(split_dim) 

982 trans_dims.insert(0, split_dim) 

983 

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

985 reshape_shape = list(a2a_input.shape) 

986 reshape_shape[0] = reshape_shape[0] * reshape_shape[1] 

987 reshape_shape.pop(1) 

988 a2a_input = a2a_input.reshape(reshape_shape) 

989 

990 a2a_input = a2a_input.contiguous() 

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

992 input_splits = [split_len] * split_count 

993 output_splits = [split_len] * split_count 

994 output = platform.differentiable_all_to_all_single( 

995 a2a_input, 

996 input_splits, 

997 output_splits, 

998 group=device_mesh.get_group(), 

999 ) 

1000 

1001 output_reshape = list(output.shape) 

1002 output_reshape[0] = split_count 

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

1004 

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

1006 first_dim = out_trans_dims.pop(0) 

1007 if concat_dim >= len(out_trans_dims): 

1008 out_trans_dims.append(first_dim) 

1009 else: 

1010 out_trans_dims.insert(concat_dim, first_dim) 

1011 

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

1013 final_reshape = list(final_output.shape) 

1014 if concat_dim < len(final_reshape) - 1: 

1015 final_reshape[concat_dim] = ( 

1016 final_reshape[concat_dim] * final_reshape[concat_dim + 1] 

1017 ) 

1018 final_reshape.pop(concat_dim + 1) 

1019 

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

1021 

1022 

1023class LinearAttentionUlyssesCPWrapper(nn.Module): 

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

1025 

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

1027 execution layout: 

1028 

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

1030 [B, S_local, full_heads]``. 

1031 """ 

1032 

1033 def __init__( 

1034 self, 

1035 module: nn.Module, 

1036 device_mesh: DeviceMesh, 

1037 *, 

1038 backend: str = "eager", 

1039 ): 

1040 super().__init__() 

1041 self.module = module 

1042 self.gdn_backend = backend 

1043 self.cp_mesh = _ensure_1d(device_mesh) 

1044 self.cp_size = self.cp_mesh.size() 

1045 self.cp_rank = self.cp_mesh.get_local_rank() 

1046 self.seq_dim = 1 

1047 self.head_dim = 2 

1048 self._validate_module() 

1049 

1050 def _validate_module(self) -> None: 

1051 if self.cp_size <= 1: 

1052 return 

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

1054 raise ValueError( 

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

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

1057 ) 

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

1059 raise ValueError( 

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

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

1062 ) 

1063 

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

1065 return _differentiable_all_to_all_shard( 

1066 tensor, 

1067 self.cp_mesh, 

1068 split_dim=self.head_dim, 

1069 concat_dim=self.seq_dim, 

1070 ) 

1071 

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

1073 return _differentiable_all_to_all_shard( 

1074 tensor, 

1075 self.cp_mesh, 

1076 split_dim=self.seq_dim, 

1077 concat_dim=self.head_dim, 

1078 ) 

1079 

1080 def _seq_to_head_qkvba( 

1081 self, 

1082 q_proj: torch.Tensor, 

1083 k_proj: torch.Tensor, 

1084 v_proj: torch.Tensor, 

1085 b: torch.Tensor, 

1086 a: torch.Tensor, 

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

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

1089 if self.cp_size == 1: 

1090 return q_proj, k_proj, v_proj, b, a 

1091 

1092 base = self.module 

1093 local_key_dim = base.key_dim // self.cp_size 

1094 local_value_dim = base.value_dim // self.cp_size 

1095 local_num_v_heads = base.num_v_heads // self.cp_size 

1096 

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

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

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

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

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

1102 rank_major_chunks = [ 

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

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

1105 ] 

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

1107 packed = self._seq_to_head(packed) 

1108 return torch.split( 

1109 packed, 

1110 [ 

1111 local_key_dim, 

1112 local_key_dim, 

1113 local_value_dim, 

1114 local_num_v_heads, 

1115 local_num_v_heads, 

1116 ], 

1117 dim=-1, 

1118 ) 

1119 

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

1121 return _slice_qkv_local_cp( 

1122 self.module.conv1d.weight, 

1123 key_dim=self.module.key_dim, 

1124 value_dim=self.module.value_dim, 

1125 dim=0, 

1126 cp_rank=self.cp_rank, 

1127 cp_size=self.cp_size, 

1128 ) 

1129 

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

1131 bias = self.module.conv1d.bias 

1132 if bias is None: 

1133 return None 

1134 return _slice_qkv_local_cp( 

1135 bias, 

1136 key_dim=self.module.key_dim, 

1137 value_dim=self.module.value_dim, 

1138 dim=0, 

1139 cp_rank=self.cp_rank, 

1140 cp_size=self.cp_size, 

1141 ) 

1142 

1143 def forward( 

1144 self, 

1145 hidden_states: torch.Tensor, 

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

1147 **kwargs, 

1148 ) -> torch.Tensor: 

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

1150 del kwargs 

1151 hidden_states = _local_tensor_at_cp_boundary(hidden_states) 

1152 

1153 base = self.module 

1154 if attention_mask is not None and attention_mask.ndim == 2: 

1155 hidden_states = hidden_states * attention_mask[:, :, None].to( 

1156 hidden_states.dtype 

1157 ) 

1158 

1159 bsz, local_seq_len, _ = hidden_states.shape 

1160 mixed_qkv = base.in_proj_qkv(hidden_states) 

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

1162 bsz, 

1163 local_seq_len, 

1164 base.num_v_heads, 

1165 base.head_v_dim, 

1166 ) 

1167 b = base.in_proj_b(hidden_states) 

1168 a = base.in_proj_a(hidden_states) 

1169 

1170 q_proj, k_proj, v_proj = torch.split( 

1171 mixed_qkv, 

1172 [base.key_dim, base.key_dim, base.value_dim], 

1173 dim=-1, 

1174 ) 

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

1176 q_proj, k_proj, v_proj, b, a 

1177 ) 

1178 

1179 full_seq_len = q_proj.shape[1] 

1180 local_key_dim = base.key_dim // self.cp_size 

1181 local_value_dim = base.value_dim // self.cp_size 

1182 local_num_k_heads = base.num_k_heads // self.cp_size 

1183 local_num_v_heads = base.num_v_heads // self.cp_size 

1184 local_conv_dim = local_key_dim * 2 + local_value_dim 

1185 

1186 mixed_qkv = torch.cat((q_proj, k_proj, v_proj), dim=-1).transpose(1, 2) 

1187 conv_out = F.conv1d( 

1188 input=mixed_qkv, 

1189 weight=self._local_conv_weight(), 

1190 bias=self._local_conv_bias(), 

1191 stride=base.conv1d.stride, 

1192 padding=base.conv1d.padding, 

1193 dilation=base.conv1d.dilation, 

1194 groups=local_conv_dim, 

1195 ) 

1196 mixed_qkv = F.silu(conv_out[:, :, :full_seq_len]).transpose(1, 2) 

1197 

1198 query, key, value = torch.split( 

1199 mixed_qkv, 

1200 [local_key_dim, local_key_dim, local_value_dim], 

1201 dim=-1, 

1202 ) 

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

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

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

1206 

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

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

1209 beta = b.sigmoid() 

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

1211 

1212 if base.kv_groups > 1: 

1213 query = query.repeat_interleave(base.kv_groups, dim=2) 

1214 key = key.repeat_interleave(base.kv_groups, dim=2) 

1215 

1216 core_attn_out, _ = chunk_gated_delta_rule( 

1217 query, 

1218 key, 

1219 value, 

1220 g=g, 

1221 beta=beta, 

1222 initial_state=None, 

1223 output_final_state=False, 

1224 use_qk_l2norm_in_kernel=True, 

1225 backend=self.gdn_backend, 

1226 ) 

1227 

1228 core_attn_out = self._head_to_seq(core_attn_out) 

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

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

1231 core_attn_out = base.norm(core_attn_out, z_flat) 

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

1233 if hasattr(base, "out_proj_input"): 

1234 core_attn_out = base.out_proj_input(core_attn_out) 

1235 return base.out_proj(core_attn_out) 

1236 

1237 

1238class LinearAttentionP2PCPWrapper(nn.Module): 

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

1240 

1241 def __init__( 

1242 self, 

1243 module: nn.Module, 

1244 device_mesh: DeviceMesh, 

1245 *, 

1246 backend: str = "eager", 

1247 ): 

1248 super().__init__() 

1249 self.module = module 

1250 self.gdn_backend = backend 

1251 self.cp_mesh = _ensure_1d(device_mesh) 

1252 self.cp_size = self.cp_mesh.size() 

1253 self.cp_rank = self.cp_mesh.get_local_rank() 

1254 self._validate_module() 

1255 

1256 def _validate_module(self) -> None: 

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

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

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

1260 ): 

1261 raise NotImplementedError( 

1262 "linear attention P2P Triton backend requires " 

1263 "head_k_dim=head_v_dim=128." 

1264 ) 

1265 conv = self.module.conv1d 

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

1267 raise ValueError( 

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

1269 ) 

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

1271 raise ValueError( 

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

1273 ) 

1274 if ( 

1275 conv.in_channels != self.module.conv_dim 

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

1277 ): 

1278 raise ValueError( 

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

1280 ) 

1281 

1282 def _conv1d_with_halo(self, mixed_qkv: torch.Tensor) -> torch.Tensor: 

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

1284 return _causal_conv1d_with_cp_halo( 

1285 mixed_qkv, 

1286 self.module.conv1d, 

1287 self.cp_mesh, 

1288 self.cp_rank, 

1289 self.cp_size, 

1290 ) 

1291 

1292 def forward( 

1293 self, 

1294 hidden_states: torch.Tensor, 

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

1296 **kwargs, 

1297 ) -> torch.Tensor: 

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

1299 del kwargs 

1300 hidden_states = _local_tensor_at_cp_boundary(hidden_states) 

1301 

1302 base = self.module 

1303 if attention_mask is not None and attention_mask.ndim == 2: 

1304 hidden_states = hidden_states * attention_mask[:, :, None].to( 

1305 hidden_states.dtype 

1306 ) 

1307 

1308 bsz, local_seq_len, _ = hidden_states.shape 

1309 mixed_qkv = base.in_proj_qkv(hidden_states) 

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

1311 bsz, 

1312 local_seq_len, 

1313 base.num_v_heads, 

1314 base.head_v_dim, 

1315 ) 

1316 b = base.in_proj_b(hidden_states) 

1317 a = base.in_proj_a(hidden_states) 

1318 

1319 mixed_qkv = self._conv1d_with_halo(mixed_qkv) 

1320 query, key, value = torch.split( 

1321 mixed_qkv, 

1322 [base.key_dim, base.key_dim, base.value_dim], 

1323 dim=-1, 

1324 ) 

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

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

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

1328 

1329 beta = b.sigmoid() 

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

1331 

1332 if base.kv_groups > 1: 

1333 query = query.repeat_interleave(base.kv_groups, dim=2) 

1334 key = key.repeat_interleave(base.kv_groups, dim=2) 

1335 

1336 if self.gdn_backend == "triton": 

1337 if local_seq_len % 64 != 0: 

1338 raise NotImplementedError( 

1339 "linear attention P2P Triton backend requires each CP " 

1340 f"rank's local sequence length ({local_seq_len}) to be " 

1341 "divisible by 64." 

1342 ) 

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

1344 raise RuntimeError( 

1345 "linear attention P2P Triton backend requires an NPU " 

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

1347 "triton-ascend 3.2.x installation." 

1348 ) 

1349 core_attn_out = _gdn_state_p2p_triton( 

1350 query, 

1351 key, 

1352 value, 

1353 g, 

1354 beta, 

1355 self.cp_mesh, 

1356 self.cp_rank, 

1357 self.cp_size, 

1358 ) 

1359 else: 

1360 core_attn_out = _gdn_state_p2p_summary( 

1361 query, 

1362 key, 

1363 value, 

1364 g, 

1365 beta, 

1366 self.cp_mesh, 

1367 self.cp_rank, 

1368 self.cp_size, 

1369 use_qk_l2norm_in_kernel=True, 

1370 ) 

1371 

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

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

1374 core_attn_out = base.norm(core_attn_out, z_flat) 

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

1376 if hasattr(base, "out_proj_input"): 

1377 core_attn_out = base.out_proj_input(core_attn_out) 

1378 return base.out_proj(core_attn_out) 

1379 

1380 

1381class LinearAttentionAllGatherCPWrapper(nn.Module): 

1382 """Sequence-sharded GDN CP using all-gathered recurrent-state summaries.""" 

1383 

1384 def __init__(self, module: nn.Module, device_mesh: DeviceMesh): 

1385 super().__init__() 

1386 self.module = module 

1387 self.cp_mesh = _ensure_1d(device_mesh) 

1388 self.cp_size = self.cp_mesh.size() 

1389 self.cp_rank = self.cp_mesh.get_local_rank() 

1390 self._validate_module() 

1391 

1392 def _validate_module(self) -> None: 

1393 """Validate the Conv1d requirements of the all-gather CP path.""" 

1394 conv = self.module.conv1d 

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

1396 raise ValueError( 

1397 "linear attention all-gather CP currently supports only " 

1398 "conv1d stride=1." 

1399 ) 

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

1401 raise ValueError( 

1402 "linear attention all-gather CP expects depthwise conv1d " 

1403 "groups=conv_dim." 

1404 ) 

1405 if ( 

1406 conv.in_channels != self.module.conv_dim 

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

1408 ): 

1409 raise ValueError( 

1410 "linear attention all-gather CP expects conv1d channels to " 

1411 "match conv_dim." 

1412 ) 

1413 

1414 def _conv1d_with_halo(self, mixed_qkv: torch.Tensor) -> torch.Tensor: 

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

1416 return _causal_conv1d_with_cp_halo( 

1417 mixed_qkv, 

1418 self.module.conv1d, 

1419 self.cp_mesh, 

1420 self.cp_rank, 

1421 self.cp_size, 

1422 ) 

1423 

1424 def forward( 

1425 self, 

1426 hidden_states: torch.Tensor, 

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

1428 **kwargs, 

1429 ) -> torch.Tensor: 

1430 """Run Gated DeltaNet on local sequence shards with all-gather state summaries.""" 

1431 del kwargs 

1432 hidden_states = _local_tensor_at_cp_boundary(hidden_states) 

1433 

1434 base = self.module 

1435 if attention_mask is not None and attention_mask.ndim == 2: 

1436 hidden_states = hidden_states * attention_mask[:, :, None].to( 

1437 hidden_states.dtype 

1438 ) 

1439 

1440 bsz, local_seq_len, _ = hidden_states.shape 

1441 mixed_qkv = base.in_proj_qkv(hidden_states) 

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

1443 bsz, 

1444 local_seq_len, 

1445 base.num_v_heads, 

1446 base.head_v_dim, 

1447 ) 

1448 b = base.in_proj_b(hidden_states) 

1449 a = base.in_proj_a(hidden_states) 

1450 

1451 mixed_qkv = self._conv1d_with_halo(mixed_qkv) 

1452 query, key, value = torch.split( 

1453 mixed_qkv, 

1454 [base.key_dim, base.key_dim, base.value_dim], 

1455 dim=-1, 

1456 ) 

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

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

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

1460 

1461 beta = b.sigmoid() 

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

1463 

1464 if base.kv_groups > 1: 

1465 query = query.repeat_interleave(base.kv_groups, dim=2) 

1466 key = key.repeat_interleave(base.kv_groups, dim=2) 

1467 

1468 core_attn_out = _gdn_state_all_gather( 

1469 query, 

1470 key, 

1471 value, 

1472 g, 

1473 beta, 

1474 self.cp_mesh, 

1475 self.cp_rank, 

1476 self.cp_size, 

1477 use_qk_l2norm_in_kernel=True, 

1478 ) 

1479 

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

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

1482 core_attn_out = base.norm(core_attn_out, z_flat) 

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

1484 if hasattr(base, "out_proj_input"): 

1485 core_attn_out = base.out_proj_input(core_attn_out) 

1486 return base.out_proj(core_attn_out) 

1487 

1488 

1489class LinearAttentionContextParallel(ParallelStyle): 

1490 """Apply context parallel execution to a Gated DeltaNet module.""" 

1491 

1492 def __init__(self, *, mode: str = "ulysses", backend: str = "eager") -> None: 

1493 if mode not in {"ulysses", "p2p", "all_gather"}: 

1494 raise NotImplementedError( 

1495 "LinearAttentionContextParallel currently supports mode='ulysses', " 

1496 "mode='p2p', and mode='all_gather'." 

1497 ) 

1498 if backend not in {"eager", "triton"}: 

1499 raise ValueError( 

1500 "LinearAttentionContextParallel backend must be 'eager' or " 

1501 f"'triton', got {backend!r}." 

1502 ) 

1503 if mode == "all_gather" and backend == "triton": 

1504 raise NotImplementedError( 

1505 "linear attention all-gather CP does not yet support the " 

1506 "Triton backend." 

1507 ) 

1508 self.mode = mode 

1509 self.backend = backend 

1510 

1511 def apply(self, module: nn.Module, device_mesh: DeviceMesh) -> nn.Module: 

1512 """Patch ``module.forward`` with a linear-attention CP executor.""" 

1513 if self.mode == "ulysses": 

1514 executor = LinearAttentionUlyssesCPWrapper( 

1515 module, 

1516 device_mesh, 

1517 backend=self.backend, 

1518 ) 

1519 elif self.mode == "all_gather": 

1520 executor = LinearAttentionAllGatherCPWrapper(module, device_mesh) 

1521 else: 

1522 executor = LinearAttentionP2PCPWrapper( 

1523 module, 

1524 device_mesh, 

1525 backend=self.backend, 

1526 ) 

1527 object.__setattr__(module, "_hp_linear_attention_cp_executor", executor) 

1528 object.__setattr__(module, "_hp_linear_attention_original_forward", module.forward) 

1529 

1530 def _forward(*args, **kwargs): 

1531 return executor(*args, **kwargs) 

1532 

1533 object.__setattr__(module, "forward", _forward) 

1534 return module