|
| 1 | +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. |
| 2 | +"""Contiguous packed sequence rolls with explicit groups and differentiable boundary exchange.""" |
| 3 | + |
| 4 | +import torch |
| 5 | +import torch.distributed as dist |
| 6 | + |
| 7 | + |
| 8 | +def _continuation_mask(tensor, packed_seq_params, cp_group, dims): |
| 9 | + """Return the local rows whose next token belongs to the same real document.""" |
| 10 | + size = cp_group.size() if cp_group is not None else 1 |
| 11 | + rank = cp_group.rank() if cp_group is not None else 0 |
| 12 | + length = tensor.shape[dims] |
| 13 | + rows = torch.arange(rank * length, (rank + 1) * length, device=tensor.device) |
| 14 | + if packed_seq_params is None: |
| 15 | + ends = torch.full_like(rows, length * size) |
| 16 | + return rows + 1 < ends |
| 17 | + real = packed_seq_params.cu_seqlens_q |
| 18 | + physical = packed_seq_params.cu_seqlens_q_padded |
| 19 | + physical = real if physical is None else physical |
| 20 | + seq = torch.bucketize(rows, physical[1:], right=True) |
| 21 | + in_pack = seq < physical.numel() - 1 |
| 22 | + seq = seq.clamp_max(physical.numel() - 2) |
| 23 | + ends = physical[seq] + (real[1:] - real[:-1])[seq] |
| 24 | + valid = in_pack & (rows >= physical[seq]) & (rows + 1 < ends) |
| 25 | + return valid |
| 26 | + |
| 27 | + |
| 28 | +class _RollLeft(torch.autograd.Function): |
| 29 | + """One differentiable left roll, including the reverse neighbor exchange.""" |
| 30 | + |
| 31 | + @staticmethod |
| 32 | + def forward(ctx, tensor, group, dims): |
| 33 | + """Shift local values and receive the next rank's first row.""" |
| 34 | + ctx.group, ctx.dims = group, dims |
| 35 | + result = torch.roll(tensor, -1, dims) |
| 36 | + result.select(dims, -1).zero_() |
| 37 | + if group is not None and group.size() > 1: |
| 38 | + rank, size = group.rank(), group.size() |
| 39 | + send = tensor.select(dims, 0).contiguous() |
| 40 | + recv = torch.empty_like(send) |
| 41 | + ops = [] |
| 42 | + if rank > 0: |
| 43 | + ops.append( |
| 44 | + dist.P2POp(dist.isend, send, dist.get_global_rank(group, rank - 1), group) |
| 45 | + ) |
| 46 | + if rank + 1 < size: |
| 47 | + ops.append( |
| 48 | + dist.P2POp(dist.irecv, recv, dist.get_global_rank(group, rank + 1), group) |
| 49 | + ) |
| 50 | + for work in dist.batch_isend_irecv(ops): |
| 51 | + work.wait() |
| 52 | + if rank + 1 < size: |
| 53 | + result.select(dims, -1).copy_(recv) |
| 54 | + return result |
| 55 | + |
| 56 | + @staticmethod |
| 57 | + def backward(ctx, grad): |
| 58 | + """Return the boundary gradient to its original token owner.""" |
| 59 | + group, dims = ctx.group, ctx.dims |
| 60 | + result = torch.roll(grad, 1, dims) |
| 61 | + result.select(dims, 0).zero_() |
| 62 | + if group is not None and group.size() > 1: |
| 63 | + rank, size = group.rank(), group.size() |
| 64 | + send = grad.select(dims, -1).contiguous() |
| 65 | + recv = torch.empty_like(send) |
| 66 | + ops = [] |
| 67 | + if rank + 1 < size: |
| 68 | + ops.append( |
| 69 | + dist.P2POp(dist.isend, send, dist.get_global_rank(group, rank + 1), group) |
| 70 | + ) |
| 71 | + if rank > 0: |
| 72 | + ops.append( |
| 73 | + dist.P2POp(dist.irecv, recv, dist.get_global_rank(group, rank - 1), group) |
| 74 | + ) |
| 75 | + for work in dist.batch_isend_irecv(ops): |
| 76 | + work.wait() |
| 77 | + if rank > 0: |
| 78 | + result.select(dims, 0).copy_(recv) |
| 79 | + return result, None, None |
| 80 | + |
| 81 | + |
| 82 | +def roll_contiguous(tensor, dims, cp_group, packed_seq_params=None, fill_value=0): |
| 83 | + """Roll a dense or packed contiguous shard by one, preserving real document boundaries.""" |
| 84 | + if tensor.shape[dims] == 0: |
| 85 | + return tensor.clone() |
| 86 | + valid = _continuation_mask(tensor, packed_seq_params, cp_group, dims) |
| 87 | + shape = [1] * tensor.ndim |
| 88 | + shape[dims] = tensor.shape[dims] |
| 89 | + return _RollLeft.apply(tensor, cp_group, dims).masked_fill(~valid.view(shape), fill_value) |
| 90 | + |
| 91 | + |
| 92 | +def roll_contiguous_fields(tensors, cp_group, packed_seq_params=None, fill_values=None): |
| 93 | + """Roll non-differentiable token fields together, preserving each dtype and fill value. |
| 94 | +
|
| 95 | + This batches one depth's IDs/positions/masks; activations continue to use the |
| 96 | + differentiable roll above. No cross-layer cache or model-wide state is introduced. |
| 97 | + """ |
| 98 | + present = [tensor for tensor in tensors if tensor is not None] |
| 99 | + if not present: |
| 100 | + return tuple(tensors) |
| 101 | + reference = present[0] |
| 102 | + length = reference.shape[-1] |
| 103 | + if any( |
| 104 | + t.requires_grad or t.shape[-1] != length or t.device != reference.device for t in present |
| 105 | + ): |
| 106 | + raise ValueError("Token fields require matching sequence lengths/devices and no gradients.") |
| 107 | + if fill_values is None: |
| 108 | + fill_values = (0,) * len(tensors) |
| 109 | + if len(fill_values) != len(tensors): |
| 110 | + raise ValueError("Each token field needs one fill value.") |
| 111 | + if length == 0: |
| 112 | + return tuple(None if t is None else t.clone() for t in tensors) |
| 113 | + valid = _continuation_mask(reference, packed_seq_params, cp_group, -1) |
| 114 | + rank = cp_group.rank() if cp_group is not None else 0 |
| 115 | + size = cp_group.size() if cp_group is not None else 1 |
| 116 | + outputs, received, sends, ops = [], [], [], [] |
| 117 | + for tensor, fill in zip(tensors, fill_values): |
| 118 | + if tensor is None: |
| 119 | + outputs.append(None) |
| 120 | + continue |
| 121 | + result = torch.roll(tensor, -1, -1) |
| 122 | + result[..., -1].fill_(fill) |
| 123 | + outputs.append(result) |
| 124 | + if rank > 0: |
| 125 | + send = tensor[..., 0].contiguous() |
| 126 | + sends.append(send) # Retain temporary send storage until all work completes. |
| 127 | + ops.append( |
| 128 | + dist.P2POp(dist.isend, send, dist.get_global_rank(cp_group, rank - 1), cp_group) |
| 129 | + ) |
| 130 | + if rank + 1 < size: |
| 131 | + recv = torch.empty_like(tensor[..., 0]) |
| 132 | + received.append((result, recv)) |
| 133 | + ops.append( |
| 134 | + dist.P2POp(dist.irecv, recv, dist.get_global_rank(cp_group, rank + 1), cp_group) |
| 135 | + ) |
| 136 | + for work in dist.batch_isend_irecv(ops) if ops else []: |
| 137 | + work.wait() |
| 138 | + for result, recv in received: |
| 139 | + result[..., -1].copy_(recv) |
| 140 | + for result, fill in zip(outputs, fill_values): |
| 141 | + if result is not None: |
| 142 | + result.masked_fill_(~valid, fill) |
| 143 | + return tuple(outputs) |
0 commit comments