Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / modules / kimi_delta_attention.py: 82%

372 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"""Pure PyTorch Kimi Delta Attention operators and training layer. 

16 

17The implementations in this module intentionally favor readable equations over 

18performance. They are the numerical oracle shared by the standalone layer and 

19context-parallel wrappers. 

20""" 

21# pylint: disable=invalid-name 

22# This module mirrors the existing Torch model building blocks in this package. 

23# pylint: disable=forbidden-backend-import 

24from __future__ import annotations 

25 

26__all__ = [ 

27 "KimiDeltaAttention", 

28 "KimiRMSNormGated", 

29 "chunk_kda", 

30 "is_triton_kda_available", 

31 "torch_apply_kda_state_summary", 

32 "torch_chunk_kda", 

33 "torch_compose_kda_state_summaries", 

34 "torch_kda_gate", 

35 "torch_kda_state_summary", 

36 "torch_recurrent_kda", 

37] 

38 

39from collections.abc import Sequence 

40from typing import Any, Optional 

41 

42import torch 

43from torch import nn 

44from torch.nn import functional as F 

45 

46from hyper_parallel.components.functional.kimi_delta_attention_ascendc import ascendc_chunk_kda 

47from hyper_parallel.components.functional.linear_attention_packed import ( 

48 packed_causal_conv1d, packed_reference, resolve_packed_sequence, 

49) 

50 

51_KDA_BACKENDS = frozenset({"eager", "triton", "ascendc"}) 

52_TRITON_KDA_HEAD_DIM = 128 

53_TRITON_KDA_CHUNK_SIZE = 64 

54 

55 

56def _has_triton_kda_shapes( 

57 query: torch.Tensor, 

58 value: torch.Tensor, 

59 gate: torch.Tensor, 

60 beta: torch.Tensor, 

61 a_log: torch.Tensor, 

62 dt_bias: torch.Tensor, 

63 chunk_size: int, 

64) -> bool: 

65 """Check the fixed dense shapes accepted by the Triton KDA path.""" 

66 batch_size, sequence_length, num_query_heads, key_dim = query.shape 

67 num_value_heads, value_dim = value.shape[2:] 

68 return all(( 

69 value.shape[:2] == (batch_size, sequence_length), 

70 gate.shape == (batch_size, sequence_length, num_value_heads, key_dim), 

71 beta.shape == (batch_size, sequence_length, num_value_heads), 

72 num_value_heads % num_query_heads == 0, 

73 a_log.numel() == num_value_heads, 

74 dt_bias.numel() == num_value_heads * key_dim, 

75 key_dim == value_dim == _TRITON_KDA_HEAD_DIM, 

76 chunk_size == _TRITON_KDA_CHUNK_SIZE, 

77 )) 

78 

79 

80def _is_triton_kda_input_supported( 

81 query: torch.Tensor, 

82 key: Optional[torch.Tensor], 

83 value: Optional[torch.Tensor], 

84 gate: Optional[torch.Tensor], 

85 beta: Optional[torch.Tensor], 

86 a_log: Optional[torch.Tensor], 

87 dt_bias: Optional[torch.Tensor], 

88 *, 

89 chunk_size: int, 

90 lower_bound: Optional[float], 

91) -> bool: 

92 """Check the fixed Kimi K3 contract validated by the fused backend.""" 

93 operands = (key, value, gate, beta, a_log, dt_bias) 

94 if any(tensor is None for tensor in operands): 

95 return False 

96 if lower_bound is None: 

97 return False 

98 

99 basic_contract = ( 

100 query.device.type == "npu", 

101 all(tensor.dtype == torch.bfloat16 for tensor in (query, key, value, gate, beta)), 

102 all(tensor.dtype == torch.float32 for tensor in (a_log, dt_bias)), 

103 all(tensor.ndim == 4 for tensor in (query, key, value, gate)), 

104 beta.ndim == 3, 

105 query.shape == key.shape, 

106 ) 

107 if not all(basic_contract): 

108 return False 

109 

110 shape_supported = _has_triton_kda_shapes( 

111 query, value, gate, beta, a_log, dt_bias, chunk_size, 

112 ) 

113 lower_bound_supported = -5.0 <= lower_bound < 0 

114 if not (shape_supported and lower_bound_supported): 

115 return False 

116 return all(tensor.device == query.device for tensor in operands) 

117 

118 

119def is_triton_kda_available( 

120 query: Optional[torch.Tensor] = None, 

121 key: Optional[torch.Tensor] = None, 

122 value: Optional[torch.Tensor] = None, 

123 gate: Optional[torch.Tensor] = None, 

124 beta: Optional[torch.Tensor] = None, 

125 a_log: Optional[torch.Tensor] = None, 

126 dt_bias: Optional[torch.Tensor] = None, 

127 *, 

128 chunk_size: int = _TRITON_KDA_CHUNK_SIZE, 

129 lower_bound: Optional[float] = -5.0, 

130) -> bool: 

131 """Return whether the validated Triton-Ascend KDA backend is available.""" 

132 if chunk_size != _TRITON_KDA_CHUNK_SIZE: 

133 return False 

134 if query is not None and not _is_triton_kda_input_supported( 

135 query, 

136 key, 

137 value, 

138 gate, 

139 beta, 

140 a_log, 

141 dt_bias, 

142 chunk_size=chunk_size, 

143 lower_bound=lower_bound, 

144 ): 

145 return False 

146 from hyper_parallel.components.functional.kimi_delta_attention_fla_adapter import ( # pylint: disable=import-outside-toplevel 

147 is_fla_triton_kda_available, 

148 ) 

149 return is_fla_triton_kda_available() 

150 

151 

152def _validate_kda_inputs( 

153 query: torch.Tensor, 

154 key: torch.Tensor, 

155 value: torch.Tensor, 

156 gate: torch.Tensor, 

157 beta: torch.Tensor, 

158) -> tuple[int, int, int, int, int, int]: 

159 """Validate dense token-first KDA inputs and return their dimensions.""" 

160 if (query.dim(), key.dim(), value.dim(), gate.dim()) != (4, 4, 4, 4): 

161 raise ValueError("query, key, value, and gate must be rank-4 tensors.") 

162 if beta.dim() != 3: 

163 raise ValueError("beta must be a rank-3 tensor.") 

164 if query.shape != key.shape: 

165 raise ValueError("query and key must have identical shapes.") 

166 

167 batch_size, sequence_length, num_k_heads, k_head_dim = query.shape 

168 value_batch, value_sequence, num_v_heads, v_head_dim = value.shape 

169 if (value_batch, value_sequence) != (batch_size, sequence_length): 

170 raise ValueError("value batch and sequence dimensions must match query.") 

171 if gate.shape != (batch_size, sequence_length, num_v_heads, k_head_dim): 

172 raise ValueError( 

173 "gate must have shape [B, S, num_v_heads, k_head_dim], got " 

174 f"{tuple(gate.shape)}." 

175 ) 

176 if beta.shape != (batch_size, sequence_length, num_v_heads): 

177 raise ValueError( 

178 "beta must have shape [B, S, num_v_heads], got " 

179 f"{tuple(beta.shape)}." 

180 ) 

181 if num_v_heads % num_k_heads != 0: 

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

183 return ( 

184 batch_size, 

185 sequence_length, 

186 num_k_heads, 

187 num_v_heads, 

188 k_head_dim, 

189 v_head_dim, 

190 ) 

191 

192 

193def _reshape_gate_bias( 

194 parameter: torch.Tensor, 

195 num_v_heads: int, 

196 k_head_dim: int, 

197 name: str, 

198) -> torch.Tensor: 

199 """Return a gate parameter as ``[num_v_heads, k_head_dim]``.""" 

200 if parameter.numel() != num_v_heads * k_head_dim: 

201 raise ValueError( 

202 f"{name} must contain {num_v_heads * k_head_dim} elements, " 

203 f"got {parameter.numel()}." 

204 ) 

205 return parameter.reshape(num_v_heads, k_head_dim) 

206 

207 

208def torch_kda_gate( 

209 gate_logits: torch.Tensor, 

210 a_log: torch.Tensor, 

211 dt_bias: torch.Tensor, 

212 *, 

213 lower_bound: Optional[float] = None, 

214) -> torch.Tensor: 

215 """Activate KDA log-decay logits. 

216 

217 ``lower_bound`` selects the Kimi K3 parameterization 

218 ``g_min * sigmoid(exp(a_log) * (logits + bias))``. With no lower bound, 

219 this function implements the earlier Kimi Linear negative-Softplus gate. 

220 """ 

221 if gate_logits.dim() != 4: 

222 raise ValueError("gate_logits must have shape [B, S, H, K].") 

223 num_v_heads, k_head_dim = gate_logits.shape[-2:] 

224 if a_log.numel() != num_v_heads: 

225 raise ValueError( 

226 f"a_log must contain {num_v_heads} elements, got {a_log.numel()}." 

227 ) 

228 bias = _reshape_gate_bias(dt_bias, num_v_heads, k_head_dim, "dt_bias") 

229 scale = a_log.float().exp().reshape(1, 1, num_v_heads, 1) 

230 preactivation = gate_logits.float() + bias.float().reshape( 

231 1, 1, num_v_heads, k_head_dim 

232 ) 

233 if lower_bound is None: 

234 return -scale * F.softplus(preactivation) # pylint: disable=not-callable 

235 if lower_bound >= 0: 

236 raise ValueError(f"lower_bound must be negative, got {lower_bound}.") 

237 return float(lower_bound) * torch.sigmoid(scale * preactivation) 

238 

239 

240def _prepare_kda_inputs( 

241 query: torch.Tensor, 

242 key: torch.Tensor, 

243 value: torch.Tensor, 

244 gate: torch.Tensor, 

245 beta: torch.Tensor, 

246 *, 

247 a_log: Optional[torch.Tensor], 

248 dt_bias: Optional[torch.Tensor], 

249 scale: Optional[float], 

250 use_qk_l2norm_in_kernel: bool, 

251 use_gate_in_kernel: bool, 

252 use_beta_sigmoid_in_kernel: bool, 

253 lower_bound: Optional[float], 

254) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, float]: 

255 """Apply the common KDA input transformations in FP32.""" 

256 _, _, num_k_heads, num_v_heads, k_head_dim, _ = _validate_kda_inputs( 

257 query, key, value, gate, beta 

258 ) 

259 if use_qk_l2norm_in_kernel: 

260 query = query * torch.rsqrt( 

261 (query.float() * query.float()).sum(dim=-1, keepdim=True) + 1e-6 

262 ).to(query.dtype) 

263 key = key * torch.rsqrt( 

264 (key.float() * key.float()).sum(dim=-1, keepdim=True) + 1e-6 

265 ).to(key.dtype) 

266 if use_gate_in_kernel: 

267 if a_log is None or dt_bias is None: 

268 raise ValueError("a_log and dt_bias are required when gate activation is enabled.") 

269 gate = torch_kda_gate( 

270 gate, 

271 a_log, 

272 dt_bias, 

273 lower_bound=lower_bound, 

274 ) 

275 if use_beta_sigmoid_in_kernel: 

276 beta = beta.float().sigmoid() 

277 

278 value_groups = num_v_heads // num_k_heads 

279 query = query.float().repeat_interleave(value_groups, dim=2) 

280 key = key.float().repeat_interleave(value_groups, dim=2) 

281 value = value.float() 

282 gate = gate.float() 

283 beta = beta.float() 

284 effective_scale = k_head_dim ** -0.5 if scale is None else scale 

285 return query, key, value, gate, beta, float(effective_scale) 

286 

287 

288def _initial_kda_state( 

289 value: torch.Tensor, 

290 k_head_dim: int, 

291 initial_state: Optional[torch.Tensor], 

292) -> torch.Tensor: 

293 """Build or validate a KDA state in FP32.""" 

294 expected_shape = (value.shape[0], value.shape[2], k_head_dim, value.shape[3]) 

295 if initial_state is None: 

296 return value.new_zeros(expected_shape, dtype=torch.float32) 

297 if tuple(initial_state.shape) != expected_shape: 

298 raise ValueError( 

299 f"initial_state must have shape {expected_shape}, got " 

300 f"{tuple(initial_state.shape)}." 

301 ) 

302 return initial_state.float() 

303 

304 

305def torch_recurrent_kda( 

306 query: torch.Tensor, 

307 key: torch.Tensor, 

308 value: torch.Tensor, 

309 gate: torch.Tensor, 

310 beta: torch.Tensor, 

311 *, 

312 a_log: Optional[torch.Tensor] = None, 

313 dt_bias: Optional[torch.Tensor] = None, 

314 scale: Optional[float] = None, 

315 initial_state: Optional[torch.Tensor] = None, 

316 output_final_state: bool = False, 

317 use_qk_l2norm_in_kernel: bool = False, 

318 use_gate_in_kernel: bool = False, 

319 use_beta_sigmoid_in_kernel: bool = False, 

320 lower_bound: Optional[float] = None, 

321) -> tuple[torch.Tensor, Optional[torch.Tensor]]: 

322 """Evaluate the exact token-recurrent KDA equations with PyTorch ops.""" 

323 output_dtype = value.dtype 

324 query, key, value, gate, beta, scale = _prepare_kda_inputs( 

325 query, 

326 key, 

327 value, 

328 gate, 

329 beta, 

330 a_log=a_log, 

331 dt_bias=dt_bias, 

332 scale=scale, 

333 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

334 use_gate_in_kernel=use_gate_in_kernel, 

335 use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, 

336 lower_bound=lower_bound, 

337 ) 

338 state = _initial_kda_state(value, key.shape[-1], initial_state) 

339 outputs = [] 

340 for token_idx in range(query.shape[1]): 

341 query_i = query[:, token_idx] 

342 key_i = key[:, token_idx] 

343 value_i = value[:, token_idx] 

344 gate_i = gate[:, token_idx] 

345 beta_i = beta[:, token_idx] 

346 

347 state = state * gate_i.exp().unsqueeze(-1) 

348 state_value = torch.einsum("bhk,bhkv->bhv", key_i, state) 

349 residual = value_i - state_value 

350 state = state + torch.einsum( 

351 "bhk,bhv->bhkv", 

352 key_i * beta_i.unsqueeze(-1), 

353 residual, 

354 ) 

355 outputs.append(torch.einsum("bhk,bhkv->bhv", query_i * scale, state)) 

356 

357 output = torch.stack(outputs, dim=1).to(output_dtype) 

358 return output, state if output_final_state else None 

359 

360 

361def _inverse_unit_lower_triangular(strict_lower: torch.Tensor) -> torch.Tensor: 

362 """Invert ``I + strict_lower`` using differentiable forward substitution.""" 

363 chunk_size = strict_lower.shape[-1] 

364 rows = [] 

365 for row_idx in range(chunk_size): 

366 base = -strict_lower[..., row_idx, :] 

367 if rows: 

368 previous = torch.stack(rows, dim=-2) 

369 row = torch.einsum( 

370 "...i,...ij->...j", 

371 base[..., :row_idx], 

372 previous, 

373 ) 

374 else: 

375 row = torch.zeros_like(base) 

376 diagonal = F.one_hot( # pylint: disable=not-callable 

377 torch.tensor(row_idx, device=strict_lower.device), 

378 num_classes=chunk_size, 

379 ).to(strict_lower.dtype) 

380 rows.append(row + diagonal) 

381 return torch.stack(rows, dim=-2) 

382 

383 

384def _prepare_kda_wy( 

385 query: torch.Tensor, 

386 key: torch.Tensor, 

387 value: torch.Tensor, 

388 gate: torch.Tensor, 

389 beta: torch.Tensor, 

390 chunk_size: int, 

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

392 """Build the cumulative gate, causal mask, and chunkwise WY factors.""" 

393 gate = gate.cumsum(dim=-2) 

394 causal = torch.ones( 

395 chunk_size, 

396 chunk_size, 

397 dtype=torch.bool, 

398 device=query.device, 

399 ).tril() 

400 strict_causal = causal.logical_xor( 

401 torch.eye(chunk_size, dtype=torch.bool, device=query.device) 

402 ) 

403 

404 gate_pair = gate.unsqueeze(-2) - gate.unsqueeze(-3) 

405 key_pair = torch.einsum( 

406 "...ik,...jk,...ijk->...ij", 

407 key, 

408 key, 

409 gate_pair.exp(), 

410 ) 

411 strict_lower = ( 

412 key_pair * beta.unsqueeze(-1) 

413 ).masked_fill(~strict_causal, 0) 

414 inverse = _inverse_unit_lower_triangular(strict_lower) 

415 transform = inverse * beta.unsqueeze(-2) 

416 w = transform @ (key * gate.exp()) 

417 u = transform @ value 

418 

419 return gate, causal, w, u 

420 

421 

422def _run_kda_chunks( 

423 query: torch.Tensor, 

424 key: torch.Tensor, 

425 gate: torch.Tensor, 

426 w: torch.Tensor, 

427 u: torch.Tensor, 

428 state: torch.Tensor, 

429 causal: torch.Tensor, 

430 scale: float, 

431 num_chunks: int, 

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

433 """Apply chunk WY factors to the recurrent state in sequence order.""" 

434 output_chunks = [] 

435 for chunk_idx in range(num_chunks): 

436 query_i = query[:, :, chunk_idx] 

437 key_i = key[:, :, chunk_idx] 

438 gate_i = gate[:, :, chunk_idx] 

439 w_i = w[:, :, chunk_idx] 

440 u_i = u[:, :, chunk_idx] 

441 

442 relative_gate = gate_i.unsqueeze(-2) - gate_i.unsqueeze(-3) 

443 qk = torch.einsum( 

444 "bhik,bhjk,bhijk->bhij", 

445 query_i, 

446 key_i, 

447 relative_gate.exp(), 

448 ).masked_fill(~causal, 0) 

449 value_new = u_i - w_i @ state 

450 output_inter = (query_i * gate_i.exp()) @ state 

451 output_chunks.append(output_inter * scale + qk @ value_new * scale) 

452 

453 last_gate = gate_i[:, :, -1] 

454 key_decay = key_i * (last_gate.unsqueeze(-2) - gate_i).exp() 

455 state = state * last_gate.exp().unsqueeze(-1) 

456 state = state + key_decay.transpose(-1, -2) @ value_new 

457 

458 return output_chunks, state 

459 

460 

461def torch_chunk_kda( 

462 query: torch.Tensor, 

463 key: torch.Tensor, 

464 value: torch.Tensor, 

465 gate: torch.Tensor, 

466 beta: torch.Tensor, 

467 *, 

468 a_log: Optional[torch.Tensor] = None, 

469 dt_bias: Optional[torch.Tensor] = None, 

470 scale: Optional[float] = None, 

471 initial_state: Optional[torch.Tensor] = None, 

472 output_final_state: bool = False, 

473 use_qk_l2norm_in_kernel: bool = False, 

474 use_gate_in_kernel: bool = False, 

475 use_beta_sigmoid_in_kernel: bool = False, 

476 lower_bound: Optional[float] = None, 

477 chunk_size: int = 64, 

478 cu_seqlens: Optional[torch.Tensor] = None, 

479) -> tuple[torch.Tensor, Optional[torch.Tensor]]: 

480 """Evaluate chunkwise KDA using the UT/WY representation. 

481 

482 This implementation is algebraically independent from 

483 :func:`torch_recurrent_kda`; comparing the two catches errors in the 

484 chunk transform that a second token loop would hide. 

485 """ 

486 if cu_seqlens is not None: 

487 return packed_reference( 

488 torch_chunk_kda, (query, key, value, gate, beta), cu_seqlens, 

489 initial_state=initial_state, output_final_state=output_final_state, 

490 a_log=a_log, dt_bias=dt_bias, scale=scale, 

491 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, use_gate_in_kernel=use_gate_in_kernel, 

492 use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, lower_bound=lower_bound, chunk_size=chunk_size, 

493 ) 

494 if chunk_size <= 0: 

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

496 output_dtype = value.dtype 

497 sequence_length = query.shape[1] 

498 query, key, value, gate, beta, scale = _prepare_kda_inputs( 

499 query, 

500 key, 

501 value, 

502 gate, 

503 beta, 

504 a_log=a_log, 

505 dt_bias=dt_bias, 

506 scale=scale, 

507 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

508 use_gate_in_kernel=use_gate_in_kernel, 

509 use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, 

510 lower_bound=lower_bound, 

511 ) 

512 batch_size, _, num_heads, k_head_dim = query.shape 

513 v_head_dim = value.shape[-1] 

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

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

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

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

518 gate = F.pad(gate, (0, 0, 0, 0, 0, pad_size)) 

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

520 num_chunks = query.shape[1] // chunk_size 

521 

522 def chunk_view(tensor: torch.Tensor) -> torch.Tensor: 

523 """Rearrange a token-first tensor to head-first chunks.""" 

524 shape = tensor.shape 

525 return tensor.reshape( 

526 shape[0], num_chunks, chunk_size, shape[2], *shape[3:] 

527 ).permute(0, 3, 1, 2, *range(4, tensor.dim() + 1)) 

528 

529 query, key, value, gate, beta = [ 

530 chunk_view(tensor) for tensor in (query, key, value, gate, beta) 

531 ] 

532 gate, causal, w, u = _prepare_kda_wy(query, key, value, gate, beta, chunk_size) 

533 

534 state = _initial_kda_state( 

535 value.permute(0, 2, 3, 1, 4).reshape( 

536 batch_size, num_chunks * chunk_size, num_heads, v_head_dim 

537 ), 

538 k_head_dim, 

539 initial_state, 

540 ) 

541 output_chunks, state = _run_kda_chunks(query, key, gate, w, u, state, causal, scale, num_chunks) 

542 

543 output = torch.stack(output_chunks, dim=2) 

544 output = output.permute(0, 2, 3, 1, 4).reshape( 

545 batch_size, 

546 num_chunks * chunk_size, 

547 num_heads, 

548 v_head_dim, 

549 ) 

550 output = output[:, :sequence_length].to(output_dtype) 

551 return output, state if output_final_state else None 

552 

553 

554def _normalize_kda_backend(backend: str) -> str: 

555 """Normalize the selected backend and reject unsupported names.""" 

556 backend = backend.lower() 

557 if backend not in _KDA_BACKENDS: 

558 raise ValueError( 

559 f"unsupported KDA backend {backend!r}; " 

560 f"expected one of {sorted(_KDA_BACKENDS)}." 

561 ) 

562 return backend 

563 

564 

565def _validate_fused_state_contract(initial_state, output_final_state, qk_norm, gate, beta, backend) -> None: 

566 """Keep raw-input dispatch separate from the stateful CP backend contract.""" 

567 if initial_state is not None or output_final_state: 

568 raise NotImplementedError( 

569 f"KDA backend={backend!r} currently supports zero initial state without " 

570 "returning a final state; stateful execution is owned by the CP backend." 

571 ) 

572 if not (qk_norm and gate and beta): 

573 raise NotImplementedError( 

574 f"KDA backend={backend!r} requires fused Q/K normalization, gate activation, " 

575 "and beta sigmoid." 

576 ) 

577 

578 

579def chunk_kda( 

580 query: torch.Tensor, 

581 key: torch.Tensor, 

582 value: torch.Tensor, 

583 gate: torch.Tensor, 

584 beta: torch.Tensor, 

585 *, 

586 a_log: Optional[torch.Tensor] = None, 

587 dt_bias: Optional[torch.Tensor] = None, 

588 scale: Optional[float] = None, 

589 initial_state: Optional[torch.Tensor] = None, 

590 output_final_state: bool = False, 

591 use_qk_l2norm_in_kernel: bool = False, 

592 use_gate_in_kernel: bool = False, 

593 use_beta_sigmoid_in_kernel: bool = False, 

594 lower_bound: Optional[float] = None, 

595 chunk_size: int = 64, 

596 backend: str = "eager", 

597 cu_seqlens: Optional[torch.Tensor | Sequence[int]] = None, 

598) -> tuple[torch.Tensor, Optional[torch.Tensor]]: 

599 """Dispatch dense or packed KDA to the explicitly selected local backend.""" 

600 if query.ndim == 3: 

601 output, final = chunk_kda( 

602 *(tensor.unsqueeze(0) for tensor in (query, key, value, gate, beta)), 

603 a_log=a_log, dt_bias=dt_bias, scale=scale, initial_state=initial_state, 

604 output_final_state=output_final_state, use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

605 use_gate_in_kernel=use_gate_in_kernel, use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, 

606 lower_bound=lower_bound, chunk_size=chunk_size, backend=backend, cu_seqlens=cu_seqlens) 

607 return output.squeeze(0), final 

608 backend = _normalize_kda_backend(backend) 

609 if backend == "eager": 

610 return torch_chunk_kda( 

611 query, 

612 key, 

613 value, 

614 gate, 

615 beta, 

616 a_log=a_log, 

617 dt_bias=dt_bias, 

618 scale=scale, 

619 initial_state=initial_state, 

620 output_final_state=output_final_state, 

621 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

622 use_gate_in_kernel=use_gate_in_kernel, 

623 use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, 

624 lower_bound=lower_bound, 

625 chunk_size=chunk_size, 

626 cu_seqlens=cu_seqlens, 

627 ) 

628 _validate_fused_state_contract(initial_state, output_final_state, use_qk_l2norm_in_kernel, 

629 use_gate_in_kernel, use_beta_sigmoid_in_kernel, backend) 

630 if backend == "ascendc": 

631 output = ascendc_chunk_kda( 

632 query, key, value, gate, beta, 

633 a_log=a_log.float() if a_log is not None else None, 

634 dt_bias=dt_bias.float() if dt_bias is not None else None, 

635 scale=scale, lower_bound=lower_bound, chunk_size=chunk_size, cu_seqlens=cu_seqlens, 

636 ) 

637 return output, None 

638 triton_a_log = a_log.float() if a_log is not None else None 

639 triton_dt_bias = dt_bias.float() if dt_bias is not None else None 

640 if not _is_triton_kda_input_supported( 

641 query, 

642 key, 

643 value, 

644 gate, 

645 beta, 

646 triton_a_log, 

647 triton_dt_bias, 

648 chunk_size=chunk_size, 

649 lower_bound=lower_bound, 

650 ): 

651 raise RuntimeError( 

652 "KDA backend='triton' received an unsupported input contract; it requires " 

653 "q/k/v/gate/beta=bf16, a_log/dt_bias=fp32, " 

654 "head_k_dim=head_v_dim=128, chunk_size=64, and a supported safe gate." 

655 ) 

656 

657 from hyper_parallel.components.functional.kimi_delta_attention import ( # pylint: disable=import-outside-toplevel 

658 fused_chunk_kda, 

659 ) 

660 

661 output = fused_chunk_kda( 

662 query, 

663 key, 

664 value, 

665 gate, 

666 beta, 

667 a_log=triton_a_log, 

668 dt_bias=triton_dt_bias, 

669 scale=scale, 

670 lower_bound=lower_bound, 

671 chunk_size=chunk_size, 

672 safe_gate=True, 

673 cu_seqlens=cu_seqlens, 

674 ) 

675 return output, None 

676 

677 

678def _chunk_kda_summary_inputs( 

679 key: torch.Tensor, 

680 value: torch.Tensor, 

681 gate: torch.Tensor, 

682 beta: torch.Tensor, 

683 chunk_size: int, 

684) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, int]: 

685 """Pad and convert token-first KDA tensors into head-first chunks.""" 

686 sequence_length = key.shape[1] 

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

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

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

690 gate = F.pad(gate, (0, 0, 0, 0, 0, pad_size)) 

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

692 num_chunks = key.shape[1] // chunk_size 

693 

694 def chunk_view(tensor: torch.Tensor) -> torch.Tensor: 

695 """Rearrange one token-first tensor to head-first chunks.""" 

696 shape = tensor.shape 

697 return tensor.reshape( 

698 shape[0], num_chunks, chunk_size, shape[2], *shape[3:] 

699 ).permute(0, 3, 1, 2, *range(4, tensor.dim() + 1)) 

700 

701 key, value, gate, beta = ( 

702 chunk_view(tensor) for tensor in (key, value, gate, beta) 

703 ) 

704 return key, value, gate, beta, num_chunks 

705 

706 

707def torch_kda_state_summary( 

708 query: torch.Tensor, 

709 key: torch.Tensor, 

710 value: torch.Tensor, 

711 gate: torch.Tensor, 

712 beta: torch.Tensor, 

713 *, 

714 a_log: Optional[torch.Tensor] = None, 

715 dt_bias: Optional[torch.Tensor] = None, 

716 scale: Optional[float] = None, 

717 use_qk_l2norm_in_kernel: bool = False, 

718 use_gate_in_kernel: bool = False, 

719 use_beta_sigmoid_in_kernel: bool = False, 

720 lower_bound: Optional[float] = None, 

721 chunk_size: int = 64, 

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

723 """Summarize a local KDA segment as ``H_out = M @ H_in + S``. 

724 

725 The implementation follows the same chunkwise WY equations as 

726 :func:`torch_chunk_kda`, but computes only the state-independent affine 

727 transition. ``S`` has shape ``[B, HV, K, V]`` and ``M`` has shape 

728 ``[B, HV, K, K]``. Query is accepted to share KDA input preprocessing and 

729 grouped-value validation; the state transition itself is independent of 

730 query and output scale. 

731 """ 

732 if chunk_size <= 0: 

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

734 _, key, value, gate, beta, _ = _prepare_kda_inputs( 

735 query, 

736 key, 

737 value, 

738 gate, 

739 beta, 

740 a_log=a_log, 

741 dt_bias=dt_bias, 

742 scale=scale, 

743 use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, 

744 use_gate_in_kernel=use_gate_in_kernel, 

745 use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, 

746 lower_bound=lower_bound, 

747 ) 

748 key, value, gate, beta, num_chunks = _chunk_kda_summary_inputs( 

749 key, 

750 value, 

751 gate, 

752 beta, 

753 chunk_size, 

754 ) 

755 gate = gate.cumsum(dim=-2) 

756 strict_causal = torch.ones( 

757 chunk_size, 

758 chunk_size, 

759 dtype=torch.bool, 

760 device=key.device, 

761 ).tril(diagonal=-1) 

762 gate_pair = gate.unsqueeze(-2) - gate.unsqueeze(-3) 

763 key_pair = torch.einsum( 

764 "...ik,...jk,...ijk->...ij", 

765 key, 

766 key, 

767 gate_pair.exp(), 

768 ) 

769 strict_lower = (key_pair * beta.unsqueeze(-1)).masked_fill( 

770 ~strict_causal, 

771 0, 

772 ) 

773 inverse = _inverse_unit_lower_triangular(strict_lower) 

774 transform = inverse * beta.unsqueeze(-2) 

775 w = transform @ (key * gate.exp()) 

776 u = transform @ value 

777 

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

779 v_head_dim = value.shape[-1] 

780 eye = torch.eye( 

781 k_head_dim, 

782 dtype=torch.float32, 

783 device=key.device, 

784 ).reshape(1, 1, k_head_dim, k_head_dim) 

785 state_ext = torch.zeros( 

786 batch_size, 

787 num_heads, 

788 k_head_dim, 

789 v_head_dim, 

790 dtype=torch.float32, 

791 device=key.device, 

792 ) 

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

794 

795 for chunk_idx in range(num_chunks): 

796 key_i = key[:, :, chunk_idx] 

797 gate_i = gate[:, :, chunk_idx] 

798 w_i = w[:, :, chunk_idx] 

799 u_i = u[:, :, chunk_idx] 

800 last_gate = gate_i[:, :, -1] 

801 key_decay = key_i * (last_gate.unsqueeze(-2) - gate_i).exp() 

802 transition_i = torch.diag_embed(last_gate.exp()) 

803 transition_i = transition_i - key_decay.transpose(-1, -2) @ w_i 

804 state_ext_i = key_decay.transpose(-1, -2) @ u_i 

805 state_ext = transition_i @ state_ext + state_ext_i 

806 transition = transition_i @ transition 

807 

808 return state_ext.float(), transition.float() 

809 

810 

811def torch_apply_kda_state_summary( 

812 state_ext: torch.Tensor, 

813 transition: torch.Tensor, 

814 initial_state: Optional[torch.Tensor], 

815) -> torch.Tensor: 

816 """Apply a KDA affine state summary to an incoming state.""" 

817 if initial_state is None: 

818 return state_ext.float() 

819 return (transition @ initial_state.to(transition) + state_ext).float() 

820 

821 

822def torch_compose_kda_state_summaries( 

823 first_state_ext: torch.Tensor, 

824 first_transition: torch.Tensor, 

825 second_state_ext: torch.Tensor, 

826 second_transition: torch.Tensor, 

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

828 """Compose two chronological KDA summaries, first then second.""" 

829 state_ext = second_transition @ first_state_ext + second_state_ext 

830 transition = second_transition @ first_transition 

831 return state_ext, transition 

832 

833 

834class KimiRMSNormGated(nn.Module): 

835 """Per-head RMSNorm followed by the sigmoid output gate used by KDA.""" 

836 

837 def __init__(self, hidden_size: int, eps: float = 1e-6) -> None: 

838 """Initialize the per-head normalization weight and epsilon.""" 

839 super().__init__() 

840 self.weight = nn.Parameter(torch.ones(hidden_size)) 

841 self.variance_epsilon = eps 

842 

843 def forward(self, hidden_states: torch.Tensor, gate: torch.Tensor) -> torch.Tensor: 

844 """Normalize in FP32 and apply a sigmoid gate in the input dtype.""" 

845 input_dtype = hidden_states.dtype 

846 normalized = hidden_states.float() 

847 variance = normalized.square().mean(dim=-1, keepdim=True) 

848 normalized = normalized * torch.rsqrt(variance + self.variance_epsilon) 

849 normalized = normalized * self.weight.float() 

850 return (normalized * gate.float().sigmoid()).to(input_dtype) 

851 

852 

853def _make_short_convolution(channels: int, kernel_size: int) -> nn.Conv1d: 

854 """Create the depthwise causal convolution used by the training layer.""" 

855 convolution = nn.Conv1d( 

856 channels, 

857 channels, 

858 kernel_size=kernel_size, 

859 groups=channels, 

860 padding=kernel_size - 1, 

861 bias=False, 

862 ) 

863 convolution.activation = "silu" 

864 return convolution 

865 

866 

867class KimiDeltaAttention(nn.Module): 

868 """Training-only Kimi Delta Attention layer implemented with eager Torch. 

869 

870 The layer owns the projection, ShortConv, gate, normalization, and output 

871 parameters used by Kimi K3. Accelerator selection is intentionally outside 

872 this model module; context-parallel policies may replace ``forward`` while 

873 preserving these parameters and state-dict names. 

874 """ 

875 

876 def __init__( 

877 self, 

878 hidden_size: int, 

879 num_heads: int = 32, 

880 num_v_heads: Optional[int] = None, 

881 head_k_dim: int = 128, 

882 head_v_dim: int = 128, 

883 conv_kernel_size: int = 4, 

884 chunk_size: int = 64, 

885 gate_lower_bound: float = -5.0, 

886 use_full_rank_gate: bool = True, 

887 rms_norm_eps: float = 1e-6, 

888 layer_idx: int = 0, 

889 ) -> None: 

890 """Initialize a configurable, training-only KDA attention layer.""" 

891 super().__init__() 

892 if hidden_size <= 0 or num_heads <= 0: 

893 raise ValueError("hidden_size and num_heads must be positive.") 

894 if num_v_heads is None: 

895 num_v_heads = num_heads 

896 if num_v_heads <= 0 or num_v_heads % num_heads: 

897 raise ValueError("num_v_heads must be positive and divisible by num_heads.") 

898 if head_k_dim <= 0 or head_v_dim <= 0: 

899 raise ValueError("KDA head dimensions must be positive.") 

900 if conv_kernel_size <= 0 or chunk_size <= 0: 

901 raise ValueError("conv_kernel_size and chunk_size must be positive.") 

902 if gate_lower_bound >= 0: 

903 raise ValueError("gate_lower_bound must be negative.") 

904 

905 self.hidden_size = hidden_size 

906 self.num_heads = num_heads 

907 self.num_k_heads = num_heads 

908 self.num_v_heads = num_v_heads 

909 self.head_k_dim = head_k_dim 

910 self.head_v_dim = head_v_dim 

911 self.head_dim = head_v_dim 

912 self.key_dim = num_heads * head_k_dim 

913 self.value_dim = num_v_heads * head_v_dim 

914 self.conv_kernel_size = conv_kernel_size 

915 self.conv_size = conv_kernel_size 

916 self.chunk_size = chunk_size 

917 self.layer_idx = layer_idx 

918 self.gate_lower_bound = gate_lower_bound 

919 self.lower_bound = gate_lower_bound 

920 self.safe_gate = True 

921 self.allow_neg_eigval = False 

922 self.use_short_conv = True 

923 self.use_full_rank_gate = use_full_rank_gate 

924 

925 self.q_proj = nn.Linear(hidden_size, self.key_dim, bias=False) 

926 self.k_proj = nn.Linear(hidden_size, self.key_dim, bias=False) 

927 self.v_proj = nn.Linear(hidden_size, self.value_dim, bias=False) 

928 self.q_conv1d = _make_short_convolution(self.key_dim, conv_kernel_size) 

929 self.k_conv1d = _make_short_convolution(self.key_dim, conv_kernel_size) 

930 self.v_conv1d = _make_short_convolution(self.value_dim, conv_kernel_size) 

931 

932 self.A_log = nn.Parameter(torch.empty(num_v_heads, dtype=torch.float32)) 

933 self.dt_bias = nn.Parameter( 

934 torch.zeros(num_v_heads * head_k_dim, dtype=torch.float32) 

935 ) 

936 with torch.no_grad(): 

937 self.A_log.copy_(torch.empty_like(self.A_log).uniform_(1, 16).log()) 

938 

939 self.f_a_proj = nn.Linear(hidden_size, head_k_dim, bias=False) 

940 self.f_b_proj = nn.Linear(head_k_dim, num_v_heads * head_k_dim, bias=False) 

941 self.b_proj = nn.Linear(hidden_size, num_v_heads, bias=False) 

942 if use_full_rank_gate: 

943 self.g_proj = nn.Linear(hidden_size, self.value_dim, bias=False) 

944 else: 

945 self.g_a_proj = nn.Linear(hidden_size, head_v_dim, bias=False) 

946 self.g_b_proj = nn.Linear(head_v_dim, self.value_dim, bias=False) 

947 self.o_norm = KimiRMSNormGated(head_v_dim, eps=rms_norm_eps) 

948 self.o_proj = nn.Linear(self.value_dim, hidden_size, bias=False) 

949 

950 @staticmethod 

951 def _run_short_convolution( 

952 projected: torch.Tensor, 

953 convolution: nn.Conv1d, 

954 sequence_info=None, 

955 ) -> torch.Tensor: 

956 """Apply one causal depthwise convolution and trim its right padding.""" 

957 if sequence_info is not None: 

958 return packed_causal_conv1d(projected, convolution, sequence_info) 

959 sequence_length = projected.shape[1] 

960 output = convolution(projected.transpose(1, 2))[:, :, :sequence_length] 

961 return F.silu(output.transpose(1, 2)) 

962 

963 def _output_gate(self, hidden_states: torch.Tensor) -> torch.Tensor: 

964 """Project the layer input to the per-head output gate.""" 

965 if self.use_full_rank_gate: 

966 return self.g_proj(hidden_states) 

967 return self.g_b_proj(self.g_a_proj(hidden_states)) 

968 

969 def forward( 

970 self, 

971 hidden_states: torch.Tensor, 

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

973 *, 

974 use_cache: bool = False, 

975 output_attentions: bool = False, 

976 **kwargs: Any, 

977 ) -> torch.Tensor: 

978 """Run dense or packed training-time KDA without recurrent cache.""" 

979 if use_cache or kwargs.get("cache_params") is not None: 

980 raise NotImplementedError("KimiDeltaAttention does not support inference cache.") 

981 if output_attentions: 

982 raise NotImplementedError("KDA does not materialize attention weights.") 

983 if attention_mask is not None and not bool(attention_mask.bool().all().item()): 

984 raise NotImplementedError("Padded KDA batches are not implemented.") 

985 

986 is_flat_input = hidden_states.ndim == 2 

987 if is_flat_input: 

988 hidden_states = hidden_states.unsqueeze(0) 

989 batch_size, sequence_length, _ = hidden_states.shape 

990 sequence_info = resolve_packed_sequence(kwargs, sequence_length) 

991 if sequence_info is not None and batch_size != 1: 

992 raise ValueError("Packed KDA requires a flattened batch dimension of one.") 

993 query = self._run_short_convolution( 

994 self.q_proj(hidden_states), self.q_conv1d, sequence_info 

995 ).reshape(batch_size, sequence_length, self.num_heads, self.head_k_dim) 

996 key = self._run_short_convolution( 

997 self.k_proj(hidden_states), self.k_conv1d, sequence_info 

998 ).reshape(batch_size, sequence_length, self.num_heads, self.head_k_dim) 

999 value = self._run_short_convolution( 

1000 self.v_proj(hidden_states), self.v_conv1d, sequence_info 

1001 ).reshape(batch_size, sequence_length, self.num_v_heads, self.head_v_dim) 

1002 gate = self.f_b_proj(self.f_a_proj(hidden_states)).reshape( 

1003 batch_size, sequence_length, self.num_v_heads, self.head_k_dim 

1004 ) 

1005 beta = self.b_proj(hidden_states).reshape( 

1006 batch_size, sequence_length, self.num_v_heads 

1007 ) 

1008 output, _ = torch_chunk_kda( 

1009 query, 

1010 key, 

1011 value, 

1012 gate, 

1013 beta, 

1014 a_log=self.A_log, 

1015 dt_bias=self.dt_bias, 

1016 initial_state=None, 

1017 output_final_state=False, 

1018 use_qk_l2norm_in_kernel=True, 

1019 use_gate_in_kernel=True, 

1020 use_beta_sigmoid_in_kernel=True, 

1021 lower_bound=self.gate_lower_bound, 

1022 chunk_size=self.chunk_size, 

1023 cu_seqlens=None if sequence_info is None else sequence_info.local_offsets, 

1024 ) 

1025 output_gate = self._output_gate(hidden_states).reshape( 

1026 batch_size, sequence_length, self.num_v_heads, self.head_v_dim 

1027 ) 

1028 output = self.o_norm(output, output_gate) 

1029 output = self.o_proj(output.reshape(batch_size, sequence_length, self.value_dim)) 

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