From 7e28fc0e7168ad0657e7f6ee50c6e4cdb3df2c6d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 11:37:26 +0100 Subject: [PATCH 1/8] isort --- src/gfloat/__init__.py | 7 +++---- src/gfloat/decode.py | 4 ++-- src/gfloat/round.py | 5 +++-- src/gfloat/types.py | 1 + test/test_decode.py | 7 ++++--- test/test_encode.py | 2 +- test/test_finfo.py | 2 +- test/test_round.py | 4 ++-- 8 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/gfloat/__init__.py b/src/gfloat/__init__.py index dc33b85..9b9be9d 100644 --- a/src/gfloat/__init__.py +++ b/src/gfloat/__init__.py @@ -1,10 +1,9 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. -from .types import FormatInfo, FloatClass, FloatValue, RoundMode from .decode import decode_float -from .round import round_float, encode_float -import gfloat.formats +from .round import encode_float, round_float +from .types import FloatClass, FloatValue, FormatInfo, RoundMode # Don't automatically import from .formats. # If the user wants them in their namespace, they can explicitly import -# from .formats import * +# from gfloat.formats import * diff --git a/src/gfloat/decode.py b/src/gfloat/decode.py index 8b2d78a..4e45298 100644 --- a/src/gfloat/decode.py +++ b/src/gfloat/decode.py @@ -1,9 +1,9 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. -from .types import FormatInfo, FloatValue, FloatClass - import numpy as np +from .types import FloatClass, FloatValue, FormatInfo + def decode_float(fi: FormatInfo, i: int) -> FloatValue: """ diff --git a/src/gfloat/round.py b/src/gfloat/round.py index 5b40167..c8592ad 100644 --- a/src/gfloat/round.py +++ b/src/gfloat/round.py @@ -1,11 +1,12 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. +import math from enum import Enum + import numpy as np -import math -from .types import FormatInfo, RoundMode, FloatValue from .decode import decode_float +from .types import FloatValue, FormatInfo, RoundMode def _isodd(v: int): diff --git a/src/gfloat/types.py b/src/gfloat/types.py index 39fc2ac..e8bd593 100644 --- a/src/gfloat/types.py +++ b/src/gfloat/types.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from enum import Enum + import numpy as np diff --git a/test/test_decode.py b/test/test_decode.py index 1eac9e2..67f46b2 100644 --- a/test/test_decode.py +++ b/test/test_decode.py @@ -1,9 +1,10 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. -import pytest -import numpy as np import ml_dtypes -from gfloat import decode_float, FloatClass +import numpy as np +import pytest + +from gfloat import FloatClass, decode_float from gfloat.formats import * diff --git a/test/test_encode.py b/test/test_encode.py index eeb8419..beb82f6 100644 --- a/test/test_encode.py +++ b/test/test_encode.py @@ -1,8 +1,8 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. -import pytest import ml_dtypes import numpy as np +import pytest from gfloat import decode_float, encode_float from gfloat.formats import * diff --git a/test/test_finfo.py b/test/test_finfo.py index 40c9ecc..95e08ea 100644 --- a/test/test_finfo.py +++ b/test/test_finfo.py @@ -2,9 +2,9 @@ # Test that finfo methods on FloatFormat agree with numpy/ml_dtypes -import pytest import ml_dtypes import numpy as np +import pytest from gfloat import decode_float, round_float from gfloat.formats import * diff --git a/test/test_round.py b/test/test_round.py index 82f5465..0e42225 100644 --- a/test/test_round.py +++ b/test/test_round.py @@ -1,10 +1,10 @@ # Copyright (c) 2024 Graphcore Ltd. All rights reserved. -import pytest import ml_dtypes import numpy as np +import pytest -from gfloat import decode_float, round_float, RoundMode +from gfloat import RoundMode, decode_float, round_float from gfloat.formats import * From f78308b7f6ce22282dc7d8b725717a452ed3fb4d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 11:45:45 +0100 Subject: [PATCH 2/8] mypy and new test exposing mypy-identified bug --- src/gfloat/round.py | 2 +- test/test_encode.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/gfloat/round.py b/src/gfloat/round.py index c8592ad..97592b7 100644 --- a/src/gfloat/round.py +++ b/src/gfloat/round.py @@ -62,7 +62,7 @@ def round_float(fi: FormatInfo, v: float, rnd=RoundMode.TiesToEven, sat=False) - if vpos < fi.smallest_subnormal / 2: # Test against smallest_subnormal to avoid subnormals in frexp below # Note that this restricts us to types narrower than float64 - result = 0 + result = 0.0 elif np.isinf(vpos): result = np.inf diff --git a/test/test_encode.py b/test/test_encode.py index beb82f6..6c11d84 100644 --- a/test/test_encode.py +++ b/test/test_encode.py @@ -25,3 +25,20 @@ def test_encode(fi): fv2 = decode_float(fi, ival) assert (i == ival) or np.isnan(fv.fval) np.testing.assert_equal(fv2.fval, fv.fval) + + +@pytest.mark.parametrize("fi", all_formats, ids=str) +def test_encode_edges(fi): + assert encode_float(fi, fi.max) == fi.code_of_max + + assert encode_float(fi, fi.max * 1.25) == ( + fi.code_of_posinf + if fi.has_infs + else fi.code_of_nan if fi.num_nans > 0 else fi.code_of_max + ) + + assert encode_float(fi, fi.min * 1.25) == ( + fi.code_of_neginf + if fi.has_infs + else fi.code_of_nan if fi.num_nans > 0 else fi.code_of_min + ) From d573f30ccb66734c996e113f47f9b97826cf5528 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 11:46:51 +0100 Subject: [PATCH 3/8] Fix bug exposed in f78308b --- src/gfloat/round.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/gfloat/round.py b/src/gfloat/round.py index 97592b7..b0b25f3 100644 --- a/src/gfloat/round.py +++ b/src/gfloat/round.py @@ -164,17 +164,18 @@ def encode_float(fi: FormatInfo, v: float) -> int: # Overflow/underflow if v > fi.max: - return ( - fi.code_of_posinf - if fi.has_infs - else fi.code_of_nan if fi.num_nans > 0 else fi.max - ) + if fi.has_infs: + return fi.code_of_posinf + if fi.num_nans > 0: + return fi.code_of_nan + return fi.code_of_max + if v < fi.min: - return ( - fi.code_of_neginf - if fi.has_infs - else fi.code_of_nan if fi.num_nans > 0 else fi.min - ) + if fi.has_infs: + return fi.code_of_neginf + if fi.num_nans > 0: + return fi.code_of_nan + return fi.code_of_min # Finite values sign = np.signbit(v) From 3956f6b656aebd46be4be4b5d484c0a5d30e7660 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 13:30:54 +0100 Subject: [PATCH 4/8] pre-commit --- .github/workflows/ci.yaml | 1 - .pre-commit-config.yaml | 11 +++++++++++ README.md | 5 ++--- docs/source/index.rst | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5c2f3da..3ccde34 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,4 +32,3 @@ jobs: - name: Ensure that docs build run: | cd docs && make html - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3b8fffd --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 24.4.0 + hooks: + - id: black diff --git a/README.md b/README.md index 7806895..137a21a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ See https://gfloat.readthedocs.io for documentation. ``` pip install -e . -cd docs +cd docs make html cd .. ``` @@ -28,6 +28,5 @@ echo __token__ | twine upload --repository pypi dist/* --verbose #### Notes -All NaNs are the same, with no distinction between signalling or quiet, +All NaNs are the same, with no distinction between signalling or quiet, or between differently encoded NaNs. - diff --git a/docs/source/index.rst b/docs/source/index.rst index f5f0b93..146206d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,7 @@ of: * Precision (p) * Maximum exponent (emax) -with additional fields defining the encoding of infinities, Not-a-number (NaN) values, +with additional fields defining the encoding of infinities, Not-a-number (NaN) values, and negative zero. This allows an implementation of generic floating point encode/decode logic, From b89408f0c7722948a9c081f90e25b58d01447f3d Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 17:57:44 +0100 Subject: [PATCH 5/8] Add "smallest" --- src/gfloat/types.py | 16 ++++++++++++++-- test/test_decode.py | 3 +-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/gfloat/types.py b/src/gfloat/types.py index e8bd593..8ef283e 100644 --- a/src/gfloat/types.py +++ b/src/gfloat/types.py @@ -320,8 +320,10 @@ def smallest_normal(self) -> float: The smallest positive floating point number with 1 as leading bit in the significand following IEEE-754. """ - assert self.has_subnormals, "not implemented" - return 2 ** (1 - self.expBias) + if self.has_subnormals: + return 2 ** (1 - self.expBias) + else: + return 2**-self.expBias + 2 ** (-self.expBias - self.tSignificandBits) @property def smallest_subnormal(self) -> float: @@ -332,5 +334,15 @@ def smallest_subnormal(self) -> float: assert self.has_subnormals, "not implemented" return 2 ** -(self.expBias + self.tSignificandBits - 1) + @property + def smallest(self) -> float: + """ + The smallest positive floating point number. + """ + if self.has_subnormals: + return self.smallest_subnormal + else: + return self.smallest_normal + def __str__(self): return f"{self.name}" diff --git a/test/test_decode.py b/test/test_decode.py index 67f46b2..f6966a6 100644 --- a/test/test_decode.py +++ b/test/test_decode.py @@ -161,8 +161,7 @@ def test_specials_decode(fi): assert dec(fi.code_of_max) == fi.max assert dec(fi.code_of_min) == fi.min - if fi.has_subnormals: - assert dec(1) == fi.smallest_subnormal + assert dec(1) == fi.smallest @pytest.mark.parametrize( From d97c611e22f88a8c9fb586aa71d6ed4ca0b09b37 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 17:59:41 +0100 Subject: [PATCH 6/8] Docs improvements --- docs/source/01-decode.ipynb | 395 ++++++++++++++++++++++++++++++++++++ docs/source/api.rst | 17 ++ docs/source/conf.py | 4 +- docs/source/formats.rst | 14 ++ docs/source/index.rst | 47 ++--- docs/source/notebooks.rst | 9 + notebooks/01-decode.ipynb | 335 ------------------------------ src/gfloat/round.py | 2 +- 8 files changed, 454 insertions(+), 369 deletions(-) create mode 100644 docs/source/01-decode.ipynb create mode 100644 docs/source/api.rst create mode 100644 docs/source/formats.rst create mode 100644 docs/source/notebooks.rst delete mode 100644 notebooks/01-decode.ipynb diff --git a/docs/source/01-decode.ipynb b/docs/source/01-decode.ipynb new file mode 100644 index 0000000..4fb6461 --- /dev/null +++ b/docs/source/01-decode.ipynb @@ -0,0 +1,395 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "## GFloat Basics\n", + "\n", + "This notebook shows the use of `decode_float` to explore properties of some float formats.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "# Install packages\n", + "from pandas import DataFrame\n", + "import numpy as np\n", + "\n", + "from gfloat import decode_float\n", + "from gfloat.formats import *" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### List all the values in a format\n", + "\n", + "The first example shows how to list all values in a given format.\n", + "We will choose the [OCP](https://www.opencompute.org/documents/ocp-8-bit-floating-point-specification-ofp8-revision-1-0-2023-12-01-pdf-1) E5M2 format.\n", + "\n", + "The object `format_info_ocp_e5m2` is from the `gfloat.formats` package, and describes the characteristics of that format:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "FormatInfo(name='ocp_e5m2', k=8, precision=3, emax=15, has_nz=True, has_infs=True, num_high_nans=3, has_subnormals=True)" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "format_info_ocp_e5m2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We shall use the format to decode all values from 0..255, and gather them in a pandas DataFrame.\n", + "We see that `decode_float` returns a lot more than just the value - it also splits out the exponent, significand, and sign, and returns the `FloatClass`, which allows us to distinguish normal and subnormal numbers, as well as zero, infinity, and nan." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
fvalexpexpvalsignificandfsignificandsignbitfclass
ival
00.000000e+000-1400.000FloatClass.ZERO
11.525879e-050-1410.250FloatClass.SUBNORMAL
23.051758e-050-1420.500FloatClass.SUBNORMAL
34.577637e-050-1430.750FloatClass.SUBNORMAL
46.103516e-051-1401.000FloatClass.NORMAL
........................
251-5.734400e+04301531.751FloatClass.NORMAL
252-inf311601.001FloatClass.INFINITE
253NaN311611.251FloatClass.NAN
254NaN311621.501FloatClass.NAN
255NaN311631.751FloatClass.NAN
\n", + "

256 rows × 7 columns

\n", + "
" + ], + "text/plain": [ + " fval exp expval significand fsignificand signbit \\\n", + "ival \n", + "0 0.000000e+00 0 -14 0 0.00 0 \n", + "1 1.525879e-05 0 -14 1 0.25 0 \n", + "2 3.051758e-05 0 -14 2 0.50 0 \n", + "3 4.577637e-05 0 -14 3 0.75 0 \n", + "4 6.103516e-05 1 -14 0 1.00 0 \n", + "... ... ... ... ... ... ... \n", + "251 -5.734400e+04 30 15 3 1.75 1 \n", + "252 -inf 31 16 0 1.00 1 \n", + "253 NaN 31 16 1 1.25 1 \n", + "254 NaN 31 16 2 1.50 1 \n", + "255 NaN 31 16 3 1.75 1 \n", + "\n", + " fclass \n", + "ival \n", + "0 FloatClass.ZERO \n", + "1 FloatClass.SUBNORMAL \n", + "2 FloatClass.SUBNORMAL \n", + "3 FloatClass.SUBNORMAL \n", + "4 FloatClass.NORMAL \n", + "... ... \n", + "251 FloatClass.NORMAL \n", + "252 FloatClass.INFINITE \n", + "253 FloatClass.NAN \n", + "254 FloatClass.NAN \n", + "255 FloatClass.NAN \n", + "\n", + "[256 rows x 7 columns]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fmt = format_info_ocp_e5m2\n", + "vals = [decode_float(fmt, i) for i in range(256)]\n", + "DataFrame(vals).set_index(\"ival\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Additional format info: special values, min, max, dynamic range\n", + "\n", + "In addition, `FormatInfo` can tell us about other characteristics of each format.\n", + "To reproduce some of the OCP spec's tables 1 and 2:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exponent bias 7 15\n", + "emax 8 15\n", + "Infinities 0 2\n", + "Number of NaNs 2 6\n", + "Number of zeros 2 2\n", + "Max normal number 448.0 57344.0\n", + "Min normal number 0.015625 6.103515625e-05\n", + "Min subnormal number 0.001953125 1.52587890625e-05\n", + "Dynamic range (binades) 18 32\n" + ] + } + ], + "source": [ + "def compute_dynamic_range(fi):\n", + " return np.log2(fi.max/fi.smallest)\n", + "\n", + "for prop, probe in (\n", + " (\"Exponent bias \", lambda fi: fi.expBias),\n", + " (\"emax \", lambda fi: fi.emax),\n", + " (\"Infinities \", lambda fi: 2*fi.has_infs),\n", + " (\"Number of NaNs \", lambda fi: fi.num_nans),\n", + " (\"Number of zeros \", lambda fi: 1 + fi.has_nz),\n", + " (\"Max normal number \", lambda fi: fi.max),\n", + " (\"Min normal number \", lambda fi: fi.smallest_normal),\n", + " (\"Min subnormal number \", lambda fi: fi.smallest_subnormal),\n", + " (\"Dynamic range (binades)\", lambda x: round(compute_dynamic_range(x))),\n", + "):\n", + " print(f\"{prop} {probe(format_info_ocp_e4m3):<20} {probe(format_info_ocp_e5m2)}\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How do subnormals affect dynamic range?\n", + "\n", + "Most, if not all, low-precision formats include subnormal numbers, as they increase the number of values near zero, and increase dynamic range.\n", + "A natural question is \"by how much?\". To answer this, we can create a mythical new format, a copy of `e4m3`, but with `has_subnormals` set to true." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "e4m3_no_subnormals = copy.copy(format_info_ocp_e4m3)\n", + "e4m3_no_subnormals.has_subnormals = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And now compute the dynamic range with and without:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dynamic range with subnormals = 17.807354922057606\n", + "Dynamic range without subnormals = 15.637429920615292\n", + "Ratio = 4.5\n" + ] + } + ], + "source": [ + "dr_with = compute_dynamic_range(format_info_ocp_e4m3)\n", + "dr_without = compute_dynamic_range(e4m3_no_subnormals)\n", + "\n", + "print(f'Dynamic range with subnormals = {dr_with}')\n", + "print(f'Dynamic range without subnormals = {dr_without}')\n", + "print(f'Ratio = {2**(dr_with - dr_without):.1f}')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "ml_dtypes", + "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.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..7c4d5bf --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,17 @@ +API +=== + +.. module:: gfloat + +.. autofunction:: decode_float +.. autofunction:: round_float +.. autofunction:: encode_float + +.. autoclass:: FormatInfo() + :members: +.. autoclass:: FloatClass() + :members: +.. autoclass:: RoundMode() + :members: +.. autoclass:: FloatValue() + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py index d57a255..dd80f39 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,7 +5,7 @@ # -- Project information project = "GFloat" -copyright = "2023, Andrew Fitzgibbon" +copyright = "2024, Graphcore Ltd" author = "Andrew Fitzgibbon" release = "0.0.5" version = "0.0.5" @@ -19,6 +19,8 @@ "sphinx.ext.autosummary", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", + "sphinx_paramlinks", + "myst_nb", ] autodoc_default_options = { diff --git a/docs/source/formats.rst b/docs/source/formats.rst new file mode 100644 index 0000000..2014f5e --- /dev/null +++ b/docs/source/formats.rst @@ -0,0 +1,14 @@ +Defined Formats +=============== + +.. module:: gfloat.formats + +.. autodata:: format_info_binary32 +.. autodata:: format_info_binary16 +.. autodata:: format_info_bfloat16 +.. autodata:: format_info_ocp_e5m2 +.. autodata:: format_info_ocp_e4m3 +.. autofunction:: format_info_p3109 +.. autodata:: format_info_ocp_e3m2 +.. autodata:: format_info_ocp_e2m3 +.. autodata:: format_info_ocp_e2m1 diff --git a/docs/source/index.rst b/docs/source/index.rst index 146206d..7f255cb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,10 +3,6 @@ GFloat: Generic floating point formats in Python ================================================ -.. toctree:: - :maxdepth: 2 - :caption: Contents: - GFloat is designed to allow experimentation with a variety of floating-point formats in Python. Formats are parameterized by the primary IEEE-754 parameters of: @@ -16,7 +12,7 @@ of: * Maximum exponent (emax) with additional fields defining the encoding of infinities, Not-a-number (NaN) values, -and negative zero. +and negative zero, among others. This allows an implementation of generic floating point encode/decode logic, handling various current and proposed floating point types: @@ -25,39 +21,26 @@ handling various current and proposed floating point types: - `OCP Float8 `_: E5M2, E4M3 - `IEEE WG P3109 `_: P{p} for p in 1..7 +The library strongly favours readability and extensibility over speed - for fast +implementations of these datatypes see, for example, +`ml_dtypes `_, +`bitstring `_, +`MX PyTorch Emulation Library `_. -API -=== - -.. module:: gfloat +To get started with the library, we recommend perusing the notebooks, +otherwise you may wish to jump straight into the API. -.. autofunction:: decode_float -.. autofunction:: round_float -.. autofunction:: encode_float +.. toctree:: + :hidden: -.. autoclass:: FormatInfo() - :members: -.. autoclass:: FloatClass() - :members: -.. autoclass:: RoundMode() - :members: -.. autoclass:: FloatValue() - :members: + self -Defined Formats -=============== +.. toctree:: -.. module:: gfloat.formats + notebooks + api + formats -.. autodata:: format_info_binary32 -.. autodata:: format_info_binary16 -.. autodata:: format_info_bfloat16 -.. autodata:: format_info_ocp_e5m2 -.. autodata:: format_info_ocp_e4m3 -.. autofunction:: format_info_p3109 -.. autodata:: format_info_ocp_e3m2 -.. autodata:: format_info_ocp_e2m3 -.. autodata:: format_info_ocp_e2m1 Index and Search ================ diff --git a/docs/source/notebooks.rst b/docs/source/notebooks.rst new file mode 100644 index 0000000..a7c76d1 --- /dev/null +++ b/docs/source/notebooks.rst @@ -0,0 +1,9 @@ +Notebooks +========= + +Some notebooks to illustrate uses of the library + +.. toctree:: + :maxdepth: 1 + + 01-decode.ipynb diff --git a/notebooks/01-decode.ipynb b/notebooks/01-decode.ipynb deleted file mode 100644 index ea1a915..0000000 --- a/notebooks/01-decode.ipynb +++ /dev/null @@ -1,335 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright (c) 2024 Graphcore Ltd. All rights reserved.\n", - "\n", - "from pandas import DataFrame\n", - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
fvalvalstrexpexpvalsignificandfsignificandsignbitfclass
ival
00.000000e+000.00-1400.000FloatClass.ZERO
11.525879e-05~1.5258789e-050-1410.250FloatClass.SUBNORMAL
23.051758e-05~3.0517578e-050-1420.500FloatClass.SUBNORMAL
34.577637e-05~4.5776367e-050-1430.750FloatClass.SUBNORMAL
46.103516e-05~6.1035156e-051-1401.000FloatClass.NORMAL
...........................
251-5.734400e+04-57344.0301531.751FloatClass.NORMAL
252-inf-inf311601.001FloatClass.INFINITE
253NaNnan311611.251FloatClass.NAN
254NaNnan311621.501FloatClass.NAN
255NaNnan311631.751FloatClass.NAN
\n", - "

256 rows × 8 columns

\n", - "
" - ], - "text/plain": [ - " fval valstr exp expval significand fsignificand \\\n", - "ival \n", - "0 0.000000e+00 0.0 0 -14 0 0.00 \n", - "1 1.525879e-05 ~1.5258789e-05 0 -14 1 0.25 \n", - "2 3.051758e-05 ~3.0517578e-05 0 -14 2 0.50 \n", - "3 4.577637e-05 ~4.5776367e-05 0 -14 3 0.75 \n", - "4 6.103516e-05 ~6.1035156e-05 1 -14 0 1.00 \n", - "... ... ... ... ... ... ... \n", - "251 -5.734400e+04 -57344.0 30 15 3 1.75 \n", - "252 -inf -inf 31 16 0 1.00 \n", - "253 NaN nan 31 16 1 1.25 \n", - "254 NaN nan 31 16 2 1.50 \n", - "255 NaN nan 31 16 3 1.75 \n", - "\n", - " signbit fclass \n", - "ival \n", - "0 0 FloatClass.ZERO \n", - "1 0 FloatClass.SUBNORMAL \n", - "2 0 FloatClass.SUBNORMAL \n", - "3 0 FloatClass.SUBNORMAL \n", - "4 0 FloatClass.NORMAL \n", - "... ... ... \n", - "251 1 FloatClass.NORMAL \n", - "252 1 FloatClass.INFINITE \n", - "253 1 FloatClass.NAN \n", - "254 1 FloatClass.NAN \n", - "255 1 FloatClass.NAN \n", - "\n", - "[256 rows x 8 columns]" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from gfloat import decode_float\n", - "from gfloat.formats import *\n", - "\n", - "# Print all values in a float8 datatype\n", - "fmt = format_info_ocp_e5m2\n", - "vals = [decode_float(fmt, i) for i in range(256)]\n", - "DataFrame(vals).set_index(\"ival\").drop(columns=[\"fi\"])" - ] - }, - { - "cell_type": "code", - "execution_count": 42, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No assertion failure - all binary16 values decode consistently\n" - ] - } - ], - "source": [ - "# Check that our decodings match float16\n", - "def check_decodes(fmt, np_fmt, np_int, int_range):\n", - " for i in range(int_range):\n", - " val = decode_float(fmt, i)\n", - " npval_us = np.array(val.fval)\n", - " npval_np = np.array(i, dtype=np_int).view(dtype=np_fmt)\n", - " assert np.array_equal(\n", - " npval_us, npval_np, equal_nan=True\n", - " ), f\"{npval_us} != {npval_np}\"\n", - "\n", - " print(f\"No assertion failure - all {fmt.name} values decode consistently\")\n", - "\n", - "\n", - "check_decodes(format_info_binary16, np.float16, np.uint16, 0xFFFF)" - ] - }, - { - "cell_type": "code", - "execution_count": 43, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No assertion failure - all bfloat16 values decode consistently\n" - ] - } - ], - "source": [ - "# Check that our decodings match bfloat16\n", - "import ml_dtypes\n", - "\n", - "check_decodes(format_info_bfloat16, ml_dtypes.bfloat16, np.uint16, 0xFFFF)" - ] - }, - { - "cell_type": "code", - "execution_count": 44, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "No assertion failure - all ocp_e4m3 values decode consistently\n" - ] - } - ], - "source": [ - "# Check that our decodings match ml_dtypes 8-bit formats\n", - "import ml_dtypes\n", - "\n", - "check_decodes(format_info_ocp_e4m3, ml_dtypes.float8_e4m3fn, np.uint8, 0xFF)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "ml_dtypes", - "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.10.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/src/gfloat/round.py b/src/gfloat/round.py index b0b25f3..9ae5013 100644 --- a/src/gfloat/round.py +++ b/src/gfloat/round.py @@ -18,7 +18,7 @@ def round_float(fi: FormatInfo, v: float, rnd=RoundMode.TiesToEven, sat=False) - Round input to the given :py:class:`FormatInfo`, given rounding mode and saturation flag An input NaN will convert to a NaN in the target. - An input Infinity will convert to the largest float if |sat|, + An input Infinity will convert to the largest float if :paramref:`sat`, otherwise to an Inf, if present, otherwise to a NaN. Negative zero will be returned if the format has negative zero, otherwise zero. From bc5b3cb844c49faf61754fa9da15ad5d567e9f92 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 18:02:06 +0100 Subject: [PATCH 7/8] rerun, black --- docs/source/01-decode.ipynb | 38 +++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/source/01-decode.ipynb b/docs/source/01-decode.ipynb index 4fb6461..18b7541 100644 --- a/docs/source/01-decode.ipynb +++ b/docs/source/01-decode.ipynb @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -48,7 +48,7 @@ "FormatInfo(name='ocp_e5m2', k=8, precision=3, emax=15, has_nz=True, has_infs=True, num_high_nans=3, has_subnormals=True)" ] }, - "execution_count": 23, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -302,20 +302,21 @@ ], "source": [ "def compute_dynamic_range(fi):\n", - " return np.log2(fi.max/fi.smallest)\n", + " return np.log2(fi.max / fi.smallest)\n", + "\n", "\n", "for prop, probe in (\n", - " (\"Exponent bias \", lambda fi: fi.expBias),\n", - " (\"emax \", lambda fi: fi.emax),\n", - " (\"Infinities \", lambda fi: 2*fi.has_infs),\n", - " (\"Number of NaNs \", lambda fi: fi.num_nans),\n", - " (\"Number of zeros \", lambda fi: 1 + fi.has_nz),\n", - " (\"Max normal number \", lambda fi: fi.max),\n", - " (\"Min normal number \", lambda fi: fi.smallest_normal),\n", - " (\"Min subnormal number \", lambda fi: fi.smallest_subnormal),\n", - " (\"Dynamic range (binades)\", lambda x: round(compute_dynamic_range(x))),\n", + " (\"Exponent bias \", lambda fi: fi.expBias),\n", + " (\"emax \", lambda fi: fi.emax),\n", + " (\"Infinities \", lambda fi: 2 * fi.has_infs),\n", + " (\"Number of NaNs \", lambda fi: fi.num_nans),\n", + " (\"Number of zeros \", lambda fi: 1 + fi.has_nz),\n", + " (\"Max normal number \", lambda fi: fi.max),\n", + " (\"Min normal number \", lambda fi: fi.smallest_normal),\n", + " (\"Min subnormal number \", lambda fi: fi.smallest_subnormal),\n", + " (\"Dynamic range (binades)\", lambda x: round(compute_dynamic_range(x))),\n", "):\n", - " print(f\"{prop} {probe(format_info_ocp_e4m3):<20} {probe(format_info_ocp_e5m2)}\")\n" + " print(f\"{prop} {probe(format_info_ocp_e4m3):<20} {probe(format_info_ocp_e5m2)}\")" ] }, { @@ -330,11 +331,12 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "import copy\n", + "\n", "e4m3_no_subnormals = copy.copy(format_info_ocp_e4m3)\n", "e4m3_no_subnormals.has_subnormals = False" ] @@ -348,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -365,9 +367,9 @@ "dr_with = compute_dynamic_range(format_info_ocp_e4m3)\n", "dr_without = compute_dynamic_range(e4m3_no_subnormals)\n", "\n", - "print(f'Dynamic range with subnormals = {dr_with}')\n", - "print(f'Dynamic range without subnormals = {dr_without}')\n", - "print(f'Ratio = {2**(dr_with - dr_without):.1f}')" + "print(f\"Dynamic range with subnormals = {dr_with}\")\n", + "print(f\"Dynamic range without subnormals = {dr_without}\")\n", + "print(f\"Ratio = {2**(dr_with - dr_without):.1f}\")" ] } ], From 5001b6bcd8bb848169eda860e8aeb8e76598a2a8 Mon Sep 17 00:00:00 2001 From: Andrew Fitzgibbon Date: Fri, 26 Apr 2024 18:05:13 +0100 Subject: [PATCH 8/8] update requirements --- docs/requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/requirements.txt b/docs/requirements.txt index 53fc1f3..a9b2ada 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,4 @@ sphinx==7.1.2 sphinx-rtd-theme==1.3.0rc1 +sphinx_paramlinks +myst_nb