use std::cmp::Ordering;
use std::fmt::{self, Debug, Display};
use std::ops::{Add, Div, Mul, Neg, Sub};
use crate::backend;
use super::gemm::{self, GemmTask};
use super::{Differentiable, Element, Elementary};
#[derive(Clone, Copy)]
pub struct Bf16(u16);
impl Bf16 {
pub const ZERO: Self = Self(0x0000);
pub const ONE: Self = Self(0x3F80);
pub fn from_f32(value: f32) -> Self {
let bits = value.to_bits();
if value.is_nan() {
return Self(((bits >> 16) | 0x0040) as u16);
}
let rounding_bias = 0x7FFF + ((bits >> 16) & 1);
Self(((bits + rounding_bias) >> 16) as u16)
}
pub fn to_f32(self) -> f32 {
f32::from_bits((self.0 as u32) << 16)
}
pub fn to_bits(self) -> u16 {
self.0
}
pub fn from_bits(bits: u16) -> Self {
Self(bits)
}
}
impl From<f32> for Bf16 {
fn from(value: f32) -> Self {
Self::from_f32(value)
}
}
impl From<Bf16> for f32 {
fn from(value: Bf16) -> f32 {
value.to_f32()
}
}
impl PartialEq for Bf16 {
fn eq(&self, other: &Self) -> bool {
self.to_f32() == other.to_f32()
}
}
impl PartialOrd for Bf16 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.to_f32().partial_cmp(&other.to_f32())
}
}
impl Default for Bf16 {
fn default() -> Self {
Self::ZERO
}
}
impl From<f64> for Bf16 {
fn from(value: f64) -> Self {
Self::from_f32(value as f32)
}
}
impl From<Bf16> for f64 {
fn from(value: Bf16) -> f64 {
f64::from(value.to_f32())
}
}
impl Debug for Bf16 {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.to_f32(), formatter)
}
}
impl Display for Bf16 {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.to_f32(), formatter)
}
}
impl Add for Bf16 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::from_f32(self.to_f32() + rhs.to_f32())
}
}
impl Sub for Bf16 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::from_f32(self.to_f32() - rhs.to_f32())
}
}
impl Mul for Bf16 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self::from_f32(self.to_f32() * rhs.to_f32())
}
}
impl Div for Bf16 {
type Output = Self;
fn div(self, rhs: Self) -> Self {
Self::from_f32(self.to_f32() / rhs.to_f32())
}
}
impl Neg for Bf16 {
type Output = Self;
fn neg(self) -> Self {
Self(self.0 ^ 0x8000)
}
}
impl Differentiable for Bf16 {
type Accumulator = f32;
fn promote(&self) -> f32 {
self.to_f32()
}
fn demote(accumulated: f32) -> Self {
Self::from_f32(accumulated)
}
fn zero() -> Self {
Self::ZERO
}
fn one() -> Self {
Self::ONE
}
fn from_count(count: usize) -> Self {
Self::from_f32(count as f32)
}
fn is_count(&self, count: usize) -> bool {
*self == Self::from_f32(count as f32)
}
}
impl Elementary for Bf16 {
fn exp(&self) -> Self {
Self::from_f32(Elementary::exp(&self.to_f32()))
}
fn ln(&self) -> Self {
Self::from_f32(Elementary::ln(&self.to_f32()))
}
fn sqrt(&self) -> Self {
Self::from_f32(self.to_f32().sqrt())
}
fn tanh(&self) -> Self {
Self::from_f32(Elementary::tanh(&self.to_f32()))
}
fn sin(&self) -> Self {
Self::from_f32(Elementary::sin(&self.to_f32()))
}
fn cos(&self) -> Self {
Self::from_f32(Elementary::cos(&self.to_f32()))
}
fn log1p(&self) -> Self {
Self::from_f32(Elementary::log1p(&self.to_f32()))
}
fn expm1(&self) -> Self {
Self::from_f32(Elementary::expm1(&self.to_f32()))
}
fn erf(&self) -> Self {
Self::from_f32(Elementary::erf(&self.to_f32()))
}
fn erf_derivative(&self) -> Self {
Self::from_f32(Elementary::erf_derivative(&self.to_f32()))
}
fn powf(&self, exponent: Self) -> Self {
Self::from_f32(Elementary::powf(&self.to_f32(), exponent.to_f32()))
}
fn maximum(&self, other: &Self) -> Self {
Self::from_f32(self.to_f32().max(other.to_f32()))
}
fn step(&self, threshold: &Self) -> Self {
if self.to_f32() >= threshold.to_f32() {
Self::ONE
} else {
Self::ZERO
}
}
fn gemm(task: &GemmTask<'_, Self>) -> Option<Vec<Self>> {
let a: Vec<f32> = task.a().iter().map(|element| element.to_f32()).collect();
let b: Vec<f32> = task.b().iter().map(|element| element.to_f32()).collect();
let expanded = GemmTask::new(
&a,
task.a_strides(),
&b,
task.b_strides(),
task.m(),
task.k(),
task.n(),
);
let product = backend::offered(&expanded).unwrap_or_else(|| gemm::multiply(&expanded));
Some(product.into_iter().map(Self::from_f32).collect())
}
}
impl Element for Bf16 {}
#[cfg(test)]
#[path = "tests/bf16_tests.rs"]
mod tests;