Diff Coverage

Diff: origin/master...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/components/functional/gated_delta_net_ascendc.py 0.0% 17,19-23,25-26,29-30,32-36,46-54,57,59-60,62-67,70-71,74,76-79,83,86,89-90,93-95,105-107,110-112,114-116,118-119,125-128,131,157-162
hyper_parallel/components/functional/kimi_delta_attention_ascendc.py 20.1% 37-41,49-57,61,84-86,94-96,101,106-107,109-112,115-116,121-124,126,157-160,164-172,176,184-185,189-192,197-201,215-224,229-238,246-249,258-265,270,284-306,309-311,315-338,343-349,354-358,360,363-365,367-368,395-401,403-405,407-411,414,416-419,422-426,437-439,441,443-445,448-449,451-452,455,466-467,470-476,507-517
hyper_parallel/components/functional/kimi_delta_attention_state_summary.py 0.0% 376-377,388-389
hyper_parallel/components/functional/linear_attention_packed.py 100%  
hyper_parallel/components/modules/gated_delta_net.py 10.0% 258-262,264-265,267,270
hyper_parallel/components/modules/kimi_delta_attention.py 89.1% 558,628,630-631,637
hyper_parallel/distributed/context_parallel/gated_delta_net.py 6.2% 729,1042-1043,1046-1048,1095-1096,1105-1106,1276-1278,1319-1320
hyper_parallel/distributed/context_parallel/kimi_delta_attention.py 17.6% 77,79-80,83,259-260,852-855,931,933,936,1461
hyper_parallel/distributed/context_parallel/kimi_delta_attention_rd.py 9.8% 237-240,258-266,268,270-273,277-280,284-293,295-299
hyper_parallel/distributed/context_parallel/kimi_delta_attention_rd_plan.py 33.3% 163-164,190-193
hyper_parallel/distributed/context_parallel/kimi_delta_attention_state.py 25.0% 175-177
hyper_parallel/components/functional/gated_delta_net_ascendc.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# limitations under the License.
# ============================================================================
"""Optional Ascend C GDN core with effective scalar gate and beta inputs."""

from __future__ import annotations

import importlib
import inspect
from collections.abc import Sequence
from functools import lru_cache
from typing import Any

import torch
from torch.autograd.function import once_differentiable


@lru_cache(maxsize=1)
def _load_ops():
    """Keep the optional native runtime out of ordinary framework imports."""
    try:
        ops = importlib.import_module("fla_npu.ops.ascendc")
    except ImportError as error:
        raise RuntimeError("GDN backend='ascendc' requires a compatible fla-npu installation.") from error
    required = {
        "chunk_gated_delta_rule_fwd": (
            "layout", "use_exp2", "cu_seqlens", "chunk_indices", "disable_recompute",
            "use_qk_l2norm_in_kernel", "use_beta_sigmoid_in_kernel", "use_gate_in_kernel",
        ),
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
            "layout", "use_exp2", "cu_seqlens", "chunk_indices", "use_qk_l2norm_in_kernel",
            "use_beta_sigmoid_in_kernel", "use_gate_in_kernel",
        ),
    }
    for name, parameters in required.items():
        try:
            signature = inspect.signature(getattr(ops, name, None))
        except (TypeError, ValueError) as error:
            raise RuntimeError(f"fla-npu lacks a compatible public {name} API.") from error
        missing = set(parameters).difference(signature.parameters)
        if missing:
            raise RuntimeError(f"fla-npu {name} lacks required parameters {sorted(missing)}.")
    return ops


def _packed_offsets(cu_seqlens, tokens):
    """Freeze validated document offsets without a device-to-host transfer."""
    if isinstance(cu_seqlens, torch.Tensor):
        if (cu_seqlens.device.type != "cpu" or cu_seqlens.ndim != 1
                or cu_seqlens.dtype not in (torch.int32, torch.int64)):
            raise ValueError("Ascend C GDN cu_seqlens must be a one-dimensional CPU integer tensor or sequence.")
        cu_seqlens = cu_seqlens.tolist()
    if not isinstance(cu_seqlens, Sequence):
        raise ValueError("Ascend C GDN cu_seqlens must be a CPU integer tensor or sequence.")
    offsets = tuple(cu_seqlens)
    if (len(offsets) < 2 or offsets[0] != 0 or offsets[-1] != tokens
            or any(not isinstance(value, int) or isinstance(value, bool) for value in offsets)
            or any(start >= end for start, end in zip(offsets, offsets[1:]))):
        raise ValueError("Ascend C GDN cu_seqlens must strictly increase from zero to the packed token count.")
    return offsets


def _packed_metadata(cu_seqlens, tokens):
    """Derive canonical native chunk indices from immutable host offsets."""
    if cu_seqlens is None:
        return {}
    offsets = _packed_offsets(cu_seqlens, tokens)
    chunks = tuple(component
                   for sequence, (start, end) in enumerate(zip(offsets, offsets[1:]))
                   for chunk in range((end - start + 63) // 64)
                   for component in (sequence, chunk))
    return {"cu_seqlens": offsets, "chunk_indices": chunks}


class _AscendCGDN(torch.autograd.Function):
    """Own each invocation's native backward intermediates independently."""

    @staticmethod
    def forward(ctx: Any, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
                gate: torch.Tensor, beta: torch.Tensor, metadata: dict) -> torch.Tensor:
        """Save only the tensors consumed by the native composite backward."""
        scale = query.shape[-1] ** -0.5
        layout = "TND" if metadata else "BSND"
        result = _load_ops().chunk_gated_delta_rule_fwd(
            query, key, value, gate, beta, scale=scale, layout=layout, chunk_size=64,
            initial_state=None, output_final_state=False, disable_recompute=True,
            return_intermediate_states=False, state_v_first=False, use_exp2=False,
            use_qk_l2norm_in_kernel=False, use_beta_sigmoid_in_kernel=False,
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
        )
        # The public ABI keeps ten slots even when optional norm/state outputs
        # are disabled: o, final_state, g_cumsum, A, beta_eff, h, q_hat,
        # k_hat, q_rstd, k_rstd. This wrapper only consumes slots 0, 2 and 3.
        if len(result) != 10 or any(not isinstance(result[index], torch.Tensor) for index in (0, 2, 3)):
            raise RuntimeError("fla-npu GDN forward must return ten slots with output, g_cumsum and A tensors.")
        output, _, gate_cumsum, matrix_a, *_ = result
        # Gate and beta transformations remain in the caller's autograd graph.
        # Native backward consumes cumulative gate but returns the raw-gate gradient.
        ctx.save_for_backward(query, key, value, gate_cumsum, beta, matrix_a)
        ctx.native_metadata, ctx.layout, ctx.scale = metadata, layout, scale
        return output

    @staticmethod
    @once_differentiable
    def backward(ctx: Any, grad_output: torch.Tensor) -> tuple:
        """Return effective-input gradients without applying norm or sigmoid twice."""
        query, key, value, gate_cumsum, beta, matrix_a = ctx.saved_tensors
        gradients = _load_ops().chunk_gated_delta_rule_bwd(
            query, key, value, gate_cumsum, beta, matrix_a, grad_output.contiguous(), ctx.scale,
            layout=ctx.layout, chunk_size=64, initial_state=None, dht=None, use_exp2=False,
            use_qk_l2norm_in_kernel=False, use_beta_sigmoid_in_kernel=False,
            use_gate_in_kernel=False, state_v_first=False, **ctx.native_metadata,
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
            layout=ctx.layout, chunk_size=64, initial_state=None, dht=None, use_exp2=False,
            use_qk_l2norm_in_kernel=False, use_beta_sigmoid_in_kernel=False,
            use_gate_in_kernel=False, state_v_first=False, **ctx.native_metadata,
        )
        if len(gradients) != 8 or any(not isinstance(gradient, torch.Tensor) for gradient in gradients[:5]):
            raise RuntimeError("fla-npu GDN backward must return eight slots with all five input gradients.")
        dq, dk, dv, dbeta, dgate, *_ = gradients
        return dq, dk, dv, dgate, dbeta, None


def chunk_gated_delta_rule_ascendc(
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    g: torch.Tensor,
153
154
155
156
157
158
159
160
161
162
    Raises:
        ValueError: Inputs violate the explicit native training contract.
        RuntimeError: The optional native API or device is unsupported.
    """
    if query.ndim != 4:
        raise ValueError("GDN inputs must have shape [B,T,H,K].")
    if cu_seqlens is not None and query.shape[0] != 1:
        raise ValueError("Packed Ascend C GDN requires physical batch size one.")
    metadata = _packed_metadata(cu_seqlens, query.shape[1])
    return _AscendCGDN.apply(query, key, value, g, beta, metadata), None
hyper_parallel/components/functional/kimi_delta_attention_ascendc.py
33
34
35
36
37
38
39
40
41
42
43
44
45

@lru_cache(maxsize=1)
def _load_ops():
    """Keep the optional native runtime out of ordinary framework imports."""
    try:
        ops = importlib.import_module("fla_npu.ops.ascendc")
    except ImportError as error:
        raise RuntimeError("backend='ascendc' requires a compatible fla-npu installation.") from error
    required = {
        "chunk_kda_fwd_prepare": ("backward_mode", "use_qk_l2norm_in_kernel", "use_beta_sigmoid_in_kernel"),
        "chunk_fwd_h": ("output_final_state", "cu_seqlens", "chunk_indices"),
        "chunk_kda_fwd_finalize": ("output_layout", "cu_seqlens", "chunk_indices"),
        "chunk_kda_bwd_prepare": ("scale", "layout", "cu_seqlens", "chunk_indices"),
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
        "chunk_kda_bwd_prepare": ("scale", "layout", "cu_seqlens", "chunk_indices"),
        "chunk_gated_delta_rule_bwd_dhu": ("gK", "dht", "cu_seqlens", "chunk_indices"),
        "chunk_kda_bwd_finalize": ("q_rstd", "k_rstd", "use_beta_sigmoid_in_kernel", "output_layout"),
    }
    for name, parameters in required.items():
        operation = getattr(ops, name, None)
        try:
            signature = inspect.signature(operation)
        except (TypeError, ValueError) as error:
            raise RuntimeError(f"fla-npu lacks a compatible public {name} API.") from error
        missing = set(parameters).difference(signature.parameters)
        if missing:
            raise RuntimeError(
                f"fla-npu {name} lacks required parameters {sorted(missing)}; "
                "install a fla-npu version providing these public KDA parameters."
            )
    return ops


class _KDAPrepared(NamedTuple):
    """Public Prepare save-mode slots, in the native ABI's fixed order."""
80
81
82
83
84
85
86
87
88
89
90


def _prepare_kda(inputs, scale, lower_bound, metadata):
    """Prepare once for native state summaries and the local state recurrence."""
    ops = _load_ops()
    query, key, value, gate, beta, a_log, dt_bias = inputs
    result = ops.chunk_kda_fwd_prepare(
        query, key, value, gate.transpose(1, 2).contiguous(), beta.transpose(1, 2).contiguous(),
        scale, layout="BNSD", chunk_size=64,
        safe_gate=True, lower_bound=lower_bound, use_gate_in_kernel=True,
        A_log=a_log, dt_bias=dt_bias.flatten(), epsilon=1e-6,
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
        A_log=a_log, dt_bias=dt_bias.flatten(), epsilon=1e-6,
        use_qk_l2norm_in_kernel=True, use_beta_sigmoid_in_kernel=True,
        use_exp2=True, backward_mode="save", **metadata,
    )
    if len(result) != 13 or any(not isinstance(tensor, torch.Tensor) for tensor in result):
        raise RuntimeError("fla-npu Prepare must return all 13 tensors for native-norm save mode.")
    return _KDAPrepared(*result)


def _native_backward_view(tensor: torch.Tensor, packed: bool) -> torch.Tensor:
    """Expose packed NTD views without copying the B=1 Prepare/FwdH storage."""
    return tensor.squeeze(0) if packed else tensor


def _packed_offsets(cu_seqlens, tokens):
    """Snapshot host offsets before deriving native chunk metadata."""
    if isinstance(cu_seqlens, torch.Tensor):
        if (cu_seqlens.device.type != "cpu" or cu_seqlens.ndim != 1
                or cu_seqlens.dtype not in (torch.int32, torch.int64)):
            raise ValueError("Ascend C cu_seqlens must be a one-dimensional CPU integer tensor or sequence.")
        cu_seqlens = cu_seqlens.tolist()
    offsets = tuple(cu_seqlens)
    if (len(offsets) < 2 or offsets[0] != 0 or offsets[-1] != tokens
            or any(not isinstance(x, int) or isinstance(x, bool) for x in offsets)
            or any(begin >= end for begin, end in zip(offsets, offsets[1:]))):
        raise ValueError("Ascend C cu_seqlens must strictly increase from zero to the packed token count.")
    return offsets


def _packed_metadata(cu_seqlens, tokens):
    """Snapshot validated host offsets and derive the canonical chunk order."""
    if cu_seqlens is None:
        return {}
    offsets = _packed_offsets(cu_seqlens, tokens)
    indices = tuple(value for seq, (begin, end) in enumerate(zip(offsets, offsets[1:]))
                    for chunk in range((end - begin + 63) // 64) for value in (seq, chunk))
    return {"cu_seqlens": offsets, "chunk_indices": indices}


def ascendc_chunk_kda(
    query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
        BF16 output with the query shape. External recurrent states and double
        backward are not exposed. Native Q/K norm backward uses save mode;
        native capability checks select the supported device implementation.
    """
    if query.ndim == 3:
        if cu_seqlens is None:
            raise ValueError("Packed TND inputs require cu_seqlens.")
        return ascendc_chunk_kda(
            *(tensor.unsqueeze(0) for tensor in (query, key, value, gate, beta)),
            a_log=a_log, dt_bias=dt_bias, scale=scale, lower_bound=lower_bound,
            chunk_size=chunk_size, cu_seqlens=cu_seqlens).squeeze(0)
    if query.ndim != 4:
        raise ValueError("KDA inputs must have shape [B,T,H,K] or packed [T,H,K].")
    if chunk_size != 64:
        raise ValueError("Ascend C KDA uses chunk_size=64.")
    metadata = _packed_metadata(cu_seqlens, query.shape[1])
    if metadata and query.shape[0] != 1:
        raise ValueError("Packed Ascend C KDA requires B=1.")
    scale = query.shape[-1] ** -0.5 if scale is None else float(scale)
    tokens = tuple(tensor.transpose(1, 2).contiguous() for tensor in (query, key, value))
    # Gate/beta stay token-major for backward. Native operators validate their
    # dtype, shape and device; only adapter-owned layout/metadata checks live here.
    # Clone sliced parameters to give native tiling this shard's storage descriptor.
    return _AscendCKDAStages.apply(
        *tokens, gate.contiguous(), beta.contiguous(), a_log.reshape(-1).clone(),
        dt_bias.reshape(-1, query.shape[-1]).clone(), scale, lower_bound, metadata, None)

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204

@lru_cache(maxsize=1)
def _load_state_ops():
    """Check every native stage before entering the first state collective."""
    ops = _load_ops()
    required = (
        "pre_process_fwd_kernel_merged", "pre_process_bwd_kernel_merged",
        "merge_fwd_bwd_kernel",
    )
    missing = [name for name in required if not callable(getattr(ops, name, None))]
    if missing:
        raise RuntimeError(f"Native KDA state CP requires these fla-npu APIs: {', '.join(missing)}.")
    return ops


def _native_ag_intervals(info, tokens, rank, size):
    """Return tail/head windows and document-bounded exclusive scan lengths."""
    if info is None:
        return (0, tokens), (0, tokens), rank, size - rank - 1
    first_rank = (info.start - info.preceding_sample_tokens) // tokens
    last_rank = (info.global_offsets[bisect_left(info.global_offsets, info.end)] - 1) // tokens
    return ((info.tail.start, info.tail.stop), (info.head.start, info.head.stop),
            rank - first_rank if info.continues_previous else 0,
            last_rank - rank if info.continues_next else 0)

211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    """

    def __init__(self, mesh: Any, executor: Any, info: Any, local_length: int, heads: int) -> None:
        """Copy communication metadata and retain only this call's sequence plan."""
        self.executor, self.info, self.heads = executor, info, heads
        self.rank = mesh.get_local_rank()
        self.size = mesh.size()
        self.ranks = tuple(mesh.rank_list)
        self.group = mesh.get_group()
        self.width = getattr(executor, "width", self.size) if executor is not None else 1
        self.block, self.local = divmod(self.rank, self.width)
        self.owner = self.block * self.width + self.width - 1
        self.is_rd = isinstance(executor, KDAStateRecursiveDoubling)
        self.plans = (tuple(executor.directional_plan(info, local_length, reverse=reverse) for reverse in (False, True))
                      if self.is_rd else ())

    def peer(self, forward: bool, receiving: bool) -> int | None:
        """Return a chronological peer, pruning disconnected ordinary P2P edges."""
        direction = 1 if forward else -1
        peer = self.owner + (-direction if receiving else direction) * self.width
        if not 0 <= peer < self.size:
            return None
        if self.width == 1 and self.info is not None:
            needs_previous = forward == receiving
            connected = self.info.continues_previous if needs_previous else self.info.continues_next
            if not connected:
                return None
        return peer

    def needs_summary(self, forward: bool) -> bool:
        """Avoid producing a summary without an exit consumer.
242
243
244
245
246
247
248
249
250
251
252

        Args:
            forward: Select the forward S/M or backward G/N direction.
        """
        if self.is_rd:
            plan = self.plans[not forward]
            return plan.shift_peers(plan.local_rank)[0] is not None
        return self.width > 1 or self.peer(forward, receiving=False) is not None

    def receive(self, reference: torch.Tensor, forward: bool) -> tuple[torch.Tensor | None, Any]:
        """Post reception before native prepare, independently of summary production.
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
        Args:
            reference: Head-major input supplying the head count and device.
            forward: Select the chronological communication direction.
        """
        if self.is_rd or self.width == self.size:
            return None, None
        incoming = reference.new_zeros((1, reference.shape[1], 128, 128), dtype=torch.float32)
        peer = self.peer(forward, receiving=True)
        handle = None
        if self.rank == self.owner and peer is not None:
            handle = dist.irecv(incoming, src=self.ranks[peer], group=self.group)
        return incoming, handle

    @staticmethod
    def apply(packed: torch.Tensor, incoming: torch.Tensor | None) -> torch.Tensor:
        """Apply forward M or native backward N directly, without transposing N."""
        return apply_kda_state_summary(packed[..., :128], packed[..., 128:], incoming)

    def exchange(self, ops: Any, packed: torch.Tensor | None,
                 reception: tuple[torch.Tensor | None, Any], forward: bool,
                 count: int) -> tuple[torch.Tensor, Any, torch.Tensor | None]:
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
            reception: Previously posted receive buffer and handle.
            forward: Select the chronological communication direction.
            count: Number of maps in the relevant AllGather prefix or suffix.
        """
        if self.is_rd:
            state_shape = (1, self.heads, 128, 128)
            maps = None if packed is None else packed.reshape(1, self.heads, 128, 256)
            result = self.executor.scan_affine(maps, self.plans[not forward], state_shape, reverse=not forward)
            return result, None, None
        if self.width == self.size:
            maps = self.executor.gather_packed(packed.reshape(1, *packed.shape[-3:]))
            incoming = maps.new_zeros((maps.shape[1], 128, 128))
            if count:
                ops.merge_fwd_bwd_kernel(incoming, maps, count, self.rank, forward=forward)
            return incoming.unsqueeze(0), None, None
        incoming, recv_handle = reception
        if self.width > 1:
            return self._grouped_exchange(packed, incoming, recv_handle, forward)
        transfer = self.info is None or self.info.num_sequences == 1
        if recv_handle is not None and transfer:
            recv_handle.wait()
            recv_handle = None
        send_handle, outgoing = None, None
        peer = self.peer(forward, receiving=False)
        if peer is not None:
            outgoing = self.apply(packed, incoming if transfer else None).contiguous()
            send_handle = dist.isend(outgoing, dst=self.ranks[peer], group=self.group)
        # A reset permits sending the independent tail/head before this wait,
        # but local h/dhu still consumes the received first/last fragment state.
        if recv_handle is not None:
            recv_handle.wait()
        return incoming, send_handle, outgoing

    def _grouped_exchange(self, packed, incoming, recv_handle, forward):
        """Gather native maps within a group and relay only its exit state."""
        if self.info is not None:
            if not self.info.transfer_active or not self.info.continues_next:
                packed[..., 128:].zero_()
            connected = self.info.continues_next if forward else self.info.continues_previous
            if not connected:
                packed[..., :128].zero_()
        maps = packed.new_empty((self.width, *packed.shape[-3:]))
        dist.all_gather_into_tensor(maps, packed.reshape(1, *packed.shape[-3:]), group=self.executor.intra)
        send_handle, outgoing, result = None, None, None
        indices = range(self.width) if forward else range(self.width - 1, -1, -1)
        prefix_count = self.local if forward else self.width - self.local - 1
        if self.rank == self.owner:
            if recv_handle is not None:
                recv_handle.wait()
            peer = self.peer(forward, receiving=False)
            if peer is not None:
                outgoing, result = self._scan(maps, incoming, indices, prefix_count)
                send_handle = dist.isend(outgoing, dst=self.ranks[peer], group=self.group)
        if self.peer(forward, receiving=True) is not None:
            dist.broadcast(incoming, src=self.ranks[self.owner], group=self.executor.intra)
        if result is None:
            local_indices = range(self.local) if forward else range(self.width - 1, self.local, -1)
            result, _ = self._scan(maps, incoming, local_indices)
        return result, send_handle, outgoing

    @staticmethod
    def _scan(maps, incoming, indices, prefix_count=None):
        """Keep native M/N orientation while applying a nonzero group entry."""
        state = incoming
        prefix = incoming if prefix_count == 0 else None
        for step, index in enumerate(indices, start=1):
            state = _NativeStateExchange.apply(maps[index:index + 1], state)
            if step == prefix_count:
                prefix = state
        return state, prefix


def _exchange_native_gradient(ctx, ops, inputs, reception):
    """Own G/N production, reset injection and the stock dHT dtype boundary."""
    if ctx.exchange is None:
        return None, None, None
    gk, qg, kg, weights, grad, dv_local_head = inputs
    maps = None
    if ctx.exchange.needs_summary(False):
        # Only the stock G/N ABI rounds gk; local dhu/finalize retain FP32.
        maps = ops.pre_process_bwd_kernel_merged(
            qg, kg, weights, grad, dv_local_head, ctx.scale, 64,
            gk=gk.to(qg.dtype), cu_seqlens=ctx.head)
    terminal, send_handle, outgoing = ctx.exchange.exchange(ops, maps, reception, False, ctx.suffix)
    if ctx.exchange.info is not None:
        terminal = ctx.exchange.info.last_state(terminal)
    # Communication and summary apply retain FP32; only stock dhu requires BF16.
    terminal = None if terminal is None else terminal.to(qg.dtype)
    return terminal, send_handle, outgoing


class _AscendCKDAStages(torch.autograd.Function):
    """Share native local stages; state protocols optionally inject H and D."""
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
            lower_bound: Safe-gate lower bound.
            metadata: Native packed offsets and chunk indices.
            exchange: Optional state protocol for this invocation.
        """
        ops = _load_state_ops() if exchange is not None else _load_ops()
        inputs = (query, key, value, gate, beta, a_log, dt_bias)
        reception = None if exchange is None else exchange.receive(query, True)
        prepared = _prepare_kda(inputs, scale, lower_bound, metadata)
        initial = send_handle = outgoing = head = suffix = None
        if exchange is not None:
            tail, head, prefix, suffix = _native_ag_intervals(
                exchange.info, query.shape[2], exchange.rank, exchange.size)
            maps = None
            if exchange.needs_summary(True):
                maps = ops.pre_process_fwd_kernel_merged(
                    prepared.kg, prepared.w, prepared.u, gk=prepared.gk, cu_seqlens=tail)
            initial, send_handle, outgoing = exchange.exchange(ops, maps, reception, True, prefix)
            del maps, reception
            if exchange.info is not None:
                initial = exchange.info.first_state(initial)
        hidden, v_new, _ = ops.chunk_fwd_h(
            prepared.kg, prepared.w, prepared.u, gk=prepared.gk, initial_state=initial,
            output_final_state=False, use_exp2=True, **metadata)
        output = ops.chunk_kda_fwd_finalize(
            prepared.qg_scaled, prepared.aqk, v_new, hidden, output_layout="BSND", **metadata)
        if send_handle is not None:
            send_handle.wait()
        del outgoing
        prepared = prepared._replace(u=None, qg_scaled=None)
        # The saved hidden tensor contains entry states. Summary protocols
        # produce the outgoing gradient themselves, so native dh0 is unused.
        ctx.save_for_backward(value, gate, a_log, dt_bias, *prepared, hidden, v_new)
        ctx.dtypes = tuple(tensor.dtype for tensor in inputs)
        ctx.scale, ctx.lower_bound, ctx.native_metadata = scale, lower_bound, metadata
        ctx.exchange, ctx.head, ctx.suffix = exchange, head, suffix
        return output

    @staticmethod
    @once_differentiable
    def backward(ctx: Any, grad_output: torch.Tensor) -> tuple:
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
        Args:
            ctx: Forward-owned activations and communication metadata.
            grad_output: Sequence-major output gradient.
        """
        ops = _load_state_ops() if ctx.exchange is not None else _load_ops()
        value, gate, a_log, dt_bias, *saved = ctx.saved_tensors
        (gk, aqk, akk, weights, _, qg, kg, _, query, key,
         query_rstd, key_rstd, beta, hidden, v_new) = saved
        packed = bool(ctx.native_metadata)

        reception = None if ctx.exchange is None else ctx.exchange.receive(query, False)
        grad = grad_output.transpose(1, 2).contiguous()
        d_aqk, dv_local, dq_raw = ops.chunk_kda_bwd_prepare(
            *(_native_backward_view(tensor, packed) for tensor in (aqk, v_new, grad, hidden)), scale=ctx.scale,
            layout="NTD" if packed else "BNSD", chunk_size=64, **ctx.native_metadata)
        dv_local_head = dv_local.unsqueeze(0) if packed else dv_local
        terminal, send_handle, outgoing = _exchange_native_gradient(
            ctx, ops, (gk, qg, kg, weights, grad, dv_local_head), reception)
        del reception
        hidden_grad, _, dv_scan = ops.chunk_gated_delta_rule_bwd_dhu(
            qg, kg, weights, grad, dv_local_head, ctx.scale, 64,
            gK=gk, h0=None, dht=terminal, use_exp2=True, **ctx.native_metadata)
        dq, dk, dv, dbeta, dgate, da_log, ddt_bias = ops.chunk_kda_bwd_finalize(
            *(_native_backward_view(tensor, packed)
              for tensor in (query, key, value, gk, gate.transpose(1, 2).contiguous(), beta)),
            a_log, dt_bias,
            *(_native_backward_view(tensor, packed) for tensor in (akk, v_new, hidden, hidden_grad, dv_scan)),
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
            q_rstd=_native_backward_view(query_rstd, packed),
            k_rstd=_native_backward_view(key_rstd, packed),
            use_beta_sigmoid_in_kernel=False,
            output_layout="NTD" if packed else "BNSD", **ctx.native_metadata)
        if packed:
            dq, dk, dv, dbeta, dgate = (tensor.unsqueeze(0) for tensor in (dq, dk, dv, dbeta, dgate))
        # Prepare already saves FP32 sigmoid(beta); reuse it without device-specific
        # native epilogue selection, recomputing sigmoid, or retaining raw beta.
        dbeta = torch.ops.aten.sigmoid_backward.default(dbeta, beta)
        dbeta, dgate = dbeta.transpose(1, 2), dgate.transpose(1, 2)
        if send_handle is not None:
            send_handle.wait()
        del outgoing
        gradients = (dq, dk, dv, dgate, dbeta, da_log, ddt_bias.reshape_as(dt_bias))
        return (*(grad.to(dtype) for grad, dtype in zip(gradients, ctx.dtypes)), None, None, None, None)


def ascendc_state_kda(
    query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
        backward summary gk and local dhu dHT are converted to model dtype.
        Prepared gates, communicated states and summary apply remain FP32.
        These ABI conversions introduce rounding relative to FP32-input kernels.
    """
    if query.device.type != "npu" or query.dtype != torch.bfloat16:
        raise ValueError("Native KDA state CP requires BF16 NPU inputs.")
    if query.ndim != 4 or query.shape[0] != 1 or query.shape[-1] != 128:
        raise ValueError("Native KDA state CP requires [1,T,H,128] inputs.")
    if any(tensor.shape != query.shape for tensor in (key, value, gate)) or beta.shape != query.shape[:-1]:
        raise ValueError("Native KDA state CP requires matching Q/K/V/gate heads and beta [1,T,H].")
    metadata = _packed_metadata(None if sequence_info is None else sequence_info.local_offsets, query.shape[1])
    scale = query.shape[-1] ** -0.5 if scale is None else float(scale)
    tokens = tuple(tensor.transpose(1, 2).contiguous() for tensor in (query, key, value))
    exchange = _NativeStateExchange(cp_mesh, state_cp, sequence_info, query.shape[1], query.shape[2])
    return _AscendCKDAStages.apply(
        *tokens, gate.contiguous(), beta.contiguous(), a_log.reshape(-1).clone(),
        dt_bias.reshape(-1, query.shape[-1]).clone(), scale, lower_bound, metadata, exchange)
hyper_parallel/components/functional/kimi_delta_attention_state_summary.py
372
373
374
375
376
377
378
379
380
381
    """
    if initial_state is None:
        return state_ext
    # Model autocast must not silently quantize a communicated FP32 state.
    with torch.autocast(device_type=state_ext.device.type, enabled=False):
        return torch.matmul(transition.float(), initial_state.float()) + state_ext


def apply_kda_state_gradient_summary(
    grad_state_ext: torch.Tensor,
384
385
386
387
388
389
390
391
392
) -> torch.Tensor:
    """Apply the adjoint summary for the reverse P2P state wavefront."""
    if grad_final_state is None:
        return grad_state_ext
    with torch.autocast(device_type=grad_state_ext.device.type, enabled=False):
        return (
            torch.matmul(transition.float().transpose(-2, -1), grad_final_state.float())
            + grad_state_ext
        )
hyper_parallel/components/modules/gated_delta_net.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
            output_final_state=output_final_state,
            use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
            cu_seqlens=cu_seqlens,
        )
    if backend == "ascendc":
        if initial_state is not None or output_final_state:
            raise NotImplementedError("GDN Ascend C currently supports zero-state local or pure Ulysses training only.")
        if chunk_size != 64:
            raise ValueError("GDN Ascend C requires chunk_size=64.")
        # Preserve the existing GDN normalization graph outside the native core.
        if use_qk_l2norm_in_kernel:
            query, key = _l2norm(query), _l2norm(key)
        # Keep optional native dependencies out of eager and Triton imports.
        from hyper_parallel.components.functional.gated_delta_net_ascendc import (  # pylint: disable=import-outside-toplevel
            chunk_gated_delta_rule_ascendc,
        )
        return chunk_gated_delta_rule_ascendc(query, key, value, g, beta, cu_seqlens=cu_seqlens)
    if not is_triton_gdn_available(
        query, key, value, g, beta, chunk_size=chunk_size,
    ):
        raise RuntimeError(
hyper_parallel/components/modules/kimi_delta_attention.py
554
555
556
557
558
559
560
561
562
def _normalize_kda_backend(backend: str) -> str:
    """Normalize the selected backend and reject unsupported names."""
    backend = backend.lower()
    if backend not in _KDA_BACKENDS:
        raise ValueError(
            f"unsupported KDA backend {backend!r}; "
            f"expected one of {sorted(_KDA_BACKENDS)}."
        )
    return backend
624
625
626
627
628
629
630
631
632
633
634
635
            lower_bound=lower_bound,
            chunk_size=chunk_size,
            cu_seqlens=cu_seqlens,
        )
    _validate_fused_state_contract(initial_state, output_final_state, use_qk_l2norm_in_kernel,
                                  use_gate_in_kernel, use_beta_sigmoid_in_kernel, backend)
    if backend == "ascendc":
        output = ascendc_chunk_kda(
            query, key, value, gate, beta,
            a_log=a_log.float() if a_log is not None else None,
            dt_bias=dt_bias.float() if dt_bias is not None else None,
            scale=scale, lower_bound=lower_bound, chunk_size=chunk_size, cu_seqlens=cu_seqlens,
633
634
635
636
637
638
639
640
641
            a_log=a_log.float() if a_log is not None else None,
            dt_bias=dt_bias.float() if dt_bias is not None else None,
            scale=scale, lower_bound=lower_bound, chunk_size=chunk_size, cu_seqlens=cu_seqlens,
        )
        return output, None
    triton_a_log = a_log.float() if a_log is not None else None
    triton_dt_bias = dt_bias.float() if dt_bias is not None else None
    if not _is_triton_kda_input_supported(
        query,
hyper_parallel/distributed/context_parallel/gated_delta_net.py
725
726
727
728
729
730
731
732
733
        ).to(query.dtype)

        if send_handle is not None:
            send_handle.wait()
        del send_buffer

        empty = query.new_empty(0)
        ctx.save_for_backward(
            query_norm,
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
                f"expected one of {sorted(_GDN_BACKENDS)}."
            )
        if chunk_size <= 0:
            raise ValueError(f"chunk_size must be positive, got {chunk_size}.")
        if backend in ("triton", "ascendc") and chunk_size != 64:
            raise NotImplementedError("Fused GDN backends require chunk_size=64.")
        self.gdn_backend = backend
        self.chunk_size = chunk_size
        self.cp_mesh = None if device_mesh is None else _ensure_1d(device_mesh)
        self.cp_size = 1 if self.cp_mesh is None else self.cp_mesh.size()
        self.cp_rank = 0 if self.cp_mesh is None else self.cp_mesh.get_local_rank()
        self.seq_dim = 1
        self.head_dim = 2
        self._validate_module()
        if self.gdn_backend != "eager":
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
        if convolution.padding != (convolution.kernel_size[0] - 1,) or convolution.padding_mode != "zeros":
            raise ValueError("GDN fused convolution requires causal zero padding.")

    def _seq_to_head(self, tensor: torch.Tensor) -> torch.Tensor:
        if self.cp_size == 1:
            return tensor
        return _differentiable_all_to_all_shard(
            tensor,
            self.cp_mesh,
            split_dim=self.head_dim,
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
            concat_dim=self.seq_dim,
        )

    def _head_to_seq(self, tensor: torch.Tensor) -> torch.Tensor:
        if self.cp_size == 1:
            return tensor
        return _differentiable_all_to_all_shard(
            tensor,
            self.cp_mesh,
            split_dim=self.seq_dim,
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
        if kv_groups > 1:
            query = query.repeat_interleave(kv_groups, dim=2)
            key = key.repeat_interleave(kv_groups, dim=2)

        cu_seqlens = None
        if exchanged_info is not None:
            cu_seqlens = (exchanged_info.local_offsets if self.gdn_backend == "ascendc" else
                          exchanged_info.materialize(query.device, self.chunk_size).cu_seqlens)
        core_attn_out, _ = chunk_gated_delta_rule(
            query,
            key,
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
    ):
        super().__init__()
        self.module = module
        backend = backend.lower()
        if backend == "ascendc":
            raise NotImplementedError("GDN Ascend C state P2P is not supported; select the pure Ulysses wrapper.")
        if backend not in _GDN_BACKENDS:
            raise ValueError(
                f"unsupported GDN backend {backend!r}; "
                f"expected one of {sorted(_GDN_BACKENDS)}."
hyper_parallel/distributed/context_parallel/kimi_delta_attention.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
    backend: str,
    sequence_info: Optional[PackedSequenceInfo] = None,
) -> torch.Tensor:
    """Run the explicitly selected local KDA implementation."""
    if backend in ("triton", "ascendc") and not safe_gate:
        raise RuntimeError("The fused KDA backend requires the lower-bounded gate.")
    cu_seqlens = None
    if sequence_info is not None:
        # Native APIs consume host metadata; do not materialize an NPU plan
        # only to copy its offsets back to the host at every layer.
        cu_seqlens = (sequence_info.local_offsets if backend == "ascendc" else
                      sequence_info.materialize(query.device, chunk_size).cu_seqlens)
    output, _ = chunk_kda(
        query,
        key,
255
256
257
258
259
260
261
262
263
264
    sequence_info: Optional[PackedSequenceInfo] = None,
    backend: str = "eager",
) -> torch.Tensor:
    """Apply one local ShortConv, optionally with a preceding-rank halo."""
    if backend != "eager":
        return fused_causal_conv1d(
            tensor, convolution.weight, convolution.bias, sequence_info, halo,
            backend=backend, dilation=convolution.dilation[0])
    if sequence_info is not None:
        return packed_causal_conv1d(tensor, convolution, sequence_info, halo)
848
849
850
851
852
853
854
855
856
857
858
859
        self.cp_mesh = _ensure_1d(device_mesh)
        self.cp_size = self.cp_mesh.size()
        self.cp_rank = self.cp_mesh.get_local_rank()
        self.cp_group = self.cp_mesh.get_group()
        if backend == "ascendc" and (chunk_size != 64 or not safe_gate):
            raise ValueError("Ascend C KDA requires safe_gate=True and chunk_size=64.")
        if backend == "eager" and state_cp_method != "p2p":
            raise ValueError("Eager KDA state exchange supports P2P only.")
        self.state_cp = build_kda_state_cp(self.cp_mesh, state_cp_method, allgather_group_size)
        self.prev_rank = _global_peer_rank(
            self.cp_mesh,
            max(self.cp_rank - 1, 0),
927
928
929
930
931
932
933
934
935
936
937
938
939
                cp_size=self.cp_size,
                sequence_info=sequence_info,
            )

        if self.backend == "ascendc":
            # The native package remains optional; loading it belongs to the selected backend.
            from hyper_parallel.components.functional.kimi_delta_attention_ascendc import (  # pylint: disable=import-outside-toplevel
                ascendc_state_kda,
            )
            return ascendc_state_kda(
                query, key, value, gate, beta, a_log=a_log, dt_bias=dt_bias, scale=scale,
                lower_bound=self.lower_bound, state_cp=self.state_cp, cp_mesh=self.cp_mesh,
                sequence_info=sequence_info)
1457
1458
1459
1460
1461
1462
1463
1464
1465
        device_mesh = _ensure_1d(device_mesh)
        if state_cp_method not in ("p2p", "allgather", "grouped_allgather_p2p", "recursive_doubling"):
            raise ValueError("KDA requires a supported state_cp_method.")
        if backend.lower() == "eager" and state_cp_method != "p2p":
            raise ValueError("Eager KDA state exchange supports P2P only.")
        _validate_cp_degrees(device_mesh.size(), ulysses_degree, allgather_group_size)
        if state_cp_method != "grouped_allgather_p2p" and allgather_group_size != 1:
            raise ValueError("allgather_group_size is only valid for grouped_allgather_p2p.")
        if lower_bound >= 0:
hyper_parallel/distributed/context_parallel/kimi_delta_attention_rd.py
233
234
235
236
237
238
239
240
241
242
243
244
            sequence_info: Packed metadata, or None for one continuous document.
            local_length: Equal token interval length in this state group.
            reverse: Whether to scan fresh G/N in reverse chronological order.
        """
        if sequence_info is not None and sequence_info.start != self.rank * local_length:
            raise ValueError("RD packed interval must follow the state process-group order")
        documents = (0, self.size * local_length) if sequence_info is None else sequence_info.documents
        return build_reset_plan(documents, local_length, self.size, self.rank, reverse=reverse)

    def scan_affine(
        self, packed: torch.Tensor | None, plan: RDResetPlan,
        state_shape: tuple[int, ...], reverse: bool = False,
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
        Returns:
            Exclusive input state, or None at a real document boundary.
            N is already a reverse transfer and is never transposed here.
        """
        rank = self.size - self.rank - 1 if reverse else self.rank
        if plan.size != self.size or plan.local_rank != rank:
            raise ValueError("RD affine scan received another rank's directional plan")
        rounds = plan.execution_rounds(rank)
        destination, source = plan.shift_peers(rank)
        if packed is None:
            if rounds or destination is not None:
                raise ValueError("RD affine map may be absent only without an exit consumer")
            state = None
        else:
            if (packed.shape != (*state_shape[:-1], 256) or packed.dtype != torch.float32
                    or packed.device != self.device or not packed.is_contiguous()):
                raise ValueError("RD affine maps must be contiguous FP32 [B,H,128,256] on the protocol device")
            with torch.autocast(device_type=packed.device.type, enabled=False):
                state = self._scan_independent_maps(packed, plan, rounds, reverse)
        return self._exchange_direction(state, destination, source, reverse, state_shape)

    def _exchange_direction(self, payload, destination, source, reverse, receive_shape=None):
        """Reverse only logical peers; retain the original communicator and global ranks."""
        if reverse:
            destination = None if destination is None else self.size - destination - 1
            source = None if source is None else self.size - source - 1
        return self.exchange(payload, destination, source, receive_shape=receive_shape)

    def _scan_independent_maps(self, packed, plan, rounds, reverse):
        """Use FP32 Torch GEMM/add while sharing RD transport and pruning plans."""
        state = packed[..., :128]
        matrix = packed[..., 128:].contiguous() if rounds else None
        for step in rounds:
            final = step.stage == len(plan.distances) - 1
            payload = state.contiguous() if final else packed
            received = self._exchange_direction(payload, step.destination, step.source, reverse)
            if received is None:
                continue
            if final:
                state = matrix @ received + state
            else:
                updated = matrix @ received
                updated[..., :128].add_(state)
                packed, state = updated, updated[..., :128]
                matrix = updated[..., 128:].contiguous() if step.save_matrix else None
        return state.contiguous()

    def _shift_peers(self, plan: RDResetPlan | None) -> tuple[int | None, int | None]:
        """Only continued documents consume the preceding rank's exit."""
        if plan is not None:
hyper_parallel/distributed/context_parallel/kimi_delta_attention_rd_plan.py
159
160
161
162
163
164
165
166
167
168

@lru_cache(maxsize=32)
def _reverse_documents(offsets: tuple[int, ...] | PackedDocumentInfo) -> PackedDocumentInfo:
    """Cache the reversed immutable snapshot across layers of the same batch."""
    values = offsets.offsets if isinstance(offsets, PackedDocumentInfo) else offsets
    return PackedDocumentInfo(tuple(values[-1] - value for value in reversed(values)))


@lru_cache(maxsize=32)
def build_reset_plan(
186
187
188
189
190
191
192
193
194
195
196
197
    """
    if rank is not None and not 0 <= rank < size:
        raise ValueError("RD execution-plan rank is outside the state process group")
    if reverse:
        if not isinstance(offsets, PackedDocumentInfo):
            _validate_intervals(offsets, local_length, size)
        offsets = _reverse_documents(offsets)
        rank = None if rank is None else size - rank - 1
    if isinstance(offsets, PackedDocumentInfo):
        offsets = offsets.offsets
        if size < 1 or local_length < 1 or offsets[-1] != size * local_length:
            raise ValueError("RD reset planning requires ordered documents covering equal state-rank intervals")
hyper_parallel/distributed/context_parallel/kimi_delta_attention_state.py
171
172
173
174
175
176
177
178
179
180

        The autograd caller owns the returned storage and its reverse protocol;
        this metadata-only executor retains no per-invocation tensors.
        """
        output = packed.new_empty((self.size, *packed.shape[1:]))
        dist.all_gather_into_tensor(output, packed, group=self.group)
        return output

    def forward(self, state: torch.Tensor, matrix: torch.Tensor) -> tuple[torch.Tensor, tuple[torch.Tensor, ...]]:
        """Return the incoming state and this invocation's original local M.