SE-0277: Float16

The review of SE-0277 — Float16 begins now and runs through February 17, 2020.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager (via email or direct message in the Swift forums).

What goes into a review of a proposal?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift.

When reviewing a proposal, here are some questions to consider:

  • What is your evaluation of the proposal?

  • Is the problem being addressed significant enough to warrant a change to Swift?

  • Does this proposal fit well with the feel and direction of Swift?

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Thanks,
Ben Cohen
Review Manager

20 Likes

+1, yes, yes.

One minor omission that I assume was intended: the Clang importer should also be updated to import APIs with the C version of this into Swift.

Another question: is there (or should there) be any bridging support for NSNumber on apple platforms?

Quick read, it is a nice short proposal with clear motivation.

-Chris

7 Likes

This is implemented on the branch. For both C types that use the format (__fp16 and _Float16). =)

4 Likes

Cool thanks, it is a nit, but it would be great to mention that in the "detailed design" section of the proposal.

3 Likes

It is mentioned in the intro (though not the __fp16 part).

  • List item

+1 - a great addition that will find immediate use in my code. I look forward to being able to delete my UInt16-backed structs.

As more of an implementation curiosity: how will the SIMD conformance (and, I suppose, Float16 operation sequences in general) be handled on platforms that don't natively support Float16? Is the compiler sufficiently capable to defer converting between SIMD4<Float16> and SIMD4<Float> to the end of a sequence of arithmetic operations rather than before and after each operation? Compiler Explorer in C looks like cause for concern.

If we did that, it would not be a faithful implementation of Float16 arithmetic. Swift does not evaluate in a wider type unless the results are identical. (Note, however, that converting to Float for each operation individually and rounding back to Float16 is a faithful implementation of Float16 arithmetic.)

If you want that behavior, you need to explicitly convert to SIMD4<Float>. We could consider some way to annotate a code block with relaxed floating-point semantics in the future that would allow this and other transforms, but that’s a separate (and much larger) proposal.

4 Likes

+1

The proposal does a good job of explaining why this is useful.

One nitpick:

Q: What about math library support?

A: If this proposal is approved, I will add conformance to Real in Swift Numerics, providing the math library functions (by using the corresponding Float implementations initially).

IIRC, Real is actually destined for the standard library (at least, we have an accepted proposal for it, even if other issues prevent it being implemented yet).

If that is still the plan, it might be worth saying in the body of the proposal that Float16 conforms to Real (rather than in the "alternatives considered" section) -- even if that protocol and conformance only exist in the numerics library for now.

Actually, I suppose that Real, ElementaryFunctions, and the associated stdlib conformances (including Float16's conformances) should really all go in the stdlib preview package now.

1 Like

Float80 doesn't bridge with NSNumber, so neither should Float16.

A tiny but nice consequence of this is that after a conversion to AnyHashable, its values won't need to compare equal to values of any other numeric type. (I believe this is a good thing.)

1 Like

Super big +1!
Float16 and alias of Half should be primitive type. Let’s do it.

+1

Will be very useful, especially for interactions with image processing, machine learning, and GPUs APIs. Float16 typically was a blind spot requiring UInt16 modeling and conversions using specific libs to interact with. I'm also interested to see alternative types when that stabilizes.

1 Like

Ben and I discussed this briefly; our feeling is that it shouldn't go into the preview package until the necessary typechecking improvements are made to make it so that it can be integrated into the standard library (because it's entirely possible that we still may need to make some changes to keep the typechecker happy, and until we know if that is necessary--and precisely what changes are necessary--we can't commit to the final API that inclusion in preview would imply). So the protocols (and Float16's conformance to them) will have to remain in Swift Numerics for the medium-term.

+1

It always seemed like an odd omission to not have a smaller floating point Type available, especially given the plethora of Int## options.

+1
This will become incredibly useful when working with GPUs (or anything graphics related). Looking forward to seeing this in a (near) future Swift release.

Instant yes, yes, a 1000x yes.

My only quibble is that I’d love to see built-in support for fast conversions between Doubles & Floats & Float16s but I get that this should be in a separate proposal.

I mean, something a little more “one-line” than this:

    var halfsImageBuffer: vImage_Buffer = halfs.withUnsafeMutableBytes {
        vImage_Buffer(
            data: $0.baseAddress,
            height: vImagePixelCount(height),
            width: vImagePixelCount(width * channelsPerPixel),
            rowBytes: width * channelsPerPixel * MemoryLayout<Float>.size / 2
        )
    }
    
    var floatsImageBuffer = vImage_Buffer(
        data: UnsafeMutableRawPointer(mutating: floatsPointer),
        height: vImagePixelCount(height),
        width: vImagePixelCount(width * channelsPerPixel),
        rowBytes: width * channelsPerPixel * MemoryLayout<Float>.size)
    
    vImageConvert_Planar16FtoPlanarF(&halfsImageBuffer, &floatsImageBuffer, vImage_Flags(kvImagePrintDiagnosticsToConsole))

But then maybe if I just did it in a tight loop the Swift compiler would optimize the heck out of it? I guess we’ll see.

The trick with "fast" conversion operations like this is that you rarely only want to convert; you want to convert, and then do other things, and then maybe convert back. This means that you end up loading and storing the data many times, which swamps the actual cost of the arithmetic. If you're clever and tile to your L1 data cache, it's not too costly, but it's still often better to get autovectorized code that runs at (say) 50% speed but doesn't needlessly load and store.

Also, note that your code example will not work in the presence of optimization; the pointer produced by withUnsafeMutableBytes is only valid for the duration of the closure, so you can't store it in a vImage_Buffer object whose lifetime extends beyond that closure in general.

Ah, good point — the was some old code and I was still struggling with this whole withBlahBlah paradigm. Oddly Swift 5.2 throws warnings on a lot these mistakes now, but didn’t here.

In this case I actually do just want to convert, because all the math is done earlier, in Floats, which I then store on disk. So, yah, loading from disk is going to be the slow part, but there’s still an advantage in getting textures from main RAM to the GPU quickly.

1 Like

+1
I think the additional interoperability and use cases this will bring to Swift warrants the addition. The proposed naming and protocol conformances fit very well with the feel and direction of Swift. I have followed the proposal from the pitch phase, but do not consider myself an expert on IEEE 754 or floating point implementations.

1 Like

Did a relatively brief review, but it seems clear there is plenty of reasonable benefit, so I am in favor of this proposal.

I am also in favor of NOT adding extra aliases (like “half”), until and unless there is a strong use case for them.

I’m +1 on adding 16-bit floating point. I suspect that if the Swift project were starting today the type would be included off the bat.

I’m in favor of having both Half and Float16 as names, since they match the precedent for 32- and 64-bit floating point. Developers with experience in different languages/frameworks/domains are likely to know this type by different names, and it isn’t clear that one should be preferred to the point of excluding the other.

I’ve followed the pitch thread and read the proposal.

3 Likes