Diff Coverage

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

Source File Diff Coverage (%) Missing Lines
hyper_parallel/components/functional/_triton/gated_delta_net/chunk_delta_h.py 0.0% 99-100,108-109,192-194,196,294,296,318
hyper_parallel/components/functional/_triton/gated_delta_net/state_summary.py 0.0% 88-89,101,120-121,203-204,217,227,244-247,253-254
hyper_parallel/components/functional/_triton/kimi_delta_attention/state_summary.py 0.0% 114-115,142-143,196,198-200,275-276,288,305,314-317
hyper_parallel/components/functional/gated_delta_net_state_summary.py 0.0% 73-74,108-109,127-128
hyper_parallel/components/functional/kimi_delta_attention.py 0.0% 27,78,244-248,268-269,272,283-286,290,292,294,296,299-300,303,305,311-313,317-319,335,352-354,378-385,387,392,420-422,425-426,430,432,437-439,443-445,478,556-559
hyper_parallel/components/functional/kimi_delta_attention_state_summary.py 0.0% 49-50,88-89
hyper_parallel/components/functional/linear_attention_convolution.py 18.7% 41,58,60-64,68-72,80,82-83,88,103-107,109-111,124,126-131,133-135,137,141,152-163,173-178,215-232,234-235
hyper_parallel/components/functional/linear_attention_packed.py 82.5% 43,81,104,114,119,124-125,131,139,150,171-175,179-183,190,198,231,240,279,286,291,294,296,330,335
hyper_parallel/components/modules/gated_delta_net.py 100%  
hyper_parallel/components/modules/kimi_delta_attention.py 63.6% 524-525,529-530,558,564,590,946
hyper_parallel/distributed/context_parallel/gated_delta_net.py 4.4% 197-202,204,539-543,546,550-553,555-556,644-650,655,657,680-681,687-688,690,693-694,700,702-704,724-725,739,741-743,776-777,781-784,789,795,810-811,816-817,819,822-823,829,835-838,874-876,1048-1050,1084-1090,1174-1178,1182-1183,1187,1196,1210-1213,1215-1216,1248-1249,1295-1296,1324-1326,1394-1397,1399-1400,1416,1473-1474
hyper_parallel/distributed/context_parallel/kimi_delta_attention.py 6.9% 183,185-188,190-192,252-253,498-502,509-514,537-538,596,768-770,772,774,1111-1112,1116-1117,1161,1163,1180-1182,1232,1346,1416,1442-1443,1476-1478,1480-1484,1487,1494,1496
hyper_parallel/components/functional/_triton/gated_delta_net/chunk_delta_h.py
 95
 96
 97
 98
 99
100
101
102
103
104
    if IS_VARLEN:
        v = v + (i_h * T_all + bos) * V
        k = k + (i_h * T_all + bos) * K
        w = w + (i_h * T_all + bos) * K
        if USE_G:
            g = g + i_h * T_all + bos
        h = h + (i_h * NT_all + boh) * K * V
        if SAVE_NEW_VALUE:
            v_new_base = v_new + (i_h * T_all + bos) * V
    else:
104
105
106
107
108
109
110
111
112
    else:
        v = v + (i_n * H + i_h) * T * V
        k = k + (i_n * H + i_h) * T * K
        w = w + (i_n * H + i_h) * T * K
        if USE_G:
            g = g + (i_n * H + i_h) * T
        h = h + (i_n * H + i_h) * NT * K * V
        if SAVE_NEW_VALUE:
            v_new_base = v_new + (i_n * H + i_h) * T * V
188
189
190
191
192
193
194
195
196
197
198
199
200
                b_h4 *= b_g_last_exp

        # Apply key gate gk
        if USE_GK:
            o_k1 = tl.arange(0, 64)
            if IS_VARLEN:
                gk_base_ptr = gk + (i_h * T_all + bos) * K
            else:
                gk_base_ptr = gk + (i_n * H + i_h) * T * K
            b_gk_last1 = tl.load(gk_base_ptr + last_idx * K + o_k1, mask=(o_k1 < K), other=0.)
            b_h1 *= tl.exp(b_gk_last1)[:, None]
            if K > 64:
                o_k2 = 64 + o_k1
290
291
292
293
294
295
296
297
298
299
300
    v_new = torch.empty_like(u).permute(0, 2, 1, 3).contiguous() if save_new_value else None
    k = k.permute(0, 2, 1, 3).contiguous()
    w = w.permute(0, 2, 1, 3).contiguous()
    u = u.permute(0, 2, 1, 3).contiguous()
    g = g.permute(0, 2, 1).contiguous() if g is not None else None
    # Match the head-major layout used by the forward kernel's token strides.
    gk = gk.permute(0, 2, 1, 3).contiguous() if gk is not None else None
    chunk_gated_delta_rule_fwd_kernel_h_blockdim64[(triton.cdiv(V, BV), N * H)](
        k=k,
        v=u,
        w=w,
314
315
316
317
318
319
320
321
322
        BV=BV,
        NT=NT,
    )
    h = h.permute(0, 2, 1, 3, 4).contiguous()
    v_new = v_new.permute(0, 2, 1, 3).contiguous() if v_new is not None else None
    return h, v_new, final_state


@triton.heuristics({
hyper_parallel/components/functional/_triton/gated_delta_net/state_summary.py
84
85
86
87
88
89
90
91
92
93
        )
        p_w2 = tl.make_block_ptr(
            w, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)
        )
        b_w1 = tl.load(p_w1, boundary_check=(0, 1), padding_option='zero')
        b_w2 = tl.load(p_w2, boundary_check=(0, 1), padding_option='zero')
        b_v = tl.dot(b_w1, b_h1.to(b_w1.dtype))
        b_v += tl.dot(b_w2, b_h2.to(b_w2.dtype))

        p_u = tl.make_block_ptr(
 97
 98
 99
100
101
102
103
104
105
            (i_t * BT, i_v * BV),
            (BT, BV),
            (1, 0),
        )
        b_v = tl.load(p_u, boundary_check=(0, 1), padding_option='zero') - b_v

        last_idx = min((i_t + 1) * BT, T) - 1
        token = i_t * BT + tl.arange(0, BT)
        mask = token < T
116
117
118
119
120
121
122
123
124
125
        )
        p_k2 = tl.make_block_ptr(
            k, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
        )
        b_h1 += tl.dot(tl.load(p_k1, boundary_check=(0, 1), padding_option='zero'), b_v)
        b_h2 += tl.dot(tl.load(p_k2, boundary_check=(0, 1), padding_option='zero'), b_v)

    p_out1 = tl.make_block_ptr(
        packed_summary,
        (K, V + K),
199
200
201
202
203
204
205
206
207
        )
        p_k2 = tl.make_block_ptr(
            k, (T, K), (stride_k, 1), (i_t * BT, 64), (BT, 64), (1, 0)
        )
        b_k1 = tl.load(p_k1, boundary_check=(0, 1), padding_option='zero')
        b_k2 = tl.load(p_k2, boundary_check=(0, 1), padding_option='zero')
        b_dv = tl.dot(b_k1, b_dh1.to(b_k1.dtype))
        b_dv += tl.dot(b_k2, b_dh2.to(b_k2.dtype))
        b_dv *= tl.where(mask, tl.exp(b_g_last - b_g), 0.0)[:, None]
213
214
215
216
217
218
219
220
221
            (i_t * BT, i_v * BV),
            (BT, BV),
            (1, 0),
        )
        b_dv += tl.load(p_dv, boundary_check=(0, 1), padding_option='zero')

        p_do = tl.make_block_ptr(
            do,
            (T, V),
223
224
225
226
227
228
229
230
            (i_t * BT, i_v * BV),
            (BT, BV),
            (1, 0),
        )
        b_do = tl.load(p_do, boundary_check=(0, 1), padding_option='zero')
        decay = tl.exp(b_g_last)
        b_dh1 *= decay
        b_dh2 *= decay
240
241
242
243
244
245
246
247
248
249
250
251
        )
        p_w2 = tl.make_block_ptr(
            w, (K, T), (1, stride_k), (64, i_t * BT), (64, BT), (0, 1)
        )
        b_q1 = tl.load(p_q1, boundary_check=(0, 1), padding_option='zero')
        b_q2 = tl.load(p_q2, boundary_check=(0, 1), padding_option='zero')
        b_w1 = tl.load(p_w1, boundary_check=(0, 1), padding_option='zero')
        b_w2 = tl.load(p_w2, boundary_check=(0, 1), padding_option='zero')
        gate = tl.exp(b_g)[None, :]
        b_q1 *= gate
        b_q2 *= gate
        # Match native dhu's rounding: form the signed update before adding it
249
250
251
252
253
254
255
256
257
258
        b_q1 *= gate
        b_q2 *= gate
        # Match native dhu's rounding: form the signed update before adding it
        # to the recurrent accumulator, which is rounded again next chunk.
        b_dh1 += tl.dot(b_q1, b_do.to(b_q1.dtype)) * scale - tl.dot(b_w1, b_dv.to(b_w1.dtype))
        b_dh2 += tl.dot(b_q2, b_do.to(b_q2.dtype)) * scale - tl.dot(b_w2, b_dv.to(b_w2.dtype))

    p_out1 = tl.make_block_ptr(
        grad_state_ext, (K, V), (V, 1), (0, i_v * BV), (64, BV), (1, 0)
    )
hyper_parallel/components/functional/_triton/kimi_delta_attention/state_summary.py
110
111
112
113
114
115
116
117
118
119
    num_chunks = tl.cdiv(T, BT)
    for chunk_idx in range(num_chunks):
        w1_ptr = tl.make_block_ptr(w, (T, K), (stride_w, 1), (chunk_idx * BT, 0), (BT, 64), (1, 0))
        w2_ptr = tl.make_block_ptr(w, (T, K), (stride_w, 1), (chunk_idx * BT, 64), (BT, 64), (1, 0))
        w1 = tl.load(w1_ptr, boundary_check=(0, 1), padding_option='zero')
        w2 = tl.load(w2_ptr, boundary_check=(0, 1), padding_option='zero')
        if OUTPUT_MODE == 1:
            # Match Ascend FLA h: state and residual feedback stay in FP32.
            w1 = w1.to(tl.float32)
            w2 = w2.to(tl.float32)
138
139
140
141
142
143
144
145
146
147
        state2 *= _exp2(gate_last2)[:, None]

        key1_ptr = tl.make_block_ptr(key, (K, T), (1, stride_k), (0, chunk_idx * BT), (64, BT), (0, 1))
        key2_ptr = tl.make_block_ptr(key, (K, T), (1, stride_k), (64, chunk_idx * BT), (64, BT), (0, 1))
        key1 = tl.load(key1_ptr, boundary_check=(0, 1), padding_option='zero')
        key2 = tl.load(key2_ptr, boundary_check=(0, 1), padding_option='zero')
        if OUTPUT_MODE == 1:
            key1 = key1.to(tl.float32)
            key2 = key2.to(tl.float32)
        state1 += tl.dot(key1, value_new)
192
193
194
195
196
197
198
199
200
201
202
203
204
    # Ascend cannot carry the Cube result directly into the next Cube input.
    # Each program owns disjoint output columns, so this FP32 feedback needs
    # neither an inter-program barrier nor a per-chunk matrix workspace.
    tl.store(transition + output_offsets, matrix)
    for chunk in range(tl.cdiv(T, BT)):
        offsets = ((batch * T + chunk * BT + tokens[:, None]) * H + local_head) * K + rows[None, :]
        key_tile = tl.load(key + offsets, mask=chunk * BT + tokens[:, None] < T, other=0.0)
        weight_tile = tl.load(w + offsets, mask=chunk * BT + tokens[:, None] < T, other=0.0)
        last = ((batch * T + tl.minimum((chunk + 1) * BT, T) - 1) * H + local_head) * K + rows
        decay = tl.exp2(tl.load(gate + last).to(tl.float32))
        diagonal = tl.where(rows[:, None] == rows[None, :], decay[:, None], 0.0)
        interaction = tl.dot(tl.trans(key_tile), weight_tile)
        chunk_transition = diagonal - interaction
271
272
273
274
275
276
277
278
279
280
        chunk_idx = num_chunks - 1 - reverse_idx

        key1_ptr = tl.make_block_ptr(key, (T, K), (stride_k, 1), (chunk_idx * BT, 0), (BT, 64), (1, 0))
        key2_ptr = tl.make_block_ptr(key, (T, K), (stride_k, 1), (chunk_idx * BT, 64), (BT, 64), (1, 0))
        key1 = tl.load(key1_ptr, boundary_check=(0, 1), padding_option='zero')
        key2 = tl.load(key2_ptr, boundary_check=(0, 1), padding_option='zero')
        value_grad = tl.dot(key1, state1.to(key1.dtype))
        value_grad += tl.dot(key2, state2.to(key2.dtype))

        grad_value_ptr = tl.make_block_ptr(
284
285
286
287
288
289
290
291
292
            (chunk_idx * BT, i_col * BV),
            (BT, BV),
            (1, 0),
        )
        value_grad += tl.load(grad_value_ptr, boundary_check=(0, 1), padding_option='zero')

        last_idx = min((chunk_idx + 1) * BT, T) - 1
        gate_last_ptr = gate + last_idx * H_TOTAL * K
        gate_last1 = tl.load(gate_last_ptr + row1, mask=row1 < K, other=0.0)
301
302
303
304
305
306
307
308
            (chunk_idx * BT, i_col * BV),
            (BT, BV),
            (1, 0),
        )
        output_grad = tl.load(output_grad_ptr, boundary_check=(0, 1), padding_option='zero')
        # Match FLA dhu's scale-before-cast contract for the Q contribution.
        output_grad = output_grad * scale
        output_grad_second = output_grad + 0.0
310
311
312
313
314
315
316
317
318
319
320
321
        query1_ptr = tl.make_block_ptr(query, (K, T), (1, stride_k), (0, chunk_idx * BT), (64, BT), (0, 1))
        query2_ptr = tl.make_block_ptr(query, (K, T), (1, stride_k), (64, chunk_idx * BT), (64, BT), (0, 1))
        w1_ptr = tl.make_block_ptr(w, (K, T), (1, stride_k), (0, chunk_idx * BT), (64, BT), (0, 1))
        w2_ptr = tl.make_block_ptr(w, (K, T), (1, stride_k), (64, chunk_idx * BT), (64, BT), (0, 1))
        query1 = tl.load(query1_ptr, boundary_check=(0, 1), padding_option='zero')
        query2 = tl.load(query2_ptr, boundary_check=(0, 1), padding_option='zero')
        w1 = tl.load(w1_ptr, boundary_check=(0, 1), padding_option='zero')
        w2 = tl.load(w2_ptr, boundary_check=(0, 1), padding_option='zero')
        state1 += tl.dot(query1, output_grad.to(query1.dtype)) - tl.dot(w1, value_grad.to(w1.dtype))
        state2 += tl.dot(query2, output_grad_second.to(query2.dtype)) - tl.dot(w2, value_grad.to(w2.dtype))

    output1_ptr = tl.make_block_ptr(grad_state_ext, (K, V), (V, 1), (0, i_col * BV), (64, BV), (1, 0))
hyper_parallel/components/functional/gated_delta_net_state_summary.py
69
70
71
72
73
74
75
76
77
78
    value_dim = u.shape[-1]
    _validate_fixed_summary_shape(key_dim, value_dim, chunk_size)
    if block_size not in (64, 128):
        raise ValueError(f"GDN state-summary block_size must be 64 or 128, got {block_size}.")
    if seq_len <= 0:
        raise ValueError("State summary requires a nonempty sample fragment.")
    if w.shape != key.shape or u.shape[:3] != key.shape[:3] or g.shape != key.shape[:3]:
        raise ValueError(
            "Incompatible GDN state-summary shapes: "
            f"key={tuple(key.shape)}, w={tuple(w.shape)}, "
104
105
106
107
108
109
110
111
112
        BV=block_size,
        NT=triton.cdiv(seq_len, chunk_size),
    )
    state_ext = packed_summary[..., :value_dim].contiguous()
    if not output_transition:
        return state_ext, None
    transition = packed_summary[..., value_dim:].contiguous()
    return state_ext, transition

123
124
125
126
127
128
129
130
131
132
    """Validate the reverse summary inputs before launching its kernel."""
    batch, seq_len, heads, key_dim = query.shape
    value_dim = grad_output.shape[-1]
    _validate_fixed_summary_shape(key_dim, value_dim, chunk_size)
    if seq_len <= 0:
        raise ValueError("State summary requires a nonempty sample fragment.")
    qk_shape = (batch, seq_len, heads, key_dim)
    value_shape = (batch, seq_len, heads, value_dim)
    if (key.shape, w.shape, g.shape, grad_output.shape, dv.shape) != (
        qk_shape, qk_shape, qk_shape[:3], value_shape, value_shape
hyper_parallel/components/functional/kimi_delta_attention.py
23
24
25
26
27
28
29
30
31

import torch
import torch.distributed as dist

from hyper_parallel.components.functional.linear_attention_packed import PackedSequenceInfo, resolve_packed_sequence

from .kimi_delta_attention_fla_adapter import get_fla_kda_staged_ops, run_fla_chunk_kda
from .kimi_delta_attention_state_summary import (
    apply_kda_state_gradient_summary,
74
75
76
77
78
79
80
81
82
    chunk_size: int,
    lower_bound: float,
) -> None:
    """Validate the fixed-shape local KDA backend before compiling kernels."""
    _, key_dim, value_dim, num_value_heads = _validate_local_shapes(
        query, key, value, gate, beta
    )
    if not (
        query.dtype == key.dtype == value.dtype == gate.dtype == beta.dtype
240
241
242
243
244
245
246
247
248
249
250
251
252
            cp_size: Number of state partitions.
            state_cp: Optional gather executor; None selects P2P.
        """
        ops = get_fla_kda_staged_ops()
        plan = None if sequence_info is None else sequence_info.materialize(query.device, chunk_size)
        varlen = {} if plan is None else plan.kwargs()
        recurrent_varlen = dict(varlen)
        if plan is not None:
            recurrent_varlen["chunk_offsets"] = plan.chunk_offsets

        # FLA consumes base-2 log gates: exp(x) = exp2(x / ln(2)).
        # Use the same conversion when recomputing gates in backward.
        rcp_ln2 = 1.4426950408889634
264
265
266
267
268
269
270
271
272
273
274
275
276
            **varlen,
        )

        state_shape = (query.shape[0], value.shape[2], query.shape[-1], value.shape[-1])
        receive_previous = cp_rank > 0 and (sequence_info is None or sequence_info.continues_previous)
        send_next = cp_rank < cp_size - 1 and (sequence_info is None or sequence_info.continues_next)
        initial_state = None
        recv_handle = None
        if state_cp is None and receive_previous:
            initial_state = torch.empty(state_shape, device=query.device, dtype=torch.float32)
            recv_handle = dist.irecv(initial_state, src=prev_rank, group=cp_group)

        w, u, _, kg, attention_qk, attention_kk = ops.chunk_kda_fwd_intra(
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
304
305
306
307
308
309
            **varlen,
        )
        state_ext = None
        transition = None
        if sequence_info is not None:
            if state_cp is not None or send_next:
                tail = sequence_info.tail
                state_ext, transition = kda_state_summary_forward_from_prepared(
                    kg[:, tail], w[:, tail], u[:, tail], gate[:, tail], chunk_size=chunk_size,
                    output_transition=sequence_info.transfer_active,
                )
                if state_cp is not None and not sequence_info.transfer_active:
                    # Collective protocols still require an explicit zero transfer.
                    transition = state_ext.new_zeros((*state_shape[:-1], query.shape[-1]))
        else:
            if state_cp is None and cp_rank == 0 and cp_size > 1:
                # Zero entry uses S directly, and rank 0 has no reverse-state recipient.
                state_ext, transition = kda_state_summary_forward_from_prepared(
                    kg, w, u, gate, chunk_size=chunk_size, output_transition=False,
                )
            elif state_cp is not None or cp_rank < cp_size - 1:
                state_ext, transition = kda_state_summary_forward_from_prepared(kg, w, u, gate, chunk_size=chunk_size)
        # A reset makes the outgoing summary independent of the incoming state.
        # Wait before apply only when it actually reads that state; local h still waits below.
        if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active):
            recv_handle.wait()
            recv_handle = None

        send_handle = None
        send_state = None
        if state_cp is not None:
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
        send_handle = None
        send_state = None
        if state_cp is not None:
            initial_state = state_cp.forward(state_ext, transition)
        elif send_next:
            summary_initial = initial_state if transition is not None else None
            final_state = apply_kda_state_summary(state_ext, transition, summary_initial)
            send_state = final_state.contiguous()
            send_handle = dist.isend(send_state, dst=next_rank, group=cp_group)

        if recv_handle is not None:
            recv_handle.wait()
        kernel_initial_state = initial_state if sequence_info is None else sequence_info.first_state(initial_state)
        states, value_new, _ = ops.chunk_gated_delta_rule_fwd_h(
            k=kg, w=w, u=u, gk=gate, initial_state=kernel_initial_state,
            output_final_state=False, chunk_size=chunk_size,
            **recurrent_varlen,
331
332
333
334
335
336
337
338
339

        saved_initial_state = initial_state
        if saved_initial_state is None:
            saved_initial_state = query.new_empty(0, dtype=torch.float32)
        packed_tensors = () if plan is None else (
            plan.cu_seqlens, plan.chunk_indices, plan.chunk_offsets)
        ctx.save_for_backward(
            query, query_rstd, key, key_rstd, value, gate_raw, beta_raw, a_log, dt_bias, attention_qk,
            attention_kk, transition if transition is not None else query.new_empty(0), saved_initial_state,
348
349
350
351
352
353
354
355
356
357
358
        ctx.next_rank = next_rank
        ctx.cp_rank = cp_rank
        ctx.cp_size = cp_size
        ctx.state_cp = state_cp
        ctx.receive_previous = receive_previous
        ctx.send_next = send_next
        ctx.sequence_info = sequence_info
        return output.to(value.dtype)

    @staticmethod
    def backward(  # pylint: disable=arguments-differ,too-many-locals
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
        (
            query, query_rstd, key, key_rstd, value, gate_raw, beta_raw, a_log, dt_bias,
            attention_qk, attention_kk, transition, saved_initial_state, *packed_tensors,
        ) = ctx.saved_tensors
        sequence_info = ctx.sequence_info
        cu_seqlens = chunk_indices = chunk_offsets = None
        if sequence_info is not None:
            cu_seqlens, chunk_indices, chunk_offsets = packed_tensors
        varlen = {} if sequence_info is None else {"cu_seqlens": cu_seqlens, "chunk_indices": chunk_indices}
        recurrent_varlen = dict(varlen)
        if sequence_info is not None:
            recurrent_varlen["chunk_offsets"] = chunk_offsets
        initial_state = saved_initial_state if saved_initial_state.numel() else None
        kernel_initial_state = initial_state if sequence_info is None else sequence_info.first_state(initial_state)
        grad_output = grad_output.contiguous()

        grad_final_state = None
        recv_handle = None
        if ctx.state_cp is None and ctx.send_next:
            grad_final_state = torch.empty(
                (query.shape[0], value.shape[2], query.shape[-1], value.shape[-1]), device=query.device,
                dtype=torch.float32,
            )
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
            chunk_size=ctx.chunk_size,
            **varlen,
        )
        grad_state_ext = None
        if ctx.state_cp is not None or ctx.receive_previous:
            if sequence_info is not None and not sequence_info.continues_previous:
                grad_state_ext = query.new_zeros(
                    (1, value.shape[2], query.shape[-1], value.shape[-1]), dtype=torch.float32)
            else:
                head = slice(None) if sequence_info is None else sequence_info.head
                grad_state_ext = kda_state_gradient_summary_from_prepared(
                    query_gated[:, head], key_gated[:, head], w[:, head], gate[:, head],
                    grad_output[:, head], grad_value_local[:, head], ctx.scale, chunk_size=ctx.chunk_size,
                )
        if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active):
            recv_handle.wait()
            recv_handle = None
        send_handle = None
        send_state_gradient = None
        if ctx.state_cp is not None:
            grad_final_state = ctx.state_cp.backward(grad_state_ext, transition)
        elif ctx.receive_previous:
            summary_final_gradient = grad_final_state if transition.numel() else None
            grad_initial_state = apply_kda_state_gradient_summary(grad_state_ext, transition, summary_final_gradient)
            send_state_gradient = grad_initial_state.contiguous()
            send_handle = dist.isend(send_state_gradient, dst=ctx.prev_rank, group=ctx.cp_group)

        if recv_handle is not None:
            recv_handle.wait()
        kernel_grad_final = grad_final_state if sequence_info is None else sequence_info.last_state(grad_final_state)
        grad_states, _, grad_value = ops.chunk_gated_delta_rule_bwd_dhu(
            q=query_gated, k=key_gated, w=w, gk=gate, h0=kernel_initial_state, dht=kernel_grad_final, do=grad_output,
            dv=grad_value_local, scale=ctx.scale, chunk_size=ctx.chunk_size,
            **recurrent_varlen,
474
475
476
477
478
479
480
481
482
                dim=3,
            )
            grad_key = grad_key.view(*grad_key.shape[:2], num_query_heads, groups, grad_key.shape[-1]).sum(dim=3)

        grad_gate = ops.chunk_local_cumsum(grad_gate, chunk_size=ctx.chunk_size, reverse=True, **varlen)
        grad_gate, grad_a_log, grad_dt_bias = ops.kda_gate_bwd(
            g=gate_raw, A_log=a_log, dt_bias=dt_bias, dyg=grad_gate, lower_bound=ctx.lower_bound,
        )
        grad_beta = ops.fused_beta_sigmoid_bwd(beta_raw, grad_beta)
552
553
554
555
556
557
558
559
560
561
562
563
        lower_bound=lower_bound,
        cp_rank=cp_rank,
        cp_size=cp_size,
    )
    if sequence_info is not None:
        if query.shape[0] != 1:
            raise ValueError("Packed state CP requires batch dimension one.")
        resolve_packed_sequence({"packed_sequence_info": sequence_info}, query.shape[1],
                                cp_rank=cp_rank, cp_size=cp_size)
    effective_scale = query.shape[-1] ** -0.5 if scale is None else float(scale)
    return _KDAStateP2PFunction.apply(
        query,
hyper_parallel/components/functional/kimi_delta_attention_state_summary.py
45
46
47
48
49
50
51
52
53
54
        raise NotImplementedError(
            "Triton KDA state summary requires key_dim=value_dim=128 and "
            "chunk_size=64."
        )
    if sequence_length <= 0:
        raise ValueError("State summary requires a nonempty sample fragment.")
    if w.shape != key.shape or gate.shape != key.shape:
        raise ValueError(
            "KDA prepared key, w, and gate must have identical shapes, got "
            f"key={tuple(key.shape)}, w={tuple(w.shape)}, gate={tuple(gate.shape)}."
84
85
86
87
88
89
90
91
92
93
        raise NotImplementedError(
            "Triton KDA state-gradient summary requires key_dim=value_dim=128 "
            "and chunk_size=64."
        )
    if sequence_length <= 0:
        raise ValueError("State summary requires a nonempty sample fragment.")
    prepared_shape = (batch, sequence_length, heads, key_dim)
    value_shape = (batch, sequence_length, heads, value_dim)
    if key.shape != prepared_shape or w.shape != prepared_shape:
        raise ValueError(
hyper_parallel/components/functional/linear_attention_convolution.py
37
38
39
40
41
42
43
44
45
    Raises:
        ValueError: If the backend is unsupported.
    """
    if backend not in ("eager", "triton", "ascendc"):
        raise ValueError(f"Unsupported convolution backend {backend!r}; expected eager, triton, or ascendc.")
    return backend


class _AscendCConvolution(torch.autograd.Function):
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
        offsets: tuple[int, ...] | None,
    ) -> torch.Tensor:
        """Save preactivation and per-call metadata for joint backward."""
        # Optional native operators must not be loaded by ordinary framework imports.
        from fla_npu.ops.ascendc import causal_conv1d_fn  # pylint: disable=import-outside-toplevel

        op_inputs = inputs if offsets is None else inputs.reshape(-1, inputs.shape[-1])
        op_inputs = op_inputs.contiguous()
        op_weight = weight[:, 0].transpose(0, 1).contiguous()
        cpu_offsets = None if offsets is None else torch.tensor(offsets, dtype=torch.int64, device="cpu")
        preactivation = causal_conv1d_fn(
            op_inputs, op_weight, bias, conv_states=None, activation=None,
            query_start_loc_cpu=cpu_offsets, head_num=0,
        )
        ctx.save_for_backward(op_inputs, op_weight, preactivation, cpu_offsets)
        ctx.input_shape = tuple(inputs.shape)
        ctx.has_bias = bias is not None
        ctx.input_layout = "BSND" if offsets is None else "TND"
        return F.silu(preactivation).reshape_as(inputs)

    @staticmethod
    def backward(
        ctx: Any, grad_output: torch.Tensor,
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
        ctx: Any, grad_output: torch.Tensor,
    ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None, None]:
        """Differentiate SiLU once inside the native convolution backward."""
        # Keep the optional native dependency lazy in backward as well as forward.
        from fla_npu.ops.ascendc import causal_conv1d_bwd  # pylint: disable=import-outside-toplevel

        inputs, weight, preactivation, cpu_offsets = ctx.saved_tensors
        grad_input, grad_weight, grad_bias, _ = causal_conv1d_bwd(
            x=inputs, y=preactivation, weight=weight,
            dy=grad_output.reshape_as(inputs).contiguous(),
            query_start_loc=cpu_offsets, activation=1, input_layout=ctx.input_layout,
        )
        return (
            grad_input.reshape(ctx.input_shape),
            grad_weight.transpose(0, 1).unsqueeze(1).contiguous(),
            grad_bias if ctx.has_bias else None,
            None,
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    info: PackedSequenceInfo | None,
    width: int,
) -> tuple[torch.Tensor, tuple[int, ...] | None, int]:
    """Prepend only the first fragment's true history without inventing a reset."""
    if info is None:
        return inputs, None, 0
    if halo is None:
        return inputs, info.local_offsets, 0
    prefix = min(width - 1, info.preceding_sample_tokens)
    # The empty slice preserves a zero halo gradient when a real document resets.
    extended = torch.cat((halo[:, width - 1 - prefix:], inputs), dim=1)
    offsets = (0, *(offset + prefix for offset in info.local_offsets[1:]))
    return extended, offsets, prefix


def _triton_convolution(
    inputs: torch.Tensor,
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    use_plan: bool,
) -> torch.Tensor:
    """Use one FLA entry for dense batches and immutable packed document plans."""
    # FLA is optional and must not be imported for the default eager backend.
    from fla.modules.conv import causal_conv1d  # pylint: disable=import-outside-toplevel

    if info is None:
        cu_seqlens, cu_seqlens_cpu, chunk_indices = None, None, None
    elif use_plan:
        plan = info.materialize(inputs.device, 64)
        cu_seqlens, cu_seqlens_cpu = plan.cu_seqlens, plan.cu_seqlens_cpu
        chunk_indices = plan.chunk_indices
    else:
        cu_seqlens_cpu = torch.tensor(offsets, dtype=torch.int64, device="cpu")
        cu_seqlens = cu_seqlens_cpu.to(device=inputs.device, dtype=torch.int32)
        chunk_indices = None
    # Contiguous BTD enables the NPU kernel without its strided fallback workspace.
    output, _ = causal_conv1d(
        inputs.contiguous(), weight=weight[:, 0], bias=bias, activation=None, backend="triton",
        cu_seqlens=cu_seqlens, cu_seqlens_cpu=cu_seqlens_cpu, chunk_indices=chunk_indices,
    )
    return F.silu(output)


def _validate_fused_inputs(
    inputs: torch.Tensor,
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    info: PackedSequenceInfo | None,
    halo: torch.Tensor | None,
) -> None:
    """Check layouts before a selected backend is allowed to launch kernels."""
    if inputs.ndim != 3 or any(size < 1 for size in inputs.shape):
        raise ValueError("Fused convolution expects nonempty [B,S,C] or packed [1,T,C] inputs.")
    if weight.ndim != 3 or weight.shape[:2] != (inputs.shape[-1], 1):
        raise ValueError("Fused convolution weight must have shape [C,1,W] matching input channels.")
    if bias is not None and bias.shape != (inputs.shape[-1],):
        raise ValueError("Fused convolution bias must have shape [C] matching input channels.")
    if weight.device != inputs.device or (bias is not None and bias.device != inputs.device):
        raise ValueError("Fused convolution input, weight and bias must be on the same device.")
    if info is not None and inputs.shape[:2] != (1, info.length):
        raise ValueError("Packed convolution expects [1,T,C] matching its token interval.")
    if halo is not None:
        _validate_fused_history(inputs, halo, info, weight.shape[-1])


def _validate_fused_history(
    inputs: torch.Tensor,
169
170
171
172
173
174
175
176
177
178
179
180
181
182
    info: PackedSequenceInfo | None,
    width: int,
) -> None:
    """Reject history that cannot represent the same document and activation layout."""
    if info is None:
        raise ValueError("Fused convolution halo requires explicit packed sequence metadata.")
    if halo.shape != (1, width - 1, inputs.shape[-1]):
        raise ValueError("Fused convolution halo has an incompatible shape.")
    if halo.dtype != inputs.dtype or halo.device != inputs.device:
        raise ValueError("Fused convolution halo must match the input dtype and device.")


def fused_causal_conv1d(
    inputs: torch.Tensor,
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
        preactivation. Unlike eager or Triton, its backward does not round the SiLU
        gradient to the input dtype before convolution backward. Dense batch samples
        remain separate; only packed inputs with explicit metadata are flattened.
    """
    if backend not in ("triton", "ascendc"):
        raise ValueError(f"Fused convolution requires triton or ascendc, got {backend!r}.")
    if dilation != 1:
        raise ValueError("Fused convolution requires dilation=1.")
    if inputs.device.type != "npu":
        raise ValueError("Fused convolution currently supports NPU tensors only.")
    _validate_fused_inputs(inputs, weight, bias, info, halo)
    width = weight.shape[-1]
    if backend == "ascendc":
        if width not in (2, 3, 4) or inputs.shape[-1] % 16:
            raise ValueError("AscendC convolution requires width 2/3/4 and channels divisible by 16.")
        if inputs.dtype not in (torch.bfloat16, torch.float16):
            raise ValueError("AscendC convolution requires BF16 or FP16 activations.")
        if weight.dtype != inputs.dtype or (bias is not None and bias.dtype != inputs.dtype):
            raise ValueError("AscendC convolution requires matching input/weight/bias dtypes; no implicit cast.")
    extended, offsets, prefix = _with_history(inputs, halo, info, width)
    if backend == "triton":
        output = _triton_convolution(extended, weight, bias, info, offsets, halo is None)
    else:
        output = _AscendCConvolution.apply(extended, weight, bias, offsets)
    return output[:, prefix:]
hyper_parallel/components/functional/linear_attention_packed.py
39
40
41
42
43
44
45
46
47
    chunk_offsets: torch.Tensor

    def kwargs(self) -> dict[str, torch.Tensor]:
        """Return the common FLA varlen arguments without copying device tensors."""
        return {"cu_seqlens": self.cu_seqlens, "chunk_indices": self.chunk_indices}


@dataclass(frozen=True)
class PackedSequenceInfo:
77
78
79
80
81
82
83
84
            not isinstance(self.start, int) or isinstance(self.start, bool)
            or not isinstance(self.end, int) or isinstance(self.end, bool)
            or not 0 <= self.start < self.end <= offsets[-1]
        ):
            raise ValueError("Packed token interval must be nonempty and covered by global sample offsets.")
        local = (0, *(x - self.start for x in offsets if self.start < x < self.end), self.end - self.start)
        object.__setattr__(self, "global_offsets", offsets)
        object.__setattr__(self, "local_offsets", local)
100
101
102
103
104
105
106
107
108

    @property
    def continues_next(self) -> bool:
        """Whether the last fragment feeds the following interval's state."""
        return self.end not in self.global_offsets

    @property
    def transfer_active(self) -> bool:
        """Whether the preceding interval can affect this interval's exit."""
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

    @property
    def head(self) -> slice:
        """Return the first fragment's local token slice."""
        return slice(0, self.local_offsets[1])

    @property
    def tail(self) -> slice:
        """Return the last fragment's local token slice."""
        return slice(self.local_offsets[-2], self.length)

    @property
    def preceding_sample_tokens(self) -> int:
        """Return how many earlier tokens belong to the first local sample."""
        index = bisect_right(self.global_offsets, self.start) - 1
        return self.start - self.global_offsets[index]

    def interval(self, start: int, end: int) -> PackedSequenceInfo:
        """Describe another contiguous interval without retaining artificial cuts."""
        key = (start, end)
127
128
129
130
131
132
133
134
    def interval(self, start: int, end: int) -> PackedSequenceInfo:
        """Describe another contiguous interval without retaining artificial cuts."""
        key = (start, end)
        if key == (self.start, self.end):
            return self
        if key not in self._intervals:
            self._intervals[key] = PackedSequenceInfo(self.global_offsets, start, end)
        return self._intervals[key]
135
136
137
138
139
140
141
142
143

    def ulysses(self, degree: int, local_rank: int) -> PackedSequenceInfo:
        """Describe the token interval after consecutive-rank AllToAll."""
        if degree < 1 or not 0 <= local_rank < degree:
            raise ValueError("Invalid Ulysses degree or local rank.")
        start = self.start - local_rank * self.length
        return self.interval(start, start + degree * self.length)

    def materialize(self, device: torch.device, chunk_size: int) -> PackedChunkPlan:
146
147
148
149
150
151
152
153
154
        cache_key = (device, chunk_size)
        if cache_key in self._plans:
            return self._plans[cache_key]
        if chunk_size < 1:
            raise ValueError("chunk_size must be positive.")
        counts = [
            (end - start + chunk_size - 1) // chunk_size
            for start, end in zip(self.local_offsets, self.local_offsets[1:])
        ]
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
        return plan

    def first_state(self, state: torch.Tensor | None) -> torch.Tensor | None:
        """Expand only a continued first fragment's state for a varlen kernel."""
        if state is None or not self.continues_previous:
            return None
        if self.num_sequences == 1:
            return state
        return torch.cat((state, state.new_zeros((self.num_sequences - 1, *state.shape[1:]))))

    def last_state(self, state: torch.Tensor | None) -> torch.Tensor | None:
        """Expand a terminal state gradient into the last fragment slot."""
        if state is None or not self.continues_next:
            return None
        if self.num_sequences == 1:
            return state
        return torch.cat((state.new_zeros((self.num_sequences - 1, *state.shape[1:])), state))


def _offset_tuple(value: torch.Tensor | Sequence[int]) -> tuple[int, ...]:
    """Snapshot user metadata so a later batch cannot mutate saved semantics."""
186
187
188
189
190
191
192
193
def _offset_tuple(value: torch.Tensor | Sequence[int]) -> tuple[int, ...]:
    """Snapshot user metadata so a later batch cannot mutate saved semantics."""
    if isinstance(value, torch.Tensor):
        if value.ndim != 1 or value.dtype not in (torch.int32, torch.int64):
            raise ValueError("Cumulative sample offsets must be a one-dimensional integer tensor.")
        return tuple(value.detach().cpu().tolist())
    return tuple(value)

194
195
196
197
198
199
200
201
202

def _global_offsets(info: PackedSequenceInfo | None, aliases: list) -> tuple[int, ...]:
    """Check alias agreement before constructing or reusing local metadata."""
    if info is not None and not isinstance(info, PackedSequenceInfo):
        raise TypeError("packed_sequence_info must be PackedSequenceInfo.")
    offsets = info.global_offsets if info is not None else _offset_tuple(aliases[0])
    if any(_offset_tuple(value) != offsets for value in aliases):
        raise ValueError("Packed sequence aliases disagree; provide one consistent set of global sample offsets.")
    return offsets
227
228
229
230
231
232
233
234
235
        )
    if info is None and not aliases:
        return None
    if not 0 <= cp_rank < cp_size:
        raise ValueError("Invalid CP rank or size for packed sequence metadata.")
    offsets = _global_offsets(info, aliases)
    if info is None:
        info = PackedSequenceInfo(offsets, cp_rank * local_length, (cp_rank + 1) * local_length)
    if (
236
237
238
239
240
241
242
243
244
        info.length != local_length
        or info.start != cp_rank * local_length
        or info.global_offsets[-1] != cp_size * local_length
    ):
        raise ValueError("Packed metadata must cover the chronological, equal-length CP token intervals.")
    return info


def packed_causal_conv1d(
275
276
277
278
279
280
281
282
283
        ImportError: If an explicitly selected optional kernel package is unavailable.
    """
    validate_conv_backend(backend)
    if inputs.ndim != 3 or inputs.shape[:2] != (1, info.length):
        raise ValueError("Packed convolution expects [1,T,C] matching its token interval.")
    channels = inputs.shape[-1]
    if (
        convolution.groups != convolution.in_channels
        or convolution.out_channels != convolution.in_channels
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
        convolution.groups != convolution.in_channels
        or convolution.out_channels != convolution.in_channels
        or convolution.stride != (1,)
    ):
        raise ValueError("Packed convolution requires a stride-one depthwise Conv1d.")
    if weight is None:
        weight, bias = convolution.weight, convolution.bias
    width, dilation = convolution.kernel_size[0], convolution.dilation[0]
    if weight.shape != (channels, 1, width):
        raise ValueError("Packed convolution weight must match the local head channels.")
    halo_width = (width - 1) * dilation
    if halo is not None and halo.shape != (1, halo_width, channels):
        raise ValueError("Packed convolution halo has an incompatible shape.")
    if backend != "eager":
        return fused_causal_conv1d(
            inputs, weight, bias, info, halo, backend=backend, dilation=dilation,
        )
    if halo is None:
        halo = inputs.new_zeros((1, halo_width, channels))
326
327
328
329
330
331
332
333
334
335
336
337
338
339
    **kwargs: Any,
) -> tuple[torch.Tensor, torch.Tensor | None]:
    """Evaluate the portable reference independently per sample fragment."""
    if inputs[0].shape[0] != 1:
        raise ValueError("Packed reference requires batch dimension one.")
    info = PackedSequenceInfo(_offset_tuple(cu_seqlens), 0, inputs[0].shape[1])
    if info.global_offsets[-1] != info.length:
        raise ValueError("Local cumulative offsets must end at the input token count.")
    if initial_state is not None and initial_state.shape[0] != info.num_sequences:
        raise ValueError("Packed initial_state requires one slot per local sample fragment.")
    outputs, finals = [], []
    for index, (start, end) in enumerate(zip(info.local_offsets, info.local_offsets[1:])):
        state = None if initial_state is None else initial_state[index : index + 1]
        output, final = operator(
hyper_parallel/components/modules/kimi_delta_attention.py
520
521
522
523
524
525
526
527
528
529
530
531
532
533


def _validate_triton_state_contract(initial_state, output_final_state, qk_norm, gate, beta) -> None:
    """Keep raw-input dispatch separate from the stateful CP backend contract."""
    if initial_state is not None or output_final_state:
        raise NotImplementedError(
            "KDA backend='triton' currently supports zero initial state without "
            "returning a final state; stateful execution is owned by the CP backend."
        )
    if not (qk_norm and gate and beta):
        raise NotImplementedError(
            "KDA backend='triton' requires fused Q/K normalization, gate activation, "
            "and beta sigmoid."
        )
554
555
556
557
558
559
560
561
562
    cu_seqlens: Optional[torch.Tensor] = None,
) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
    """Dispatch KDA to an explicitly selected eager or Triton backend."""
    if query.ndim == 3:
        output, final = chunk_kda(
            *(tensor.unsqueeze(0) for tensor in (query, key, value, gate, beta)),
            a_log=a_log, dt_bias=dt_bias, scale=scale, initial_state=initial_state,
            output_final_state=output_final_state, use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
            use_gate_in_kernel=use_gate_in_kernel, use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel,
560
561
562
563
564
565
566
567
568
            a_log=a_log, dt_bias=dt_bias, scale=scale, initial_state=initial_state,
            output_final_state=output_final_state, use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
            use_gate_in_kernel=use_gate_in_kernel, use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel,
            lower_bound=lower_bound, chunk_size=chunk_size, backend=backend, cu_seqlens=cu_seqlens)
        return output.squeeze(0), final
    backend = backend.lower()
    if backend not in _KDA_BACKENDS:
        raise ValueError(
            f"unsupported KDA backend {backend!r}; "
586
587
588
589
590
591
592
593
594
            lower_bound=lower_bound,
            chunk_size=chunk_size,
            cu_seqlens=cu_seqlens,
        )
    _validate_triton_state_contract(initial_state, output_final_state, use_qk_l2norm_in_kernel,
                                   use_gate_in_kernel, use_beta_sigmoid_in_kernel)
    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(
942
943
944
945
946
947
948
949
950
            hidden_states = hidden_states.unsqueeze(0)
        batch_size, sequence_length, _ = hidden_states.shape
        sequence_info = resolve_packed_sequence(kwargs, sequence_length)
        if sequence_info is not None and batch_size != 1:
            raise ValueError("Packed KDA requires a flattened batch dimension of one.")
        query = self._run_short_convolution(
            self.q_proj(hidden_states), self.q_conv1d, sequence_info
        ).reshape(batch_size, sequence_length, self.num_heads, self.head_k_dim)
        key = self._run_short_convolution(
hyper_parallel/distributed/context_parallel/gated_delta_net.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
    """Run causal depthwise Conv1d with only the previous rank's boundary."""
    kernel_size = conv1d.kernel_size[0]
    dilation = conv1d.dilation[0]
    halo_width = (kernel_size - 1) * dilation
    if sequence_info is not None:
        halo = None
        if halo_width and cp_size > 1:
            if mixed_qkv.shape[1] < halo_width:
                raise ValueError("Packed CP requires a local interval at least as long as its convolution halo.")
            halo = _all_to_all_previous_rank_halo(
                mixed_qkv[:, -halo_width:].contiguous(), cp_mesh, cp_rank, cp_size)
        return packed_causal_conv1d(mixed_qkv, conv1d, sequence_info, halo)
    if halo_width == 0 or cp_size == 1:
        conv_out = conv1d(mixed_qkv.transpose(1, 2))
        return F.silu(conv_out[:, :, : mixed_qkv.shape[1]]).transpose(1, 2)
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
    The rank-ordered critical path then contains only ``M @ state + S`` and
    the small state transfer. Token outputs retain the ordinary PyTorch graph,
    while the two custom autograd boundaries reverse the state communication.
    """
    if sequence_info is not None:
        cp_group = None if cp_mesh is None else cp_mesh.get_group()
        incoming = None
        if cp_rank > 0:
            incoming = _RecvInitialStateP2PFunction.apply(
                query, cp_group, _global_peer_rank(cp_mesh, cp_rank - 1),
                (1, value.shape[2], query.shape[-1], value.shape[-1]))
        output, finals = torch_chunk_gated_delta_rule(
            query, key, value, g, beta, chunk_size=chunk_size,
            initial_state=sequence_info.first_state(incoming), output_final_state=True,
            use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, cu_seqlens=sequence_info.local_offsets)
        if incoming is not None:
            output = output + incoming.sum().to(output.dtype) * 0
        if cp_rank < cp_size - 1:
            token = _SendFinalStateP2PFunction.apply(
                finals[-1:].contiguous(), cp_group, _global_peer_rank(cp_mesh, cp_rank + 1))
            output = output + token.to(output.dtype) * 0
        return output
    if cp_size == 1:
        core_attn_out, _ = torch_chunk_gated_delta_rule(
            query,
            key,
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
            apply_gdn_state_summary,
            chunk_gated_delta_rule_state_summary_fwd,
        )

        receive_previous = cp_rank > 0 and (sequence_info is None or sequence_info.continues_previous)
        send_next = cp_rank < cp_size - 1 and (sequence_info is None or sequence_info.continues_next)
        initial_state = None
        recv_buffer = None
        recv_handle = None
        if receive_previous:
            recv_buffer = torch.empty(
                (query.shape[0], query.shape[2], query.shape[3], value.shape[3]),
                device=query.device,
                dtype=torch.float32,
            )
            recv_handle = dist.irecv(recv_buffer, src=prev_rank, group=cp_group)

        cu_seqlens = None if sequence_info is None else sequence_info.materialize(query.device, 64).cu_seqlens
        (
            query_norm,
            key_norm,
            _,
676
677
678
679
680
681
682
683
684
685
        )

        state_ext = None
        transition = None
        if send_next:
            tail = slice(None) if sequence_info is None else sequence_info.tail
            state_ext, transition = chunk_gated_delta_rule_state_summary_fwd(
                key_norm[:, tail], w[:, tail], u[:, tail], g_cumsum[:, tail],
                output_transition=sequence_info is None or sequence_info.transfer_active,
            )
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
                key_norm[:, tail], w[:, tail], u[:, tail], g_cumsum[:, tail],
                output_transition=sequence_info is None or sequence_info.transfer_active,
            )

        if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active):
            recv_handle.wait()
            initial_state = recv_buffer
            recv_handle = None

        send_buffer = None
        send_handle = None
        if send_next:
            send_buffer = apply_gdn_state_summary(
                state_ext,
                transition,
                initial_state if transition is not None else None,
696
697
698
699
700
701
702
703
704
705
706
707
708
                state_ext,
                transition,
                initial_state if transition is not None else None,
            ).contiguous()
            send_handle = dist.isend(send_buffer, dst=next_rank, group=cp_group)

        if recv_handle is not None:
            recv_handle.wait()
            initial_state = recv_buffer
        h, v_new, _ = chunk_gated_delta_rule_fwd_apply_state_saved(
            key_norm,
            g_cumsum,
            w,
720
721
722
723
724
725
726
727
728
729
            scale,
            cu_seqlens=cu_seqlens,
        ).to(query.dtype)

        if send_handle is not None:
            send_handle.wait()

        empty = query.new_empty(0)
        ctx.save_for_backward(
            query_norm,
735
736
737
738
739
740
741
742
743
744
745
746
747
            initial_state if initial_state is not None else empty,
            transition if transition is not None else empty,
            cu_seqlens if cu_seqlens is not None else query.new_empty(0, dtype=torch.int64),
        )
        ctx.sequence_info = sequence_info
        ctx.has_initial_state = initial_state is not None
        ctx.has_transition = transition is not None
        ctx.receive_previous = receive_previous
        ctx.send_next = send_next
        ctx.cp_rank = cp_rank
        ctx.cp_size = cp_size
        ctx.cp_group = cp_group
        ctx.prev_rank = prev_rank
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
            initial_state,
            transition,
            saved_cu_seqlens,
        ) = ctx.saved_tensors
        sequence_info = ctx.sequence_info
        cu_seqlens = None if sequence_info is None else saved_cu_seqlens
        if not ctx.has_initial_state:
            initial_state = None

        grad_final_state = None
        recv_handle = None
        if ctx.send_next:
            recv_buffer = torch.empty(
                (query.shape[0], query.shape[2], query.shape[3], value.shape[3]),
                device=grad_output.device,
                dtype=torch.float32,
            )
            recv_handle = dist.irecv(
                recv_buffer,
                src=ctx.next_rank,
                group=ctx.cp_group,
            )
791
792
793
794
795
796
797
798
799
                src=ctx.next_rank,
                group=ctx.cp_group,
            )

        kernel_initial = initial_state if sequence_info is None else sequence_info.first_state(initial_state)
        w, h, v_new, dv = chunk_gated_delta_rule_bwd_prepare_saved(
            query,
            key,
            value,
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
            cu_seqlens=cu_seqlens,
        )

        grad_state_ext = None
        if ctx.receive_previous:
            head = slice(None) if sequence_info is None else sequence_info.head
            grad_state_ext = chunk_gated_delta_rule_state_gradient_summary_bwd(
                query[:, head], key[:, head], w[:, head], g_cumsum[:, head],
                grad_output[:, head], dv[:, head], ctx.scale)

        if recv_handle is not None and (sequence_info is None or sequence_info.transfer_active):
            recv_handle.wait()
            grad_final_state = recv_buffer
            recv_handle = None

        send_buffer = None
        send_handle = None
        if ctx.receive_previous:
            send_buffer = apply_gdn_state_gradient_summary(
                grad_state_ext,
                transition,
                grad_final_state if ctx.has_transition else None,
825
826
827
828
829
830
831
832
833
                grad_state_ext,
                transition,
                grad_final_state if ctx.has_transition else None,
            ).contiguous()
            send_handle = dist.isend(
                send_buffer,
                dst=ctx.prev_rank,
                group=ctx.cp_group,
            )
831
832
833
834
835
836
837
838
839
840
841
842
                dst=ctx.prev_rank,
                group=ctx.cp_group,
            )

        if recv_handle is not None:
            recv_handle.wait()
            grad_final_state = recv_buffer
        kernel_grad_final = grad_final_state if sequence_info is None else sequence_info.last_state(grad_final_state)
        dh, _, dv = chunk_gated_delta_rule_bwd_state_saved(
            query,
            key,
            g_cumsum,
870
871
872
873
874
875
876
877
878
879
880
            use_qk_l2norm_in_kernel=False,
            cu_seqlens=cu_seqlens,
        )

        if send_handle is not None:
            send_handle.wait()
        return dq, dk, dv, dg, dbeta, None, None, None, None, None, None


def _gdn_state_p2p_triton(
    query: torch.Tensor,
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
        self.cp_rank = self.cp_mesh.get_local_rank()
        self.seq_dim = 1
        self.head_dim = 2
        self._validate_module()
        if self.gdn_backend != "eager":
            _validate_gdn_activation(self.module)
            self._validate_fused_conv()

    def _validate_module(self) -> None:
        """Validate the wrapped GDN module and Ulysses layout constraints."""
        required = (
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
        _validate_gdn_activation(self.module)

    def _validate_fused_conv(self) -> None:
        """Reject source convolutions whose semantics differ from a causal depthwise kernel."""
        convolution = self.module.conv1d
        if convolution.stride != (1,) or convolution.dilation != (1,):
            raise ValueError("GDN fused convolution requires stride=1 and dilation=1.")
        if convolution.groups != convolution.in_channels or convolution.out_channels != convolution.in_channels:
            raise ValueError("GDN fused convolution requires equal input/output channels and depthwise groups.")
        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:
        return _differentiable_all_to_all_shard(
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
        )

    def _convolve_exchanged_qkv(self, q_proj, k_proj, v_proj, exchanged_info, full_seq_len, local_conv_dim):
        """Apply head-local ShortConv with the restored document boundaries."""
        base = self.module
        mixed_qkv = torch.cat((q_proj, k_proj, v_proj), dim=-1)
        weight, bias = self._local_conv_weight(), self._local_conv_bias()
        if exchanged_info is not None:
            return packed_causal_conv1d(
                mixed_qkv, base.conv1d, exchanged_info,
                weight=weight, bias=bias, backend=self.gdn_backend,
            )
        if self.gdn_backend != "eager":
            return fused_causal_conv1d(
                mixed_qkv, weight, bias, backend=self.gdn_backend,
                dilation=base.conv1d.dilation[0],
            )
        conv_out = F.conv1d(
            input=mixed_qkv.transpose(1, 2),
            weight=weight,
            bias=bias,
            stride=base.conv1d.stride,
1192
1193
1194
1195
1196
1197
1198
1199
1200
            padding=base.conv1d.padding,
            dilation=base.conv1d.dilation,
            groups=local_conv_dim,
        )
        return F.silu(conv_out[:, :, :full_seq_len]).transpose(1, 2)


    def forward(
        self,
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
        """Run Gated DeltaNet with pure Ulysses context parallel."""
        if cache_params is not None:
            raise NotImplementedError("GDN Ulysses CP currently supports training without cache only.")
        hidden_states = _local_tensor_at_cp_boundary(hidden_states)
        is_flat_input = hidden_states.ndim == 2
        if is_flat_input:
            hidden_states = hidden_states.unsqueeze(0)
        sequence_info = resolve_packed_sequence(
            kwargs, hidden_states.shape[1], cp_rank=self.cp_rank, cp_size=self.cp_size)
        if sequence_info is not None and hidden_states.shape[0] != 1:
            raise ValueError("Packed GDN requires batch dimension one.")

        base = self.module
        hidden_states = _apply_local_padding_mask(hidden_states, attention_mask)
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
        local_num_k_heads = base.num_k_heads // self.cp_size
        local_num_v_heads = base.num_v_heads // self.cp_size
        local_conv_dim = local_key_dim * 2 + local_value_dim

        exchanged_info = None if sequence_info is None else sequence_info.ulysses(self.cp_size, self.cp_rank)
        mixed_qkv = self._convolve_exchanged_qkv(
            q_proj, k_proj, v_proj, exchanged_info, full_seq_len, local_conv_dim,
        )

        query, key, value = torch.split(
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
        core_attn_out = base.norm(core_attn_out, z_flat)
        core_attn_out = core_attn_out.reshape(bsz, local_seq_len, base.value_dim)
        if hasattr(base, "out_proj_input"):
            core_attn_out = base.out_proj_input(core_attn_out)
        output = base.out_proj(core_attn_out)
        return output.squeeze(0) if is_flat_input else output


class GatedDeltaNetP2PCP(nn.Module):
    """Sequence-sharded GDN CP with an affine-summary state wavefront."""
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
        if backend == "triton" and chunk_size != 64:
            raise NotImplementedError("GDN Triton backend requires 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._validate_module()

    def _validate_module(self) -> None:
        """Validate the Conv1d requirements of the P2P CP path."""
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
        """Run Gated DeltaNet on local sequence shards with recurrent-state P2P."""
        if cache_params is not None:
            raise NotImplementedError("GDN P2P CP currently supports training without cache only.")
        hidden_states = _local_tensor_at_cp_boundary(hidden_states)
        is_flat_input = hidden_states.ndim == 2
        if is_flat_input:
            hidden_states = hidden_states.unsqueeze(0)
        sequence_info = resolve_packed_sequence(
            kwargs, hidden_states.shape[1], cp_rank=self.cp_rank, cp_size=self.cp_size)
        if sequence_info is not None and hidden_states.shape[0] != 1:
            raise ValueError("Packed GDN requires batch dimension one.")

        base = self.module
        hidden_states = _apply_local_padding_mask(hidden_states, attention_mask)
1412
1413
1414
1415
1416
1417
1418
1419
1420
        )
        b = base.in_proj_b(hidden_states)
        a = base.in_proj_a(hidden_states)

        mixed_qkv = self._conv1d_with_halo(mixed_qkv, sequence_info=sequence_info)
        query, key, value = torch.split(
            mixed_qkv,
            [base.key_dim, base.key_dim, base.value_dim],
            dim=-1,
1469
1470
1471
1472
1473
1474
        core_attn_out = base.norm(core_attn_out, z_flat)
        core_attn_out = core_attn_out.reshape(bsz, local_seq_len, base.value_dim)
        if hasattr(base, "out_proj_input"):
            core_attn_out = base.out_proj_input(core_attn_out)
        output = base.out_proj(core_attn_out)
        return output.squeeze(0) if is_flat_input else output
hyper_parallel/distributed/context_parallel/kimi_delta_attention.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
    cp_rank: int,
    cp_size: int,
) -> tuple[torch.Tensor, Optional[PackedSequenceInfo], bool]:
    """Normalize a local layer input and resolve this invocation's packed metadata."""
    hidden_states = _local_cp_tensor(hidden_states)
    # Restore the caller's layout on return; offsets define document boundaries.
    is_flat_input = hidden_states.ndim == 2
    if is_flat_input:
        hidden_states = hidden_states.unsqueeze(0)
    sequence_info = resolve_packed_sequence(
        kwargs, hidden_states.shape[1], cp_rank=cp_rank, cp_size=cp_size)
    if sequence_info is not None and hidden_states.shape[0] != 1:
        raise ValueError("Packed KDA requires batch dimension one.")
    return hidden_states, sequence_info, is_flat_input


def _global_peer_rank(cp_mesh: DeviceMesh, local_rank: int) -> int:
    """Map one CP-local rank index to its global distributed rank."""
248
249
250
251
252
253
254
255
256
257
    halo: Optional[torch.Tensor] = None,
    sequence_info: Optional[PackedSequenceInfo] = None,
) -> torch.Tensor:
    """Apply one local ShortConv, optionally with a preceding-rank halo."""
    if sequence_info is not None:
        return packed_causal_conv1d(tensor, convolution, sequence_info, halo)
    if halo is None:
        conv_input = tensor.transpose(1, 2)
        padding = convolution.padding
    else:
494
495
496
497
498
499
500
501
502
503
504
505
506
    sequence_info: Optional[PackedSequenceInfo] = None,
) -> torch.Tensor:
    """Run native per-document recurrence with differentiable neighbor states."""
    # Portable semantic reference: native fragment recurrence, fixed sends.
    incoming = None
    if cp_rank > 0:
        shape = (1, value.shape[2], query.shape[-1], value.shape[-1])
        incoming = _RecvKDAInitialState.apply(query, cp_group, prev_rank, shape)
    output, finals = chunk_kda(
        query, key, value, gate, beta, a_log=a_log, dt_bias=dt_bias, scale=scale,
        initial_state=sequence_info.first_state(incoming), output_final_state=True,
        use_qk_l2norm_in_kernel=True, use_gate_in_kernel=True, use_beta_sigmoid_in_kernel=True,
        lower_bound=lower_bound, chunk_size=chunk_size, backend="eager",
505
506
507
508
509
510
511
512
513
514
515
516
517
518
        use_qk_l2norm_in_kernel=True, use_gate_in_kernel=True, use_beta_sigmoid_in_kernel=True,
        lower_bound=lower_bound, chunk_size=chunk_size, backend="eager",
        cu_seqlens=sequence_info.local_offsets,
    )
    if incoming is not None:
        output = output + incoming.sum().to(output.dtype) * 0
    if cp_rank < cp_size - 1:
        token = _SendKDAFinalState.apply(finals[-1:].contiguous(), cp_group, next_rank)
        output = output + token.to(output.dtype) * 0
    return output


def _eager_kda_state_p2p(
    query: torch.Tensor,
533
534
535
536
537
538
539
540
541
542
    cp_size: int,
    sequence_info: Optional[PackedSequenceInfo] = None,
) -> torch.Tensor:
    """Run the differentiable Torch reference for affine state-P2P KDA."""
    if sequence_info is not None:
        return _eager_kda_packed_p2p(
            query, key, value, gate, beta, a_log=a_log, dt_bias=dt_bias, scale=scale,
            lower_bound=lower_bound, chunk_size=chunk_size, cp_group=cp_group,
            prev_rank=prev_rank, next_rank=next_rank, cp_rank=cp_rank, cp_size=cp_size,
            sequence_info=sequence_info,
592
593
594
595
596
597
598
599
600
        )

    send_token = None
    if cp_rank < cp_size - 1:
        final_state = torch_apply_kda_state_summary(state_ext, transition, initial_state)
        send_token = _SendKDAFinalState.apply(final_state, cp_group, next_rank)

    output, _ = chunk_kda(
        query,
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
            a_log: Decay parameter with one value per value head.
            dt_bias: Learned bias with one value per gate channel.
            scale: Optional attention scaling factor.
        """
        is_flat_input = query.ndim == 3
        if is_flat_input:
            output = self.forward(*(tensor.unsqueeze(0) for tensor in (query, key, value, gate, beta)),
                                  a_log=a_log, dt_bias=dt_bias, scale=scale, sequence_info=sequence_info)
            return output.squeeze(0)
        self._validate_inputs(query, key, value, gate, beta, a_log, dt_bias)
        exchanged_info = None if sequence_info is None else sequence_info.ulysses(self.cp_size, self.cp_rank)
        num_v_heads = value.shape[2]
        k_head_dim = query.shape[-1]

        query = self._seq_to_head(query)
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
        weight = convolution.weight.narrow(0, channel_start, local_channels)
        bias = convolution.bias
        if bias is not None:
            bias = bias.narrow(0, channel_start, local_channels)
        if sequence_info is not None:
            return packed_causal_conv1d(
                projected, convolution, sequence_info, weight=weight, bias=bias,
                backend=self.backend,
            )
        if self.backend != "eager":
            return fused_causal_conv1d(
                projected, weight, bias, backend=self.backend,
                dilation=convolution.dilation[0],
            )
        output = F.conv1d(  # pylint: disable=not-callable
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
            raise NotImplementedError("KDA does not materialize attention weights.")
        if attention_mask is not None and not bool(attention_mask.bool().all().item()):
            raise NotImplementedError("Padded KDA CP batches are not implemented.")

        hidden_states, sequence_info, is_flat_input = _prepare_layer_input(
            hidden_states, kwargs, self.cp_rank, self.cp_size)
        exchanged_info = None if sequence_info is None else sequence_info.ulysses(self.cp_size, self.cp_rank)
        base = self.module
        batch_size, local_seq_len, _ = hidden_states.shape
        query = base.q_proj(hidden_states)
        key = base.k_proj(hidden_states)
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
        )

        query, key, value, gate, beta = self._pack_projected_inputs(query, key, value, gate, beta)
        if _uses_short_conv(base):
            query = self._local_short_conv(query, base.q_conv1d, sequence_info=exchanged_info)
            key = self._local_short_conv(key, base.k_conv1d, sequence_info=exchanged_info)
            value = self._local_short_conv(value, base.v_conv1d, sequence_info=exchanged_info)
        else:
            query, key, value = (F.silu(tensor) for tensor in (query, key, value))

        full_seq_len = query.shape[1]
1228
1229
1230
1231
1232
1233
1234
1235
1236
        output = output.reshape(
            batch_size, local_seq_len, _value_projection_dim(base)
        )
        output = base.o_proj(output)
        return _format_layer_output(base, output.squeeze(0) if is_flat_input else output)


def _validate_cp_degrees(cp_size: int, ulysses_degree: int, allgather_group_size: int) -> None:
    """Validate both split extents before head division or process-group creation."""
1342
1343
1344
1345
1346
1347
1348
1349
1350
            raise NotImplementedError("KDA does not materialize attention weights.")
        if attention_mask is not None and not bool(attention_mask.bool().all().item()):
            raise NotImplementedError("Padded KDA CP batches are not implemented.")

        hidden_states, sequence_info, is_flat_input = _prepare_layer_input(
            hidden_states, kwargs, self.cp_rank, self.cp_size)
        base = self.module
        batch_size, local_seq_len, _ = hidden_states.shape
        query = base.q_proj(hidden_states)
1412
1413
1414
1415
1416
1417
1418
1419
1420
        output = output.reshape(
            batch_size, local_seq_len, _value_projection_dim(base)
        )
        output = base.o_proj(output)
        return _format_layer_output(base, output.squeeze(0) if is_flat_input else output)


class KimiDeltaAttentionCoreCP(nn.Module):
    """Compose optional Ulysses redistribution with local KDA and state exchange.
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
        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:
            raise ValueError("lower_bound must be negative.")
        self.cp_size = device_mesh.size()
        self.cp_rank = device_mesh.get_local_rank()
        self.ulysses_degree = ulysses_degree
        self.ulysses_mesh = None
        self.ulysses_rank = 0
        state_mesh = device_mesh
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491

        Returns:
            Output restored to this rank's original token/head layout.
        """
        is_flat_input = query.ndim == 3
        if is_flat_input:
            output = self.forward(*(tensor.unsqueeze(0) for tensor in (query, key, value, gate, beta)),
                                  a_log=a_log, dt_bias=dt_bias, scale=scale, sequence_info=sequence_info)
            return output.squeeze(0)
        if sequence_info is not None:
            if query.shape[0] != 1:
                raise ValueError("Packed KDA core requires batch dimension one.")
            resolve_packed_sequence({"packed_sequence_info": sequence_info}, query.shape[1],
                                    cp_rank=self.cp_rank, cp_size=self.cp_size)
        if self.ulysses_mesh is None:
            return self.state(query, key, value, gate, beta, a_log=a_log, dt_bias=dt_bias, scale=scale,
                              sequence_info=sequence_info)
        _validate_projected_inputs(self.ulysses_degree, query, key, value, gate, beta, a_log, dt_bias)
        heads, key_dim = value.shape[2], query.shape[-1]
        inputs = self._exchange_inputs((query, key, value, gate, beta))
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
        heads, key_dim = value.shape[2], query.shape[-1]
        inputs = self._exchange_inputs((query, key, value, gate, beta))
        local_a = _slice_local_heads(a_log.reshape(heads), self.ulysses_rank, self.ulysses_degree)
        local_bias = _slice_local_heads(dt_bias.reshape(heads, key_dim), self.ulysses_rank, self.ulysses_degree)
        exchanged_info = None if sequence_info is None else sequence_info.ulysses(
            self.ulysses_degree, self.ulysses_rank)
        output = self.state(*inputs, a_log=local_a, dt_bias=local_bias, scale=scale, sequence_info=exchanged_info)
        return _differentiable_all_to_all_shard(output, self.ulysses_mesh, split_dim=1, concat_dim=2)

    def _exchange_inputs(self, inputs: tuple[torch.Tensor, ...]) -> tuple[torch.Tensor, ...]:
        """Redistribute projected inputs using the established synchronous schedule."""