Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / components / functional / gated_delta_net_ascendc.py: 0%
69 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« 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"""Optional Ascend C GDN core with effective scalar gate and beta inputs."""
17from __future__ import annotations
19import importlib
20import inspect
21from collections.abc import Sequence
22from functools import lru_cache
23from typing import Any
25import torch
26from torch.autograd.function import once_differentiable
29@lru_cache(maxsize=1)
30def _load_ops():
31 """Keep the optional native runtime out of ordinary framework imports."""
32 try:
33 ops = importlib.import_module("fla_npu.ops.ascendc")
34 except ImportError as error:
35 raise RuntimeError("GDN backend='ascendc' requires a compatible fla-npu installation.") from error
36 required = {
37 "chunk_gated_delta_rule_fwd": (
38 "layout", "use_exp2", "cu_seqlens", "chunk_indices", "disable_recompute",
39 "use_qk_l2norm_in_kernel", "use_beta_sigmoid_in_kernel", "use_gate_in_kernel",
40 ),
41 "chunk_gated_delta_rule_bwd": (
42 "layout", "use_exp2", "cu_seqlens", "chunk_indices", "use_qk_l2norm_in_kernel",
43 "use_beta_sigmoid_in_kernel", "use_gate_in_kernel",
44 ),
45 }
46 for name, parameters in required.items():
47 try:
48 signature = inspect.signature(getattr(ops, name, None))
49 except (TypeError, ValueError) as error:
50 raise RuntimeError(f"fla-npu lacks a compatible public {name} API.") from error
51 missing = set(parameters).difference(signature.parameters)
52 if missing:
53 raise RuntimeError(f"fla-npu {name} lacks required parameters {sorted(missing)}.")
54 return ops
57def _packed_offsets(cu_seqlens, tokens):
58 """Freeze validated document offsets without a device-to-host transfer."""
59 if isinstance(cu_seqlens, torch.Tensor):
60 if (cu_seqlens.device.type != "cpu" or cu_seqlens.ndim != 1
61 or cu_seqlens.dtype not in (torch.int32, torch.int64)):
62 raise ValueError("Ascend C GDN cu_seqlens must be a one-dimensional CPU integer tensor or sequence.")
63 cu_seqlens = cu_seqlens.tolist()
64 if not isinstance(cu_seqlens, Sequence):
65 raise ValueError("Ascend C GDN cu_seqlens must be a CPU integer tensor or sequence.")
66 offsets = tuple(cu_seqlens)
67 if (len(offsets) < 2 or offsets[0] != 0 or offsets[-1] != tokens
68 or any(not isinstance(value, int) or isinstance(value, bool) for value in offsets)
69 or any(start >= end for start, end in zip(offsets, offsets[1:]))):
70 raise ValueError("Ascend C GDN cu_seqlens must strictly increase from zero to the packed token count.")
71 return offsets
74def _packed_metadata(cu_seqlens, tokens):
75 """Derive canonical native chunk indices from immutable host offsets."""
76 if cu_seqlens is None:
77 return {}
78 offsets = _packed_offsets(cu_seqlens, tokens)
79 chunks = tuple(component
80 for sequence, (start, end) in enumerate(zip(offsets, offsets[1:]))
81 for chunk in range((end - start + 63) // 64)
82 for component in (sequence, chunk))
83 return {"cu_seqlens": offsets, "chunk_indices": chunks}
86class _AscendCGDN(torch.autograd.Function):
87 """Own each invocation's native backward intermediates independently."""
89 @staticmethod
90 def forward(ctx: Any, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
91 gate: torch.Tensor, beta: torch.Tensor, metadata: dict) -> torch.Tensor:
92 """Save only the tensors consumed by the native composite backward."""
93 scale = query.shape[-1] ** -0.5
94 layout = "TND" if metadata else "BSND"
95 result = _load_ops().chunk_gated_delta_rule_fwd(
96 query, key, value, gate, beta, scale=scale, layout=layout, chunk_size=64,
97 initial_state=None, output_final_state=False, disable_recompute=True,
98 return_intermediate_states=False, state_v_first=False, use_exp2=False,
99 use_qk_l2norm_in_kernel=False, use_beta_sigmoid_in_kernel=False,
100 use_gate_in_kernel=False, **metadata,
101 )
102 # The public ABI keeps ten slots even when optional norm/state outputs
103 # are disabled: o, final_state, g_cumsum, A, beta_eff, h, q_hat,
104 # k_hat, q_rstd, k_rstd. This wrapper only consumes slots 0, 2 and 3.
105 if len(result) != 10 or any(not isinstance(result[index], torch.Tensor) for index in (0, 2, 3)):
106 raise RuntimeError("fla-npu GDN forward must return ten slots with output, g_cumsum and A tensors.")
107 output, _, gate_cumsum, matrix_a, *_ = result
108 # Gate and beta transformations remain in the caller's autograd graph.
109 # Native backward consumes cumulative gate but returns the raw-gate gradient.
110 ctx.save_for_backward(query, key, value, gate_cumsum, beta, matrix_a)
111 ctx.native_metadata, ctx.layout, ctx.scale = metadata, layout, scale
112 return output
114 @staticmethod
115 @once_differentiable
116 def backward(ctx: Any, grad_output: torch.Tensor) -> tuple:
117 """Return effective-input gradients without applying norm or sigmoid twice."""
118 query, key, value, gate_cumsum, beta, matrix_a = ctx.saved_tensors
119 gradients = _load_ops().chunk_gated_delta_rule_bwd(
120 query, key, value, gate_cumsum, beta, matrix_a, grad_output.contiguous(), ctx.scale,
121 layout=ctx.layout, chunk_size=64, initial_state=None, dht=None, use_exp2=False,
122 use_qk_l2norm_in_kernel=False, use_beta_sigmoid_in_kernel=False,
123 use_gate_in_kernel=False, state_v_first=False, **ctx.native_metadata,
124 )
125 if len(gradients) != 8 or any(not isinstance(gradient, torch.Tensor) for gradient in gradients[:5]):
126 raise RuntimeError("fla-npu GDN backward must return eight slots with all five input gradients.")
127 dq, dk, dv, dbeta, dgate, *_ = gradients
128 return dq, dk, dv, dgate, dbeta, None
131def chunk_gated_delta_rule_ascendc(
132 query: torch.Tensor,
133 key: torch.Tensor,
134 value: torch.Tensor,
135 g: torch.Tensor,
136 beta: torch.Tensor,
137 *,
138 cu_seqlens: torch.Tensor | Sequence[int] | None = None,
139) -> tuple[torch.Tensor, None]:
140 """Run the optional native GDN local core for dense or packed Ulysses.
142 Args:
143 query: Normalized or unnormalized BF16 query of shape [B,T,H,128].
144 key: BF16 key with the same shape as query.
145 value: BF16 value with the same shape as query.
146 g: Effective FP32 log-decay of shape [B,T,H], before chunk cumsum.
147 beta: Effective BF16 delta coefficient of shape [B,T,H].
148 cu_seqlens: Immutable document boundaries supplied as host integers.
150 Returns:
151 The output with the same shape as value, and None for final state.
153 Raises:
154 ValueError: Inputs violate the explicit native training contract.
155 RuntimeError: The optional native API or device is unsupported.
156 """
157 if query.ndim != 4:
158 raise ValueError("GDN inputs must have shape [B,T,H,K].")
159 if cu_seqlens is not None and query.shape[0] != 1:
160 raise ValueError("Packed Ascend C GDN requires physical batch size one.")
161 metadata = _packed_metadata(cu_seqlens, query.shape[1])
162 return _AscendCGDN.apply(query, key, value, g, beta, metadata), None