是的,我们可以,但每个存储字节将花费 64 KiByte 内存
浮点数存储在 4 字节 = 32 位中,
您可以使用整数指针“读取”它。
不将/转换/舍入为整数,而是“读取”原始字节。
在c代码中:
float f = 3.1415;
int *ip = NULL;
ip = (int *)&f;
printf("f = %f\ni = %i = 0x%x\n", f, *ip, *ip);
// f = 3.141500
// i = 1078529622 = 0x40490e56
// ^^^^
// bfloat16
使用所有浮点数作为数组索引,
我们需要 2**32 Byte = 4 GiByte 的内存
为了减少内存使用,我们可以使用bfloat16类型,
这只是一个“截断”版本(有损压缩)
32 位浮点数,
只有 7 位尾数:
bfloat16 = SEEEEEEE EMMMMMMM
float32 = SEEEEEEE EMMMMMMM MMMMMMMM MMMMMMMM
bfloat16 来炒作“机器学习”,
但也用于传感器数据。
一些奇特的处理器具有对 bfloat16 的硬件支持。
float32 和 bfloat16 之间的转换很简单,
仅由不同的字节顺序(字节序)复杂化
bfloat16.c
// convert between float32 and bfloat16
// use float as array index --> see end of file
// public domain + no warranty
#include <stdint.h> // uint16_t, int8_t
#include <stdlib.h> // malloc, free, size_t
#include <string.h> // memset
#include <stdbool.h> // bool
#include <stdio.h> // printf
typedef uint16_t bfloat16; // --> "maybe remove"
typedef float float32;
// stolen from tensorflow bfloat16.cc
void float32_to_bfloat16_be(float32 *src, bfloat16 *dst, size_t size)
{
uint16_t *s = (uint16_t *)src;
uint16_t *d = (uint16_t *)dst; // maybe remove
for (; size != 0; s += 2, d++, size--) {
// big endian byte order
*d = s[0];
}
}
void float32_to_bfloat16_le(float32 *src, bfloat16 *dst, size_t size)
{
uint16_t *s = (uint16_t *)src;
uint16_t *d = (uint16_t *)dst; // maybe remove
for (; size != 0; s += 2, d++, size--) {
// little endian byte order
*d = s[1];
}
}
void bfloat16_to_float32_be(bfloat16* src, float32* dst, size_t size)
{
uint16_t *s = (uint16_t *)src; // maybe remove
uint16_t *d = (uint16_t *)dst;
for (; size != 0; s++, d += 2, size--) {
// big endian byte order
d[0] = *s;
d[1] = 0;
}
}
void bfloat16_to_float32_le(bfloat16* src, float32* dst, size_t size)
{
uint16_t *s = (uint16_t *)src; // maybe remove
uint16_t *d = (uint16_t *)dst;
for (; size != 0; s++, d += 2, size--) {
// little endian byte order
d[0] = 0;
d[1] = *s;
}
}
// detect byte order at runtime
// http://esr.ibiblio.org/?p=5095#comment-415728
// this usually does not generate any code at all with GCC even with -O1
// ignore byte orders other than LE and BE
static inline bool is_big_endian() {
const uint16_t endianness = 256; // 0b 00000001 00000000
return *(const uint8_t *)&endianness;
}
// demo program
int main()
{
// function pointers for the current byte order
void (*float32_to_bfloat16)(float32*, bfloat16*, size_t) = NULL;
void (*bfloat16_to_float32)(bfloat16*, float32*, size_t) = NULL;
// detect byte order at runtime
if (is_big_endian())
{
printf("byte order is big endian\n");
float32_to_bfloat16 = &float32_to_bfloat16_be;
bfloat16_to_float32 = &bfloat16_to_float32_be;
}
else
{
printf("byte order is little endian\n");
float32_to_bfloat16 = &float32_to_bfloat16_le;
bfloat16_to_float32 = &bfloat16_to_float32_le;
}
// convert one number
float32 a1 = 3.1415;
bfloat16 b1 = 0;
float32_to_bfloat16(&a1, &b1, 1);
bfloat16 a2 = 0x4049;
float32 b2 = 0;
bfloat16_to_float32(&a2, &b2, 1);
printf("%1.4f --> 0x%04x\n", a1, b1);
printf("%1.4f <-- 0x%04x\n", b2, a2);
// convert many numbers
float32 a3[2] = {2.7182, 1.4142};
bfloat16 b3[2] = {0};
float32_to_bfloat16(a3, b3, 2);
bfloat16 a4[2] = {0x402d, 0x3fb5};
float32 b4[2] = {0};
bfloat16_to_float32(a4, b4, 2);
printf("%1.4f --> 0x%04x\n", a3[0], b3[0]);
printf("%1.4f <-- 0x%04x\n", b4[0], a4[0]);
printf("%1.4f --> 0x%04x\n", a3[1], b3[1]);
printf("%1.4f <-- 0x%04x\n", b4[1], a4[1]);
// array with float index [half-float index]
int8_t *n_fi = NULL; // int8_t = -128 .... +127
// init array
n_fi = malloc(0xffff * sizeof(typeof(*n_fi)));
memset(n_fi, 0, 0xffff * sizeof(typeof(*n_fi)));
// 0xffff = 2**16-1 = 65535 = 64Ki-1
// key
float32 k = 3.1415;
// convert key
bfloat16 k16 = 0;
float32_to_bfloat16(&k, &k16, 1);
// value
int8_t v = 123;
// write
n_fi[k16] = v;
// read
printf("n_fi[0x%04x] = %i\n", k16, n_fi[k16]);
// read next = zero from array init
printf("n_fi[0x%04x] = %i\n", k16+1, n_fi[k16+1]);
// close array
free(n_fi);
}
64 KiByte per Byte 是不是太多了?
我们可以将关键空间减少到“小的正浮点数”
但是我们需要边界检查来防止段错误。