| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | # |
| 4 | # Modified by Roberto L. Castro (Roberto.LopezCastro@ist.ac.at). |
| 5 | # |
| 6 | # Copied from https://github.com/pytorch/ao/tree/main/torchao/prototype/mx_formats |
| 7 | # |
| 8 | # Copyright (c) Meta Platforms, Inc. and affiliates. |
| 9 | # All rights reserved. |
| 10 | |
| 11 | # This source code is licensed under the license found in the |
| 12 | # LICENSE file in the root directory of this source tree. |
| 13 | |
| 14 | from typing import Literal |
| 15 | |
| 16 | import torch |
| 17 | from torch.library import wrap_triton |
| 18 | |
| 19 | from vllm.triton_utils import tl, triton |
| 20 | from vllm.utils.math_utils import cdiv |
| 21 | |
| 22 | |
| 23 | @triton.jit |
| 24 | def triton_scale_swizzle( |
| 25 | scale_ptr: torch.Tensor, |
| 26 | scale_rows: int, |
| 27 | scale_cols: int, |
| 28 | output_ptr: torch.Tensor, |
| 29 | input_row_stride: int, |
| 30 | output_block_stride: int, |
| 31 | BLOCK_ROWS: tl.constexpr, |
| 32 | BLOCK_COLS: tl.constexpr, |
| 33 | ): |
| 34 | """Rearranges tensor data from row-major to block-scaled swizzle format. |
| 35 | |
| 36 | Args: |
| 37 | scale_ptr: Pointer to the input scale tensor |
| 38 | scale_rows: Number of rows in the scale tensor |
| 39 | scale_cols: Number of columns in the scale tensor |
| 40 | output_ptr: Pointer to the output tensor |
| 41 | input_row_stride: Stride between rows in the input tensor |
| 42 | output_block_stride: Stride between blocks in the output tensor |
| 43 | BLOCK_ROWS: Number of rows in a tile (compile-time constant) |
| 44 | BLOCK_COLS: Number of columns in a tile (compile-time constant) |
| 45 | |
| 46 | """ |
| 47 | pid_row = tl.program_id(0) |
| 48 | pid_col = tl.program_id(1) |
| 49 | |
| 50 | rows = tl.arange(0, BLOCK_ROWS)[:, None] |
| 51 | cols = tl.arange(0, BLOCK_COLS)[None, :] |
| 52 | |
| 53 | # Calculate starting row and column for this tile |
| 54 | start_row = pid_row * BLOCK_ROWS |
| 55 | start_col = pid_col * BLOCK_COLS |
| 56 | global_rows = start_row + rows |
| 57 | global_cols = start_col + cols |
| 58 | |
| 59 | mask = (global_rows < scale_rows) & (global_cols < scale_cols) |
| 60 | |
| 61 | input_scales = tl.load( |
| 62 | scale_ptr + global_rows * input_row_stride + global_cols, |
| 63 | mask=mask, |
| 64 | other=0.0, |
| 65 | ) |
| 66 | |
| 67 | r_div_32 = rows // 32 |
| 68 | r_mod_32 = rows % 32 |
| 69 | |
| 70 | # 2) Rearrange to (32, 4, 4) then to final (32, 16) coordinates |
| 71 | dest_indices = r_mod_32 * 16 + r_div_32 * 4 + cols |
| 72 | |
| 73 | # Flatten |
| 74 | dest_indices_flat = tl.reshape(dest_indices, (BLOCK_ROWS * BLOCK_COLS)) |
| 75 | scales_flat = tl.reshape(input_scales, (BLOCK_ROWS * BLOCK_COLS)) |
| 76 | |
| 77 | # Calculate block offset using provided output block stride |
| 78 | LOCAL_NUMEL = BLOCK_ROWS * BLOCK_COLS |
| 79 | block_offset = pid_col * LOCAL_NUMEL + (pid_row * output_block_stride) |
| 80 | |
| 81 | tl.store( |
| 82 | output_ptr + block_offset + dest_indices_flat, |
| 83 | scales_flat, |
| 84 | ) |
| 85 | |
| 86 | |
| 87 | @torch.library.custom_op("vllm::triton_mx_block_rearrange", mutates_args=()) |
| 88 | def triton_mx_block_rearrange(scale_tensor: torch.Tensor) -> torch.Tensor: |
| 89 | """Rearranges an E8M0 tensor scale from row-major format to |
| 90 | block-scaled swizzle format. |
| 91 | |
| 92 | This format is suitable for Tmem as described in NVIDIA documentation: |
| 93 | https://docs.nvidia.com/cuda/cublas/index.html#d-block-scaling-factors-layout |
| 94 | |
| 95 | Args: |
| 96 | scale_tensor: Input tensor in row-major format with 8-bit elements |
| 97 | |
| 98 | Returns: |
| 99 | Rearranged tensor in block-scaled swizzle format |
| 100 | |
| 101 | """ |
| 102 | assert scale_tensor.element_size() == 1, ( |
| 103 | "Expected element size to be 1 byte (8 bits)" |
| 104 | ) |
| 105 | assert scale_tensor.is_contiguous(), "Input tensor must be contiguous" |
| 106 | |
| 107 | rows, cols = scale_tensor.shape |
| 108 | |
| 109 | # Calculate blocks needed |
| 110 | n_row_blocks = triton.cdiv(rows, 128) |
| 111 | n_col_blocks = triton.cdiv(cols, 4) |
| 112 | padded_rows = n_row_blocks * 128 |
| 113 | padded_cols = n_col_blocks * 4 |
| 114 | |
| 115 | out = scale_tensor.new_empty((padded_rows, padded_cols)) |
| 116 | |
| 117 | # Input stride (for row-major format) |
| 118 | input_row_stride = cols |
| 119 | |
| 120 | # We probably want handle multiple blocks per tile but |
| 121 | # for now keep it simple |
| 122 | BLOCK_ROWS, BLOCK_COLS = 128, 4 |
| 123 | |
| 124 | # Output block stride for the rearranged format |
| 125 | output_block_stride = BLOCK_ROWS * BLOCK_COLS * (padded_cols // BLOCK_COLS) |
| 126 | |
| 127 | grid = lambda META: ( |
| 128 | triton.cdiv(padded_rows, BLOCK_ROWS), |
| 129 | triton.cdiv(padded_cols, BLOCK_COLS), |
| 130 | ) |
| 131 | |
| 132 | wrap_triton(triton_scale_swizzle)[grid]( |
| 133 | scale_tensor.view(torch.uint8), |
| 134 | rows, |
| 135 | cols, |
| 136 | out.view(torch.uint8), |
| 137 | input_row_stride, |
| 138 | output_block_stride, |
| 139 | BLOCK_ROWS=BLOCK_ROWS, |
| 140 | BLOCK_COLS=BLOCK_COLS, |
| 141 | ) |
| 142 | |
| 143 | return out |
| 144 | |
| 145 | |
| 146 | @triton_mx_block_rearrange.register_fake |
| 147 | def _triton_mx_block_rearrange_fake(scale_tensor: torch.Tensor) -> torch.Tensor: |
| 148 | rows, cols = scale_tensor.shape |
| 149 | padded_rows = cdiv(rows, 128) * 128 |
| 150 | padded_cols = cdiv(cols, 4) * 4 |
| 151 | return scale_tensor.new_empty((padded_rows, padded_cols)) |
| 152 | |
| 153 | |
| 154 | def to_blocked( |
| 155 | input_matrix: torch.Tensor, backend: Literal["torch", "triton"] = "triton" |
| 156 | ) -> torch.Tensor: |
| 157 | """Rearrange a large matrix by breaking it into blocks and applying |
| 158 | the rearrangement pattern. |
| 159 | |
| 160 | See: |
| 161 | https://docs.nvidia.com/cuda/cublas/index.html#d-block-scaling-factors-layout |
| 162 | |
| 163 | Args: |
| 164 | input_matrix: Input tensor of shape (H, W) |
| 165 | backend: "torch" (PyTorch path) or "triton" (Triton kernel) |
| 166 | |
| 167 | Returns: |
| 168 | Rearranged flattened tensor of size (32*cdiv(H,128) * 16*cdiv(W,4)) |
| 169 | |
| 170 | """ |
| 171 | if backend == "triton": |
| 172 | return triton_mx_block_rearrange(input_matrix).flatten() |
| 173 | elif backend != "torch": |
| 174 | raise ValueError(f'backend must be "torch" or "triton", got {backend!r}') |
| 175 | |
| 176 | rows, cols = input_matrix.shape |
| 177 | n_row_blocks = cdiv(rows, 128) |
| 178 | n_col_blocks = cdiv(cols, 4) |
| 179 | |
| 180 | # Calculate the padded shape |
| 181 | padded_rows = n_row_blocks * 128 |
| 182 | padded_cols = n_col_blocks * 4 |
| 183 | |
| 184 | padded = input_matrix |
| 185 | assert (rows, cols) == (padded_rows, padded_cols) |
| 186 | |
| 187 | # Rearrange the blocks |
| 188 | blocks = padded.view(n_row_blocks, 128, n_col_blocks, 4).permute(0, 2, 1, 3) |
| 189 | rearranged = blocks.reshape(-1, 4, 32, 4).transpose(1, 2).reshape(-1, 32, 16) |
| 190 | |
| 191 | return rearranged.flatten() |