diff --git a/.buildinfo b/.buildinfo index 83ed1f5f..6964678a 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. -config: c9e032fa7f49dc809f164063dfb2e28e +config: dabe4189cc532017a30e750cb5d38b1e tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/_downloads/2b236384a146de2d4a64081ddb0a7c9a/developer_01_ir_builder.zip b/_downloads/2b236384a146de2d4a64081ddb0a7c9a/developer_01_ir_builder.zip index 37c81a1b..5736fe5f 100644 Binary files a/_downloads/2b236384a146de2d4a64081ddb0a7c9a/developer_01_ir_builder.zip and b/_downloads/2b236384a146de2d4a64081ddb0a7c9a/developer_01_ir_builder.zip differ diff --git a/_downloads/39c6904b3f007c07e3d59200d0bf98b4/dive_03_composition.ipynb b/_downloads/39c6904b3f007c07e3d59200d0bf98b4/dive_03_composition.ipynb new file mode 100644 index 00000000..b695a4e5 --- /dev/null +++ b/_downloads/39c6904b3f007c07e3d59200d0bf98b4/dive_03_composition.ipynb @@ -0,0 +1,183 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Kernel Composition\n\n**Author**: Hongzheng Chen (hzchen@cs.cornell.edu)\n\nThis document will discuss kernel composition.\nIn the previous tutorials, we have seen how to write a simple kernel.\nHowever, in real applications, we often need to compose multiple kernels together.\n\nIn the following example, we define a ``matrix_add`` and a ``gemm`` kernel, and wrap them into a ``top``-level function.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import allo\nfrom allo.ir.types import int32, float32\n\nM, K, N = 32, 32, 32\n\n\ndef matrix_add(A: int32[M, N]) -> int32[M, N]:\n B: int32[M, N] = 0\n for i, j in allo.grid(M, N):\n B[i, j] = A[i, j] + 1\n return B\n\n\ndef gemm(A: int32[M, K], B: int32[K, N]) -> int32[M, N]:\n C: int32[M, N] = 0\n for i, j in allo.grid(M, N):\n for k in allo.reduction(K):\n C[i, j] += A[i, k] * B[k, j]\n return C\n\n\ndef top(A: int32[M, K], B: int32[K, N]) -> int32[M, N]:\n C = gemm(A, B)\n D = matrix_add(C)\n return D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Different teams or people can then work on different parts of the code and optimize each kernel.\nWe first create a schedule for the ``matrix_add`` kernel, and add several optimizations.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s1 = allo.customize(matrix_add)\ns1.pipeline(\"j\")\nprint(s1.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then we create a schedule for the ``gemm`` kernel and optimize it.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s2 = allo.customize(gemm)\ns2.reorder(\"k\", \"j\")\ns2.buffer_at(s2.C, axis=\"i\")\ns2.pipeline(\"j\")\nprint(s2.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice that now we only optimize the separate kernels but do not incorporate them into the top-level function, as shown in the following printed module.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s = allo.customize(top)\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Therefore, after each part has been optimized, we need to explicitly *compose* them together.\nIn Allo, we can use the ``.compose()`` primitive to compose the schedules together into the parent function.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s.compose([s1, s2])\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the schedules for the ``matrix_add`` and ``gemm`` kernels are both correctly optimized in the top-level function.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Template Composition\nSometimes we may define template kernels and invoke the kernel with different template arguments. Allo provides an *id* option to specify the exact kernel to be composed.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def kernel[T_in, T_out, S](A: \"T_in[S]\") -> \"T_out[S]\":\n B: T_out[S] = 0\n for i in range(S):\n with allo.meta_if(T_out == int32):\n B[i] = A[i] + 1\n with allo.meta_else():\n B[i] = A[i] * 2\n return B\n\n\ndef top2(A: int32[M]) -> float32[M]:\n C = kernel[int32, int32, M, \"K1\"](A)\n D = kernel[int32, float32, M, \"K2\"](C)\n return D" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Specifically, the last argument of the template kernel is the *id* of the kernel. Later on we can use this ID for distinguishing different kernels during composition.\nWe also customize the two template kernels with different optimizations first.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s1 = allo.customize(kernel, instantiate=[int32, int32, M])\ns1.unroll(\"i\", factor=4)\nprint(s1.module)\n\ns2 = allo.customize(kernel, instantiate=[int32, float32, M])\ns2.pipeline(\"i\")\nprint(s2.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we compose the two template kernels into the top-level function with the ID specified.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s = allo.customize(top2)\ns.compose(s1, id=\"K1\")\ns.compose(s2, id=\"K2\")\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see from the printed module that the loop in the first kernel is unrolled by a factor of 4, and the loop in the second kernel is pipelined.\n\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/4569d89feac47262c8c4e3e128da7a7e/dive_04_features.zip b/_downloads/4569d89feac47262c8c4e3e128da7a7e/dive_04_features.zip new file mode 100644 index 00000000..bc7cd824 Binary files /dev/null and b/_downloads/4569d89feac47262c8c4e3e128da7a7e/dive_04_features.zip differ diff --git a/_downloads/48b69635df4cfe1643d9d6b9bdf6cd79/tutorial_01_get_started.zip b/_downloads/48b69635df4cfe1643d9d6b9bdf6cd79/tutorial_01_get_started.zip index 5c881bf2..49c413eb 100644 Binary files a/_downloads/48b69635df4cfe1643d9d6b9bdf6cd79/tutorial_01_get_started.zip and b/_downloads/48b69635df4cfe1643d9d6b9bdf6cd79/tutorial_01_get_started.zip differ diff --git a/_downloads/4fba383e419c1fc1ea22179140eb2d12/dive_01_data_types.py b/_downloads/4fba383e419c1fc1ea22179140eb2d12/dive_01_data_types.py new file mode 100644 index 00000000..180bf685 --- /dev/null +++ b/_downloads/4fba383e419c1fc1ea22179140eb2d12/dive_01_data_types.py @@ -0,0 +1,114 @@ +# Copyright Allo authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Data Types and Type Casting +=========================== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss the Allo-supported data types in detail. +All the data types are defined in the ``allo.ir.types`` module. +""" + +import allo +from allo.ir.types import int16, int32, float32, Int, UInt, Float, Fixed + +############################################################################## +# Currently, Allo supports three base data types for mathematical operations: +# +# - Integers: ``Int(bitwdith)``, ``UInt(bitwidth)`` +# - Floating points: ``Float(bitwidth)`` (only support 16, 32, and 64 bits) +# - Fixed points: ``Fixed(bitwidth, frac)``, ``UFixed(bitwidth, frac)`` +# +# For example, one can declare a 15-bit integer as ``Int(15)`` and an unsigned 8-bit fixed-point number with 3 fractional bits as ``UFixed(8, 3)``. +# For all the C/C++ supported data types, we provide shorthands like ``float32`` and ``int16`` to easily declare them. + +# %% +# Notice different from native Python, Allo requires the program to be **strongly and statically typed**. +# The variable types are either declared explicitly or inferred from the context. +# For a variable that first appears in the program, we should declare it with an expected data type using Python's type hint notation: + +a: int32 + +# %% +# Once the data types are defined, an important consideration is how to handle +# operations between variables of different types. Allo supports two types of casting: +# (1) implicit casting that is automatically done by the Allo compiler; +# and (2) explicit casting that is manually done by the user. + +############################################################################## +# Implicit Casting +# ---------------- +# Allo has a strong type system that follows the `MLIR convention `_ to enforce the operand types are the same for the arithmetic operations. +# However, it is burdensome for users to cast the variables every time, and it is also error-prone to avoid overflow when performing computations. +# Therefore, Allo is equipped with builtin casting rules to automatically cast the variables to the same type before the operation, which is called *implicit casting*. +# An example is shown below: + + +def add(a: int32, b: int32) -> int32: + return a + b + + +s = allo.customize(add) +print(s.module) + +# %% +# We can see that ``a`` and ``b`` are firstly casted to ``int33``, added +# together, and converted back to ``int32``. +# This is to avoid overflow and is automatically inferred by the Allo compiler. + + +############################################################################## +# Explicit Casting +# ---------------- +# One can also explicitly cast the variable to a specific type by creating an intermediate variable, +# or use Python-builtin functions like ``float()`` and ``int()`` to explicitly cast a variable to ``float32`` or ``int32``. +# Another example is shown below: + + +def cast(a: int32) -> int16: + b: float32 = a # explicit + c: float32 = b * 2 + d: float32 = float(a) * 2 + e: int16 = c + d + return e + + +s = allo.customize(cast) +print(s.module) + +# %% +# By explicitly creating an intermediate variable ``b``, we can cast the ``int32`` variable ``a`` to the desired floating-point type. +# Similarly, calling ``float(a)`` can also cast ``a`` to a floating-point type. +# +# .. note:: +# +# The above stated explicit casting between integers and floating points preserves the value but the precision may be changed. +# If you want to use a union type to represent both integers and floating points, please use the `.bitcast()` API instead. For example, ``a.bitcast()`` can convert ``int32`` to ``float32`` representation with the bit pattern preserved. + +############################################################################## +# Bit Operations +# -------------- +# As hardware accelerators have ability to manipulate each bit of the data, Allo supports bit operations on +# those integer types. For example, we can access a specific bit in an integer ``a`` using the indexing operator: +# +# .. code-block:: python +# +# a[15] + +# %% +# We can also extract a chunk of bits from an integer using the slicing operator: +# +# .. code-block:: python +# +# a[0:16] +# +# .. note:: +# +# Allo follows the Python convention that the upper bound is not included, so ``[0:16]`` means +# extracting the first 16 bits, which is different from the Xilinx HLS convention that uses ``[0:15]`` +# to indicate the first 16 bits. + +# %% +# Not only constant values are supported, but also variables can be used as the index or the slice range. diff --git a/_downloads/5c3db288c9103701a8cc33d4c4f30066/dive_03_composition.zip b/_downloads/5c3db288c9103701a8cc33d4c4f30066/dive_03_composition.zip new file mode 100644 index 00000000..ecea986b Binary files /dev/null and b/_downloads/5c3db288c9103701a8cc33d4c4f30066/dive_03_composition.zip differ diff --git a/_downloads/68e0932078b39343e70c899a03d3ae7c/dive_01_data_types.ipynb b/_downloads/68e0932078b39343e70c899a03d3ae7c/dive_01_data_types.ipynb new file mode 100644 index 00000000..690f7223 --- /dev/null +++ b/_downloads/68e0932078b39343e70c899a03d3ae7c/dive_01_data_types.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Data Types and Type Casting\n\n**Author**: Hongzheng Chen (hzchen@cs.cornell.edu)\n\nThis document will discuss the Allo-supported data types in detail.\nAll the data types are defined in the ``allo.ir.types`` module.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import allo\nfrom allo.ir.types import int16, int32, float32, Int, UInt, Float, Fixed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Currently, Allo supports three base data types for mathematical operations:\n\n- Integers: ``Int(bitwdith)``, ``UInt(bitwidth)``\n- Floating points: ``Float(bitwidth)`` (only support 16, 32, and 64 bits)\n- Fixed points: ``Fixed(bitwidth, frac)``, ``UFixed(bitwidth, frac)``\n\nFor example, one can declare a 15-bit integer as ``Int(15)`` and an unsigned 8-bit fixed-point number with 3 fractional bits as ``UFixed(8, 3)``.\nFor all the C/C++ supported data types, we provide shorthands like ``float32`` and ``int16`` to easily declare them.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Notice different from native Python, Allo requires the program to be **strongly and statically typed**.\nThe variable types are either declared explicitly or inferred from the context.\nFor a variable that first appears in the program, we should declare it with an expected data type using Python's type hint notation:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "a: int32" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the data types are defined, an important consideration is how to handle\noperations between variables of different types. Allo supports two types of casting:\n(1) implicit casting that is automatically done by the Allo compiler;\nand (2) explicit casting that is manually done by the user.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implicit Casting\nAllo has a strong type system that follows the [MLIR convention](https://mlir.llvm.org/docs/Dialects/ArithOps/) to enforce the operand types are the same for the arithmetic operations.\nHowever, it is burdensome for users to cast the variables every time, and it is also error-prone to avoid overflow when performing computations.\nTherefore, Allo is equipped with builtin casting rules to automatically cast the variables to the same type before the operation, which is called *implicit casting*.\nAn example is shown below:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def add(a: int32, b: int32) -> int32:\n return a + b\n\n\ns = allo.customize(add)\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that ``a`` and ``b`` are firstly casted to ``int33``, added\ntogether, and converted back to ``int32``.\nThis is to avoid overflow and is automatically inferred by the Allo compiler.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Explicit Casting\nOne can also explicitly cast the variable to a specific type by creating an intermediate variable,\nor use Python-builtin functions like ``float()`` and ``int()`` to explicitly cast a variable to ``float32`` or ``int32``.\nAnother example is shown below:\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def cast(a: int32) -> int16:\n b: float32 = a # explicit\n c: float32 = b * 2\n d: float32 = float(a) * 2\n e: int16 = c + d\n return e\n\n\ns = allo.customize(cast)\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By explicitly creating an intermediate variable ``b``, we can cast the ``int32`` variable ``a`` to the desired floating-point type.\nSimilarly, calling ``float(a)`` can also cast ``a`` to a floating-point type.\n\n

Note

The above stated explicit casting between integers and floating points preserves the value but the precision may be changed.\n If you want to use a union type to represent both integers and floating points, please use the `.bitcast()` API instead. For example, ``a.bitcast()`` can convert ``int32`` to ``float32`` representation with the bit pattern preserved.

\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bit Operations\nAs hardware accelerators have ability to manipulate each bit of the data, Allo supports bit operations on\nthose integer types. For example, we can access a specific bit in an integer ``a`` using the indexing operator:\n\n```python\na[15]\n```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also extract a chunk of bits from an integer using the slicing operator:\n\n```python\na[0:16]\n```\n

Note

Allo follows the Python convention that the upper bound is not included, so ``[0:16]`` means\n extracting the first 16 bits, which is different from the Xilinx HLS convention that uses ``[0:15]``\n to indicate the first 16 bits.

\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Not only constant values are supported, but also variables can be used as the index or the slice range.\n\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/77583a2b4d388a5f4b2bf7b3eec828d2/dive_01_data_types.zip b/_downloads/77583a2b4d388a5f4b2bf7b3eec828d2/dive_01_data_types.zip new file mode 100644 index 00000000..f8f8a0b2 Binary files /dev/null and b/_downloads/77583a2b4d388a5f4b2bf7b3eec828d2/dive_01_data_types.zip differ diff --git a/_downloads/849a5b43539eae829c9f79867111880a/dive_02_template.zip b/_downloads/849a5b43539eae829c9f79867111880a/dive_02_template.zip new file mode 100644 index 00000000..e06fcf76 Binary files /dev/null and b/_downloads/849a5b43539eae829c9f79867111880a/dive_02_template.zip differ diff --git a/_downloads/8d3af32bb0bffe35477d27ae08e595fe/tutorial_01_get_started.py b/_downloads/8d3af32bb0bffe35477d27ae08e595fe/tutorial_01_get_started.py index 815d271d..253d0411 100644 --- a/_downloads/8d3af32bb0bffe35477d27ae08e595fe/tutorial_01_get_started.py +++ b/_downloads/8d3af32bb0bffe35477d27ae08e595fe/tutorial_01_get_started.py @@ -34,8 +34,10 @@ # %% # We then define a function that takes two 32x32 matrices as inputs and # returns a 32x32 matrix as output. The variable declaration is defined -# as ``: []``. We require **strict type annotation** in -# Allo's kernels, which is different from directly programming in Python. +# as ``: []``, and the function type is defined as +# ``(, , ...) -> ``. +# We require **strict type annotation** in Allo's kernels, which is different +# from directly programming in Python. # # Inside the kernel, we provide a shorthand for the loop iterator. For example, # ``for i, j, k in allo.grid(32, 32, 32)`` is equivalent to the following diff --git a/_downloads/90b883f891c63f481ffa4756cd7e0781/dive_04_features.ipynb b/_downloads/90b883f891c63f481ffa4756cd7e0781/dive_04_features.ipynb new file mode 100644 index 00000000..a90ba5e3 --- /dev/null +++ b/_downloads/90b883f891c63f481ffa4756cd7e0781/dive_04_features.ipynb @@ -0,0 +1,86 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Other Features\n\n**Author**: Hongzheng Chen (hzchen@cs.cornell.edu)\n\nThis document will discuss other features that are not covered in the previous tutorials.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dynamic Shapes\nIn some cases, the shape of the tensor is not known at compile time, so we can use ``[...]`` to represent the dynamic shape.\nFrom the generated MLIR module, we can see it has a ``\"?\"`` in the shape of the tensor, which means the shape is not predefined,\nbut we can still run the LLVM module with arbitrary shapes of NumPy arrays.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import allo\nfrom allo.ir.types import int32, float32\nimport numpy as np\n\n\ndef kernel(A: float32[...], B: float32[...], size: int32):\n for i in range(size):\n B[i] = A[i]\n\n\ns = allo.customize(kernel)\nprint(s.module)\nnp_A = np.random.random((256,)).astype(np.float32)\nallo_A = np.zeros((256,)).astype(np.float32)\nmod = s.build()\nmod(np_A, allo_A, 256)\nnp.testing.assert_allclose(np_A, allo_A)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also check the generated HLS code that the arguments are declared as pointers.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "code = s.build(target=\"vhls\")\nprint(code)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tuple Return\nAnother feature is the tuple support. As in Python, we can return multiple values from a function, Allo\nalso supports this by explicitly specifying the return type as a tuple.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def callee(a: float32, b: float32) -> (float32, float32):\n c: float32 = a + b\n d: float32 = a - b\n return c, d\n\n\ndef kernel(A: float32[10], B: float32[10]) -> (float32[10], float32[10]):\n C: float32[10] = 0\n D: float32[10] = 0\n for i in range(10):\n C[i], D[i] = callee(A[i], B[i])\n return C, D\n\n\ns = allo.customize(kernel)\nprint(s.module)\nmod = s.build()\nnp_A = np.random.random((10,)).astype(np.float32)\nnp_B = np.random.random((10,)).astype(np.float32)\nnp_C, np_D = mod(np_A, np_B)\nnp_C_ref = np.zeros((10,), dtype=np.float32)\nnp_D_ref = np.zeros((10,), dtype=np.float32)\nfor i in range(10):\n np_C_ref[i], np_D_ref[i] = callee(np_A[i], np_B[i])\nnp.testing.assert_allclose(np_C, np_C_ref)\nnp.testing.assert_allclose(np_D, np_D_ref)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_downloads/9b041eab9a2cc4c12c883027dcc37a54/dive_02_template.py b/_downloads/9b041eab9a2cc4c12c883027dcc37a54/dive_02_template.py new file mode 100644 index 00000000..d0868c4c --- /dev/null +++ b/_downloads/9b041eab9a2cc4c12c883027dcc37a54/dive_02_template.py @@ -0,0 +1,82 @@ +# Copyright Allo authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Template Kernels +================ + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document explains how to write a template kernel in Allo. +Template kernels are useful when we need to reuse a kernel with different data types or when certain computation patterns depend on specific constants. +By leveraging template kernels, we can achieve greater flexibility and reusability in the code. +""" + +import allo +from allo.ir.types import int32, float32 + +# %% +# We follow Python's convention to use *type variable* to define a template kernel. +# Specifically, the type variable is specified after the function name using square brackets: ``def kernel[T](...)``, and the type variable can be used in the function signature and body. +# Importantly, as the native Python interpreter does not support Allo's type declaration (i.e., base type + shape), we need to use string annotations like ``"T[10]"`` to specify the type of the variables. +# Otherwise, it will raise a type error. +# +# In the following, we define a simple addition function that adds 1 to each element of the input array. +# To invoke the kernel with a specific data type, we can use the ``instantiate`` argument in the ``allo.customize`` function. + + +def kernel[T](A: "T[10]") -> "T[10]": + B: T[10] + for i in range(10): + B[i] = A[i] + 1 + return B + + +s = allo.customize(kernel, instantiate=[int32]) +print(s.module) + +# %% +# We can see that the kernel is specialized with the given ``int32`` data type. +# Similarly, we can directly declare a new kernel by specifying ``float32`` as the data type. + +s = allo.customize(kernel, instantiate=[float32]) +print(s.module) + +# %% +# If we not only want to specialize the data type but also the shape of the array, we can provide another type variable, and pass it to the ``instantiate`` argument. +# Note that here we also use the ``: base_type`` notation to constrain the type of the type variable. Here we constrain the type variable ``M`` to be an integer. + + +def kernel2[T, M: int32](A: "T[M]") -> "T[M]": + B: T[M] + for i in range(M): + B[i] = A[i] + 1 + return B + + +s = allo.customize(kernel2, instantiate=[int32, 20]) +print(s.module) + +# %% +# Furthermore, Allo's template also enables metaprogramming that can evaluate type variables at compile time. +# Specifically, we can use the ``allo.meta_if``, ``allo.meta_elif``, and ``allo.meta_else`` to conditionally generate code based on the type variables. +# Just to make sure the conditions can be determined at compile time. + + +def kernel3[T, M: int32](A: "T[M]") -> "T[M]": + B: T[M] + for i in range(M): + with allo.meta_if(T == int32): + B[i] = A[i] + 1 + with allo.meta_else(): + B[i] = A[i] - 1 + return B + + +# %% +# In final generated code, we can see that only a single branch is generated based on the given data type. + +s = allo.customize(kernel3, instantiate=[int32, 20]) +print(s.module) +s = allo.customize(kernel3, instantiate=[float32, 20]) +print(s.module) diff --git a/_downloads/9fbd96ba55c84b58bccde28bb525c3ff/developer_02_mlir.zip b/_downloads/9fbd96ba55c84b58bccde28bb525c3ff/developer_02_mlir.zip index b1ad6292..c7d6b5b1 100644 Binary files a/_downloads/9fbd96ba55c84b58bccde28bb525c3ff/developer_02_mlir.zip and b/_downloads/9fbd96ba55c84b58bccde28bb525c3ff/developer_02_mlir.zip differ diff --git a/_downloads/a1303c8436389bcc90cc384bd5c2d23e/tutorial_02_vhls.zip b/_downloads/a1303c8436389bcc90cc384bd5c2d23e/tutorial_02_vhls.zip index a6b639a4..87d20891 100644 Binary files a/_downloads/a1303c8436389bcc90cc384bd5c2d23e/tutorial_02_vhls.zip and b/_downloads/a1303c8436389bcc90cc384bd5c2d23e/tutorial_02_vhls.zip differ diff --git a/_downloads/aac8c815d185f6d5646a9509ba2daa13/dive_03_composition.py b/_downloads/aac8c815d185f6d5646a9509ba2daa13/dive_03_composition.py new file mode 100644 index 00000000..ef3fc175 --- /dev/null +++ b/_downloads/aac8c815d185f6d5646a9509ba2daa13/dive_03_composition.py @@ -0,0 +1,120 @@ +# Copyright Allo authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Kernel Composition +================== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss kernel composition. +In the previous tutorials, we have seen how to write a simple kernel. +However, in real applications, we often need to compose multiple kernels together. + +In the following example, we define a ``matrix_add`` and a ``gemm`` kernel, and wrap them into a ``top``-level function. +""" + +import allo +from allo.ir.types import int32, float32 + +M, K, N = 32, 32, 32 + + +def matrix_add(A: int32[M, N]) -> int32[M, N]: + B: int32[M, N] = 0 + for i, j in allo.grid(M, N): + B[i, j] = A[i, j] + 1 + return B + + +def gemm(A: int32[M, K], B: int32[K, N]) -> int32[M, N]: + C: int32[M, N] = 0 + for i, j in allo.grid(M, N): + for k in allo.reduction(K): + C[i, j] += A[i, k] * B[k, j] + return C + + +def top(A: int32[M, K], B: int32[K, N]) -> int32[M, N]: + C = gemm(A, B) + D = matrix_add(C) + return D + + +# %% +# Different teams or people can then work on different parts of the code and optimize each kernel. +# We first create a schedule for the ``matrix_add`` kernel, and add several optimizations. + +s1 = allo.customize(matrix_add) +s1.pipeline("j") +print(s1.module) + +# %% +# Then we create a schedule for the ``gemm`` kernel and optimize it. + +s2 = allo.customize(gemm) +s2.reorder("k", "j") +s2.buffer_at(s2.C, axis="i") +s2.pipeline("j") +print(s2.module) + +# %% +# Notice that now we only optimize the separate kernels but do not incorporate them into the top-level function, as shown in the following printed module. + +s = allo.customize(top) +print(s.module) + +# %% +# Therefore, after each part has been optimized, we need to explicitly *compose* them together. +# In Allo, we can use the ``.compose()`` primitive to compose the schedules together into the parent function. + +s.compose([s1, s2]) +print(s.module) + +# %% +# We can see that the schedules for the ``matrix_add`` and ``gemm`` kernels are both correctly optimized in the top-level function. + +############################################################################## +# Template Composition +# -------------------- +# Sometimes we may define template kernels and invoke the kernel with different template arguments. Allo provides an *id* option to specify the exact kernel to be composed. + + +def kernel[T_in, T_out, S](A: "T_in[S]") -> "T_out[S]": + B: T_out[S] = 0 + for i in range(S): + with allo.meta_if(T_out == int32): + B[i] = A[i] + 1 + with allo.meta_else(): + B[i] = A[i] * 2 + return B + + +def top2(A: int32[M]) -> float32[M]: + C = kernel[int32, int32, M, "K1"](A) + D = kernel[int32, float32, M, "K2"](C) + return D + + +# %% +# Specifically, the last argument of the template kernel is the *id* of the kernel. Later on we can use this ID for distinguishing different kernels during composition. +# We also customize the two template kernels with different optimizations first. + +s1 = allo.customize(kernel, instantiate=[int32, int32, M]) +s1.unroll("i", factor=4) +print(s1.module) + +s2 = allo.customize(kernel, instantiate=[int32, float32, M]) +s2.pipeline("i") +print(s2.module) + +# %% +# Finally, we compose the two template kernels into the top-level function with the ID specified. + +s = allo.customize(top2) +s.compose(s1, id="K1") +s.compose(s2, id="K2") +print(s.module) + +# %% +# We can see from the printed module that the loop in the first kernel is unrolled by a factor of 4, and the loop in the second kernel is pipelined. diff --git a/_downloads/addf17760130f22dafec92dedc62e16a/tutorial_01_get_started.ipynb b/_downloads/addf17760130f22dafec92dedc62e16a/tutorial_01_get_started.ipynb index d2f89745..95e32ef5 100644 --- a/_downloads/addf17760130f22dafec92dedc62e16a/tutorial_01_get_started.ipynb +++ b/_downloads/addf17760130f22dafec92dedc62e16a/tutorial_01_get_started.ipynb @@ -40,7 +40,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We then define a function that takes two 32x32 matrices as inputs and\nreturns a 32x32 matrix as output. The variable declaration is defined\nas ``: []``. We require **strict type annotation** in\nAllo's kernels, which is different from directly programming in Python.\n\nInside the kernel, we provide a shorthand for the loop iterator. For example,\n``for i, j, k in allo.grid(32, 32, 32)`` is equivalent to the following\nnested for-loop:\n\n```python\nfor i in range(32):\n for j in range(32):\n for k in range(32):\n # body\n```\nThe ``allo.grid`` API is used to define the iteration space of the loop.\nThe arguments denote the upper bounds of the loop iterators.\nNotice the above range-loop is also supported in the new Allo, so\nusers have more flexibility to define the loop structure.\n\n" + "We then define a function that takes two 32x32 matrices as inputs and\nreturns a 32x32 matrix as output. The variable declaration is defined\nas ``: []``, and the function type is defined as\n``(, , ...) -> ``.\nWe require **strict type annotation** in Allo's kernels, which is different\nfrom directly programming in Python.\n\nInside the kernel, we provide a shorthand for the loop iterator. For example,\n``for i, j, k in allo.grid(32, 32, 32)`` is equivalent to the following\nnested for-loop:\n\n```python\nfor i in range(32):\n for j in range(32):\n for k in range(32):\n # body\n```\nThe ``allo.grid`` API is used to define the iteration space of the loop.\nThe arguments denote the upper bounds of the loop iterators.\nNotice the above range-loop is also supported in the new Allo, so\nusers have more flexibility to define the loop structure.\n\n" ] }, { diff --git a/_downloads/d58a09ade6135cf6e79cb2fe738ace28/dive_04_features.py b/_downloads/d58a09ade6135cf6e79cb2fe738ace28/dive_04_features.py new file mode 100644 index 00000000..e087285f --- /dev/null +++ b/_downloads/d58a09ade6135cf6e79cb2fe738ace28/dive_04_features.py @@ -0,0 +1,76 @@ +# Copyright Allo authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +""" +Other Features +============== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss other features that are not covered in the previous tutorials. +""" + +############################################################################## +# Dynamic Shapes +# -------------- +# In some cases, the shape of the tensor is not known at compile time, so we can use ``[...]`` to represent the dynamic shape. +# From the generated MLIR module, we can see it has a ``"?"`` in the shape of the tensor, which means the shape is not predefined, +# but we can still run the LLVM module with arbitrary shapes of NumPy arrays. + +import allo +from allo.ir.types import int32, float32 +import numpy as np + + +def kernel(A: float32[...], B: float32[...], size: int32): + for i in range(size): + B[i] = A[i] + + +s = allo.customize(kernel) +print(s.module) +np_A = np.random.random((256,)).astype(np.float32) +allo_A = np.zeros((256,)).astype(np.float32) +mod = s.build() +mod(np_A, allo_A, 256) +np.testing.assert_allclose(np_A, allo_A) + +# %% +# We can also check the generated HLS code that the arguments are declared as pointers. + +code = s.build(target="vhls") +print(code) + +############################################################################## +# Tuple Return +# ------------ +# Another feature is the tuple support. As in Python, we can return multiple values from a function, Allo +# also supports this by explicitly specifying the return type as a tuple. + + +def callee(a: float32, b: float32) -> (float32, float32): + c: float32 = a + b + d: float32 = a - b + return c, d + + +def kernel(A: float32[10], B: float32[10]) -> (float32[10], float32[10]): + C: float32[10] = 0 + D: float32[10] = 0 + for i in range(10): + C[i], D[i] = callee(A[i], B[i]) + return C, D + + +s = allo.customize(kernel) +print(s.module) +mod = s.build() +np_A = np.random.random((10,)).astype(np.float32) +np_B = np.random.random((10,)).astype(np.float32) +np_C, np_D = mod(np_A, np_B) +np_C_ref = np.zeros((10,), dtype=np.float32) +np_D_ref = np.zeros((10,), dtype=np.float32) +for i in range(10): + np_C_ref[i], np_D_ref[i] = callee(np_A[i], np_B[i]) +np.testing.assert_allclose(np_C, np_C_ref) +np.testing.assert_allclose(np_D, np_D_ref) diff --git a/_downloads/de72dcd3242a3c85b41c9c54a3424409/dive_02_template.ipynb b/_downloads/de72dcd3242a3c85b41c9c54a3424409/dive_02_template.ipynb new file mode 100644 index 00000000..b910fc97 --- /dev/null +++ b/_downloads/de72dcd3242a3c85b41c9c54a3424409/dive_02_template.ipynb @@ -0,0 +1,133 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n# Template Kernels\n\n**Author**: Hongzheng Chen (hzchen@cs.cornell.edu)\n\nThis document explains how to write a template kernel in Allo.\nTemplate kernels are useful when we need to reuse a kernel with different data types or when certain computation patterns depend on specific constants.\nBy leveraging template kernels, we can achieve greater flexibility and reusability in the code.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import allo\nfrom allo.ir.types import int32, float32" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We follow Python's convention to use *type variable* to define a template kernel.\nSpecifically, the type variable is specified after the function name using square brackets: ``def kernel[T](...)``, and the type variable can be used in the function signature and body.\nImportantly, as the native Python interpreter does not support Allo's type declaration (i.e., base type + shape), we need to use string annotations like ``\"T[10]\"`` to specify the type of the variables.\nOtherwise, it will raise a type error.\n\nIn the following, we define a simple addition function that adds 1 to each element of the input array.\nTo invoke the kernel with a specific data type, we can use the ``instantiate`` argument in the ``allo.customize`` function.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def kernel[T](A: \"T[10]\") -> \"T[10]\":\n B: T[10]\n for i in range(10):\n B[i] = A[i] + 1\n return B\n\n\ns = allo.customize(kernel, instantiate=[int32])\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the kernel is specialized with the given ``int32`` data type.\nSimilarly, we can directly declare a new kernel by specifying ``float32`` as the data type.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s = allo.customize(kernel, instantiate=[float32])\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we not only want to specialize the data type but also the shape of the array, we can provide another type variable, and pass it to the ``instantiate`` argument.\nNote that here we also use the ``: base_type`` notation to constrain the type of the type variable. Here we constrain the type variable ``M`` to be an integer.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def kernel2[T, M: int32](A: \"T[M]\") -> \"T[M]\":\n B: T[M]\n for i in range(M):\n B[i] = A[i] + 1\n return B\n\n\ns = allo.customize(kernel2, instantiate=[int32, 20])\nprint(s.module)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Furthermore, Allo's template also enables metaprogramming that can evaluate type variables at compile time.\nSpecifically, we can use the ``allo.meta_if``, ``allo.meta_elif``, and ``allo.meta_else`` to conditionally generate code based on the type variables.\nJust to make sure the conditions can be determined at compile time.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def kernel3[T, M: int32](A: \"T[M]\") -> \"T[M]\":\n B: T[M]\n for i in range(M):\n with allo.meta_if(T == int32):\n B[i] = A[i] + 1\n with allo.meta_else():\n B[i] = A[i] - 1\n return B" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In final generated code, we can see that only a single branch is generated based on the given data type.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "s = allo.customize(kernel3, instantiate=[int32, 20])\nprint(s.module)\ns = allo.customize(kernel3, instantiate=[float32, 20])\nprint(s.module)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/_images/sphx_glr_dive_01_data_types_thumb.png b/_images/sphx_glr_dive_01_data_types_thumb.png new file mode 100644 index 00000000..8a5fed58 Binary files /dev/null and b/_images/sphx_glr_dive_01_data_types_thumb.png differ diff --git a/_images/sphx_glr_dive_02_template_thumb.png b/_images/sphx_glr_dive_02_template_thumb.png new file mode 100644 index 00000000..8a5fed58 Binary files /dev/null and b/_images/sphx_glr_dive_02_template_thumb.png differ diff --git a/_images/sphx_glr_dive_03_composition_thumb.png b/_images/sphx_glr_dive_03_composition_thumb.png new file mode 100644 index 00000000..8a5fed58 Binary files /dev/null and b/_images/sphx_glr_dive_03_composition_thumb.png differ diff --git a/_images/sphx_glr_dive_04_features_thumb.png b/_images/sphx_glr_dive_04_features_thumb.png new file mode 100644 index 00000000..8a5fed58 Binary files /dev/null and b/_images/sphx_glr_dive_04_features_thumb.png differ diff --git a/_modules/allo/customize.html b/_modules/allo/customize.html index e034f2ee..4b0822d9 100644 --- a/_modules/allo/customize.html +++ b/_modules/allo/customize.html @@ -5,7 +5,7 @@ allo.customize — Allo Documentation - + @@ -140,6 +140,15 @@

Quick search

  • Getting Started
  • Vivado/Vitis HLS Backend
  • +

    Deep Dive

    +

    Developer Guide

    • Developer Setup
    • @@ -166,17 +175,17 @@

      Source code for allo.customize

       # SPDX-License-Identifier: Apache-2.0
       # pylint: disable=no-name-in-module
       
      -import re
      -import inspect
      -import textwrap
      -import copy
      -from dataclasses import dataclass
      -from functools import wraps
      -from types import FunctionType as PyFunctionType
      -from typing import Union
      -from collections.abc import Callable
      -
      -from ._mlir.ir import (
      +import re
      +import inspect
      +import textwrap
      +import copy
      +from dataclasses import dataclass
      +from functools import wraps
      +from types import FunctionType as PyFunctionType
      +from typing import Union
      +from collections.abc import Callable
      +
      +from ._mlir.ir import (
           Context,
           Location,
           InsertionPoint,
      @@ -192,7 +201,7 @@ 

      Source code for allo.customize

           AffineMap,
           AffineMapAttr,
       )
      -from ._mlir.dialects import (
      +from ._mlir.dialects import (
           allo as allo_d,
           memref as memref_d,
           affine as affine_d,
      @@ -200,50 +209,46 @@ 

      Source code for allo.customize

           arith as arith_d,
           func as func_d,
       )
      -from ._mlir.dialects.affine import (
      +from ._mlir.dialects.affine import (
           AffineExpr,
           AffineDimExpr,
       )
      -from ._mlir.exceptions import (
      +from ._mlir.exceptions import (
           AlloValueError,
       )
       
      -from . import primitives as prim
      -from .ir.visitor import ASTContext
      -from .ir.utils import MockArg, MockBuffer, parse_ast, get_global_vars
      -from .ir.builder import ASTTransformer
      -from .ir.infer import TypeInferer
      -from .ir.transform import (
      +from . import primitives as prim
      +from .ir.visitor import ASTContext
      +from .ir.utils import MockArg, MockBuffer, parse_ast, get_global_vars
      +from .ir.builder import ASTTransformer
      +from .ir.infer import TypeInferer
      +from .ir.transform import (
           get_affine_loop_nests,
           find_loop_in_bands,
           find_buffer,
           find_func_in_module,
           LoopWrapper,
       )
      -from .ir.use_def import UseDefChain
      -from .passes import (
      +from .passes import (
           _mlir_lower_pipeline,
           lower_linalg_and_attach_names,
      +    analyze_use_def,
       )
      -from .backend.llvm import LLVMModule
      -from .backend.hls import HLSModule
      -from .library import KERNEL2SCHEDULE
      +from .backend.llvm import LLVMModule
      +from .backend.hls import HLSModule
      +from .library import KERNEL2SCHEDULE
       
       
      -def getsourcefile(obj):
      +def getsourcefile(obj):
           ret = inspect.getsourcefile(obj)
           if ret is None:
               ret = inspect.getfile(obj)
           return ret
       
       
      -def getsourcelines(obj):
      -    return inspect.getsourcelines(obj)
      -
      -
      -def wrapped_apply(fn):
      +def wrapped_apply(fn):
           @wraps(fn)
      -    def wrapper(*args, **kwargs):
      +    def wrapper(*args, **kwargs):
               sch = args[0]
               with sch.module.context, Location.unknown():
                   res = fn(*args, **kwargs)
      @@ -267,7 +272,7 @@ 

      Source code for allo.customize

       
       
       @dataclass
      -class Partition:
      +class Partition:
           Complete = 0
           Block = 1
           Cyclic = 2
      @@ -275,15 +280,14 @@ 

      Source code for allo.customize

       
       
      [docs] -class Schedule: - def __init__( +class Schedule: + def __init__( self, module, top_func, func_args, ip, ext_libs=None, - use_def_chain=None, inst_list=None, ): self.module = module @@ -295,24 +299,23 @@

      Source code for allo.customize

               if ext_libs is None:
                   ext_libs = []
               self.ext_libs = ext_libs
      -        self.use_def_chain = use_def_chain
               self.partitioned_arrays = {}
               self.inst_list = inst_list if inst_list is not None else []
       
      -    def get_loops(self, func=None):
      +    def get_loops(self, func=None):
               if isinstance(func, str):
                   func = self._find_function(func)
               if func is None:
                   func = self.top_func
               return get_affine_loop_nests(func)
       
      -    def _find_band(self, band_name, func=None):
      +    def _find_band(self, band_name, func=None):
               loops = self.get_loops(func)
               if band_name in loops.loops:
                   return loops[band_name]
               raise RuntimeError(f"Band {band_name} not found")
       
      -    def _find_function(self, name, error=True):
      +    def _find_function(self, name, error=True):
               for func in self.module.body.operations:
                   if isinstance(func, func_d.FuncOp) and func.name.value == name:
                       return func
      @@ -320,7 +323,7 @@ 

      Source code for allo.customize

                   raise RuntimeError(f"Function {name} not found")
               return None
       
      -    def _get_func_and_axis(self, axis):
      +    def _get_func_and_axis(self, axis):
               if isinstance(axis, LoopWrapper):
                   func = self._find_function(axis.func)
                   return func, axis
      @@ -334,7 +337,7 @@ 

      Source code for allo.customize

       
      [docs] @wrapped_apply - def split(self, axis, factor): + def split(self, axis, factor): """ `split` will find the loop with loop index `axis` and tile it with each tile size `factor` The new inner loop will be named `axis.inner` and the outer loop will be named `axis.outer` @@ -360,7 +363,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def reorder(self, *args): + def reorder(self, *args): """ Reorders nested loops with indices listed in `args` such that the outermost loop is the first index listed in `args`, the second is the second outermost, and so on. @@ -385,7 +388,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def unroll(self, axis, factor=0): + def unroll(self, axis, factor=0): """ Unrolls a loop with loop index `axis` by `factor`. @@ -411,7 +414,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def fuse(self, *args): + def fuse(self, *args): """ Combines loops with indices listed in `args` into a single loop over a single index. @@ -435,7 +438,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def partition(self, target, partition_type=Partition.Complete, dim=0, factor=0): + def partition(self, target, partition_type=Partition.Complete, dim=0, factor=0): """ Partitions a given array, for example if the array is `B`, this would be `<schedule>.B`. There are three types, `Partition.Complete`, `Partition.Block`, and `Partition.cyclic`. @@ -464,14 +467,15 @@

      Source code for allo.customize

                   raise AlloValueError("Invalid dimension")
               if factor < 0:
                   raise AlloValueError("Invalid factor")
      -        if partition_type == Partition.Complete:
      -            partition_type = 0
      -        elif partition_type == Partition.Block:
      -            partition_type = 1
      -        elif partition_type == Partition.Cyclic:
      -            partition_type = 2
      -        else:
      -            raise AlloValueError("Not supported partition type")
      +        match partition_type:
      +            case Partition.Complete:
      +                partition_type = 0
      +            case Partition.Block:
      +                partition_type = 1
      +            case Partition.Cyclic:
      +                partition_type = 2
      +            case _:
      +                raise AlloValueError("Not supported partition type")
               # test whether partitioning the same array
               for parray, items in self.partitioned_arrays.items():
                   for item in items:
      @@ -492,15 +496,23 @@ 

      Source code for allo.customize

               visited_target_names = []
               visited_func_calls = []
       
      -        def recursive_partition(inner_target):
      +        def recursive_partition(inner_target):
                   name = f"{inner_target.func}:{inner_target.name}"
                   if name in visited_target_names:
                       return
                   visited_target_names.append(name)
                   _, _, mlir_target = find_buffer(self.module, inner_target, self.func_args)
                   # equivalent users
      -            for tensor in self.use_def_chain.get_equivalent_tensors(name):
      -                recursive_partition(MockBuffer(tensor.path, tensor.name))
      +            if inner_target.name in self.func_args[inner_target.func]:
      +                # is a function argument
      +                idx = self.func_args[inner_target.func].index(inner_target.name)
      +                name = f"{inner_target.func}:{idx}"
      +            for buf_name in self.get_equivalent_variables(name):
      +                path, buf_name = buf_name.split(":")
      +                if buf_name.isdigit():
      +                    # function argument
      +                    buf_name = self.func_args[path][int(buf_name)]
      +                recursive_partition(MockBuffer(path, buf_name))
                   # calling the same function
                   if isinstance(mlir_target, func_d.CallOp):
                       visited_func_calls.append(mlir_target)
      @@ -590,7 +602,7 @@ 

      Source code for allo.customize

       
      [docs] @wrapped_apply - def buffer_at(self, target, axis): + def buffer_at(self, target, axis): """ Creates a chip buffer to hold the values of `target` written to in loop with index `axis` instead of immediately writing them to memory. @@ -617,7 +629,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def reshape(self, target, shape): + def reshape(self, target, shape): """ Takes an array in the kernel, `target`, for example if the array is `B`, then would be `target` would be `<schedule>.B`, and reshapes it to tuple `shape`. As an example, if the desired shape is 32 by 4 by 8, the `<shape>` would be `(32, 4, 8)`. @@ -639,7 +651,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def pipeline(self, axis, initiation_interval=1, rewind=False): + def pipeline(self, axis, initiation_interval=1, rewind=False): """ Pipelines a loop with index `axis` into `initiation_interval` stages. @@ -670,7 +682,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def parallel(self, axis): + def parallel(self, axis): """ Instantiates a loop with index `axis` to be computed in parallel with the loops it is nested with. @@ -691,7 +703,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def inline(self, axis=None): + def inline(self, axis=None): """ Inlines a function `axis`. @@ -711,7 +723,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def dataflow(self, axis): + def dataflow(self, axis): """ Applies a "dataflow" attribute to function `axis`. This allows for parallelism if the given function uses streams or the `to` schedule. @@ -731,7 +743,7 @@

      Source code for allo.customize

               band_name = band_name.split(":")[1]
               cnt = 0
       
      -        def locate_loop(op):
      +        def locate_loop(op):
                   nonlocal cnt
                   for ope in op.body.operations:
                       if isinstance(ope, (scf_d.ForOp, affine_d.AffineForOp)):
      @@ -758,7 +770,7 @@ 

      Source code for allo.customize

       
      [docs] @wrapped_apply - def compute_at(self, from_loop, target_loop): + def compute_at(self, from_loop, target_loop): """ If `from_loop` and `target_loop` are indices over the same range, `<schedule>.compute_at(from_loop, target_loop)` merges the two loops, taking the body of `from_loop` and appending it to the body of `target_loop`. @@ -787,7 +799,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def reuse_at(self, target, axis): + def reuse_at(self, target, axis): """ Takes an array in a kernel, for example if the array is `B`, this would be `<schedule>.B`, accessed by index `axis` and creates a reuse buffer to reuse values from `target` which are accessed in a sequentially moving window. @@ -809,7 +821,7 @@

      Source code for allo.customize

               loop_hdl = allo_d.CreateLoopHandleOp(op_hdl.result, StringAttr.get(axis), ip=ip)
               memref_type = MemRefType.get((1,), F32Type.get())
       
      -        def find_reuse_buffers(res):
      +        def find_reuse_buffers(res):
                   for func in self.module.body.operations:
                       if isinstance(func, func_d.FuncOp):
                           for op in func.entry_block.operations:
      @@ -841,7 +853,7 @@ 

      Source code for allo.customize

       
      [docs] @wrapped_apply - def to(self, target, dst, axis=None, depth=-1): + def to(self, target, dst, axis=None, depth=-1): """ Takes an array in the kernel, `target`, for example if the array is `B`, this would be `target` would be `<schedule>.B`, and converts it into a stream. `dst` is the name of the array any value of `target` is written to. @@ -871,7 +883,7 @@

      Source code for allo.customize

       
      [docs] @wrapped_apply - def unfold(self, band_name, axes): + def unfold(self, band_name, axes): """ Finds a set of nested loops with name `band_name` and for every `<i>` in list `axes`. The `<i>th` nested loop is unfolded into a constant number of copies of it's loop body. @@ -920,7 +932,7 @@

      Source code for allo.customize

                       if isinstance(op, affine_d.AffineYieldOp):
                           break
       
      -            def update_operand(op, old, new):
      +            def update_operand(op, old, new):
                       if isinstance(op, affine_d.AffineForOp):
                           # pylint: disable=cell-var-from-loop
                           for in_op in op.body.operations:
      @@ -974,7 +986,7 @@ 

      Source code for allo.customize

       
      [docs] @wrapped_apply - def compose(self, schs: list, id=None, instantiate=None): + def compose(self, schs: list, id=None, instantiate=None): """ Uses `schs`, a schedule for a kernel called in this kernel, in this kernel. @@ -995,7 +1007,7 @@

      Source code for allo.customize

                   This is a list of objects used to instantiate types `schs` is generic over.
               """
       
      -        def get_name(arg):
      +        def get_name(arg):
                   if isinstance(arg, (LoopWrapper, MockBuffer)):
                       arg = copy.copy(arg)
                       orig_func_name = arg.func if arg.func is not None else sch.top_func_name
      @@ -1073,7 +1085,14 @@ 

      Source code for allo.customize

                           self.primitive_sequences.append((primitive[0], args, kwargs))
      - def build(self, target=None, mode=None, project=None, configs=None): + def get_equivalent_variables(self, name): + use_def = analyze_use_def(self.module) + for ele in use_def: + if name in ele: + return ele + return [] + + def build(self, target=None, mode=None, project=None, configs=None, wrap_io=True): if target is None or target == "llvm": target = "llvm" return LLVMModule( @@ -1081,22 +1100,32 @@

      Source code for allo.customize

                       top_func_name=self.top_func_name,
                       ext_libs=self.ext_libs,
                   )
      -        if target in {"vhls", "vivado_hls", "vitis_hls"}:
      +        if target in {"vhls", "vivado_hls", "vitis_hls", "tapa", "ihls"}:
      +            match target:
      +                case "vitis_hls":
      +                    platform = "vitis_hls"
      +                case "tapa":
      +                    platform = "tapa"
      +                case "ihls":
      +                    platform = "intel_hls"
      +                case _:
      +                    platform = "vivado_hls"
                   return HLSModule(
                       self.module,
                       top_func_name=self.top_func_name,
      -                platform="vivado_hls" if target != "vitis_hls" else "vitis_hls",
      +                platform=platform,
                       mode=mode,
                       project=project,
                       ext_libs=self.ext_libs,
                       configs=configs,
                       func_args=self.func_args,
      +                wrap_io=wrap_io,
                   )
               raise NotImplementedError(f"Target {target} is not supported")
      -def customize( +def customize( fn: Union[Callable, str], verbose: bool = False, enable_tensor: bool = False, @@ -1107,37 +1136,36 @@

      Source code for allo.customize

       ):
           # Get Python AST
           if isinstance(fn, str):
      -        src = fn
      +        src, starting_line_no = fn, 1
           else:
      -        src, _ = getsourcelines(fn)
      +        src, starting_line_no = inspect.getsourcelines(fn)
               src = [textwrap.fill(line, tabsize=4, width=9999) for line in src]
               src = textwrap.dedent("\n".join(src))
      -    tree = parse_ast(src, verbose)
      +    tree = parse_ast(src, starting_line_no=starting_line_no, verbose=verbose)
           if instantiate is None:
               instantiate = []
           if global_vars is None:
               global_vars = get_global_vars(fn)
      -    # Use-def chain analysis
      -    use_def_chain = UseDefChain(global_vars.copy(), instantiate)
      -    use_def_chain.visit(tree)
           # Type construction
           ctx_type_inf = ASTContext(
      +        tree=tree,
               global_vars=global_vars.copy(),
               mlir_ctx=Context() if context is None else context,
      +        inst=instantiate,
               enable_tensor=enable_tensor,
               verbose=verbose,
           )
      -    ctx_type_inf.inst = instantiate
           tree = TypeInferer()(ctx_type_inf, tree)
           ctx_type_inf = None
           # Start building IR
           ctx = ASTContext(
      +        tree=tree,
               global_vars=global_vars,
               mlir_ctx=Context() if context is None else context,
      +        inst=instantiate,
               enable_tensor=enable_tensor,
               verbose=verbose,
           )
      -    ctx.inst = instantiate
           module = ASTTransformer()(ctx, tree)
           if lower_linalg:
               lower_linalg_and_attach_names(module)
      @@ -1148,7 +1176,6 @@ 

      Source code for allo.customize

               ctx.func_args,
               InsertionPoint.at_block_terminator(ctx.top_func.entry_block),
               ext_libs=ctx.ext_libs,
      -        use_def_chain=use_def_chain,
               inst_list=instantiate,
           )
           # Attach buffers to schedule:
      @@ -1203,7 +1230,7 @@ 

      Source code for allo.customize

       
       
           
       
      diff --git a/_modules/index.html b/_modules/index.html
      index 52084588..38110646 100644
      --- a/_modules/index.html
      +++ b/_modules/index.html
      @@ -5,7 +5,7 @@
           
           
           Overview: module code — Allo Documentation
      -    
      +    
           
           
           
      @@ -140,6 +140,15 @@ 

      Quick search

    • Getting Started
    • Vivado/Vitis HLS Backend
    +

    Deep Dive

    +

    Developer Guide

    • Developer Setup
    • @@ -187,7 +196,7 @@

      All modules for which code is available

      diff --git a/_sources/dive/ip.rst.txt b/_sources/dive/ip.rst.txt new file mode 100644 index 00000000..6d4fab85 --- /dev/null +++ b/_sources/dive/ip.rst.txt @@ -0,0 +1,88 @@ +.. Copyright Allo authors. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 + +.. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + +.. http://www.apache.org/licenses/LICENSE-2.0 + +.. Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +############## +IP Integration +############## + +Apart from directly writing Allo kernels in Python, we also support integrating existing C++ HLS kernels into Allo. This feature is useful when you have a existing optimized C++ HLS code that wants to be integrated into Allo. The following example shows how to integrate a simple vector addition kernel written in C++ into Allo. + +Suppose the C++ kernel header is defined in the ``vadd.h`` file: + +.. code-block:: cpp + + #ifndef VADD_H + #define VADD_H + + void vadd(int A[32], int B[32], int C[32]); + + #endif // VADD_H + +And the corresponding implementation is defined in the ``vadd.cpp`` file: + +.. code-block:: cpp + + #include "vadd.h" + using namespace std; + + void vadd(int A[32], int B[32], int C[32]) { + for (int i = 0; i < 32; ++i) { + C[i] = A[i] + B[i]; + } + } + +In Allo, we can create an *IP module* to wrap the C++ kernel. Basically, we need to provide the top-level function name, the header files, and the implementation files. Also, currently an Allo signature is required to specify the input and output types of the kernel. Allo will automatically compile the C++ kernel and generate the corresponding Python wrapper based on the provided files and signature. The last argument ``link_hls`` determines whether the C++ compiler should link the Vitis HLS libraries (e.g., ``ap_int``), which is only available when your machine has installed Vitis HLS. + +.. code-block:: python + + vadd = allo.IPModule( + top="vadd", + headers=["vadd.h"], + impls=["vadd.cpp"], + signature=["int32[32]", "int32[32]", "int32[32]"], + link_hls=False, + ) + +After creating the IP module, we can use it in Allo as a normal Python function. For example, we can directly call the ``vadd`` function to perform vector addition. The inputs and outputs will be automatically wrapped and unwrapped as NumPy arrays, which greatly simplies the burden of complex C-Python interface management. This is also very useful when you want to debug the HLS kernels with the Python data. + +.. code-block:: python + + np_A = np.random.randint(0, 100, (32,)).astype(np.int32) + np_B = np.random.randint(0, 100, (32,)).astype(np.int32) + np_C = np.zeros((32,), dtype=np.int32) + vadd(np_A, np_B, np_C) + np.testing.assert_allclose(np_A + np_B, np_C, atol=1e-6) + +Moreover, the IP module can also be called in a normal Allo kernel. In the following example, we wrap the ``vadd`` function into an Allo ``kernel`` and use it to perform vector addition. The Allo kernel can then be further customized and compiled with the external C++ HLS kernel. + +.. code-block:: python + + def kernel(A: int32[32], B: int32[32]) -> int32[32]: + C: int32[32] = 0 + vadd(A, B, C) + return C + + s = allo.customize(kernel) + print(s.module) + mod = s.build() + np_A = np.random.randint(0, 100, (32,)).astype(np.int32) + np_B = np.random.randint(0, 100, (32,)).astype(np.int32) + allo_C = mod(np_A, np_B) + np.testing.assert_allclose(np_A + np_B, allo_C, atol=1e-6) diff --git a/_sources/dive/pytorch.rst.txt b/_sources/dive/pytorch.rst.txt new file mode 100644 index 00000000..9076dd06 --- /dev/null +++ b/_sources/dive/pytorch.rst.txt @@ -0,0 +1,70 @@ +.. Copyright Allo authors. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 + +.. Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + +.. http://www.apache.org/licenses/LICENSE-2.0 + +.. Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +################### +PyTorch Integration +################### + +In this document, we will show how to directly compile PyTorch models to Allo. +First, users can define a PyTorch module as usual: + +.. code-block:: python + + import torch + import torch.nn.functional as F + import torch.nn as nn + + class Model(nn.Module): + def __init__(self): + super(Model, self).__init__() + + def forward(self, x, y): + x = x + y + x = F.relu(x) + return x + + model = Model() + model.eval() + +Then, users can compile the PyTorch model to Allo by using the ``allo.frontend.from_pytorch`` API: + +.. code-block:: python + + import allo + example_inputs = [torch.rand(1, 3, 10, 10), torch.rand(1, 3, 10, 10)] + llvm_mod = allo.frontend.from_pytorch(model, example_inputs=example_inputs) + +Then, we can use the generated Allo LLVM module as usual by passing in the NumPy inputs: + +.. code-block:: python + + golden = model(*example_inputs) + np_inputs = [x.detach().numpy() for x in example_inputs] + res = llvm_mod(*np_inputs) + torch.testing.assert_close(res, golden.detach().numpy()) + print("Passed!") + +The process should be very similar to the original Allo workflow. +The default target is LLVM. We can also change the backend to other compilers such as Vitis HLS by specifying the ``target``: + +.. code-block:: python + + mod = allo.frontend.from_pytorch(model, example_inputs=example_inputs, target="vhls") + print(mod.hls_code) diff --git a/_sources/gallery/developer_02_mlir.rst.txt b/_sources/gallery/developer_02_mlir.rst.txt index 08f9e222..35a548b1 100644 --- a/_sources/gallery/developer_02_mlir.rst.txt +++ b/_sources/gallery/developer_02_mlir.rst.txt @@ -331,7 +331,7 @@ the lowering pass from tensor dialect to LLVM dialect, and that is something we .. rst-class:: sphx-glr-timing - **Total running time of the script:** (0 minutes 0.074 seconds) + **Total running time of the script:** (0 minutes 0.070 seconds) .. _sphx_glr_download_gallery_developer_02_mlir.py: diff --git a/_sources/gallery/dive_01_data_types.rst.txt b/_sources/gallery/dive_01_data_types.rst.txt new file mode 100644 index 00000000..ed9b15aa --- /dev/null +++ b/_sources/gallery/dive_01_data_types.rst.txt @@ -0,0 +1,266 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "gallery/dive_01_data_types.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_gallery_dive_01_data_types.py: + + +Data Types and Type Casting +=========================== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss the Allo-supported data types in detail. +All the data types are defined in the ``allo.ir.types`` module. + +.. GENERATED FROM PYTHON SOURCE LINES 13-17 + +.. code-block:: Python + + + import allo + from allo.ir.types import int16, int32, float32, Int, UInt, Float, Fixed + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 18-26 + +Currently, Allo supports three base data types for mathematical operations: + +- Integers: ``Int(bitwdith)``, ``UInt(bitwidth)`` +- Floating points: ``Float(bitwidth)`` (only support 16, 32, and 64 bits) +- Fixed points: ``Fixed(bitwidth, frac)``, ``UFixed(bitwidth, frac)`` + +For example, one can declare a 15-bit integer as ``Int(15)`` and an unsigned 8-bit fixed-point number with 3 fractional bits as ``UFixed(8, 3)``. +For all the C/C++ supported data types, we provide shorthands like ``float32`` and ``int16`` to easily declare them. + +.. GENERATED FROM PYTHON SOURCE LINES 28-31 + +Notice different from native Python, Allo requires the program to be **strongly and statically typed**. +The variable types are either declared explicitly or inferred from the context. +For a variable that first appears in the program, we should declare it with an expected data type using Python's type hint notation: + +.. GENERATED FROM PYTHON SOURCE LINES 31-34 + +.. code-block:: Python + + + a: int32 + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 35-39 + +Once the data types are defined, an important consideration is how to handle +operations between variables of different types. Allo supports two types of casting: +(1) implicit casting that is automatically done by the Allo compiler; +and (2) explicit casting that is manually done by the user. + +.. GENERATED FROM PYTHON SOURCE LINES 41-47 + +Implicit Casting +---------------- +Allo has a strong type system that follows the `MLIR convention `_ to enforce the operand types are the same for the arithmetic operations. +However, it is burdensome for users to cast the variables every time, and it is also error-prone to avoid overflow when performing computations. +Therefore, Allo is equipped with builtin casting rules to automatically cast the variables to the same type before the operation, which is called *implicit casting*. +An example is shown below: + +.. GENERATED FROM PYTHON SOURCE LINES 47-56 + +.. code-block:: Python + + + + def add(a: int32, b: int32) -> int32: + return a + b + + + s = allo.customize(add) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @add(%arg0: i32, %arg1: i32) -> i32 attributes {itypes = "ss", otypes = "s"} { + %0 = arith.extsi %arg0 : i32 to i33 + %1 = arith.extsi %arg1 : i32 to i33 + %2 = arith.addi %0, %1 : i33 + %3 = arith.trunci %2 : i33 to i32 + return %3 : i32 + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 57-60 + +We can see that ``a`` and ``b`` are firstly casted to ``int33``, added +together, and converted back to ``int32``. +This is to avoid overflow and is automatically inferred by the Allo compiler. + +.. GENERATED FROM PYTHON SOURCE LINES 63-68 + +Explicit Casting +---------------- +One can also explicitly cast the variable to a specific type by creating an intermediate variable, +or use Python-builtin functions like ``float()`` and ``int()`` to explicitly cast a variable to ``float32`` or ``int32``. +Another example is shown below: + +.. GENERATED FROM PYTHON SOURCE LINES 68-81 + +.. code-block:: Python + + + + def cast(a: int32) -> int16: + b: float32 = a # explicit + c: float32 = b * 2 + d: float32 = float(a) * 2 + e: int16 = c + d + return e + + + s = allo.customize(cast) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @cast(%arg0: i32) -> i16 attributes {itypes = "s", otypes = "s"} { + %0 = arith.sitofp %arg0 : i32 to f32 + %alloc = memref.alloc() {name = "b"} : memref + affine.store %0, %alloc[] {to = "b"} : memref + %c2_i32 = arith.constant 2 : i32 + %1 = arith.sitofp %c2_i32 : i32 to f32 + %2 = affine.load %alloc[] {from = "b"} : memref + %3 = arith.mulf %2, %1 : f32 + %alloc_0 = memref.alloc() {name = "c"} : memref + affine.store %3, %alloc_0[] {to = "c"} : memref + %4 = arith.sitofp %arg0 : i32 to f32 + %c2_i32_1 = arith.constant 2 : i32 + %5 = arith.sitofp %c2_i32_1 : i32 to f32 + %6 = arith.mulf %4, %5 : f32 + %alloc_2 = memref.alloc() {name = "d"} : memref + affine.store %6, %alloc_2[] {to = "d"} : memref + %7 = affine.load %alloc_0[] {from = "c"} : memref + %8 = affine.load %alloc_2[] {from = "d"} : memref + %9 = arith.addf %7, %8 : f32 + %10 = arith.fptosi %9 : f32 to i16 + %alloc_3 = memref.alloc() {name = "e"} : memref + affine.store %10, %alloc_3[] {to = "e"} : memref + %11 = affine.load %alloc_3[] {from = "e"} : memref + %12 = affine.load %alloc_3[] {from = "e"} : memref + return %12 : i16 + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 82-89 + +By explicitly creating an intermediate variable ``b``, we can cast the ``int32`` variable ``a`` to the desired floating-point type. +Similarly, calling ``float(a)`` can also cast ``a`` to a floating-point type. + +.. note:: + + The above stated explicit casting between integers and floating points preserves the value but the precision may be changed. + If you want to use a union type to represent both integers and floating points, please use the `.bitcast()` API instead. For example, ``a.bitcast()`` can convert ``int32`` to ``float32`` representation with the bit pattern preserved. + +.. GENERATED FROM PYTHON SOURCE LINES 91-99 + +Bit Operations +-------------- +As hardware accelerators have ability to manipulate each bit of the data, Allo supports bit operations on +those integer types. For example, we can access a specific bit in an integer ``a`` using the indexing operator: + +.. code-block:: python + + a[15] + +.. GENERATED FROM PYTHON SOURCE LINES 101-112 + +We can also extract a chunk of bits from an integer using the slicing operator: + +.. code-block:: python + + a[0:16] + +.. note:: + + Allo follows the Python convention that the upper bound is not included, so ``[0:16]`` means + extracting the first 16 bits, which is different from the Xilinx HLS convention that uses ``[0:15]`` + to indicate the first 16 bits. + +.. GENERATED FROM PYTHON SOURCE LINES 114-115 + +Not only constant values are supported, but also variables can be used as the index or the slice range. + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.170 seconds) + + +.. _sphx_glr_download_gallery_dive_01_data_types.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: dive_01_data_types.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: dive_01_data_types.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: dive_01_data_types.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/gallery/dive_02_template.rst.txt b/_sources/gallery/dive_02_template.rst.txt new file mode 100644 index 00000000..1bed2753 --- /dev/null +++ b/_sources/gallery/dive_02_template.rst.txt @@ -0,0 +1,300 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "gallery/dive_02_template.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_gallery_dive_02_template.py: + + +Template Kernels +================ + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document explains how to write a template kernel in Allo. +Template kernels are useful when we need to reuse a kernel with different data types or when certain computation patterns depend on specific constants. +By leveraging template kernels, we can achieve greater flexibility and reusability in the code. + +.. GENERATED FROM PYTHON SOURCE LINES 14-18 + +.. code-block:: Python + + + import allo + from allo.ir.types import int32, float32 + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 19-26 + +We follow Python's convention to use *type variable* to define a template kernel. +Specifically, the type variable is specified after the function name using square brackets: ``def kernel[T](...)``, and the type variable can be used in the function signature and body. +Importantly, as the native Python interpreter does not support Allo's type declaration (i.e., base type + shape), we need to use string annotations like ``"T[10]"`` to specify the type of the variables. +Otherwise, it will raise a type error. + +In the following, we define a simple addition function that adds 1 to each element of the input array. +To invoke the kernel with a specific data type, we can use the ``instantiate`` argument in the ``allo.customize`` function. + +.. GENERATED FROM PYTHON SOURCE LINES 26-38 + +.. code-block:: Python + + + + def kernel[T](A: "T[10]") -> "T[10]": + B: T[10] + for i in range(10): + B[i] = A[i] + 1 + return B + + + s = allo.customize(kernel, instantiate=[int32]) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel(%arg0: memref<10xi32>) -> memref<10xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<10xi32> + affine.for %arg1 = 0 to 10 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<10xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1] {to = "B"} : memref<10xi32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc : memref<10xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 39-41 + +We can see that the kernel is specialized with the given ``int32`` data type. +Similarly, we can directly declare a new kernel by specifying ``float32`` as the data type. + +.. GENERATED FROM PYTHON SOURCE LINES 41-45 + +.. code-block:: Python + + + s = allo.customize(kernel, instantiate=[float32]) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel(%arg0: memref<10xf32>) -> memref<10xf32> attributes {itypes = "_", otypes = "_"} { + %alloc = memref.alloc() {name = "B"} : memref<10xf32> + affine.for %arg1 = 0 to 10 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<10xf32> + %c1_i32 = arith.constant 1 : i32 + %1 = arith.sitofp %c1_i32 : i32 to f32 + %2 = arith.addf %0, %1 : f32 + affine.store %2, %alloc[%arg1] {to = "B"} : memref<10xf32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc : memref<10xf32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 46-48 + +If we not only want to specialize the data type but also the shape of the array, we can provide another type variable, and pass it to the ``instantiate`` argument. +Note that here we also use the ``: base_type`` notation to constrain the type of the type variable. Here we constrain the type variable ``M`` to be an integer. + +.. GENERATED FROM PYTHON SOURCE LINES 48-60 + +.. code-block:: Python + + + + def kernel2[T, M: int32](A: "T[M]") -> "T[M]": + B: T[M] + for i in range(M): + B[i] = A[i] + 1 + return B + + + s = allo.customize(kernel2, instantiate=[int32, 20]) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel2(%arg0: memref<20xi32>) -> memref<20xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<20xi32> + affine.for %arg1 = 0 to 20 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<20xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1] {to = "B"} : memref<20xi32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc : memref<20xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 61-64 + +Furthermore, Allo's template also enables metaprogramming that can evaluate type variables at compile time. +Specifically, we can use the ``allo.meta_if``, ``allo.meta_elif``, and ``allo.meta_else`` to conditionally generate code based on the type variables. +Just to make sure the conditions can be determined at compile time. + +.. GENERATED FROM PYTHON SOURCE LINES 64-76 + +.. code-block:: Python + + + + def kernel3[T, M: int32](A: "T[M]") -> "T[M]": + B: T[M] + for i in range(M): + with allo.meta_if(T == int32): + B[i] = A[i] + 1 + with allo.meta_else(): + B[i] = A[i] - 1 + return B + + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 77-78 + +In final generated code, we can see that only a single branch is generated based on the given data type. + +.. GENERATED FROM PYTHON SOURCE LINES 78-83 + +.. code-block:: Python + + + s = allo.customize(kernel3, instantiate=[int32, 20]) + print(s.module) + s = allo.customize(kernel3, instantiate=[float32, 20]) + print(s.module) + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel3(%arg0: memref<20xi32>) -> memref<20xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<20xi32> + affine.for %arg1 = 0 to 20 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<20xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1] {to = "B"} : memref<20xi32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc : memref<20xi32> + } + } + + module { + func.func @kernel3(%arg0: memref<20xf32>) -> memref<20xf32> attributes {itypes = "_", otypes = "_"} { + %alloc = memref.alloc() {name = "B"} : memref<20xf32> + affine.for %arg1 = 0 to 20 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<20xf32> + %c1_i32 = arith.constant 1 : i32 + %1 = arith.sitofp %c1_i32 : i32 to f32 + %2 = arith.subf %0, %1 : f32 + affine.store %2, %alloc[%arg1] {to = "B"} : memref<20xf32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc : memref<20xf32> + } + } + + + + + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.372 seconds) + + +.. _sphx_glr_download_gallery_dive_02_template.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: dive_02_template.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: dive_02_template.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: dive_02_template.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/gallery/dive_03_composition.rst.txt b/_sources/gallery/dive_03_composition.rst.txt new file mode 100644 index 00000000..018b0fa8 --- /dev/null +++ b/_sources/gallery/dive_03_composition.rst.txt @@ -0,0 +1,529 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "gallery/dive_03_composition.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_gallery_dive_03_composition.py: + + +Kernel Composition +================== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss kernel composition. +In the previous tutorials, we have seen how to write a simple kernel. +However, in real applications, we often need to compose multiple kernels together. + +In the following example, we define a ``matrix_add`` and a ``gemm`` kernel, and wrap them into a ``top``-level function. + +.. GENERATED FROM PYTHON SOURCE LINES 16-44 + +.. code-block:: Python + + + import allo + from allo.ir.types import int32, float32 + + M, K, N = 32, 32, 32 + + + def matrix_add(A: int32[M, N]) -> int32[M, N]: + B: int32[M, N] = 0 + for i, j in allo.grid(M, N): + B[i, j] = A[i, j] + 1 + return B + + + def gemm(A: int32[M, K], B: int32[K, N]) -> int32[M, N]: + C: int32[M, N] = 0 + for i, j in allo.grid(M, N): + for k in allo.reduction(K): + C[i, j] += A[i, k] * B[k, j] + return C + + + def top(A: int32[M, K], B: int32[K, N]) -> int32[M, N]: + C = gemm(A, B) + D = matrix_add(C) + return D + + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 45-47 + +Different teams or people can then work on different parts of the code and optimize each kernel. +We first create a schedule for the ``matrix_add`` kernel, and add several optimizations. + +.. GENERATED FROM PYTHON SOURCE LINES 47-52 + +.. code-block:: Python + + + s1 = allo.customize(matrix_add) + s1.pipeline("j") + print(s1.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @matrix_add(%arg0: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg1 = 0 to 32 { + affine.for %arg2 = 0 to 32 { + %0 = affine.load %arg0[%arg1, %arg2] {from = "A"} : memref<32x32xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1, %arg2] {to = "B"} : memref<32x32xi32> + } {loop_name = "j", pipeline_ii = 1 : ui32} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 53-54 + +Then we create a schedule for the ``gemm`` kernel and optimize it. + +.. GENERATED FROM PYTHON SOURCE LINES 54-61 + +.. code-block:: Python + + + s2 = allo.customize(gemm) + s2.reorder("k", "j") + s2.buffer_at(s2.C, axis="i") + s2.pipeline("j") + print(s2.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @gemm(%arg0: memref<32x32xi32>, %arg1: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "ss", otypes = "s"} { + %alloc = memref.alloc() {name = "C"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg2 = 0 to 32 { + %alloc_0 = memref.alloc() : memref<32xi32> + affine.for %arg3 = 0 to 32 { + affine.store %c0_i32, %alloc_0[%arg3] : memref<32xi32> + } {buffer, loop_name = "j_init", pipeline_ii = 1 : i32} + affine.for %arg3 = 0 to 32 { + affine.for %arg4 = 0 to 32 { + %0 = affine.load %arg0[%arg2, %arg3] {from = "A"} : memref<32x32xi32> + %1 = affine.load %arg1[%arg3, %arg4] {from = "B"} : memref<32x32xi32> + %2 = arith.extsi %0 : i32 to i64 + %3 = arith.extsi %1 : i32 to i64 + %4 = arith.muli %2, %3 : i64 + %5 = affine.load %alloc_0[%arg4] : memref<32xi32> + %6 = arith.trunci %4 : i64 to i32 + %7 = arith.addi %5, %6 : i32 + affine.store %7, %alloc_0[%arg4] : memref<32xi32> + } {loop_name = "j", pipeline_ii = 1 : ui32} + } {loop_name = "k", op_name = "S_k_0", reduction} + affine.for %arg3 = 0 to 32 { + %0 = affine.load %alloc_0[%arg3] : memref<32xi32> + affine.store %0, %alloc[%arg2, %arg3] : memref<32x32xi32> + } {buffer, loop_name = "j_back", pipeline_ii = 1 : i32} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 62-63 + +Notice that now we only optimize the separate kernels but do not incorporate them into the top-level function, as shown in the following printed module. + +.. GENERATED FROM PYTHON SOURCE LINES 63-67 + +.. code-block:: Python + + + s = allo.customize(top) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @gemm(%arg0: memref<32x32xi32>, %arg1: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "ss", otypes = "s"} { + %alloc = memref.alloc() {name = "C"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg2 = 0 to 32 { + affine.for %arg3 = 0 to 32 { + affine.for %arg4 = 0 to 32 { + %0 = affine.load %arg0[%arg2, %arg4] {from = "A"} : memref<32x32xi32> + %1 = affine.load %arg1[%arg4, %arg3] {from = "B"} : memref<32x32xi32> + %2 = arith.extsi %0 : i32 to i64 + %3 = arith.extsi %1 : i32 to i64 + %4 = arith.muli %2, %3 : i64 + %5 = affine.load %alloc[%arg2, %arg3] {from = "C"} : memref<32x32xi32> + %6 = arith.trunci %4 : i64 to i32 + %7 = arith.addi %5, %6 : i32 + affine.store %7, %alloc[%arg2, %arg3] {to = "C"} : memref<32x32xi32> + } {loop_name = "k", op_name = "S_k_0", reduction} + } {loop_name = "j"} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + func.func @matrix_add(%arg0: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg1 = 0 to 32 { + affine.for %arg2 = 0 to 32 { + %0 = affine.load %arg0[%arg1, %arg2] {from = "A"} : memref<32x32xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1, %arg2] {to = "B"} : memref<32x32xi32> + } {loop_name = "j"} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + func.func @top(%arg0: memref<32x32xi32>, %arg1: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "ss", otypes = "s"} { + %0 = call @gemm(%arg0, %arg1) {name = "C"} : (memref<32x32xi32>, memref<32x32xi32>) -> memref<32x32xi32> + %1 = call @matrix_add(%0) {name = "D"} : (memref<32x32xi32>) -> memref<32x32xi32> + return %1 : memref<32x32xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 68-70 + +Therefore, after each part has been optimized, we need to explicitly *compose* them together. +In Allo, we can use the ``.compose()`` primitive to compose the schedules together into the parent function. + +.. GENERATED FROM PYTHON SOURCE LINES 70-74 + +.. code-block:: Python + + + s.compose([s1, s2]) + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @gemm(%arg0: memref<32x32xi32>, %arg1: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "ss", otypes = "s"} { + %alloc = memref.alloc() {name = "C"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg2 = 0 to 32 { + %alloc_0 = memref.alloc() : memref<32xi32> + affine.for %arg3 = 0 to 32 { + affine.store %c0_i32, %alloc_0[%arg3] : memref<32xi32> + } {buffer, loop_name = "j_init", pipeline_ii = 1 : i32} + affine.for %arg3 = 0 to 32 { + affine.for %arg4 = 0 to 32 { + %0 = affine.load %arg0[%arg2, %arg3] {from = "A"} : memref<32x32xi32> + %1 = affine.load %arg1[%arg3, %arg4] {from = "B"} : memref<32x32xi32> + %2 = arith.extsi %0 : i32 to i64 + %3 = arith.extsi %1 : i32 to i64 + %4 = arith.muli %2, %3 : i64 + %5 = affine.load %alloc_0[%arg4] : memref<32xi32> + %6 = arith.trunci %4 : i64 to i32 + %7 = arith.addi %5, %6 : i32 + affine.store %7, %alloc_0[%arg4] : memref<32xi32> + } {loop_name = "j", pipeline_ii = 1 : ui32} + } {loop_name = "k", op_name = "S_k_0", reduction} + affine.for %arg3 = 0 to 32 { + %0 = affine.load %alloc_0[%arg3] : memref<32xi32> + affine.store %0, %alloc[%arg2, %arg3] : memref<32x32xi32> + } {buffer, loop_name = "j_back", pipeline_ii = 1 : i32} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + func.func @matrix_add(%arg0: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<32x32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32x32xi32>) + affine.for %arg1 = 0 to 32 { + affine.for %arg2 = 0 to 32 { + %0 = affine.load %arg0[%arg1, %arg2] {from = "A"} : memref<32x32xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1, %arg2] {to = "B"} : memref<32x32xi32> + } {loop_name = "j", pipeline_ii = 1 : ui32} + } {loop_name = "i", op_name = "S_i_j_0"} + return %alloc : memref<32x32xi32> + } + func.func @top(%arg0: memref<32x32xi32>, %arg1: memref<32x32xi32>) -> memref<32x32xi32> attributes {itypes = "ss", otypes = "s"} { + %0 = call @gemm(%arg0, %arg1) {name = "C"} : (memref<32x32xi32>, memref<32x32xi32>) -> memref<32x32xi32> + %1 = call @matrix_add(%0) {name = "D"} : (memref<32x32xi32>) -> memref<32x32xi32> + return %1 : memref<32x32xi32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 75-76 + +We can see that the schedules for the ``matrix_add`` and ``gemm`` kernels are both correctly optimized in the top-level function. + +.. GENERATED FROM PYTHON SOURCE LINES 78-81 + +Template Composition +-------------------- +Sometimes we may define template kernels and invoke the kernel with different template arguments. Allo provides an *id* option to specify the exact kernel to be composed. + +.. GENERATED FROM PYTHON SOURCE LINES 81-99 + +.. code-block:: Python + + + + def kernel[T_in, T_out, S](A: "T_in[S]") -> "T_out[S]": + B: T_out[S] = 0 + for i in range(S): + with allo.meta_if(T_out == int32): + B[i] = A[i] + 1 + with allo.meta_else(): + B[i] = A[i] * 2 + return B + + + def top2(A: int32[M]) -> float32[M]: + C = kernel[int32, int32, M, "K1"](A) + D = kernel[int32, float32, M, "K2"](C) + return D + + + + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 100-102 + +Specifically, the last argument of the template kernel is the *id* of the kernel. Later on we can use this ID for distinguishing different kernels during composition. +We also customize the two template kernels with different optimizations first. + +.. GENERATED FROM PYTHON SOURCE LINES 102-111 + +.. code-block:: Python + + + s1 = allo.customize(kernel, instantiate=[int32, int32, M]) + s1.unroll("i", factor=4) + print(s1.module) + + s2 = allo.customize(kernel, instantiate=[int32, float32, M]) + s2.pipeline("i") + print(s2.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel(%arg0: memref<32xi32>) -> memref<32xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32xi32>) + affine.for %arg1 = 0 to 32 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<32xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1] {to = "B"} : memref<32xi32> + } {loop_name = "i", op_name = "S_i_0", unroll = 4 : i32} + return %alloc : memref<32xi32> + } + } + + module { + func.func @kernel(%arg0: memref<32xi32>) -> memref<32xf32> attributes {itypes = "s", otypes = "_"} { + %c0_i32 = arith.constant 0 : i32 + %0 = arith.sitofp %c0_i32 : i32 to f32 + %alloc = memref.alloc() {name = "B"} : memref<32xf32> + linalg.fill ins(%0 : f32) outs(%alloc : memref<32xf32>) + affine.for %arg1 = 0 to 32 { + %1 = affine.load %arg0[%arg1] {from = "A"} : memref<32xi32> + %2 = arith.extsi %1 : i32 to i64 + %c2_i32 = arith.constant 2 : i32 + %3 = arith.extsi %c2_i32 : i32 to i64 + %4 = arith.muli %2, %3 : i64 + %5 = arith.sitofp %4 : i64 to f32 + affine.store %5, %alloc[%arg1] {to = "B"} : memref<32xf32> + } {loop_name = "i", op_name = "S_i_0", pipeline_ii = 1 : ui32} + return %alloc : memref<32xf32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 112-113 + +Finally, we compose the two template kernels into the top-level function with the ID specified. + +.. GENERATED FROM PYTHON SOURCE LINES 113-119 + +.. code-block:: Python + + + s = allo.customize(top2) + s.compose(s1, id="K1") + s.compose(s2, id="K2") + print(s.module) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel_K1(%arg0: memref<32xi32>) -> memref<32xi32> attributes {itypes = "s", otypes = "s"} { + %alloc = memref.alloc() {name = "B"} : memref<32xi32> + %c0_i32 = arith.constant 0 : i32 + linalg.fill ins(%c0_i32 : i32) outs(%alloc : memref<32xi32>) + affine.for %arg1 = 0 to 32 { + %0 = affine.load %arg0[%arg1] {from = "A"} : memref<32xi32> + %1 = arith.extsi %0 : i32 to i33 + %c1_i32 = arith.constant 1 : i32 + %2 = arith.extsi %c1_i32 : i32 to i33 + %3 = arith.addi %1, %2 : i33 + %4 = arith.trunci %3 : i33 to i32 + affine.store %4, %alloc[%arg1] {to = "B"} : memref<32xi32> + } {loop_name = "i", op_name = "S_i_0", unroll = 4 : i32} + return %alloc : memref<32xi32> + } + func.func @kernel_K2(%arg0: memref<32xi32>) -> memref<32xf32> attributes {itypes = "s", otypes = "_"} { + %c0_i32 = arith.constant 0 : i32 + %0 = arith.sitofp %c0_i32 : i32 to f32 + %alloc = memref.alloc() {name = "B"} : memref<32xf32> + linalg.fill ins(%0 : f32) outs(%alloc : memref<32xf32>) + affine.for %arg1 = 0 to 32 { + %1 = affine.load %arg0[%arg1] {from = "A"} : memref<32xi32> + %2 = arith.extsi %1 : i32 to i64 + %c2_i32 = arith.constant 2 : i32 + %3 = arith.extsi %c2_i32 : i32 to i64 + %4 = arith.muli %2, %3 : i64 + %5 = arith.sitofp %4 : i64 to f32 + affine.store %5, %alloc[%arg1] {to = "B"} : memref<32xf32> + } {loop_name = "i", op_name = "S_i_0", pipeline_ii = 1 : ui32} + return %alloc : memref<32xf32> + } + func.func @top2(%arg0: memref<32xi32>) -> memref<32xf32> attributes {itypes = "s", otypes = "_"} { + %0 = call @kernel_K1(%arg0) {name = "C"} : (memref<32xi32>) -> memref<32xi32> + %1 = call @kernel_K2(%0) {name = "D"} : (memref<32xi32>) -> memref<32xf32> + return %1 : memref<32xf32> + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 120-121 + +We can see from the printed module that the loop in the first kernel is unrolled by a factor of 4, and the loop in the second kernel is pipelined. + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.731 seconds) + + +.. _sphx_glr_download_gallery_dive_03_composition.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: dive_03_composition.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: dive_03_composition.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: dive_03_composition.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/gallery/dive_04_features.rst.txt b/_sources/gallery/dive_04_features.rst.txt new file mode 100644 index 00000000..b4048409 --- /dev/null +++ b/_sources/gallery/dive_04_features.rst.txt @@ -0,0 +1,250 @@ + +.. DO NOT EDIT. +.. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. +.. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: +.. "gallery/dive_04_features.py" +.. LINE NUMBERS ARE GIVEN BELOW. + +.. only:: html + + .. note:: + :class: sphx-glr-download-link-note + + :ref:`Go to the end ` + to download the full example code. + +.. rst-class:: sphx-glr-example-title + +.. _sphx_glr_gallery_dive_04_features.py: + + +Other Features +============== + +**Author**: Hongzheng Chen (hzchen@cs.cornell.edu) + +This document will discuss other features that are not covered in the previous tutorials. + +.. GENERATED FROM PYTHON SOURCE LINES 14-19 + +Dynamic Shapes +-------------- +In some cases, the shape of the tensor is not known at compile time, so we can use ``[...]`` to represent the dynamic shape. +From the generated MLIR module, we can see it has a ``"?"`` in the shape of the tensor, which means the shape is not predefined, +but we can still run the LLVM module with arbitrary shapes of NumPy arrays. + +.. GENERATED FROM PYTHON SOURCE LINES 19-38 + +.. code-block:: Python + + + import allo + from allo.ir.types import int32, float32 + import numpy as np + + + def kernel(A: float32[...], B: float32[...], size: int32): + for i in range(size): + B[i] = A[i] + + + s = allo.customize(kernel) + print(s.module) + np_A = np.random.random((256,)).astype(np.float32) + allo_A = np.zeros((256,)).astype(np.float32) + mod = s.build() + mod(np_A, allo_A, 256) + np.testing.assert_allclose(np_A, allo_A) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @kernel(%arg0: memref, %arg1: memref, %arg2: i32) attributes {itypes = "__s", otypes = ""} { + %c0_i32 = arith.constant 0 : i32 + %0 = arith.index_cast %c0_i32 : i32 to index + %1 = arith.index_cast %arg2 : i32 to index + %c1_i32 = arith.constant 1 : i32 + %2 = arith.index_cast %c1_i32 : i32 to index + scf.for %arg3 = %0 to %1 step %2 { + %3 = memref.load %arg0[%arg3] {from = "A"} : memref + memref.store %3, %arg1[%arg3] {to = "B"} : memref + } {loop_name = "i", op_name = "S_i_0"} + return + } + } + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 39-40 + +We can also check the generated HLS code that the arguments are declared as pointers. + +.. GENERATED FROM PYTHON SOURCE LINES 40-44 + +.. code-block:: Python + + + code = s.build(target="vhls") + print(code) + + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + + //===------------------------------------------------------------*- C++ -*-===// + // + // Automatically generated file for High-level Synthesis (HLS). + // + //===----------------------------------------------------------------------===// + #include + #include + #include + #include + #include + #include + #include + #include + using namespace std; + void kernel( + float *v0, + float *v1, + int32_t v2 + ) { // L2 + int v3 = v2; // L5 + for (int v4 = 0; v4 < v3; v4 += 1) { // L8 + float v5 = *v0[v4]; // L9 + *v1[v4] = v5; // L10 + } + } + + + + + + +.. GENERATED FROM PYTHON SOURCE LINES 45-49 + +Tuple Return +------------ +Another feature is the tuple support. As in Python, we can return multiple values from a function, Allo +also supports this by explicitly specifying the return type as a tuple. + +.. GENERATED FROM PYTHON SOURCE LINES 49-77 + +.. code-block:: Python + + + + def callee(a: float32, b: float32) -> (float32, float32): + c: float32 = a + b + d: float32 = a - b + return c, d + + + def kernel(A: float32[10], B: float32[10]) -> (float32[10], float32[10]): + C: float32[10] = 0 + D: float32[10] = 0 + for i in range(10): + C[i], D[i] = callee(A[i], B[i]) + return C, D + + + s = allo.customize(kernel) + print(s.module) + mod = s.build() + np_A = np.random.random((10,)).astype(np.float32) + np_B = np.random.random((10,)).astype(np.float32) + np_C, np_D = mod(np_A, np_B) + np_C_ref = np.zeros((10,), dtype=np.float32) + np_D_ref = np.zeros((10,), dtype=np.float32) + for i in range(10): + np_C_ref[i], np_D_ref[i] = callee(np_A[i], np_B[i]) + np.testing.assert_allclose(np_C, np_C_ref) + np.testing.assert_allclose(np_D, np_D_ref) + + + + +.. rst-class:: sphx-glr-script-out + + .. code-block:: none + + module { + func.func @callee(%arg0: f32, %arg1: f32) -> (f32, f32) attributes {itypes = "__", otypes = "__"} { + %0 = arith.addf %arg0, %arg1 : f32 + %alloc = memref.alloc() {name = "c"} : memref + affine.store %0, %alloc[] {to = "c"} : memref + %1 = arith.subf %arg0, %arg1 : f32 + %alloc_0 = memref.alloc() {name = "d"} : memref + affine.store %1, %alloc_0[] {to = "d"} : memref + %2 = affine.load %alloc[] {from = "c"} : memref + %3 = affine.load %alloc_0[] {from = "d"} : memref + return %2, %3 : f32, f32 + } + func.func @kernel(%arg0: memref<10xf32>, %arg1: memref<10xf32>) -> (memref<10xf32>, memref<10xf32>) attributes {itypes = "__", otypes = "__"} { + %c0_i32 = arith.constant 0 : i32 + %0 = arith.sitofp %c0_i32 : i32 to f32 + %alloc = memref.alloc() {name = "C"} : memref<10xf32> + linalg.fill ins(%0 : f32) outs(%alloc : memref<10xf32>) + %c0_i32_0 = arith.constant 0 : i32 + %1 = arith.sitofp %c0_i32_0 : i32 to f32 + %alloc_1 = memref.alloc() {name = "D"} : memref<10xf32> + linalg.fill ins(%1 : f32) outs(%alloc_1 : memref<10xf32>) + affine.for %arg2 = 0 to 10 { + %2 = affine.load %arg0[%arg2] {from = "A"} : memref<10xf32> + %3 = affine.load %arg1[%arg2] {from = "B"} : memref<10xf32> + %4:2 = func.call @callee(%2, %3) : (f32, f32) -> (f32, f32) + affine.store %4#0, %alloc[%arg2] {to = "C"} : memref<10xf32> + affine.store %4#1, %alloc_1[%arg2] {to = "D"} : memref<10xf32> + } {loop_name = "i", op_name = "S_i_0"} + return %alloc, %alloc_1 : memref<10xf32>, memref<10xf32> + } + } + + + + + + +.. rst-class:: sphx-glr-timing + + **Total running time of the script:** (0 minutes 0.240 seconds) + + +.. _sphx_glr_download_gallery_dive_04_features.py: + +.. only:: html + + .. container:: sphx-glr-footer sphx-glr-footer-example + + .. container:: sphx-glr-download sphx-glr-download-jupyter + + :download:`Download Jupyter notebook: dive_04_features.ipynb ` + + .. container:: sphx-glr-download sphx-glr-download-python + + :download:`Download Python source code: dive_04_features.py ` + + .. container:: sphx-glr-download sphx-glr-download-zip + + :download:`Download zipped: dive_04_features.zip ` + + +.. only:: html + + .. rst-class:: sphx-glr-signature + + `Gallery generated by Sphinx-Gallery `_ diff --git a/_sources/gallery/index.rst.txt b/_sources/gallery/index.rst.txt index 192c4b2f..7213e9ff 100644 --- a/_sources/gallery/index.rst.txt +++ b/_sources/gallery/index.rst.txt @@ -28,6 +28,23 @@ Allo Documentations +.. raw:: html + +
      + +.. only:: html + + .. image:: /gallery/images/thumb/sphx_glr_dive_01_data_types_thumb.png + :alt: + + :ref:`sphx_glr_gallery_dive_01_data_types.py` + +.. raw:: html + +
      Data Types and Type Casting
      +
      + + .. raw:: html
      @@ -62,6 +79,40 @@ Allo Documentations
      +.. raw:: html + +
      + +.. only:: html + + .. image:: /gallery/images/thumb/sphx_glr_dive_02_template_thumb.png + :alt: + + :ref:`sphx_glr_gallery_dive_02_template.py` + +.. raw:: html + +
      Template Kernels
      +
      + + +.. raw:: html + +
      + +.. only:: html + + .. image:: /gallery/images/thumb/sphx_glr_dive_04_features_thumb.png + :alt: + + :ref:`sphx_glr_gallery_dive_04_features.py` + +.. raw:: html + +
      Other Features
      +
      + + .. raw:: html
      @@ -79,6 +130,23 @@ Allo Documentations
      +.. raw:: html + +
      + +.. only:: html + + .. image:: /gallery/images/thumb/sphx_glr_dive_03_composition_thumb.png + :alt: + + :ref:`sphx_glr_gallery_dive_03_composition.py` + +.. raw:: html + +
      Kernel Composition
      +
      + + .. thumbnail-parent-div-close .. raw:: html @@ -90,9 +158,13 @@ Allo Documentations :hidden: /gallery/developer_01_ir_builder + /gallery/dive_01_data_types /gallery/tutorial_02_vhls /gallery/tutorial_01_get_started + /gallery/dive_02_template + /gallery/dive_04_features /gallery/developer_02_mlir + /gallery/dive_03_composition diff --git a/_sources/gallery/sg_execution_times.rst.txt b/_sources/gallery/sg_execution_times.rst.txt index bade13f8..c5632403 100644 --- a/_sources/gallery/sg_execution_times.rst.txt +++ b/_sources/gallery/sg_execution_times.rst.txt @@ -6,7 +6,7 @@ Computation times ================= -**00:00.607** total execution time for 4 files **from gallery**: +**00:01.961** total execution time for 8 files **from gallery**: .. container:: @@ -32,14 +32,26 @@ Computation times * - Example - Time - Mem (MB) + * - :ref:`sphx_glr_gallery_dive_03_composition.py` (``dive_03_composition.py``) + - 00:00.731 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_02_template.py` (``dive_02_template.py``) + - 00:00.372 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_04_features.py` (``dive_04_features.py``) + - 00:00.240 + - 0.0 * - :ref:`sphx_glr_gallery_tutorial_02_vhls.py` (``tutorial_02_vhls.py``) - - 00:00.332 + - 00:00.192 - 0.0 * - :ref:`sphx_glr_gallery_tutorial_01_get_started.py` (``tutorial_01_get_started.py``) - - 00:00.196 + - 00:00.181 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_01_data_types.py` (``dive_01_data_types.py``) + - 00:00.170 - 0.0 * - :ref:`sphx_glr_gallery_developer_02_mlir.py` (``developer_02_mlir.py``) - - 00:00.074 + - 00:00.070 - 0.0 * - :ref:`sphx_glr_gallery_developer_01_ir_builder.py` (``developer_01_ir_builder.py``) - 00:00.005 diff --git a/_sources/gallery/tutorial_01_get_started.rst.txt b/_sources/gallery/tutorial_01_get_started.rst.txt index 9cf0bb7f..7dce558d 100644 --- a/_sources/gallery/tutorial_01_get_started.rst.txt +++ b/_sources/gallery/tutorial_01_get_started.rst.txt @@ -71,12 +71,14 @@ use ``int32`` as the data type for all the variables. -.. GENERATED FROM PYTHON SOURCE LINES 35-55 +.. GENERATED FROM PYTHON SOURCE LINES 35-57 We then define a function that takes two 32x32 matrices as inputs and returns a 32x32 matrix as output. The variable declaration is defined -as ``: []``. We require **strict type annotation** in -Allo's kernels, which is different from directly programming in Python. +as ``: []``, and the function type is defined as +``(, , ...) -> ``. +We require **strict type annotation** in Allo's kernels, which is different +from directly programming in Python. Inside the kernel, we provide a shorthand for the loop iterator. For example, ``for i, j, k in allo.grid(32, 32, 32)`` is equivalent to the following @@ -94,7 +96,7 @@ The arguments denote the upper bounds of the loop iterators. Notice the above range-loop is also supported in the new Allo, so users have more flexibility to define the loop structure. -.. GENERATED FROM PYTHON SOURCE LINES 55-64 +.. GENERATED FROM PYTHON SOURCE LINES 57-66 .. code-block:: Python @@ -114,7 +116,7 @@ users have more flexibility to define the loop structure. -.. GENERATED FROM PYTHON SOURCE LINES 65-71 +.. GENERATED FROM PYTHON SOURCE LINES 67-73 Create the Schedule ------------------- @@ -123,7 +125,7 @@ kernel in order to achieve high performance. We call ``allo.customize`` to create a schedule for the kernel, where **schedule** denotes the set of transformations. -.. GENERATED FROM PYTHON SOURCE LINES 71-74 +.. GENERATED FROM PYTHON SOURCE LINES 73-76 .. code-block:: Python @@ -137,7 +139,7 @@ transformations. -.. GENERATED FROM PYTHON SOURCE LINES 75-80 +.. GENERATED FROM PYTHON SOURCE LINES 77-82 Inspect the Intermediate Representation (IR) -------------------------------------------- @@ -145,7 +147,7 @@ Allo leverage the `MLIR `_ infrastructure to represent the program, and we can directly print out the IR by using ``s.module``. -.. GENERATED FROM PYTHON SOURCE LINES 80-83 +.. GENERATED FROM PYTHON SOURCE LINES 82-85 .. code-block:: Python @@ -188,7 +190,7 @@ represent the program, and we can directly print out the IR by using -.. GENERATED FROM PYTHON SOURCE LINES 84-99 +.. GENERATED FROM PYTHON SOURCE LINES 86-101 Let's take a close look at the generated IR. Basically an MLIR program is a set of operations in different dialects, and the operations are referred @@ -206,7 +208,7 @@ operations and some arithmetic operations. Allo also attaches some attributes to the operations, including the tensor names, loop names, and operation names, which are further used for optimization. -.. GENERATED FROM PYTHON SOURCE LINES 101-106 +.. GENERATED FROM PYTHON SOURCE LINES 103-108 Apply Transformations --------------------- @@ -214,7 +216,7 @@ Next, we start transforming the program by using the schedule primitives. We can refer to the loops by using the loop names. For example, to split the outer-most loop into two, we can call the ``.split()`` primitive as follows: -.. GENERATED FROM PYTHON SOURCE LINES 106-109 +.. GENERATED FROM PYTHON SOURCE LINES 108-111 .. code-block:: Python @@ -228,7 +230,7 @@ the outer-most loop into two, we can call the ``.split()`` primitive as follows: -.. GENERATED FROM PYTHON SOURCE LINES 110-116 +.. GENERATED FROM PYTHON SOURCE LINES 112-118 We can print out the IR again to see the effect of the transformation. @@ -237,7 +239,7 @@ We can print out the IR again to see the effect of the transformation. In the Allo DSL, all the transformations are applied **immediately**, so users can directly see the changes after they apply the transformations. -.. GENERATED FROM PYTHON SOURCE LINES 116-119 +.. GENERATED FROM PYTHON SOURCE LINES 118-121 .. code-block:: Python @@ -285,7 +287,7 @@ We can print out the IR again to see the effect of the transformation. -.. GENERATED FROM PYTHON SOURCE LINES 120-125 +.. GENERATED FROM PYTHON SOURCE LINES 122-127 We can see that the outer-most loop is split into two loops, and the original loop is replaced by the two new loops. The new loops are named @@ -293,7 +295,7 @@ as ``i.outer`` and ``i.inner``. Similarly, we can split the ``j`` loop: -.. GENERATED FROM PYTHON SOURCE LINES 125-129 +.. GENERATED FROM PYTHON SOURCE LINES 127-131 .. code-block:: Python @@ -345,13 +347,13 @@ Similarly, we can split the ``j`` loop: -.. GENERATED FROM PYTHON SOURCE LINES 130-133 +.. GENERATED FROM PYTHON SOURCE LINES 132-135 We can further reorder the loops by using ``.reorder()``. For example, we can move the splitted outer loops together, and move the splitted inner loops together. -.. GENERATED FROM PYTHON SOURCE LINES 133-137 +.. GENERATED FROM PYTHON SOURCE LINES 135-139 .. code-block:: Python @@ -402,11 +404,11 @@ loops together. -.. GENERATED FROM PYTHON SOURCE LINES 138-139 +.. GENERATED FROM PYTHON SOURCE LINES 140-141 We can see the changes from the loop names in the generated IR. -.. GENERATED FROM PYTHON SOURCE LINES 141-149 +.. GENERATED FROM PYTHON SOURCE LINES 143-151 Create the Executable --------------------- @@ -417,7 +419,7 @@ can be executed on the CPU. Otherwise, you can also specify the target as ``vhls`` to generate a Vivado HLS program that can be synthesized to an FPGA accelerator. -.. GENERATED FROM PYTHON SOURCE LINES 149-152 +.. GENERATED FROM PYTHON SOURCE LINES 151-154 .. code-block:: Python @@ -431,13 +433,13 @@ accelerator. -.. GENERATED FROM PYTHON SOURCE LINES 153-156 +.. GENERATED FROM PYTHON SOURCE LINES 155-158 .. note:: ``s.build(target="llvm")`` is equivalent to ``s.build()``. -.. GENERATED FROM PYTHON SOURCE LINES 158-167 +.. GENERATED FROM PYTHON SOURCE LINES 160-169 Prepare the Inputs/Outputs for the Executable --------------------------------------------- @@ -449,7 +451,7 @@ but we still need to make sure the data types are consistent. By default, when defining our kernel function, so we need to explicitly cast the data type to ``np.int32``. -.. GENERATED FROM PYTHON SOURCE LINES 167-173 +.. GENERATED FROM PYTHON SOURCE LINES 169-175 .. code-block:: Python @@ -466,7 +468,7 @@ to ``np.int32``. -.. GENERATED FROM PYTHON SOURCE LINES 174-179 +.. GENERATED FROM PYTHON SOURCE LINES 176-181 Run the Executable ------------------ @@ -474,7 +476,7 @@ With the prepared inputs/outputs, we can feed them to our executable. Notice our module can return a new array as output, so we can directly assign the output to a new variable. -.. GENERATED FROM PYTHON SOURCE LINES 179-182 +.. GENERATED FROM PYTHON SOURCE LINES 181-184 .. code-block:: Python @@ -488,11 +490,11 @@ assign the output to a new variable. -.. GENERATED FROM PYTHON SOURCE LINES 183-184 +.. GENERATED FROM PYTHON SOURCE LINES 185-186 Finally, we can do a sanity check to see if the results are correct. -.. GENERATED FROM PYTHON SOURCE LINES 184-188 +.. GENERATED FROM PYTHON SOURCE LINES 186-190 .. code-block:: Python @@ -516,7 +518,7 @@ Finally, we can do a sanity check to see if the results are correct. .. rst-class:: sphx-glr-timing - **Total running time of the script:** (0 minutes 0.196 seconds) + **Total running time of the script:** (0 minutes 0.181 seconds) .. _sphx_glr_download_gallery_tutorial_01_get_started.py: diff --git a/_sources/gallery/tutorial_02_vhls.rst.txt b/_sources/gallery/tutorial_02_vhls.rst.txt index a002dda2..246f3a8a 100644 --- a/_sources/gallery/tutorial_02_vhls.rst.txt +++ b/_sources/gallery/tutorial_02_vhls.rst.txt @@ -521,7 +521,7 @@ you can check the following files: .. rst-class:: sphx-glr-timing - **Total running time of the script:** (0 minutes 0.332 seconds) + **Total running time of the script:** (0 minutes 0.192 seconds) .. _sphx_glr_download_gallery_tutorial_02_vhls.py: diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt index abbfa8d1..289577db 100644 --- a/_sources/index.rst.txt +++ b/_sources/index.rst.txt @@ -40,6 +40,17 @@ Allo is an Accelerator Design Language (ADL) and compiler that facilitates the c gallery/tutorial_02_vhls.rst +.. toctree:: + :maxdepth: 1 + :caption: Deep Dive + + gallery/dive_01_data_types.rst + gallery/dive_02_template.rst + gallery/dive_03_composition.rst + dive/ip.rst + dive/pytorch.rst + gallery/dive_04_features.rst + .. toctree:: :maxdepth: 1 :caption: Developer Guide diff --git a/_sources/sg_execution_times.rst.txt b/_sources/sg_execution_times.rst.txt index 029a9c7c..bcc5f3bf 100644 --- a/_sources/sg_execution_times.rst.txt +++ b/_sources/sg_execution_times.rst.txt @@ -6,7 +6,7 @@ Computation times ================= -**00:00.607** total execution time for 4 files **from all galleries**: +**00:01.961** total execution time for 8 files **from all galleries**: .. container:: @@ -32,14 +32,26 @@ Computation times * - Example - Time - Mem (MB) + * - :ref:`sphx_glr_gallery_dive_03_composition.py` (``../../tutorials/dive_03_composition.py``) + - 00:00.731 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_02_template.py` (``../../tutorials/dive_02_template.py``) + - 00:00.372 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_04_features.py` (``../../tutorials/dive_04_features.py``) + - 00:00.240 + - 0.0 * - :ref:`sphx_glr_gallery_tutorial_02_vhls.py` (``../../tutorials/tutorial_02_vhls.py``) - - 00:00.332 + - 00:00.192 - 0.0 * - :ref:`sphx_glr_gallery_tutorial_01_get_started.py` (``../../tutorials/tutorial_01_get_started.py``) - - 00:00.196 + - 00:00.181 + - 0.0 + * - :ref:`sphx_glr_gallery_dive_01_data_types.py` (``../../tutorials/dive_01_data_types.py``) + - 00:00.170 - 0.0 * - :ref:`sphx_glr_gallery_developer_02_mlir.py` (``../../tutorials/developer_02_mlir.py``) - - 00:00.074 + - 00:00.070 - 0.0 * - :ref:`sphx_glr_gallery_developer_01_ir_builder.py` (``../../tutorials/developer_01_ir_builder.py``) - 00:00.005 diff --git a/_static/pygments.css b/_static/pygments.css index 0d49244e..5f2b0a25 100644 --- a/_static/pygments.css +++ b/_static/pygments.css @@ -6,26 +6,26 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: .highlight .hll { background-color: #ffffcc } .highlight { background: #eeffcc; } .highlight .c { color: #408090; font-style: italic } /* Comment */ -.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .err { border: 1px solid #F00 } /* Error */ .highlight .k { color: #007020; font-weight: bold } /* Keyword */ -.highlight .o { color: #666666 } /* Operator */ +.highlight .o { color: #666 } /* Operator */ .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #007020 } /* Comment.Preproc */ .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .cs { color: #408090; background-color: #FFF0F0 } /* Comment.Special */ .highlight .gd { color: #A00000 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gr { color: #F00 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #333333 } /* Generic.Output */ -.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .go { color: #333 } /* Generic.Output */ +.highlight .gp { color: #C65D09; font-weight: bold } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .gt { color: #04D } /* Generic.Traceback */ .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ @@ -33,43 +33,43 @@ span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #902000 } /* Keyword.Type */ .highlight .m { color: #208050 } /* Literal.Number */ -.highlight .s { color: #4070a0 } /* Literal.String */ -.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .s { color: #4070A0 } /* Literal.String */ +.highlight .na { color: #4070A0 } /* Name.Attribute */ .highlight .nb { color: #007020 } /* Name.Builtin */ -.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ -.highlight .no { color: #60add5 } /* Name.Constant */ -.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ -.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .nc { color: #0E84B5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60ADD5 } /* Name.Constant */ +.highlight .nd { color: #555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #D55537; font-weight: bold } /* Name.Entity */ .highlight .ne { color: #007020 } /* Name.Exception */ -.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nf { color: #06287E } /* Name.Function */ .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ -.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nn { color: #0E84B5; font-weight: bold } /* Name.Namespace */ .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .nv { color: #BB60D5 } /* Name.Variable */ .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .w { color: #BBB } /* Text.Whitespace */ .highlight .mb { color: #208050 } /* Literal.Number.Bin */ .highlight .mf { color: #208050 } /* Literal.Number.Float */ .highlight .mh { color: #208050 } /* Literal.Number.Hex */ .highlight .mi { color: #208050 } /* Literal.Number.Integer */ .highlight .mo { color: #208050 } /* Literal.Number.Oct */ -.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ -.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ -.highlight .sc { color: #4070a0 } /* Literal.String.Char */ -.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ -.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ -.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ -.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ -.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ -.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sa { color: #4070A0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070A0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070A0 } /* Literal.String.Char */ +.highlight .dl { color: #4070A0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070A0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070A0 } /* Literal.String.Double */ +.highlight .se { color: #4070A0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070A0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70A0D0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #C65D09 } /* Literal.String.Other */ .highlight .sr { color: #235388 } /* Literal.String.Regex */ -.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .s1 { color: #4070A0 } /* Literal.String.Single */ .highlight .ss { color: #517918 } /* Literal.String.Symbol */ .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ -.highlight .fm { color: #06287e } /* Name.Function.Magic */ -.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ -.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ -.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ -.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .fm { color: #06287E } /* Name.Function.Magic */ +.highlight .vc { color: #BB60D5 } /* Name.Variable.Class */ +.highlight .vg { color: #BB60D5 } /* Name.Variable.Global */ +.highlight .vi { color: #BB60D5 } /* Name.Variable.Instance */ +.highlight .vm { color: #BB60D5 } /* Name.Variable.Magic */ .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/api/index.html b/api/index.html index 96862381..2fe74412 100644 --- a/api/index.html +++ b/api/index.html @@ -6,7 +6,7 @@ Schedule Primitives — Allo Documentation - + @@ -142,6 +142,15 @@

      Quick search

    • Getting Started
    • Vivado/Vitis HLS Backend
    +

    Deep Dive

    +

    Developer Guide

    • Developer Setup
    • @@ -167,7 +176,7 @@

      Quick search

      Schedule PrimitivesΒΆ

      -class allo.customize.Schedule(module, top_func, func_args, ip, ext_libs=None, use_def_chain=None, inst_list=None)[source]ΒΆ
      +class allo.customize.Schedule(module, top_func, func_args, ip, ext_libs=None, inst_list=None)[source]ΒΆ

      Methods:

      @@ -524,7 +533,7 @@

      Data Types - © Copyright 2024, Allo Authors. + © Copyright 2025, Allo Authors. Created using Sphinx 8.1.3. diff --git a/developer/index.html b/developer/index.html index 83fea737..53d64194 100644 --- a/developer/index.html +++ b/developer/index.html @@ -6,7 +6,7 @@ Developer Setup — Allo Documentation - + @@ -24,7 +24,7 @@ - + diff --git a/dive/ip.html b/dive/ip.html new file mode 100644 index 00000000..bdcf2bf5 --- /dev/null +++ b/dive/ip.html @@ -0,0 +1,268 @@ + + + + + + + + IP Integration — Allo Documentation + + + + + + + + + + + + + + + + + + + + + +
      + + + +
      + + + + + +
      +
      +
      +
      + +
      +

      IP IntegrationΒΆ

      +

      Apart from directly writing Allo kernels in Python, we also support integrating existing C++ HLS kernels into Allo. This feature is useful when you have a existing optimized C++ HLS code that wants to be integrated into Allo. The following example shows how to integrate a simple vector addition kernel written in C++ into Allo.

      +

      Suppose the C++ kernel header is defined in the vadd.h file:

      +
      #ifndef VADD_H
      +#define VADD_H
      +
      +void vadd(int A[32], int B[32], int C[32]);
      +
      +#endif // VADD_H
      +
      +
      +

      And the corresponding implementation is defined in the vadd.cpp file:

      +
      #include "vadd.h"
      +using namespace std;
      +
      +void vadd(int A[32], int B[32], int C[32]) {
      +    for (int i = 0; i < 32; ++i) {
      +        C[i] = A[i] + B[i];
      +    }
      +}
      +
      +
      +

      In Allo, we can create an IP module to wrap the C++ kernel. Basically, we need to provide the top-level function name, the header files, and the implementation files. Also, currently an Allo signature is required to specify the input and output types of the kernel. Allo will automatically compile the C++ kernel and generate the corresponding Python wrapper based on the provided files and signature. The last argument link_hls determines whether the C++ compiler should link the Vitis HLS libraries (e.g., ap_int), which is only available when your machine has installed Vitis HLS.

      +
      vadd = allo.IPModule(
      +    top="vadd",
      +    headers=["vadd.h"],
      +    impls=["vadd.cpp"],
      +    signature=["int32[32]", "int32[32]", "int32[32]"],
      +    link_hls=False,
      +)
      +
      +
      +

      After creating the IP module, we can use it in Allo as a normal Python function. For example, we can directly call the vadd function to perform vector addition. The inputs and outputs will be automatically wrapped and unwrapped as NumPy arrays, which greatly simplies the burden of complex C-Python interface management. This is also very useful when you want to debug the HLS kernels with the Python data.

      +
      np_A = np.random.randint(0, 100, (32,)).astype(np.int32)
      +np_B = np.random.randint(0, 100, (32,)).astype(np.int32)
      +np_C = np.zeros((32,), dtype=np.int32)
      +vadd(np_A, np_B, np_C)
      +np.testing.assert_allclose(np_A + np_B, np_C, atol=1e-6)
      +
      +
      +

      Moreover, the IP module can also be called in a normal Allo kernel. In the following example, we wrap the vadd function into an Allo kernel and use it to perform vector addition. The Allo kernel can then be further customized and compiled with the external C++ HLS kernel.

      +
      def kernel(A: int32[32], B: int32[32]) -> int32[32]:
      +    C: int32[32] = 0
      +    vadd(A, B, C)
      +    return C
      +
      +s = allo.customize(kernel)
      +print(s.module)
      +mod = s.build()
      +np_A = np.random.randint(0, 100, (32,)).astype(np.int32)
      +np_B = np.random.randint(0, 100, (32,)).astype(np.int32)
      +allo_C = mod(np_A, np_B)
      +np.testing.assert_allclose(np_A + np_B, allo_C, atol=1e-6)
      +
      +
      +
      + + +
      +
      +
      +
      + + +
      +
      +
      + +
      + + + + +

      Styled using the Piccolo Theme

      + + \ No newline at end of file diff --git a/dive/pytorch.html b/dive/pytorch.html new file mode 100644 index 00000000..41b88b83 --- /dev/null +++ b/dive/pytorch.html @@ -0,0 +1,253 @@ + + + + + + + + PyTorch Integration — Allo Documentation + + + + + + + + + + + + + + + + + + + + + +
      + + + +
      + + + + + +
      +
      +
      +
      + +
      +

      PyTorch IntegrationΒΆ

      +

      In this document, we will show how to directly compile PyTorch models to Allo. +First, users can define a PyTorch module as usual:

      +
      import torch
      +import torch.nn.functional as F
      +import torch.nn as nn
      +
      +class Model(nn.Module):
      +    def __init__(self):
      +        super(Model, self).__init__()
      +
      +    def forward(self, x, y):
      +        x = x + y
      +        x = F.relu(x)
      +        return x
      +
      +model = Model()
      +model.eval()
      +
      +
      +

      Then, users can compile the PyTorch model to Allo by using the allo.frontend.from_pytorch API:

      +
      import allo
      +example_inputs = [torch.rand(1, 3, 10, 10), torch.rand(1, 3, 10, 10)]
      +llvm_mod = allo.frontend.from_pytorch(model, example_inputs=example_inputs)
      +
      +
      +

      Then, we can use the generated Allo LLVM module as usual by passing in the NumPy inputs:

      +
      golden = model(*example_inputs)
      +np_inputs = [x.detach().numpy() for x in example_inputs]
      +res = llvm_mod(*np_inputs)
      +torch.testing.assert_close(res, golden.detach().numpy())
      +print("Passed!")
      +
      +
      +

      The process should be very similar to the original Allo workflow. +The default target is LLVM. We can also change the backend to other compilers such as Vitis HLS by specifying the target:

      +
      mod = allo.frontend.from_pytorch(model, example_inputs=example_inputs, target="vhls")
      +print(mod.hls_code)
      +
      +
      +
      + + +
      +
      +
      +
      + + +
      +
      +
      +
      + + +
      + + Other Features> + +
      +
      +
      + + + + +

      Styled using the Piccolo Theme

      + + \ No newline at end of file diff --git a/gallery/developer_01_ir_builder.html b/gallery/developer_01_ir_builder.html index a640b4e8..127ed0cf 100644 --- a/gallery/developer_01_ir_builder.html +++ b/gallery/developer_01_ir_builder.html @@ -6,7 +6,7 @@ IR Builder Walkthrough — Allo Documentation - + @@ -143,6 +143,15 @@

      Quick search

    • Getting Started
    • Vivado/Vitis HLS Backend
    • +

      Deep Dive

      +

      Developer Guide

      +

      Deep Dive

      +

      Developer Guide

      • Developer Setup
      • @@ -174,8 +183,8 @@

        Quick search

        Author: Hongzheng Chen (hzchen@cs.cornell.edu)

        This guide will give some examples on how to invoke the MLIR toolchain to verify the correctness of a handwritten or generated MLIR program.

        -
        import allo
        -import numpy as np
        +
        import allo
        +import numpy as np
         
        @@ -288,7 +297,7 @@

        Define an MLIR program with linalg dialect
        def kernel(A: int32[32, 32], B: int32[32, 32]) -> int32[32, 32]:
        +
        def kernel(A: int32[32, 32], B: int32[32, 32]) -> int32[32, 32]:
             C = allo.matmul(A, B)
             return C
         
        @@ -350,7 +359,7 @@

        Define an MLIR program with Tensor dialectTotal running time of the script: (0 minutes 0.074 seconds)

        +

        Total running time of the script: (0 minutes 0.070 seconds)

      +

      Deep Dive

      +

      Developer Guide

      +

      Deep Dive

      +

      Developer Guide

      • Developer Setup
      • @@ -164,7 +173,7 @@

        Quick search

        Computation timesΒΆ

        -

        00:00.607 total execution time for 4 files from gallery:

        +

        00:01.961 total execution time for 8 files from gallery: