Fused Softmax

This tutorial demonstrates softmax operator and its launch on Ascend simulator.

16 import asc2
17 import torch
18
19
20 # The functions which are executed on Ascend NPU must be marked with `@asc2.jit` decorator.
21 # Available parameters for @asc2.jit decorator can be seen in the documentation:
22 # https://compiler-team-ru.github.io/pyasc/python-api/rst/runtime/index.html
23 @asc2.jit(reuse_alloc=1)
24 def fused_softmax(
25         # Pointers to input and output tensors should have `asc2.GlobalAddress` type.
26         input_ptr: asc2.GlobalAddress, output_ptr: asc2.GlobalAddress,
27         # Scalar parameters are passed as Python types (e.g. `int`, `float`).
28         # For optimization purposes it is recommended to pass scalar parameter as constants (e.g. `asc2.ConstExpr[int]`).
29         # Here num_rows and num_cols are compiler-time parameter:
30         num_rows: asc2.ConstExpr, num_cols: asc2.ConstExpr,
31         # tile_shape is a list and must be asc2.ConstExpr
32         tile_shape: asc2.ConstExpr):
33
34     # Tensor descriptor is created from `asc2.GlobalAddress` to represent entire tensor.
35     in_gm = asc2.global_tensor(input_ptr, [num_rows, num_cols])
36     out_gm = asc2.global_tensor(output_ptr, [num_rows, num_cols])
37
38     # Python expressions are used to calculate offset:
39     # `asc2.block_num()` function provides number of AICOREs launched.
40     rows_per_block = asc2.ceildiv(num_rows, asc2.block_num())
41     # `asc2.block_idx()` function is used to get current AICORE index.
42     block_offset = asc2.block_idx() * rows_per_block
43     # Define the loop iterating over tiles
44     ub_loop = asc2.number(asc2.ceildiv(rows_per_block, tile_shape[0]), asc2.int_)
45
46     # `unroll_factor` parameter of `asc2.range` in `for` loop can be used to manage software pipelining. Set it to `2` to enable double buffering.
47     # `parallel` parameter of `asc2.range` in `for` loop enable overlapping of store operation of `i`-th iteration and load of `i+1`-th iteration.
48     # It is user responsibility to ensure that there are no data dependencies between overlapped iterations.
49     for i in asc2.range(ub_loop, unroll_factor=2, parallel=True):
50         row_start_offset = block_offset + i * tile_shape[0]
51         # `asc2.copy_in` is used to create 2D tensor object to load from GM to UB and pad with '-inf' all values that are out of global tensor
52         rows = asc2.copy_in(in_gm, [row_start_offset, 0], [tile_shape[0], tile_shape[1]], pad_value=float('-inf'))
53         # Call high-level 2D softmax
54         out = asc2.softmax(rows)
55         # `asc2.copy_out` is used to move data from UB back to GM.
56         asc2.copy_out(out, out_gm, [row_start_offset, 0])
57
58
59 if __name__ == "__main__":
60     backend = asc2.Backend.Model  # can be "Model" for simulator or "NPU" for device
61     soc_version = asc2.Platform.Ascend950PR_9599  # Device version
62     device_id = 0  # might be necessary to provide if more than one NPU device is present in the system
63     asc2.set_platform(backend, soc_version, device_id)
64
65     input_shape_2d = [256, 98]
66     rows_per_iter = 5
67     block_num = 56
68     dtype = torch.float32
69
70     # Alignment for tile_shape
71     num_rows, num_cols = input_shape_2d
72     ALIGNMENT_ELEMENTS = 32 // dtype.itemsize
73     tile_shape = [rows_per_iter, asc2.ceildiv(num_cols, ALIGNMENT_ELEMENTS) * ALIGNMENT_ELEMENTS]
74
75     # Allocate tensors
76     in_tensor = torch.randn(input_shape_2d, dtype=dtype)
77     out_tensor = torch.zeros(input_shape_2d, dtype=dtype)
78
79     # For the kernel invocation, number of AICOREs should be provided in brackets:
80     fused_softmax[block_num](in_tensor, out_tensor, input_shape_2d[0], input_shape_2d[1], tile_shape)
81
82     expected = torch.softmax(in_tensor, dim=1)
83     torch.testing.assert_close(out_tensor, expected)

Gallery generated by Sphinx-Gallery