vyrn

std/narrow

30 exports, from std/narrow.vyrn.

Narrow number formats for storage: Float16, BFloat16, the two FP8 formats, the OCP MX blocks, the NVFP4 block and packed Int4. A program widens a value to Float32, computes, and narrows the result; the narrow types have no arithmetic of their own.

Every conversion is integer code over the Float32 bit view, so every engine gives the same bits. toFloat32 is exact. A narrowing rounds half to even and gives infinity past the largest finite value where the format has one, else NaN; its Sat form gives the largest finite value. A NaN narrows to the format's NaN code with the sign cleared. BFloat16 is Float32 rounded to its upper 16 bits, subnormals kept.

Dependencies

The import graph shows the whole library.

Imports
Imported by
type Float16 = { f16: UInt16 }

IEEE-754 binary16: a sign, 5 exponent bits with bias 15 and 10 mantissa bits. Its largest finite value is 65504 and its NaN code 0x7E00.

type BFloat16 = { bf16: UInt16 }

The upper 16 bits of a Float32: a sign, 8 exponent bits with bias 127 and 7 mantissa bits. Its NaN code is 0x7FC0.

type Float8E4M3 = { e4m3: UInt8 }

OCP FP8 E4M3: a sign, 4 exponent bits with bias 7 and 3 mantissa bits. It has no infinity; its largest finite value is 448 and its NaN code 0x7F.

type Float8E5M2 = { e5m2: UInt8 }

OCP FP8 E5M2: a sign, 5 exponent bits with bias 15 and 2 mantissa bits. Its largest finite value is 57344 and its NaN code 0x7E.

protocol Block {
    /// The value of element `i`, or NaN in a block whose scale is NaN.
    fn valueAt(self, i: Int64) -> Float32
}

A block of elements that share one scale.

type MxFp4Block = { e8m0: UInt8, e2m1: Array<UInt8, 16> }

MXFP4: E2M1 elements, which have no infinity or NaN and reach 6, two to a byte with element 2i in the low nibble of byte i.

type MxInt8Block = { e8m0: UInt8, int8: Array<Int8, 32> }

MXINT8: two's complement elements with an implicit scale of 2^-6. A narrowing keeps them within -127 to 127.

type NvFp4Block = { ue4m3: UInt8, e2m1: Array<UInt8, 8> }

NVFP4: 16 E2M1 elements, packed as in MxFp4Block, and a UE4M3 scale, an E4M3 code without a sign. A scale code of 127 is NaN; bit 7 is ignored.

fn toNvFp4(xs: read Array<Float32, 16>) -> NvFp4Block

The NVFP4 block of xs. The scale is the largest magnitude divided by 6, rounded to UE4M3 and saturated; each element is its value divided by the scale, rounded to E2M1 and saturated. An infinity counts as the largest finite Float32, and a NaN sets the scale to 127 and every element to 0.

fn packInt4(xs: read Array<Int8>) -> Array<UInt8>

xs as Int4 values, two to a byte with element 2i in the low nibble of byte i. Each value wraps to its low 4 bits, and an odd length leaves the last high nibble 0.

Tests

7 test blocks in std/narrow.vyrn. CI runs all of them.

Test names
  • the converter gives the hashes of its exhaustively checked C twin
  • every finite code widens to the value that narrows back to it
  • Float16 widens as F16C does and BFloat16 as a shift
  • the scalar formats round, overflow and keep a NaN's class
  • an MX block shares the scale of its largest magnitude
  • an NVFP4 block scales by its largest magnitude over 6
  • Int4 values pack two to a byte and wrap

Examples

    No example program imports this module.