diff --git a/.github/workflows/publish-pypi.yaml b/.github/workflows/publish-pypi.yaml new file mode 100644 index 0000000..1abb957 --- /dev/null +++ b/.github/workflows/publish-pypi.yaml @@ -0,0 +1,68 @@ +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: Build and publish Python package to PyPI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + tags: + - '*' + +jobs: + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/*.tar.gz + + build-wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, macos-13, macos-14] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build wheels + uses: pypa/cibuildwheel@v2.17.0 + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl + + publish: + name: Publish to PyPI + if: startsWith(github.ref, 'refs/tags/') + needs: [build-sdist, build-wheels] + runs-on: ubuntu-latest + permissions: + id-token: write + + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1e65f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +*.egg-info +__pycache__/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7c1a68a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "flexfloat"] + path = flexfloat + url = git@github.com:oprecomp/flexfloat.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..a808d2d --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +graft flexfloat \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c557e1 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +## PyFlexFloat + +A Python wrapper around OPRECOMP's [FlexFloat](https://github.com/oprecomp/flexfloat) library. + +## Installation + +Clone the repository: +` +git clone https://github.com/colluca/pyflexfloat.git +` + +Enter the local repository folder: +` +cd pyflexfloat +` + +Install in your active Python environment: +` +pip install . +` + +Test installation: +` +./test/test.py +` diff --git a/flexfloat b/flexfloat new file mode 160000 index 0000000..675e97b --- /dev/null +++ b/flexfloat @@ -0,0 +1 @@ +Subproject commit 675e97b0958f3b142594c3d0cec3e79114e751a3 diff --git a/pyflexfloat/FlexFloat.py b/pyflexfloat/FlexFloat.py new file mode 100755 index 0000000..bca8657 --- /dev/null +++ b/pyflexfloat/FlexFloat.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +from _flexfloat import ffi, lib +from pyflexfloat.util import max_precision +from collections.abc import Sequence, Mapping +import re + + +class FlexFloat(): + + def __init__(self, desc, val=None): + ''' Initialize a FlexFloat variable. + + Arguments: + desc: A descriptor specifying the precision of the flexfloat + type, as defined by the flexfloat library. Can be a + `Sequence` type instance, a `Mapping` type instance with + the 'exp_bits' and 'frac_bits' keys, a string of the form + 'fpXX' indicating a standardized IEEE floating-point type + of XX bits (e.g. fp16), or a string of the form 'eXmY' to + indicate a custom type with X exponent bits and Y mantissa + bits. + val: A variable of type float, integer or bytes used to + derive the flexfloat value. The pyflexfloat API only + implements a subset of the flexfloat API, and allows + initializing flexfloat values from doubles only. Integers + are implicitly cast to double before eventually being + downcasted to the same precision as `self`. A variable of + type bytes can be passed to initialize the flexfloat + variable to the exact binary representation provided. + ''' + # Parse desc argument + self.desc = desc + if isinstance(desc, str): + # Standard IEEE FP formats + if desc == 'fp16': + self.exp_bits, self.frac_bits = 5, 10 + elif desc == 'fp32': + self.exp_bits, self.frac_bits = 8, 23 + elif desc == 'fp64': + self.exp_bits, self.frac_bits = 11, 52 + # Custom FP formats + else: + # This regex pattern matches a string that: + # - starts with "e" + # - is followed by one or more digits (\d+) + # - has "m" right after the digits + # - ends with one or more digits (\d+) + match = re.match(r'^e(\d+)m(\d+)$', desc) + if match: + # Extracting N and M from the capturing groups + self.exp_bits, self.frac_bits = map(int, match.groups()) + else: + raise ValueError('Unsupported description string') + elif isinstance(desc, Sequence): + self.exp_bits, self.frac_bits = desc[0:2] + elif isinstance(desc, Mapping): + self.exp_bits, self.frac_bits = desc['exp_bits'], desc['frac_bits'] + else: + raise ValueError('Unsupported type for parameter desc') + + # Create and initialize flexfloat variable + self.ptr = ffi.new('flexfloat_t *') + desc = ffi.new('flexfloat_desc_t *', [self.exp_bits, self.frac_bits]) + if val is not None: + if isinstance(val, float): + lib.ff_init_double(self.ptr, val, desc[0]) + elif isinstance(val, int): + lib.ff_init_double(self.ptr, float(val), desc[0]) + elif isinstance(val, bytes): + lib.ff_init(self.ptr, desc[0]) + lib.flexfloat_set_bits(self.ptr, int.from_bytes(val, 'big')) + else: + raise ValueError(f'val has unsupported type {type(val)}') + else: + lib.ff_init(self.ptr, desc[0]) + + def __str__(self): + return str(lib.ff_get_double(self.ptr)) + + def _uniformize(self, other): + """Ensures that `self` and `other` have compatible types. + + Ensures that `self` and `other` have compatible types, i.e. + can be used as the two operands of a binary operation. + + Operations between FlexFloats (of any type) are already allowed. + This method casts `other` to the FlexFloat type of `self` iff it + is not already a FlexFloat. See the `__init__` method for more + information. + """ + if not isinstance(other, FlexFloat): + return FlexFloat(self.desc, other) + else: + return other + + + def __add__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_add_any(result.ptr, self.ptr, other.ptr) + return result + + def __radd__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_add_any(result.ptr, other.ptr, self.ptr) + return result + + def __iadd__(self, other): + lib.ff_add_any(self.ptr, self.ptr, other.ptr) + return self + + def __sub__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_sub_any(result.ptr, self.ptr, other.ptr) + return result + + def __rsub__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_sub_any(result.ptr, other.ptr, self.ptr) + return result + + def __isub__(self, other): + lib.ff_sub_any(self.ptr, self.ptr, other.ptr) + return self + + def __mul__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_mul_any(result.ptr, self.ptr, other.ptr) + return result + + def __rmul__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_mul_any(result.ptr, other.ptr, self.ptr) + return result + + def __imul__(self, other): + lib.ff_mul_any(self.ptr, self.ptr, other.ptr) + return self + + def __truediv__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_div_any(result.ptr, self.ptr, other.ptr) + return result + + def __rtruediv__(self, other): + other = self._uniformize(other) + result = FlexFloat(max_precision(self, other)) + lib.ff_div_any(result.ptr, other.ptr, self.ptr) + return result + + def __itruediv__(self, other): + lib.ff_div_any(self.ptr, self.ptr, other.ptr) + return self + + def __lt__(self, other): + other = self._uniformize(other) + return lib.ff_lt(self.ptr, other.ptr) + + def __le__(self, other): + other = self._uniformize(other) + return lib.ff_le(self.ptr, other.ptr) + + def __eq__(self, other): + other = self._uniformize(other) + return lib.ff_eq(self.ptr, other.ptr) + + def __ne__(self, other): + other = self._uniformize(other) + return lib.ff_neq(self.ptr, other.ptr) + + def __ge__(self, other): + other = self._uniformize(other) + return lib.ff_ge(self.ptr, other.ptr) + + def __gt__(self, other): + other = self._uniformize(other) + return lib.ff_gt(self.ptr, other.ptr) + + def __neg__(self): + return 0 - self + + def __abs__(self): + return -self if self < 0 else self + + def sqrt(self): + result = FlexFloat(self.desc) + lib.ff_sqrt(result.ptr, self.ptr) + return result + + def size(self): + return self.exp_bits + self.frac_bits + 1 + + def exp(self): + return lib.flexfloat_exp(self.ptr) + + def frac(self): + return lib.flexfloat_frac(self.ptr) + + def bits(self): + return lib.flexfloat_get_bits(self.ptr) + + def bitstring(self): + return format(self.bits(), f'0>{self.size()}b') diff --git a/pyflexfloat/__init__.py b/pyflexfloat/__init__.py new file mode 100644 index 0000000..1e0a5ac --- /dev/null +++ b/pyflexfloat/__init__.py @@ -0,0 +1,7 @@ +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +from .FlexFloat import FlexFloat +from .func import * \ No newline at end of file diff --git a/pyflexfloat/build_flexfloat_ffi.py b/pyflexfloat/build_flexfloat_ffi.py new file mode 100755 index 0000000..e72d476 --- /dev/null +++ b/pyflexfloat/build_flexfloat_ffi.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +from cffi import FFI +ffibuilder = FFI() + +# Assumes FLEXFLOAT_ON_DOUBLE configuration +ffibuilder.cdef(""" +typedef double fp_t; +typedef uint64_t uint_t; + +typedef struct _flexfloat_desc_t { + uint8_t exp_bits; + uint8_t frac_bits; +} flexfloat_desc_t; + +struct _flexfloat_t { + fp_t value; + flexfloat_desc_t desc; +}; + +typedef struct _flexfloat_t flexfloat_t; + +// Helper functions + +int_fast16_t flexfloat_exp(const flexfloat_t *a); +uint_t flexfloat_frac(const flexfloat_t *a); + +// Bit-level access + +uint_t flexfloat_get_bits(flexfloat_t *a); +void flexfloat_set_bits(flexfloat_t *a, uint_t bits); + +// Constructors + +void ff_init(flexfloat_t *obj, flexfloat_desc_t desc); +void ff_init_double(flexfloat_t *obj, double value, flexfloat_desc_t desc); + +// Casts + +double ff_get_double(const flexfloat_t *obj); + +// Artihmetic operators + +void ff_add_any(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_sub_any(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_mul_any(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_div_any(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_sqrt(flexfloat_t *dest, const flexfloat_t *a); +void ff_acc(flexfloat_t *dest, const flexfloat_t *a); +void ff_min(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_max(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b); +void ff_fma(flexfloat_t *dest, const flexfloat_t *a, const flexfloat_t *b, const flexfloat_t *c); + +// Relational operators + +bool ff_eq(const flexfloat_t *a, const flexfloat_t *b); +bool ff_neq(const flexfloat_t *a, const flexfloat_t *b); +bool ff_le(const flexfloat_t *a, const flexfloat_t *b); +bool ff_lt(const flexfloat_t *a, const flexfloat_t *b); +bool ff_ge(const flexfloat_t *a, const flexfloat_t *b); +bool ff_gt(const flexfloat_t *a, const flexfloat_t *b); +""") + +ffibuilder.set_source("_flexfloat", +""" + #include "flexfloat.h" +""", + sources=['flexfloat/src/flexfloat.c'], + include_dirs=['flexfloat/include'], + # extra_compile_args=['--std=c++11'] + # libraries=['m'] +) + +if __name__ == "__main__": + ffibuilder.compile(verbose=True) diff --git a/pyflexfloat/func.py b/pyflexfloat/func.py new file mode 100644 index 0000000..f78a56e --- /dev/null +++ b/pyflexfloat/func.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +from pyflexfloat import FlexFloat +import numpy as np + + +def array(a, desc): + shape = a.shape + a = [FlexFloat(desc, val) for val in a.flatten().tolist()] + return np.array(a).reshape(shape) + + +def frombuffer(buffer, desc): + """Return a FlexFloat array from a Python buffer object.""" + size = FlexFloat(desc).size() + assert size == 8, "frombuffer method currently supports only 8-bit sized FlexFloat types" + # Cast to a buffer with byte-sized elements + memview = memoryview(buffer).cast('B') + return np.array([FlexFloat(desc, byte.to_bytes(1, "big")) for byte in memview]) + + +# TODO: is this needed or not? Looks redundant to FlexFloat.__iadd__ to me +def acc(a, b): + # void ff_acc(flexfloat_t *dest, const flexfloat_t *a); + pass + + +def min(a, b): + # TODO: what precision should we use for result? + result = FlexFloat(a.desc) + lib.ff_min(result.ptr, a.ptr, b.ptr) + return result + + +def max(a, b): + # TODO: what precision should we use for result? + result = FlexFloat(a.desc) + lib.ff_max(result.ptr, a.ptr, b.ptr) + return result + + +def fma(a, b, c): + # TODO: what precision should we use for result? + result = FlexFloat(a.desc) + lib.ff_fma(result.ptr, a.ptr, b.ptr, c.ptr) + return result \ No newline at end of file diff --git a/pyflexfloat/util.py b/pyflexfloat/util.py new file mode 100644 index 0000000..2fcff4e --- /dev/null +++ b/pyflexfloat/util.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande + +def max_precision(a, b): + '''Calculates the maximum precision between two FlexFloat types. + + Returns: + A FlexFloat descriptor able to represent both FlexFloat variables + `a` and `b`. + ''' + return (max(a.exp_bits, b.exp_bits), max(a.frac_bits, b.frac_bits)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c5d4546 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[build-system] +requires = [ + "setuptools>=60", + "setuptools-scm>=8.0", + "cmake_build_extension==0.5.2.dev15", + "cffi" +] +build-backend = "setuptools.build_meta" + +[project] +name = "pyflexfloat" +dynamic = ["version", "dependencies"] +description = "Python wrapper for FlexFloat library" +readme = "README.md" +authors = [{name = "Luca Colagrande", email = "luca.colagrande3@gmail.com"}] +license = {file = "LICENSE"} +requires-python = ">=3.9" + +[tool.setuptools_scm] \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..436ae79 --- /dev/null +++ b/setup.py @@ -0,0 +1,29 @@ +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +import setuptools +import cmake_build_extension +import subprocess +from pathlib import Path + + +class CMakeBuild(setuptools.command.build.build): + + def run(self): + # Required to generate "flexfloat_config.h" + builddir = Path(__file__).parent / 'flexfloat/build' + builddir.mkdir(parents=True, exist_ok=True) + subprocess.run(['pwd'], cwd=builddir, check=True) + subprocess.run(['ls', '..'], cwd=builddir, check=True) + subprocess.run(['cmake', '..'], cwd=builddir, check=True) + + +setuptools.setup( + packages=setuptools.find_packages(), + setup_requires=['cffi'], + install_requires=['cffi'], + cffi_modules=["pyflexfloat/build_flexfloat_ffi.py:ffibuilder"], + cmdclass={'build': CMakeBuild} +) diff --git a/test/test.py b/test/test.py new file mode 100755 index 0000000..50783b3 --- /dev/null +++ b/test/test.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# Copyright 2024 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Author: Luca Colagrande +import pyflexfloat as ff +from pyflexfloat import FlexFloat +import numpy as np + +np.set_printoptions(formatter={'object': str}) + +if __name__ == '__main__': + # FP16 math + print('= FP16 math =') + a = FlexFloat('fp16', 3.14) + b = FlexFloat('fp16', 2) + c = FlexFloat('fp16') + print(a) + print(b) + print(a + b) + a += b + print(a) + + # FP32 math + print('= FP32 math =') + a = FlexFloat('fp32', 3.14) + b = FlexFloat('fp32', 2) + c = FlexFloat('fp32') + print(a) + print(b) + print(a + b) + a += b + print(a) + + # FP16 math with numpy + print('= FP16 math with numpy =') + length = 4 + A = np.array([FlexFloat((5, 10), i + .14) for i in range(length*length)]).reshape(length, length) + B = np.array([[FlexFloat((5, 10), 2)]*length]*length) + C = A + B + print(A) + print(B) + C = np.matmul(A, B) + print(C) + + # Random numpy data + print('= Random FP8 array with numpy =') + A = np.random.rand(length, length) + B = ff.array(A, 'e5m2') + print(A, B) + + # Test implicit type casting + print('= RHS type casting =') + a = FlexFloat('fp32', 3.14) + print(a * 2) + print('= LHS type casting =') + a = FlexFloat('fp32', 3.14) + print(2 * a) + + # Test exp and frac functions + print('= exp() and frac() =') + a = FlexFloat('e5m2', 3.14) + print(a) + print(f'exp: {a.exp()}') + print(f'frac: {a.frac()}') + print(f'bits: {a.bitstring()}') + print(f'hex: {hex(a.bits())}') + + # Test FlexFloat initialization from bytes + print('= FlexFloat(..., bytes) =') + b = FlexFloat('e5m2', a.bits().to_bytes(1, 'big')) + print(f'bits: {b.bitstring()}') + print(f'hex: {hex(b.bits())}') + + # Test comparisons + print('= Comparisons =') + a = FlexFloat('fp32', -3.14) + b = FlexFloat('fp32', 3.14) + print(f'{a} < 0: {a < 0}') + print(f'{b} < 0: {b < 0}') + + # Neg and abs test + print('= Neg and abs =') + a = FlexFloat('fp32', -3.14) + print(f'a: {a}') + print(f'-a: {-a}') + print(f'abs(a): {abs(a)}')