Skip to content
Open
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
30 changes: 15 additions & 15 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ The library has CPU and CUDA backends. HIP support is architecturally possible b
---

## Repository Layout

```
FusedKernelLibrary/
├── .clang-format # LLVM-based style, 4-space indent, 120-char column limit
Expand Down Expand Up @@ -44,7 +43,6 @@ FusedKernelLibrary/
├── utests/ # Unit tests (header .h files, auto-discovered)
└── benchmarks/ # Benchmarks (disabled by default, ENABLE_BENCHMARK=ON)
```

---

## Build System
Expand All @@ -56,15 +54,13 @@ FusedKernelLibrary/
- **MSVC**: Visual Studio 2022 or Visual Studio 2026 (MSVC_VERSION >= 1930) required;

### Configure and Build (typical)
```bash
# Linux (Ninja)
cmake -G "Ninja" -B build -DCMAKE_BUILD_TYPE=Release -S .
cmake --build build --config Release
cmake --build build --config Release --parallel 32

# Windows (Ninja, inside VS Developer Shell)
cmake -G "Ninja" -B build -DCMAKE_BUILD_TYPE=Release -S .
cmake --build build --config Release
```
cmake --build build --config Release --parallel 32

### Key CMake Options
| Option | Default | Description |
Expand All @@ -91,12 +87,8 @@ cmake --build build --config Release
---

## Running Tests

```bash
cd build
ctest --build-config Release --output-junit test_results.xml
```

Tests are registered with CTest automatically. Individual targets follow the naming pattern `<TestName>_cpp` (CPU) and `<TestName>_cu` (CUDA).

---
Expand All @@ -116,7 +108,9 @@ Tests in `tests/` and `utests/` are **not** written with a traditional test fram
6. Use `// ONLY_CPU` in a test header to suppress the `_cu` target.

### Test File Structure

Every test header must define a `launch()` function returning `int`:

```cpp
#include <tests/main.h>
#include <fused_kernel/fused_kernel.h>
Expand Down Expand Up @@ -163,13 +157,15 @@ In CPU-only mode (no NVCC, no CLANG_HOST_DEVICE), these macros degrade to standa
The `FK_STATIC_STRUCT(StructName, StructAlias)` macro marks a struct as non-constructible and non-copyable (deletes default/copy/move constructors and assignment operators).

### Type Aliases

The library defines CUDA-compatible type aliases (also available in CPU mode):

```cpp
using uchar = unsigned char;
using schar = signed char;
using uint = unsigned int;
using ushort = unsigned short;
using ulong = unsigned long;
using uchar = unsigned char;
using schar = signed char;
using uint = unsigned int;
using ushort = unsigned short;
using ulong = unsigned long;
using longlong = long long;
using ulonglong = unsigned long long;
```
Expand All @@ -190,7 +186,9 @@ Run `clang-format` using the `.clang-format` file at the repo root:
## Core API Patterns

### Executing Fused Operations

The primary entry point is `fk::executeOperations<DPPType>(stream, op1, op2, ...)`:

```cpp
#include <fused_kernel/fused_kernel.h>
using namespace fk;
Expand Down Expand Up @@ -263,3 +261,5 @@ See existing operations like `Mul`, `Add`, `SaturateCast` in `include/fused_kern

1. **Windows Ninja + NVCC path**: After CMake configure on Windows with Ninja, `<build_dir>/CMakeFiles/rules.ninja` may contain an incorrect path to `nvcc.exe`. The CI workflow patches this with PowerShell `Set-Content`. If you hit this locally, check that `CUDACXX` env var is set before invoking CMake and verify the generated `rules.ninja`.

2. **CUDA Compilation Availability**: CUDA compilation is supported and should not be declared blocked based on an unsuccessful ad hoc nvcc invocation; use the repository's documented VS Developer Shell/CMake build configuration and correct architecture/toolchain settings before concluding CUDA is unavailable.

2 changes: 1 addition & 1 deletion .github/workflows/cmake-linux-amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
build:
runs-on: [self-hosted, linux, x64]
runs-on: [self-hosted, linux, x64, cuda]

strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cmake-linux-arm64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
build:
runs-on: [self-hosted, linux, arm64]
runs-on: [self-hosted, linux, arm64, cuda]

strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cmake-windows-amd64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
build:
runs-on: [self-hosted, windows, x64]
runs-on: [self-hosted, windows, x64, cuda]

strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
jobs:
copilot-setup-steps:
# Run Copilot setup on the self-hosted Linux x64 runner
runs-on: [self-hosted, linux, x64]
runs-on: [self-hosted, linux, x64, cuda]

permissions:
contents: read
Expand Down
3 changes: 2 additions & 1 deletion include/fused_kernel/algorithms/basic_ops/math.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ namespace fk {
struct AbsFunc {
using InstanceType = UnaryType;
template <typename ST> FK_HOST_DEVICE_FUSE auto exec(const ST& s) {
if constexpr (std::is_signed_v<ST>) return s < ST(0) ? static_cast<ST>(-s) : s;
if constexpr (isReducedFloat<ST>) return ReducedFloatTraits<ST>::abs(s);
else if constexpr (std::is_signed_v<ST>) return s < ST(0) ? static_cast<ST>(-s) : s;
else return s;
}
};
Expand Down
2 changes: 1 addition & 1 deletion include/fused_kernel/algorithms/basic_ops/vector_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace fk {
static_assert(std::is_same_v<VBase<I>, VBase<O>>,
"Base types should be the same");
const auto result = cxp::discard<cn<OutputType>>::f(input);
if constexpr (std::is_fundamental_v<OutputType>) {
if constexpr (validScalar<OutputType>) {
return result.x;
} else {
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ namespace fk {
#endif
} else if constexpr (std::is_floating_point_v<OutputType>) {
return compute_luminance(input);
} else {
static_assert(isReducedFloat<OutputType>, "RGB2Gray: unsupported output type");
return static_cast<OutputType>(compute_luminance(input));
}
}
private:
Expand Down
134 changes: 90 additions & 44 deletions include/fused_kernel/core/constexpr_libs/constexpr_cmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,21 @@ namespace cxp {
constexpr T maxValue = std::numeric_limits<T>::max();

template <typename T>
constexpr T smallestPositiveValue = std::is_floating_point_v<T> ? std::numeric_limits<T>::min() : static_cast<T>(1);
constexpr T smallestPositiveValue = fk::validFloatingPoint<T> ? std::numeric_limits<T>::min() : static_cast<T>(1);

namespace detail {
// Reduced floats promote to float for generic comparisons/arithmetic: their mixed
// implicit conversions are ambiguous (fp16/bf16) or non existent (fp8/fp4).
template <typename T>
FK_HOST_DEVICE_CNST auto promoteReduced(const T& value) {
if constexpr (fk::isReducedFloat<T>) {
return static_cast<float>(value);
} else {
return value;
}
}
} // namespace detail
using detail::promoteReduced;

#define CXP_F_FUNC \
template <typename... Types> \
Expand All @@ -44,7 +58,13 @@ namespace cxp {
using InstanceType = fk::UnaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE bool exec(const ST& s) {
return s != s;
if constexpr (fk::isReducedFloat<ST>) {
// fp8/fp4 have no comparison operators, and e4m3/e2m1 NaN is not
// detectable via s != s anyway: classify by bit pattern.
return fk::ReducedFloatTraits<ST>::isNaN(s);
} else {
return s != s;
}
}
};
CXP_F_FUNC
Expand All @@ -55,7 +75,11 @@ namespace cxp {
using InstanceType = fk::UnaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE bool exec(const ST& s) {
return s == s && s != ST(0) && s + s == s;
if constexpr (fk::isReducedFloat<ST>) {
return fk::ReducedFloatTraits<ST>::isInf(s);
} else {
return s == s && s != ST(0) && s + s == s;
}
}
};
CXP_F_FUNC
Expand All @@ -79,23 +103,29 @@ namespace cxp {
using InstanceType = fk::BinaryType;
template<typename ST1, typename ST2>
FK_HOST_DEVICE_FUSE bool exec(const ST1& s1, const ST2& s2) {
static_assert(!std::is_same_v<ST1, bool> && std::is_fundamental_v<ST1>,
"First parameter must be a fundamental type other than bool");
static_assert(!std::is_same_v<ST2, bool> && std::is_fundamental_v<ST2>,
"Second parameter must be a fundamental type other than bool");
constexpr bool isAnyFloatingPoint = std::is_floating_point_v<ST1> || std::is_floating_point_v<ST2>;
constexpr bool areBothSigned = std::is_signed_v<ST1> == std::is_signed_v<ST2>;
if constexpr (isAnyFloatingPoint || areBothSigned) {
// Safe comparison cases
return s1 == s2;
} else if constexpr (std::is_signed_v<ST1>) {
// T is signed, U is unsigned, both are integers
if (s1 < 0) return false; // Negative cannot equal any unsigned.
return static_cast<std::make_unsigned_t<ST1>>(s1) == s2;
if constexpr (fk::isReducedFloat<ST1> || fk::isReducedFloat<ST2>) {
// Promote reduced floats explicitly: mixed implicit conversions between
// float and half-like types are ambiguous, and fp8/fp4 have no operators.
return exec(promoteReduced(s1), promoteReduced(s2));
} else {
// T is unsigned, U is signed, both are integers
if (s2 < 0) return false; // Negative cannot equal any unsigned.
return s1 == static_cast<std::make_unsigned_t<ST2>>(s2);
static_assert(!std::is_same_v<ST1, bool> && std::is_fundamental_v<ST1>,
"First parameter must be a fundamental type other than bool");
static_assert(!std::is_same_v<ST2, bool> && std::is_fundamental_v<ST2>,
"Second parameter must be a fundamental type other than bool");
constexpr bool isAnyFloatingPoint = std::is_floating_point_v<ST1> || std::is_floating_point_v<ST2>;
constexpr bool areBothSigned = std::is_signed_v<ST1> == std::is_signed_v<ST2>;
if constexpr (isAnyFloatingPoint || areBothSigned) {
// Safe comparison cases
return s1 == s2;
} else if constexpr (std::is_signed_v<ST1>) {
// T is signed, U is unsigned, both are integers
if (s1 < 0) return false; // Negative cannot equal any unsigned.
return static_cast<std::make_unsigned_t<ST1>>(s1) == s2;
} else {
// T is unsigned, U is signed, both are integers
if (s2 < 0) return false; // Negative cannot equal any unsigned.
return s1 == static_cast<std::make_unsigned_t<ST2>>(s2);
}
}
}
};
Expand All @@ -120,23 +150,27 @@ namespace cxp {
using InstanceType = fk::BinaryType;
template<typename ST1, typename ST2>
FK_HOST_DEVICE_FUSE bool exec(const ST1& s1, const ST2& s2) {
static_assert(!std::is_same_v<ST1, bool> && std::is_fundamental_v<ST1>,
"First parameter must be a fundamental type other than bool");
static_assert(!std::is_same_v<ST2, bool> && std::is_fundamental_v<ST2>,
"Second parameter must be a fundamental type other than bool");
constexpr bool isAnyFloatingPoint = std::is_floating_point_v<ST1> || std::is_floating_point_v<ST2>;
constexpr bool areBothSigned = std::is_signed_v<ST1> == std::is_signed_v<ST2>;
if constexpr (isAnyFloatingPoint || areBothSigned) {
// Safe comparison cases
return s1 < s2;
} else if constexpr (std::is_signed_v<ST1>) {
// T is signed, U is unsigned, both are integers
if (s1 < 0) return true; // Signed negative is always less than unsigned.
return static_cast<std::make_unsigned_t<ST1>>(s1) < s2;
if constexpr (fk::isReducedFloat<ST1> || fk::isReducedFloat<ST2>) {
return exec(promoteReduced(s1), promoteReduced(s2));
} else {
// T is unsigned, U is signed, both are integers
if (s2 < 0) return false; // Unsigned is never less than a signed negative.
return s1 < static_cast<std::make_unsigned_t<ST2>>(s2);
static_assert(!std::is_same_v<ST1, bool> && std::is_fundamental_v<ST1>,
"First parameter must be a fundamental type other than bool");
static_assert(!std::is_same_v<ST2, bool> && std::is_fundamental_v<ST2>,
"Second parameter must be a fundamental type other than bool");
constexpr bool isAnyFloatingPoint = std::is_floating_point_v<ST1> || std::is_floating_point_v<ST2>;
constexpr bool areBothSigned = std::is_signed_v<ST1> == std::is_signed_v<ST2>;
if constexpr (isAnyFloatingPoint || areBothSigned) {
// Safe comparison cases
return s1 < s2;
} else if constexpr (std::is_signed_v<ST1>) {
// T is signed, U is unsigned, both are integers
if (s1 < 0) return true; // Signed negative is always less than unsigned.
return static_cast<std::make_unsigned_t<ST1>>(s1) < s2;
} else {
// T is unsigned, U is signed, both are integers
if (s2 < 0) return false; // Unsigned is never less than a signed negative.
return s1 < static_cast<std::make_unsigned_t<ST2>>(s2);
}
}
}
};
Expand Down Expand Up @@ -296,30 +330,39 @@ namespace cxp {
using InstanceType = fk::BinaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE auto exec(const ST& s1, const ST& s2)
-> std::enable_if_t<std::is_fundamental_v<ST>, ST> {
return s1 >= s2 ? s1 : s2;
-> std::enable_if_t<fk::validScalar<ST>, ST> {
if constexpr (fk::isReducedFloat<ST>) {
// fp8/fp4 have no comparison operators: compare in float.
return static_cast<float>(s1) >= static_cast<float>(s2) ? s1 : s2;
} else {
return s1 >= s2 ? s1 : s2;
}
}
};
CXP_F_FUNC
template <typename ST>
FK_HOST_DEVICE_FUSE ST f(const ST& s) {
return s;
return s;
}
};

struct min {
struct BaseFunc {
using InstanceType = fk::BinaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE auto exec(const ST& s1, const ST& s2)
-> std::enable_if_t<std::is_fundamental_v<ST>, ST> {
return s1 <= s2 ? s1 : s2;
FK_HOST_DEVICE_FUSE auto exec(const ST& s1, const ST& s2)
-> std::enable_if_t<fk::validScalar<ST>, ST> {
if constexpr (fk::isReducedFloat<ST>) {
return static_cast<float>(s1) <= static_cast<float>(s2) ? s1 : s2;
} else {
return s1 <= s2 ? s1 : s2;
}
}
};
CXP_F_FUNC
template <typename ST>
FK_HOST_DEVICE_FUSE ST f(const ST& value) {
return value;
return value;
}
};

Expand All @@ -328,8 +371,11 @@ namespace cxp {
using InstanceType = fk::UnaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE auto exec(const ST& s) {
static_assert(std::is_fundamental_v<ST>, "abs does not support non fundamental types");
if constexpr (std::is_signed_v<ST>) {
static_assert(fk::validScalar<ST>, "abs does not support non fundamental types");
if constexpr (fk::isReducedFloat<ST>) {
// Sign-magnitude formats: clearing the sign bit is exact (and NaN safe).
return fk::ReducedFloatTraits<ST>::abs(s);
} else if constexpr (std::is_signed_v<ST>) {
// For signed integrals, when x is std::numerical_limits<T>::lowest(),
// the result is undefined behavior in C++. So, for the sake of performance,
// we will not do any special treatment for those cases.
Expand Down
7 changes: 7 additions & 0 deletions include/fused_kernel/core/constexpr_libs/constexpr_saturate.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ namespace cxp {
using InstanceType = fk::UnaryType;
template <typename ST>
FK_HOST_DEVICE_FUSE auto exec(const ST& s) {
if constexpr (fk::isReducedFloat<ST>) {
// Promote reduced float sources once: every reduced value is exactly
// representable in float, and the float path already handles rounding
// and clamping towards any output type.
return exec(static_cast<float>(s));
} else {
constexpr auto maxValOutput = maxValue<fk::VBase<OT>>;
constexpr auto minValueOutput = minValue<fk::VBase<OT>>;
if (cxp::cmp_greater::BaseFunc::exec(s, maxValOutput)) {
Expand All @@ -44,6 +50,7 @@ namespace cxp {
return static_cast<fk::VBase<OT>>(s);
}
}
}
}
};
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace cxp {
struct Exec<Op, std::enable_if_t<std::is_same_v<typename Op::InstanceType, fk::UnaryType>>> {
template <typename T>
FK_HOST_DEVICE_FUSE auto exec(const T& val) {
if constexpr (std::is_fundamental_v<T>) {
if constexpr (fk::validScalar<T>) {
return Op::exec(val);
} else {
static_assert(fk::validCUDAVec<T>, "Type not supported in Unary operation execution.");
Expand Down
Loading