Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 7 additions & 63 deletions shared/hostnumerics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,68 +68,12 @@ reference. Compare representative input-generation and reference-computation
timings, as well as their effect on complete test and benchmark runs, before
removing an old accelerated path.

C++ tests use GoogleTest 1.12.1 or newer, retaining the repository's older compatibility baseline because these assertions need no newer API. Top-level test builds default to fetching 1.17.0, matching the shared superbuild, when no compatible installed package is available; set `HOSTNUMERICS_ENABLE_FETCH=OFF` to require an existing package. When a parent enables HostNumerics tests through `add_subdirectory`, fetching defaults to `OFF`: provide a compatible GoogleTest package or target, or explicitly set `HOSTNUMERICS_ENABLE_FETCH=ON`. Existing `GTest::gtest_main` targets must expose their version through the target's `VERSION` property or `GTest_VERSION`. GoogleTest is not installed or exported with HostNumerics.
## Documentation

## Build and test
See [build and package instructions](docs/build_and_test.md) for dependencies,
configuration, testing, installation, and CMake integration, and
[error handling](docs/error_handling.md) for the component's error convention.

The standalone build requires CMake 3.25.2 or newer, a C++20 compiler, and Ninja.
The Python module also needs Python 3.10 or newer with development headers and
nanobind. From the repository root, the following creates a virtual environment
and build directory outside the source tree:

```shell
hostnumerics_venv="$PWD/../venvs/hostnumerics"
hostnumerics_build="$PWD/../builds/hostnumerics"
python3 -m venv "$hostnumerics_venv"
"$hostnumerics_venv/bin/python" -m pip install nanobind==3.0.1

cmake -S shared/hostnumerics -B "$hostnumerics_build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DPython_EXECUTABLE="$hostnumerics_venv/bin/python" \
-DHOSTNUMERICS_BUILD_TESTING=ON \
-DHOSTNUMERICS_BUILD_PYTHON=ON
cmake --build "$hostnumerics_build" --parallel 4
ctest --test-dir "$hostnumerics_build" --output-on-failure
```

For a C++-only build, set `HOSTNUMERICS_BUILD_PYTHON=OFF`; Python and nanobind
are then unnecessary.

`HOSTNUMERICS_BUILD_TESTING` and `HOSTNUMERICS_BUILD_PYTHON` both default to `ON`
when building HostNumerics standalone and `OFF` when another project includes it
with `add_subdirectory`. CI covers ON/ON with Ninja Multi-Config and OFF/OFF with
Ninja, including installation and an external C++ consumer for both. It also
checks the embedded defaults and an installed GoogleTest package with fetching disabled. When configuring the package or embedded test project directly, pass `-DHOSTNUMERICS_SOURCE_DIR=/path/to/hostnumerics` so it can locate the shared test source and CMake helper.

Install into a local prefix with:

```shell
cmake --install "$hostnumerics_build" --prefix "$hostnumerics_build/install"
```

The installed `HostNumerics` CMake package exports two C++ targets with these
intended responsibilities:

- `roc::hostnumerics-core` will provide scalar types and conversions, plus the
tensor model (shape, layout, and storage). It can be used independently of
numerical operations.
- `roc::hostnumerics` will add input generation, reference operations such as
GEMM, and numerical comparison. It links core transitively, so consumers
needing these operations only need to link `roc::hostnumerics`.

Both targets currently use `INTERFACE` libraries exposing version and scalar
type metadata. The prototype implements them as static libraries.

Consumers can add the install prefix to `CMAKE_PREFIX_PATH` and use:

```cmake
find_package(HostNumerics CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE roc::hostnumerics)
```

The Python bindings expose scalar metadata and `__version__` through the single
`hostnumerics` package; both layers will use that package as they land.

`ScalarType` identifies an encoding, and `scalarTypeInfo(type)` describes its
category, storage width, exponent, fraction, and special-value support. Python
exposes the same metadata through `scalar_type_info(type)`.
Scalar metadata and dispatch are documented in
[`scalar_type.hpp`](include/roc/hostnumerics/scalar_type.hpp). The
[scalar tests](tests/scalar_type_test.cpp) include a compiled usage example.
60 changes: 60 additions & 0 deletions shared/hostnumerics/docs/build_and_test.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Building and using HostNumerics

C++ tests use GoogleTest 1.12.1 or newer, retaining the repository's older compatibility baseline because these assertions need no newer API. Top-level test builds default to fetching 1.17.0, matching the shared superbuild, when no compatible installed package is available; set `HOSTNUMERICS_ENABLE_FETCH=OFF` to require an existing package. When a parent enables HostNumerics tests through `add_subdirectory`, fetching defaults to `OFF`: provide a compatible GoogleTest package or target, or explicitly set `HOSTNUMERICS_ENABLE_FETCH=ON`. Existing `GTest::gtest_main` targets must expose their version through the target's `VERSION` property or `GTest_VERSION`. GoogleTest is not installed or exported with HostNumerics.

## Build and test

The standalone build requires CMake 3.25.2 or newer, a C++20 compiler, and Ninja.
The Python module also needs Python 3.10 or newer with development headers and
nanobind. From the repository root, the following creates a virtual environment
and build directory outside the source tree:

```shell
hostnumerics_venv="$PWD/../venvs/hostnumerics"
hostnumerics_build="$PWD/../builds/hostnumerics"
python3 -m venv "$hostnumerics_venv"
"$hostnumerics_venv/bin/python" -m pip install nanobind==3.0.1

cmake -S shared/hostnumerics -B "$hostnumerics_build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DPython_EXECUTABLE="$hostnumerics_venv/bin/python" \
-DHOSTNUMERICS_BUILD_TESTING=ON \
-DHOSTNUMERICS_BUILD_PYTHON=ON
cmake --build "$hostnumerics_build" --parallel 4
ctest --test-dir "$hostnumerics_build" --output-on-failure
```

For a C++-only build, set `HOSTNUMERICS_BUILD_PYTHON=OFF`; Python and nanobind
are then unnecessary.

`HOSTNUMERICS_BUILD_TESTING` and `HOSTNUMERICS_BUILD_PYTHON` both default to `ON`
when building HostNumerics standalone and `OFF` when another project includes it
with `add_subdirectory`. CI covers ON/ON with Ninja Multi-Config and OFF/OFF with
Ninja, including installation and an external C++ consumer for both. It also
checks the embedded defaults and an installed GoogleTest package with fetching disabled. When configuring the package or embedded test project directly, pass `-DHOSTNUMERICS_SOURCE_DIR=/path/to/hostnumerics` so it can locate the shared test source and CMake helper.

Install into a local prefix with:

```shell
cmake --install "$hostnumerics_build" --prefix "$hostnumerics_build/install"
```

The installed `HostNumerics` CMake package exports two C++ targets with these
intended responsibilities:

- `roc::hostnumerics-core` will provide scalar types and conversions, plus the
tensor model (shape, layout, and storage). It can be used independently of
numerical operations.
- `roc::hostnumerics` will add input generation, reference operations such as
GEMM, and numerical comparison. It links core transitively, so consumers
needing these operations only need to link `roc::hostnumerics`.

Both targets currently use `INTERFACE` libraries exposing version and scalar
type metadata. The prototype implements them as static libraries.

Consumers can add the install prefix to `CMAKE_PREFIX_PATH` and use:

```cmake
find_package(HostNumerics CONFIG REQUIRED)
target_link_libraries(my_target PRIVATE roc::hostnumerics)
```
7 changes: 7 additions & 0 deletions shared/hostnumerics/docs/error_handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Error handling

HostNumerics-owned terminal errors throw `std::runtime_error`.
Expected support and compatibility failures should use explicit results
or checks. Allocation, system, dependency and visitor errors retain their
original types. Dispatch rejects invalid enum values before invoking the visitor
or moving its arguments. This policy does not promise exceptions-disabled builds.
126 changes: 125 additions & 1 deletion shared/hostnumerics/include/roc/hostnumerics/scalar_type.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
#include <complex>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>

namespace roc::hostnumerics {
enum class ScalarCategory : uint8_t {
Expand Down Expand Up @@ -175,7 +178,6 @@ inline constexpr std::string_view scalarTypeName(ScalarType type) {
}

namespace detail {
// Implementation of the native mapping, not a customization point.
template <typename T>
struct NativeScalarType;

Expand Down Expand Up @@ -247,4 +249,126 @@ template <NativeScalar T>
inline constexpr ScalarType nativeScalarType =
detail::NativeScalarType<std::remove_cvref_t<T>>::value;

namespace detail {
// The C++ type that holds one encoded value of Type. Signed integers, float,
// double and complex values use their native type. Every other whole-byte
// encoding, including bool, half, bfloat16 and the 8-bit floating-point and
// scale formats, uses an unsigned integer of the same width. If the storage
// width is not divisible by 8 bits, ScalarTag::Storage is void: elements must
// be accessed as packed bits rather than individual C++ objects.
template <ScalarType Type>
constexpr auto scalarStorageIdentity() {
constexpr uint16_t bits = scalarTypeInfo(Type).storageBits;
if constexpr (bits % 8 != 0) {
return std::type_identity<void>{};
} else if constexpr (Type == ScalarType::Int8) {
return std::type_identity<int8_t>{};
} else if constexpr (Type == ScalarType::Int16) {
return std::type_identity<int16_t>{};
} else if constexpr (Type == ScalarType::Int32) {
return std::type_identity<int32_t>{};
} else if constexpr (Type == ScalarType::Int64) {
return std::type_identity<int64_t>{};
} else if constexpr (Type == ScalarType::Float32) {
return std::type_identity<float>{};
} else if constexpr (Type == ScalarType::Float64) {
return std::type_identity<double>{};
} else if constexpr (Type == ScalarType::ComplexFloat32) {
return std::type_identity<std::complex<float>>{};
} else if constexpr (Type == ScalarType::ComplexFloat64) {
return std::type_identity<std::complex<double>>{};
} else if constexpr (bits == 8) {
return std::type_identity<uint8_t>{};
} else if constexpr (bits == 16) {
return std::type_identity<uint16_t>{};
} else if constexpr (bits == 32) {
return std::type_identity<uint32_t>{};
} else {
static_assert(bits == 64);
return std::type_identity<uint64_t>{};
}
}

template <ScalarType TypeValue>
struct ScalarTag {
static_assert(isConcreteScalarType(TypeValue));
static constexpr ScalarType type = TypeValue;
static constexpr uint16_t storageBits = scalarTypeInfo(TypeValue).storageBits;
using Storage = typename decltype(scalarStorageIdentity<TypeValue>())::type;
};
} // namespace detail

// Dispatches a type-templated operation on a runtime ScalarType by calling
// visitor.template operator()<Tag>(args...). Tag supplies type, storageBits,
// and Storage (void when the storage width is not divisible by 8 bits).
// The visitor is instantiated for every tag and must return the same type for each.
// Visitors and arguments are forwarded; value, reference, and void returns are preserved.
// Invalid types throw std::runtime_error before invoking the visitor.
template <typename Visitor, typename... Args>
decltype(auto) visitScalarType(ScalarType type, Visitor&& visitor, Args&&... args) {
auto dispatch = [&]<ScalarType TypeValue>() -> decltype(auto) {
return std::forward<Visitor>(visitor).template operator()<detail::ScalarTag<TypeValue>>(
std::forward<Args>(args)...);
};
switch (type) {
case ScalarType::Boolean:
return dispatch.template operator()<ScalarType::Boolean>();
case ScalarType::Int4:
return dispatch.template operator()<ScalarType::Int4>();
case ScalarType::Int8:
return dispatch.template operator()<ScalarType::Int8>();
case ScalarType::Int16:
return dispatch.template operator()<ScalarType::Int16>();
case ScalarType::Int32:
return dispatch.template operator()<ScalarType::Int32>();
case ScalarType::Int64:
return dispatch.template operator()<ScalarType::Int64>();
case ScalarType::UInt8:
return dispatch.template operator()<ScalarType::UInt8>();
case ScalarType::UInt16:
return dispatch.template operator()<ScalarType::UInt16>();
case ScalarType::UInt32:
return dispatch.template operator()<ScalarType::UInt32>();
case ScalarType::UInt64:
return dispatch.template operator()<ScalarType::UInt64>();
case ScalarType::Float4E2M1:
return dispatch.template operator()<ScalarType::Float4E2M1>();
case ScalarType::Float6E2M3:
return dispatch.template operator()<ScalarType::Float6E2M3>();
case ScalarType::Float6E3M2:
return dispatch.template operator()<ScalarType::Float6E3M2>();
case ScalarType::Float8E4M3:
return dispatch.template operator()<ScalarType::Float8E4M3>();
case ScalarType::Float8E5M2:
return dispatch.template operator()<ScalarType::Float8E5M2>();
case ScalarType::Float8E4M3Fnuz:
return dispatch.template operator()<ScalarType::Float8E4M3Fnuz>();
case ScalarType::Float8E5M2Fnuz:
return dispatch.template operator()<ScalarType::Float8E5M2Fnuz>();
case ScalarType::E4M3:
return dispatch.template operator()<ScalarType::E4M3>();
case ScalarType::E5M3:
return dispatch.template operator()<ScalarType::E5M3>();
case ScalarType::E8M0:
return dispatch.template operator()<ScalarType::E8M0>();
case ScalarType::E8M0Zero:
return dispatch.template operator()<ScalarType::E8M0Zero>();
case ScalarType::Float16:
return dispatch.template operator()<ScalarType::Float16>();
case ScalarType::BFloat16:
return dispatch.template operator()<ScalarType::BFloat16>();
case ScalarType::Float32:
return dispatch.template operator()<ScalarType::Float32>();
case ScalarType::Float64:
return dispatch.template operator()<ScalarType::Float64>();
case ScalarType::ComplexFloat32:
return dispatch.template operator()<ScalarType::ComplexFloat32>();
case ScalarType::ComplexFloat64:
return dispatch.template operator()<ScalarType::ComplexFloat64>();
case ScalarType::Count:
break;
}
throw std::runtime_error("Invalid ScalarType: " + std::to_string(static_cast<unsigned>(type)) +
".");
}
} // namespace roc::hostnumerics
Loading
Loading