Skip to content

feat: Golden testing against SymFPU #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9718d7c
chore: add symfpu
bollu Apr 26, 2025
9b038af
chore: add testing infra
bollu Apr 26, 2025
242bc33
chore: fix golden test
bollu Apr 29, 2025
132000e
chore: add link to IEEE FP paper
bollu Apr 27, 2025
3420ac9
Add rounding modes
spdskatr Apr 28, 2025
0a0e631
feat: implement FP negation
bollu Apr 27, 2025
93fe902
chore: fix comments from @spdskatr
bollu Apr 29, 2025
ced9f72
Add test_all toolchain
spdskatr Apr 29, 2025
52cdfe8
Implement division
spdskatr May 2, 2025
711f259
checkpoint symfpu generator (assertions fail)
spdskatr May 5, 2025
2e77e0c
Add FloatX test
spdskatr May 6, 2025
6d6a01c
Merge branch 'main' into golden
spdskatr May 6, 2025
889bc9d
Add rest of the tests to floatx
spdskatr May 6, 2025
611fa72
chore: add CI testing
bollu May 6, 2025
d7505ec
chore: add CI test for golden testing against floatx
bollu May 6, 2025
627764b
Fix bugs in rounding, mul and div
spdskatr May 7, 2025
77c9905
Merge branch 'main' into golden
spdskatr May 7, 2025
e463123
Change SymFPU tests to E3M4
spdskatr May 9, 2025
ee684cf
Add back simpleExecutable object file to build
spdskatr May 9, 2025
cc03384
Merge branch 'main' into golden
spdskatr May 10, 2025
59e8e2d
Fix ci
spdskatr May 10, 2025
73b7490
remove test
spdskatr May 12, 2025
d507cf6
chore: move folder to be called against-symfpu
bollu May 12, 2025
7c0568d
chore: add CI runner to compare symFPU and fplean
bollu May 12, 2025
80a984e
chore: add CI runner for SymFPU
bollu May 12, 2025
f66f0d6
chore: title correctly
bollu May 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export VIRTUAL_ENV=".venv"
layout python .venv/bin/python3

71 changes: 71 additions & 0 deletions .github/workflows/golden.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Golden testing
on:
push:
branches:
- "main"
pull_request:
merge_group:

permissions:
contents: write

jobs:
build:
name: Golden testing against FloatX + SymFPU
permissions:
pull-requests: write
# Exclude expensive self-hosted runner. Reserved for performance benchmarking.
# https://docs.github.com/en/enterprise-cloud@latest/actions/writing-workflows/choosing-where-your-workflow-runs/choosing-the-runner-for-a-job#choosing-github-hosted-runners
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3

- name: Install elan 🕑
run: |
set -o pipefail
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- --default-toolchain none -y
~/.elan/bin/lean --version
echo "$HOME/.elan/bin" >> $GITHUB_PATH

- name: Cache `.lake` folder
id: cache-lake
uses: actions/cache@v4
with:
path: .lake
key: ${{ runner.os }}-lake-${{ hashFiles('lake-manifest.json') }}-4

- name: Install C++ build tools (if needed)
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake # Add other C++ deps if needed

# - name: Get mathlib cache (only if no cache available)
# if: steps.cache-lake.outputs.cache-hit != 'true'
# run: |
# lake -R exe cache get # download cache of mathlib docs.
#
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10' # Specify your desired Python version

- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Run golden test for FloatX
run: |
./test/ci/compare-floatx-fplean.sh

- name: Run golden test for SymFPU
run: |
./test/ci/compare-symfpu-fplean.sh

4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*.csv
.venv/
/.lake

*.out
*.o
*.a
*.a
20 changes: 20 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
contourpy==1.3.1
cycler==0.12.1
duckdb==1.2.2
fonttools==4.56.0
importlib_metadata==8.6.1
kiwisolver==1.4.8
matplotlib==3.10.1
numpy==2.2.2
packaging==24.2
pandas==2.2.3
pillow==11.1.0
polars==1.24.0
pyparsing==3.2.1
python-dateutil==2.9.0.post0
pytz==2025.1
six==1.17.0
tabulate==0.9.0
tzdata==2025.1
visidata==3.1.1
zipp==3.21.0
3 changes: 3 additions & 0 deletions test/against-floatx/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.o
*.out

13 changes: 13 additions & 0 deletions test/against-floatx/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.PHONY: clean all

all: test.out

test.o: test.cpp
g++ -std=c++11 test.cpp -I../../third-party/FloatX/src -g -c -o test.o

test.out: test.o
g++ -std=c++11 test.o -o test.out

clean:
rm test.o test.out

55 changes: 55 additions & 0 deletions test/against-floatx/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "floatx.hpp"
#include <iostream>
#include <functional>

typedef flx::floatx<5,2> e5m2;
typedef flx::float_traits<e5m2>::backend_float bf;

e5m2 cons_fp8(uint8_t arg) {
return flx::detail::construct_number<5,2>(std::bitset<8>(arg));
}

constexpr std::bitset<8> nanBits{0b01111110};
std::bitset<8> to_bits(e5m2 arg, bool normNaN = false) {
std::bitset<8> argBits = flx::detail::get_fullbit_representation_BS<5,2>(bf(arg));
if (normNaN &&
((argBits.to_ulong() >> 2) & 0b11111) == 0b11111 &&
((argBits.to_ulong() & 3) != 0)) {
return nanBits;
}
return argBits;
}

void test_binop(std::string name, std::function<e5m2(e5m2,e5m2)> f) {
for (uint16_t i = 0; i < (1<<8); i++) {
for (uint16_t j = 0; j < (1<<8); j++) {
e5m2 a = cons_fp8(static_cast<uint8_t>(i));
e5m2 b = cons_fp8(static_cast<uint8_t>(j));
e5m2 c = f(a,b);
std::cout << name << "," << "RNE" << "," << \
to_bits(a) << "," << to_bits(b) << "," << \
to_bits(c, true) << "\n";
}
}
}

void test_predi(std::string name, std::function<bool(e5m2,e5m2)> f) {
for (uint16_t i = 0; i < (1<<8); i++) {
for (uint16_t j = 0; j < (1<<8); j++) {
e5m2 a = cons_fp8(static_cast<uint8_t>(i));
e5m2 b = cons_fp8(static_cast<uint8_t>(j));
bool c = f(a,b);
std::cout << name << "," << "RNE" << "," << \
to_bits(a) << "," << to_bits(b) << "," << \
(c ? "1" : "0") << "\n";
}
}
}

int main() {
test_binop("add", [](e5m2 a, e5m2 b) { return a + b; });
test_predi("lt" , [](e5m2 a, e5m2 b) { return a < b; });
test_binop("mul", [](e5m2 a, e5m2 b) { return a * b; });
test_binop("div", [](e5m2 a, e5m2 b) { return a / b; });
return 0;
}
3 changes: 3 additions & 0 deletions test/against-symfpu/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.o
*.out

5 changes: 5 additions & 0 deletions test/against-symfpu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Golden Testing against SymFPU

We generate golden CSV outputs to compare against SymFPU
for our own floating point implementation.

16 changes: 16 additions & 0 deletions test/against-symfpu/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY: clean all

all: test.out

test.o: test.cpp
g++ -std=c++11 test.cpp -I../../third-party/ -g -c -o test.o

test.out: test.o ../../third-party/symfpu/symfpu.a
g++ -std=c++11 ../../third-party/symfpu/symfpu.a ../../third-party/symfpu/baseTypes/simpleExecutable.o test.o -o test.out

../../third-party/symfpu/symfpu.a:
cd ../../third-party/symfpu && make

clean:
rm test.o test.out

130 changes: 130 additions & 0 deletions test/against-symfpu/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#include <cstddef>
#include "symfpu/baseTypes/simpleExecutable.h"
#include "symfpu/core/unpackedFloat.h"
#include "symfpu/core/packing.h"
#include "symfpu/core/add.h"
#include "symfpu/core/compare.h"
#include "symfpu/core/multiply.h"
#include "symfpu/core/divide.h"
#include <bitset>
#include <functional>

using namespace symfpu::simpleExecutable;
typedef symfpu::unpackedFloat<traits> uf;
typedef traits::ubv ubv;
traits::rm modes[5] = {
traits::RNA(),
traits::RNE(),
traits::RTN(),
traits::RTP(),
traits::RTZ()
};

// Note: This fails assertions because symfpu assumes the exponent is shorter than the
// mantissa
void test_rounding(void) {
// 10-bit E5M4 float
traits::fpt inFormat(5,5);
// 8-bit E5M2 float
traits::fpt outFormat(5,3);
for (traits::rm mode : modes) {
for (uint64_t i = 0; i < (1 << 1); i++) {
traits::ubv packed(10, i);
uf unpacked(symfpu::unpack<traits>(inFormat, packed));

uf rounded(symfpu::rounder<traits>(outFormat, mode, unpacked));
traits::ubv repacked(symfpu::pack<traits>(outFormat, rounded));
printf("%d\n", repacked.contents());
}
}
}

std::string to_mode(traits::rm mode) {
if (mode == traits::RNA()) return "RNA";
if (mode == traits::RNE()) return "RNE";
if (mode == traits::RTN()) return "RTN";
if (mode == traits::RTP()) return "RTP";
if (mode == traits::RTZ()) return "RTZ";
return "???";
}

// SymFPU already performs NaN normalisation
std::bitset<8> to_bits(ubv bitvec, bool normNaN = false) {
std::bitset<8> result;
uint64_t c = bitvec.contents();
for (int i = 0; i < 8; i++) {
result[7-i] = (c >> (7-i)) & 1;
}
return result;
}

// Test binary operation on 8 bits.
void test_binop(std::string name,
std::function<ubv(traits::rm, ubv, ubv)> f) {
for (traits::rm mode : modes) {
for (uint64_t i = 0; i < (1<<8); i++) {
for (uint64_t j = 0; j < (1<<8); j++) {
ubv packed1(8, i), packed2(8, j);
ubv result = f(mode, packed1, packed2);
std::cout << name << "," << to_mode(mode) << "," << \
to_bits(packed1) << "," << to_bits(packed2) << "," << \
to_bits(result, true) << "\n";
}
}
}
}

// Test binary predicate on 8 bits.
void test_predi(std::string name,
std::function<bool(traits::rm, ubv, ubv)> f) {
for (traits::rm mode : modes) {
for (uint64_t i = 0; i < (1<<8); i++) {
for (uint64_t j = 0; j < (1<<8); j++) {
ubv packed1(8, i), packed2(8, j);
bool result = f(mode, packed1, packed2);
std::cout << name << "," << to_mode(mode) << "," << \
to_bits(packed1) << "," << to_bits(packed2) << "," << \
(result ? 1 : 0) << "\n";
}
}
}
}

traits::fpt e3m4(3,5);
int main() {
test_binop("add", [](traits::rm mode, ubv a, ubv b) {
uf ua(symfpu::unpack<traits>(e3m4, a)),
ub(symfpu::unpack<traits>(e3m4, b));

uf uc(symfpu::add<traits>(e3m4, mode, ua, ub, traits::prop(true)));

return symfpu::pack<traits>(e3m4, uc);
});

test_predi("lt", [](traits::rm mode, ubv a, ubv b) {
uf ua(symfpu::unpack<traits>(e3m4, a)),
ub(symfpu::unpack<traits>(e3m4, b));

return symfpu::lessThan<traits>(e3m4, ua, ub);
});

test_binop("mul", [](traits::rm mode, ubv a, ubv b) {
uf ua(symfpu::unpack<traits>(e3m4, a)),
ub(symfpu::unpack<traits>(e3m4, b));

uf uc(symfpu::multiply<traits>(e3m4, mode, ua, ub));

return symfpu::pack<traits>(e3m4, uc);
});

test_binop("div", [](traits::rm mode, ubv a, ubv b) {
uf ua(symfpu::unpack<traits>(e3m4, a)),
ub(symfpu::unpack<traits>(e3m4, b));

uf uc(symfpu::divide<traits>(e3m4, mode, ua, ub));

return symfpu::pack<traits>(e3m4, uc);
});
return 0;
}

Loading