Herbie: Automatically improve imprecise floating point formulas

(herbie.uwplse.org)

146 points | by summarity 4 days ago

10 comments

  • shiandow 4 minutes ago
    I wonder who decided to use a step function for the speed accuracy plot. They must have thought the convex hull would be wrong because you can't really make linear combinations of algorithms (you could, but you'd have to use time not speed to make it linear). So I get why you would use step functions, but the step is the wrong way around. The current plot suggests accuracy doesn't drop if you need higher speeds
  • summarity 1 hour ago
    I posted this and it picked up steam over night, so I thought I'd add how I'm using it:

    I work on 3D/4D math in F#. As part of the testing strategy for algorithms, I've set up a custom agent with an F# script that instruments Roslyn to find FP and FP-in-loop hotspots across the codebase.

    The agent then reasons through the implementation and writes core expressions into an FPCore file next to the existing tests, running several passes, refining the pres based on realistic caller input. This logs Herbie's proposed improvements as output FPCore transformations. The agent then reasons through solutions (which is required, Herbie doesn't know algorithm design intent, see e.g. this for a good case study: https://pavpanchekha.com/blog/herbie-rust.html), and once convinced of a gap, creates additional unit tests and property tests (FsCheck/QuickCheck) to prove impact. Then every once in a while I review a batch to see what's next.

    Generally there are multiple types of issues that can be flagged:

    a) Expression-level imprecision over realistic input ranges: this is Herbie's core strength. Usually this catches "just copied the textbook formula" instance of naive math. Cancellation, Inf/NaN propagation, etc. The fixes are consistently using fma for accumulation, biggest-factor scaling to prevent Inf, hypot use, etc.

    b) Ill-conditioned algorithms. Sometimes the text books lie to you, and the algorithms themselves are unfit for purpose, especially in boundary regions. If there are multiple expressions that have a <60% precision and only a 1 to 2% improvement across seeds, it's a good sign the algo is bad - there's no form that adequately performs on target inputs.

    c) Round-off, accumulation errors. This is more a consequence of agent reasoning, but often happens after an apparent "100% -> 100%" pass. The agent is able to, via failing tests, identify parts of an algorithm that can benefit from upgrading the context to e.g. double-word arithmetic for additional precision.

  • yread 6 hours ago
    Really cool, numerical stability can be tricky as errors can accumulate with each operation and suddenly 53 bits of precision is not enough.

    Also nice to see an article thats not about AI or politics

  • notpushkin 5 hours ago
    Some trivial cases produce... interesting results.

    For x in [−1.79e308, 1.79e308]:

    Initial Program: 100.0% accurate, 1.0× speedup

      def code(x):
          return math.sqrt((x + 1.0))
    
    Alternative 1: 67.5% accurate, 5.6× speedup

      def code(x):
          return 1.0
    • lifthrasiir 5 hours ago
      That does make sense, because a half of all available fp numbers are less than 1 in their magnitude. In particular there should be a plenty of numbers x such that |x| << 1 so x + 1 ~= 1; in fact, the proportion should be just shy of 50%.
      • cocoto 5 hours ago
        But I guess using the density distribution of floating points is rarely useful in a problem. Your actual distribution will almost surely be way different. Imo, the tool presented here should provide a way to manually provide a custom density function (with some common presets like uniform and normal distributions).
      • FabHK 24 minutes ago
        Not really. 1+x/2, however, would be a good approximation to sqrt(1+x) for small (in absolute value) x.
      • andrepd 3 hours ago
        That is indeed one of the problems with IEEE floats. There are only 10^80 atoms in the universe, and a Planck length is 1^-60th of the radius of the universe. But 64-bit floats have an absurd range of over 10^±300! Worse than that, notice that there are as many bit patterns in the never-used range between 10^300 and 10^301 as there are in the super-important range between 1 and 10! Super wasteful. Not to mention the quadrillions of values reserved to represent "NaN"...

        This is one of the problems that alternative formats such as the Posit aim to solve. It's quite interesting: I've got an implementation in rust here if you want to play with it https://github.com/andrepd/posit-rust

  • fluorinerocket 10 hours ago
    How useful is this when you are using numbers in a reasonable range, like 10^-12 to 10^12? Generally I try to scale my numbers to be in this range, whether by picking the right units or scaling constraints and objectives when doing nonlinear programming/ optimization.

    Like looking at this example,

    https://herbie.uwplse.org/demo/b070b371a661191752fe37ce0321c...

    It is claimed that for the function f(x) =sqrt(x+1) -1

    Accuracy is increased by from 8.5% accuracy to 98% for alternative 5 Which has f(x) = 0.5x

    Ok so x=99, the right answer is sqrt(100) -1 = 9

    But 0.5 * 99 = 49.5 which doesn't seem too accurate to me.

    • yossi_peti 10 hours ago
      The precondition on the link you shared has -1 <= x && x <= 1, so 99 is way outside of that range. But even so, testing for x=1, which is supposed to be inside that range, 0.5 doesn't seem tolerably close to 0.4142.
      • LiamPowell 6 hours ago
        I have a suspicion that the accuracy number is the mean of accuracies over all valid floats in the range (or something approximating that), which is going to be weighted towards zero where the accuracy is higher, and perhaps where sqrt near 1 has some artefacts.
    • hmpc 10 hours ago
      Check the specification at the top. The range for x is [-1, 1]. For the range you provided the accuracy of the 0.5x alternative is reported as only 33%: https://herbie.uwplse.org/demo/570b973df0f1f4a78fe791858038a...
      • fluorinerocket 10 hours ago
        You're right I misread the graph. That said though I have played around with Herbie before, trying it out on a few of the more gnarly expressions I had in my code (analytical partial derivatives if equations of motion if launch vehicle in rotating spherical frame) and didn't see much appreciable improvement over the expected range of values, but then again I didn't check every single one.

        What would be cool is if you could some how have this kind of analysis done automatically for your whole program where it finds the needle in the haystack expression that can be improved, assuming you gave expected ranges for your variables

  • bee_rider 10 hours ago
    I wonder, is there a way to only request reformulations that don’t involve branches? The tool already seems quite nice, but that might be a good feature.

    Also, I’m not sure I understand the speedup. Is it latency or throughput?

  • reikonomusha 8 hours ago
    This is an awesome piece of software, one of my favorite little pieces of magic. Finding more precise or more stable floating point formulas is often arduous and requires a lot of familiarity with the behavior of floats. This finds good formulas completely automatically. Super useful for numerical computation.
  • urschrei 4 hours ago
    I resurrected the Rust Herbie lint (now using dylint) a while ago: https://github.com/urschrei/herbie-lint
  • andrepd 2 hours ago
    I don't quite understand how they define "accuracy".
    • perching_aix 1 hour ago
      Could you substantiate that a bit more? I don't see what'd be hard to understand about it at a skim.
      • andrepd 1 minute ago
        Is it an arithmetic average of relative error over the given range? Because if yes then it can be misleading, and potentially a bad meshes to rank alternatives (though the HTML report includes a graph over the input range, which is quite nice, so I'm talking only about the accuracy number).

        In the limit, an alternative with 10x better accuracy when x>10^150 and 10x worse in 1<x<10^150 would rank higher :) but more generally, not all inputs are equally important.

        Furthermore, floats have underflow to 0 and overflow to infinity, which screw all this up because it can lead to infinite relative error.

        Because of this you have some of the funny cases reported elsewhere in this thread :p

        I'm not sure what would be a better approach though. Weigh the scores with a normal distribution around 0? Around 1? Exponents around 0?

  • lutusp 9 hours ago
    [flagged]
    • ashton314 8 hours ago
      You didn’t even look at what the tool does, did you?

      > If the issue is that people write bad floating-point expressions, a code-writing tutorial would be a better solution.

      Yeah you are just criticizing this without even looking at it. Shame.

    • dented42 8 hours ago
      Did you read what this does? Because I get the feeling you didn’t…

      This isn’t a library, you don’t include in your application, and it doesn’t try to replace an understanding of floating point issues on the programmers part.

      Is this comment written by AI?