Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fused multiply-add: proposal to add math.fma() #73468

Closed
jurajsukop mannequin opened this issue Jan 16, 2017 · 42 comments
Closed

Fused multiply-add: proposal to add math.fma() #73468

jurajsukop mannequin opened this issue Jan 16, 2017 · 42 comments
Labels
3.7 (EOL) end of life stdlib Python modules in the Lib dir type-feature A feature request or enhancement

Comments

@jurajsukop
Copy link
Mannequin

jurajsukop mannequin commented Jan 16, 2017

BPO 29282
Nosy @mdickinson, @pitrou, @vstinner, @stevendaprano, @skrah, @nschloe, @serhiy-storchaka, @efahl, @cdonovick
PRs
  • bpo-29282: Add math.fma(): fused multiply-add function #17987
  • Files
  • xmathmodule.c
  • example.py
  • setup.py
  • fma_reference.py
  • math_fma.patch
  • math_fma2.patch
  • math_fma3.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2017-01-16.10:01:37.166>
    labels = ['3.7', 'type-feature', 'library']
    title = 'Fused multiply-add: proposal to add math.fma()'
    updated_at = <Date 2021-06-19.09:24:45.622>
    user = 'https://bugs.python.org/jurajsukop'

    bugs.python.org fields:

    activity = <Date 2021-06-19.09:24:45.622>
    actor = 'mark.dickinson'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2017-01-16.10:01:37.166>
    creator = 'juraj.sukop'
    dependencies = []
    files = ['46298', '46299', '46300', '46304', '46321', '46345', '46346']
    hgrepos = []
    issue_num = 29282
    keywords = ['patch']
    message_count = 36.0
    messages = ['285547', '285549', '285550', '285551', '285552', '285553', '285557', '285564', '285565', '285566', '285582', '285682', '285685', '285836', '285837', '285839', '285840', '285841', '285843', '285844', '285955', '285956', '285957', '285958', '285959', '286201', '312431', '312432', '312433', '312480', '312488', '359903', '359911', '359913', '359921', '359926']
    nosy_count = 11.0
    nosy_names = ['mark.dickinson', 'pitrou', 'vstinner', 'steven.daprano', 'skrah', 'nschloe', 'python-dev', 'serhiy.storchaka', 'eric.fahlgren', 'juraj.sukop', 'donovick']
    pr_nums = ['17987']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue29282'
    versions = ['Python 3.7']

    Linked PRs

    @jurajsukop
    Copy link
    Mannequin Author

    jurajsukop mannequin commented Jan 16, 2017

    Fused multiply-add (henceforth FMA) is an operation which calculates the product of two numbers and then the sum of the product and a third number with just one floating-point rounding. More concretely:

        r = x*y + z

    The value of r is the same as if the RHS was calculated with infinite precision and the rounded to a 32-bit single-precision or 64-bit double-precision floating-point number [1].

    Even though one FMA CPU instruction might be calculated faster than the two separate instructions for multiply and add, its main advantage comes from the increased precision of numerical computations that involve the accumulation of products. Examples which benefit from using FMA are: dot product [2], compensated arithmetic [3], polynomial evaluation [4], matrix multiplication, Newton's method and many more [5].

    C99 includes [6] fma function to math.h and emulates the calculation if the FMA instruction is not present on the host CPU [7]. PEP-7 states that "Python versions greater than or equal to 3.6 use C89 with several select C99 features" and that "Future C99 features may be added to this list in the future depending on compiler support" [8].

    This proposal is then about adding new fma function with the following signature to math module:

        math.fma(x, y, z)
        
    '''Return a float representing the result of the operation `x*y + z` with single rounding error, as defined by the platform C library. The result is the same as if the operation was carried with infinite precision and rounded to a floating-point number.'''

    Attached is a simple module for Python 3 demonstrating the fused multiply-add operation. On my machine, example.py prints:

    40037.524591982365   horner_double
    40037.48821639768    horner_fma
    40037.49486325783    horner_compensated
    40037.49486325783    horner_decimal
    

    [1] https://en.wikipedia.org/wiki/Multiply%E2%80%93accumulate_operation
    [2] S. Graillat, P. Langlois, N. Louvet. Accurate dot products with FMA. 2006
    [3] S. Graillat, Accurate Floating Point Product and Exponentiation. 2007.
    [4] S. Graillat, P. Langlois, N. Louvet. Improving the compensated Horner scheme with a Fused Multiply and Add. 2006
    [5] J.-M. Muller, N. Brisebarre, F. de Dinechin, C.-P. Jeannerod, V. Lefèvre, G. Melquiond, N. Revol, D. Stehlé, S. Torres. Handbook of Floating-Point Arithmetic. 2010. Chapter 5
    [6] ISO/IEC 9899:TC3, "7.12.13.1 The fma functions", Committee Draft - Septermber 7, 2007
    [7] https://git.musl-libc.org/cgit/musl/tree/src/math/fma.c
    [8] https://www.python.org/dev/peps/pep-0007/

    @jurajsukop jurajsukop mannequin added 3.7 (EOL) end of life stdlib Python modules in the Lib dir type-feature A feature request or enhancement labels Jan 16, 2017
    @vstinner
    Copy link
    Member

    @pitrou
    Copy link
    Member

    pitrou commented Jan 16, 2017

    What's the point of adding this in the math module rather than a more specialized library like Numpy?

    @jurajsukop
    Copy link
    Mannequin Author

    jurajsukop mannequin commented Jan 16, 2017

    I would say because it has wide applicability, especially considering the amount of code it adds. It is similar in spirit to copysign or fsum which are already there and C99 includes it anyway. For simpler things like dot product or polynomial evaluation importing Numpy might be too much of an dependency.

    @pitrou
    Copy link
    Member

    pitrou commented Jan 16, 2017

    I don't know. If I want to compute a dot product, the first thing I'll do is import Numpy and then use the @ operator on Numpy arrays.

    @serhiy-storchaka
    Copy link
    Member

    The performance argument unlikely is applicable in this case. I suppose that an overhead of function call in Python make two operators faster than one function call.

    Alternatives to fma() for exact computations are integer arithmetic (if all values can be scaled to integers), fractions and decimal numbers.

    But since fma() is a part of C (C99), C++ (C++11) and POSIX (POSIX.1-2001) standards for long time, I don't have objections against including it in the math module.

    @mdickinson
    Copy link
    Member

    The performance argument unlikely is applicable in this case.

    Agreed. This is mainly about accuracy, not speed: the FMA operation is a fundamental building block for floating-point arithmetic, is useful in some numerical algorithms, and essential in others (especially when doing things like double-double arithmetic). It would be valuable to have when prototyping numerical algorithms in pure Python.

    Given that it's supported in C99 and on current Windows, I'm +1 on including it in the math module.

    Note that implementing this it not quite as straightforward as simply wrapping the libm version, since we'll also want the correct exceptional behaviour, for consistency with the rest of the math module: i.e., we should be raising ValueError where the fma operation would signal the invalid FPE, and OverflowError where the fma operation would signal the overflow FPE.

    @mdickinson
    Copy link
    Member

    An implementation note: IEEE 754-2008 leaves it to the implementation to decide whether FMA operations like:

    0 * inf + nan
    

    and

    inf * 0 + nan
    

    (where nan represents a quiet NaN and the inf and 0 can have arbitrary signs) signal the invalid operation FPE or not. (Ref: 7.2(c) in IEEE 754-2008.)

    I'd suggest that in this case we follow what Intel does in its x64 chips with FMA3 support.(according to ). If I'm reading the table in section 2.3 of the Intel Advanced Vector Extensions Programming Reference correctly, Intel does *not* signal the invalid operation FPE in this case. That is, we're following the usual rule of quiet NaN in => quiet NaN out with no exception. This does unfortunately conflict with the IBM decimal specification and Python's decimal module, where these operations *do* set the "invalid" flag (see the spec, and test fmax0809 in the decimal test set).

    @pitrou
    Copy link
    Member

    pitrou commented Jan 16, 2017

    Isn't the behaviour of quiet NaNs kindof implementation-dependent already?

    @mdickinson
    Copy link
    Member

    Isn't the behaviour of quiet NaNs kindof implementation-dependent already?

    Not as far as IEEE 754-2008 is concerned, and not as far as Python's math module is concerned, either: handling of special cases is, as far as I know, both consistent across platforms and compliant with IEEE 754. That's not true across Python as a whole, but it should be true for the math module.

    If you find an exception to the above statement, please do open a bug report!

    @mdickinson
    Copy link
    Member

    Here's a pure Python reference implementation, with tests.

    @mdickinson
    Copy link
    Member

    And here's a patch.

    @serhiy-storchaka
    Copy link
    Member

    LGTM except that needed the versionadded directive and What's New entry. And maybe few additional tests.

    @mdickinson
    Copy link
    Member

    Serhiy, Victor: thanks for the reviews. Here's a new patch. Differences w.r.t. the old one:

    • Converted to argument clinic.
    • Updated docstring to talk about special cases.
    • More tests, as suggested by Serhiy.
    • whatsnew entry and ..versionadded in docs.

    @mdickinson
    Copy link
    Member

    Whoops; looks like I failed to attach the updated patch. Here it is.

    @serhiy-storchaka
    Copy link
    Member

    LGTM except that lines in What's New are too long.

    @mdickinson
    Copy link
    Member

    lines in What's New are too long.

    Thanks. Fixed (I think). I'm not sure what the limit is, but the lines are now all <= 79 characters long.

    @mdickinson
    Copy link
    Member

    Ah, the dev guide says 80 characters. (https://docs.python.org/devguide/documenting.html)

    @serhiy-storchaka
    Copy link
    Member

    Then LGTM unconditionally.

    @mdickinson
    Copy link
    Member

    Thanks, Serhiy. I'm going to hold off committing this for 24 hours or so, because I want to follow the buildbots when I do (and I don't have time for that right now). I wouldn't be at all surprised to see platform-specific test failures.

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Jan 21, 2017

    New changeset b33012ef1417 by Mark Dickinson in branch 'default':
    Issue bpo-29282: add fused multiply-add function, math.fma.
    https://hg.python.org/cpython/rev/b33012ef1417

    @serhiy-storchaka
    Copy link
    Member

    Cross fingers...

    @mdickinson
    Copy link
    Member

    Failures on the Windows buildbot (http://buildbot.python.org/all/builders/AMD64%20Windows8.1%20Non-Debug%203.x/builds/238/steps/test/logs/stdio) shown below.

    It looks as though Windows is emulating the FMA operation on this machine (and not doing a particularly good job of it). That means that if we want to support Windows (and we do), we may have to emulate ourselves, preferably using something a bit more efficient than the fractions.Fraction module.

    I'll let the buildbots complete, to see what else fails, and then roll back the commit. The patch clearly isn't good enough in its current state.

    ======================================================================
    ERROR: test_fma_overflow (test.test_math.FMATests)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "D:\buildarea\3.x.ware-win81-release\build\lib\test\test_math.py", line 1565, in test_fma_overflow
        self.assertEqual(math.fma(a, b, -c),
    OverflowError: overflow in fma

    ======================================================================
    FAIL: test_fma_zero_result (test.test_math.FMATests)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "D:\buildarea\3.x.ware-win81-release\build\lib\test\test_math.py", line 1524, in test_fma_zero_result
        self.assertIsNegativeZero(math.fma(tiny, -tiny, 0.0))
      File "D:\buildarea\3.x.ware-win81-release\build\lib\test\test_math.py", line 1642, in assertIsNegativeZero
        msg="Expected a negative zero, got {!r}".format(value)
    AssertionError: False is not true : Expected a negative zero, got 0.0

    ======================================================================
    FAIL: test_random (test.test_math.FMATests)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "D:\buildarea\3.x.ware-win81-release\build\lib\test\test_math.py", line 1623, in test_random
        self.assertEqual(math.fma(a, b, c), expected)
    AssertionError: 0.5506672157701096 != 0.5506672157701097

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Jan 21, 2017

    New changeset b5a5f13500b9 by Mark Dickinson in branch 'default':
    Issue bpo-29282: Backed out changeset b33012ef1417
    https://hg.python.org/cpython/rev/b5a5f13500b9

    @mdickinson
    Copy link
    Member

    Also failures on Gentoo: here b is positive (possibly +inf), and c is finite, so we expect an infinite result. Instead, we're apparently getting a NaN. I don't have a good guess about what's causing this: the rest of the tests are passing, so it's unlikely that we're using a bad FMA emulation. Maybe an optimization bug?

    ======================================================================
    ERROR: test_fma_infinities (test.test_math.FMATests)
    ----------------------------------------------------------------------

    Traceback (most recent call last):
      File "/buildbot/buildarea/3.x.ware-gentoo-x86.installed/build/target/lib/python3.7/test/test_math.py", line 1482, in test_fma_infinities
        self.assertEqual(math.fma(math.inf, b, c), math.inf)
    ValueError: invalid operation in fma

    @mdickinson
    Copy link
    Member

    The patch needs tests for the case where a*b overflows and c is infinite (either of the same sign as a*b or not). This combination should never return NaN, but a poor emulation of fma might do so.

    @nschloe
    Copy link
    Mannequin

    nschloe mannequin commented Feb 20, 2018

    Do I read this thread correctly assuming that this hasn't been implemented yet? If not, I would probably make my own little library for this -- I really need the feature for the precision.

    @mdickinson
    Copy link
    Member

    Do I read this thread correctly assuming that this hasn't been implemented yet?

    Yes. Existing libm implementations don't work, so simply wrapping the libm function isn't enough. And writing an implementation from scratch is non-trivial.

    @nschloe
    Copy link
    Mannequin

    nschloe mannequin commented Feb 20, 2018

    Existing libm implementations don't work,

    Okay. Is this because of the inf/NaN discrimination hiccups mentioned above or are there any other pitfalls?

    @mdickinson
    Copy link
    Member

    Is this because of the inf/NaN discrimination hiccups [...]

    No, more than that. If it were just that, we could work around it by adding the appropriate special case handling before calling the libm fma (as has been done, reluctantly, with some of the other math module functions; see the source for math.pow, for example).

    But the fma implementation on Windows is fundamentally broken. For finite numbers, it simply doesn't give what it's supposed to (a * b + c, computed with a _single_ rounding). Since that single rounding is most of the point of fma, that makes the libm fma not fit for purpose on Windows.

    It _is_ possible, with care, to code up a not-too-inefficient fma emulation using clever tricks like Veltkamp splitting and Dekker multiplication. I have half such an implementation sitting on my home computer, but so far have not had the cycles to finish it (and it's not high on the priority list right now).

    @nschloe
    Copy link
    Mannequin

    nschloe mannequin commented Feb 21, 2018

    Okay, thanks for the info.

    As a stop-gap measure, I've created pyfma [1, 2]. Install with

    pip install pyfma
    

    and use with

    pyfma.fma(3.0, 2.0, 1.0)
    

    Only works on Unix reliable then, but that's all I care about. :)

    Cheers,
    Nico

    [1] https://github.com/nschloe/pyfma
    [2] https://pypi.python.org/pypi/pyfma

    @vstinner
    Copy link
    Member

    I converted https://hg.python.org/cpython/rev/b33012ef1417 written by Mark Dickinson into a GitHub PR: PR 17987. I still expect tests failures. I plan to use the PR as a starting point to implement math.fma(). If tests continue to fail on some platforms, I plan to manually handle NaN and INF in the C code, before calling libc fma().

    @mdickinson
    Copy link
    Member

    If tests continue to fail on some platforms, I plan to manually handle NaN and INF in the C code, before calling libc fma().

    For Windows, you need to do much more than this: it's not just about handling NaNs and infinities, it's about reimplementing the entire function from scratch to give correctly rounded results. Without correctly-rounded results, there's very little point in having fma.

    If it were a couple of niche platforms that gave bad results, then we could push this through. But it's Windows. :-(

    @mdickinson
    Copy link
    Member

    Okay, looks like Windows is happy in the PR's continuous integration. If the buildbots are also happy, then I'm content to have this pushed through.

    @vstinner
    Copy link
    Member

    For Windows, you need to do much more than this: it's not just about handling NaNs and infinities, it's about reimplementing the entire function from scratch to give correctly rounded results. Without correctly-rounded results, there's very little point in having fma.

    Would it make sense to only make the function available on non-Windows platforms? As we do for other Unix-only functions in the os module. Maybe even skip more platforms if they provide a broken implementation.

    We could implement a test suite in configure to decide if fma() fits our requirements or not.

    @jurajsukop
    Copy link
    Mannequin Author

    jurajsukop mannequin commented Jan 13, 2020

    FWIW, there is a new implementation of FMA [1] which is licensed very permissively [2]. Perhaps it could be used here as well..?

    [1] https://github.com/smasher164/fma
    [2] smasher164/fma@4e85d23

    @mdickinson mdickinson removed their assignment Jun 19, 2021
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @clairvoyante
    Copy link

    FYI :
    dotnet/runtime#98704

    @tannergooding
    Copy link

    It's worth noting that if you want a performant and open source implementation, you have:

    I believe there is also one available from netlib, but I don't have that link handy. Arm typically has algorithms available on its repository, but doesn't provide one for fma because all Arm64 based hardware provide an fma instruction natively: https://github.com/ARM-software/optimized-routines

    @tannergooding
    Copy link

    Yes, if you want the 64-bit (double) versions they exist in neighboring files. Notably doing something like (float)fma((double)x, (double)y, (double)z) may not be sufficient in all cases if you want a 32-bit (float) result. There is a subset of operations for which the rounding twice is known to be a non-issue, but I don't recall off the top of my head if fma is included in that, so it may be desirable to have both (and there may even be a performance difference).

    vstinner added a commit to vstinner/cpython that referenced this issue Mar 12, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-Authored-By: Mark Dickinson <mdickinson@enthought.com>
    @vstinner
    Copy link
    Member

    My previous attempt in 2020 failed. I try again in 2024: PR #116667

    vstinner added a commit to vstinner/cpython that referenced this issue Mar 13, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-Authored-By: Mark Dickinson <mdickinson@enthought.com>
    vstinner added a commit to vstinner/cpython that referenced this issue Mar 13, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-Authored-By: Mark Dickinson <mdickinson@enthought.com>
    vstinner added a commit to vstinner/cpython that referenced this issue Mar 17, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-Authored-By: Mark Dickinson <mdickinson@enthought.com>
    vstinner added a commit that referenced this issue Mar 17, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
    @vstinner
    Copy link
    Member

    Function added by commit 8e3c953. I only took 7 years only Linux, macOS and Windows get a good support of fma() :-)

    vstinner added a commit to vstinner/cpython that referenced this issue Mar 20, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
    adorilson pushed a commit to adorilson/cpython that referenced this issue Mar 25, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
    diegorusso pushed a commit to diegorusso/cpython that referenced this issue Apr 17, 2024
    Added new math.fma() function, wrapping C99's ``fma()`` operation:
    fused multiply-add function.
    
    Co-authored-by: Mark Dickinson <mdickinson@enthought.com>
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.7 (EOL) end of life stdlib Python modules in the Lib dir type-feature A feature request or enhancement
    Projects
    None yet
    Development

    No branches or pull requests

    6 participants