This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
FusedKernelLibrary (FKL) is a header-only C++20 library for automatic GPU kernel fusion (Vertical, Horizontal, Backwards Vertical, and Divergent Horizontal Fusion), with CPU and CUDA backends. Only nvcc is supported as the CUDA compiler. main is the development branch (v0.2.0, API may break); LTS-C++17 is the frozen-API branch.
Deep per-topic docs live in .github/skills/*/SKILL.md (implementing operations, implementing DPPs, fusion techniques, data structures, using the library, language bindings, build/test). Read the relevant skill before nontrivial work. .github/copilot-instructions.md overlaps with them but contains stale claims — see "Stale documentation" below.
Requires CMake >= 3.28, a C++20 host compiler, and CUDA (nvcc). Project policy and CI treat CUDA as required, but CMake degrades gracefully: without nvcc it configures CPU-only and generates only *_cpp targets.
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release -S . # configure
cmake --build build --config Release # build everything (slow: ~50 nvcc TUs)
cd build && ctest --build-config Release # run all tests (the merge gate)Single test — each test header generates an executable target whose name equals its ctest name:
cmake --build build --target utest_softmax_cu # build just one test
./build/bin/utest_softmax_cu # run directly (Ninja puts binaries in build/bin/)
ctest -R '^utest_softmax_cu$' # or via ctest, from build/
ctest -R '_cpp$' # CPU-backend tests only (no GPU needed at runtime)Key CMake options: ENABLE_CPU (ON), ENABLE_CUDA (ON if nvcc found), BUILD_TEST (ON), BUILD_UTEST (ON), ENABLE_BENCHMARK (OFF), CUDA_ARCH ("native", passed verbatim to CUDA_ARCHITECTURES — no arch filtering exists), ARCH_FLAGS (CPU SIMD), ENABLE_NVTX, ENABLE_DEBUG, TEMPLATE_DEPTH (1000).
There is no lint/format gate. Format manually with clang-format -i using the repo-root .clang-format (LLVM base, 4-space indent, 120-char lines, PointerAlignment: Right). The merge gate is the full ctest suite building and passing across CI's compiler matrix (no -Werror anywhere, though the skills' PR checklists ask for warning-clean nvcc and clang builds). CI runs only on PRs to main (self-hosted runners): Linux amd64/arm64 with g++-13 and clang++-21 + CUDA 13.3; Windows with cl 14.44 + CUDA 13.0, cl 14.51 + CUDA 13.3, and clang-cl 14.51 + CUDA 13.3.
- Tests are header files auto-discovered by CMake (
cmake/tests/discover_tests.cmake, GLOB_RECURSE with CONFIGURE_DEPENDS) inside immediate subdirectories oftests/andutests/. Top-level files (tests/main.h,tests/operation_test_utils.h) are never tests. Paths containing_commonare excluded (shared helpers). Adding a new.htest requires no CMake edits. - Every test header defines
int launch()returning 0 for pass. A generated launcher TU compiles the header twice: as C++ (<name>_cpptarget,ParArch::CPU) and as CUDA (<name>_cu,ParArch::GPU_NVIDIA). The same source targets both backends becausedefaultParArchswitches on__NVCC__. - Backend suppression is a raw substring search: any occurrence of
ONLY_CU/ONLY_CPUanywhere in the file (even in a comment) suppresses the_cpp/_cutarget. Repo convention:#define __ONLY_CU__. - Test harness helpers (
START_ADDING_TESTS,STOP_ADDING_TESTS,RUN_ALL_TESTS,TestCaseBuilder, thetestCasesmap) live intests/operation_test_utils.h;tests/main.honly declareslaunch(). - Every public alias and
build()overload needs a utest that instantiates it — template code only breaks on instantiation (see issue #244: shipped aliases that never compiled).
- Operation: a stateless static-only struct (
FK_STATIC_STRUCTdeletes all ctors) that is always single-thread code. Anything needing thread cooperation (shared memory,__syncthreads, shuffles) belongs in a DPP (Data Parallel Pattern), never in an Operation. - IOp (InstantiableOperation) = Operation type + runtime params, produced by
Op::build(...). WrappersRead<Op>,ReadBack<Op>,Unary<Op>,Binary<Op>,Ternary<Op>,MidWrite<Op>,Write<Op>are defined incore/execution_model/operation_model/instantiable_operations.h. Types define the kernel;build()values are runtime parameters — changing a value never creates a new kernel, changing a type does. - The 11 OperationTypes and their exact
exec()signatures are documented authoritatively in the comment table ofcore/execution_model/operation_model/operation_types.h(ReadType, WriteType, UnaryType, BinaryType, ReadBackType, TernaryType, MidWriteType, Incomplete*/Open/Closed).IncompleteTernaryTypeis declared but unused — do not implement against it. - Entry point:
fk::executeOperations<TransformDPP<>>(stream, readIOp, computeIOps..., writeIOp)via#include <fused_kernel/fused_kernel.h>. First IOp must be a complete Read, last a Write; each op'sOutputTypemust equal the next op'sInputType. - Fusion machinery: vertical fusion is an
operator|fold over IOps incore/execution_model/data_parallel_patterns.h; host-side fusion (operator&,.then()) isFuserand backwards vertical fusion isBackFuser::fuse_backinoperation_model/iop_fuser.h. Notefuse_backreturns aTupleof IOps (callers unpack withget<0>), and fusing onto an IncompleteReadBack continuation (Crop/Resize) completes that op's own IOp — aFusedOperation(fused_operation.h) is only produced for non-ReadBack continuations. Horizontal fusion: passingstd::arrayparams tobuild()producesBatchRead/BatchWrite(batch_operations.h), one grid z-plane per batch item. - Grid sizing: comes from the first read IOp's
getActiveThreads()— the OUTPUT geometry (for a ReadBack stack, the last ReadBack), not the input size. Block size heuristics live incore/execution_model/executors.h. - Executors/backends:
ParArch::CPUandParArch::GPU_NVIDIAare the only implemented backends (executors.h,parallel_architectures.h).Stream=Stream_<defaultParArch>; default-constructed owns its cudaStream_t, constructing from an existingcudaStream_tis non-owning. - Thread fusion (vectorized loads,
core/execution_model/thread_fusion.h): opt-in viaTransformDPP<PA, TF::ENABLED>; requires both first read and last write to haveTHREAD_FUSION=true. Any ReadBack (Crop/Resize) or fused read chain silently disables it.
Pattern (see Mul in algorithms/basic_ops/arithmetic.h, Crop in algorithms/image_processing/crop.h):
- Private
using SelfType = MyOp<...>;thenFK_STATIC_STRUCT(MyOp, SelfType). - CRTP parent:
using Parent = BinaryOperation<I, P, O, SelfType>;(parents inoperation_model/parent_operations.h). - A
DECLARE_*_PARENTmacro generates the typedefs andbuild()overloads. ReadBack ops must useDECLARE_READBACK_PARENTfrombatch_operations.h, not the_BASICvariant — the_BASICone silently drops thestd::arraybatch builders that enable horizontal fusion. - Hand-write
FK_HOST_DEVICE_FUSE OutputType exec(...)— it must also run on the CPU backend. Compute ops need nothing else; Read/ReadBack ops additionally hand-write geometry (num_elems_x/y/z,getActiveThreads) and extrabuild()overloads the macros don't generate (e.g. Crop'sbuild(backIOp, rect)).
- ReadBack ops (Crop, Resize, Warping, BorderReader) come in incomplete/complete pairs:
Crop<>(BackIOp=NullType) is what users build with params only;BackFuserlater calls the two-argbuild(backIOp, iOp). - Golden rule: values users change per call (factors, rects, sizes) go in
ParamsType; anything that changes generated code (dtype, channel count, batch size, interpolation mode) is a template parameter. - Ops are grouped thematically per file (
arithmetic.hholds Add/Sub/Mul/Div); the same op name is specialized on a trailing InstanceType tag rather than renamed. NoteSaturateCastlives inimage_processing/saturate.h, notbasic_ops/. - Umbrella headers intentionally omit files:
basic_ops.homitsmemory_operations.h;algorithms.homitsattention/entirely. Header guards are#ifndefstyle (usuallyFK_*), never#pragma once.
fk::for nearly everything.cxp::is strictly reserved forcore/constexpr_libs/— std functionality that is unavailable on GPU, not constexpr, or both.- Function macros from
core/utils/utils.hbranch on__NVCC__/NVRTC_COMPILER/ plain C++:FK_HOST_DEVICE_FUSE(=__host__ __device__ __forceinline__ static constexpr),FK_DEVICE_FUSE,FK_HOST_FUSE, etc. Naming:FUSE= static constexpr,CNST= constexpr non-static,STATIC= static non-constexpr.gpuErrchk(expr)throwsstd::runtime_error(only defined under__NVCC__). - GPU-compatible std replacements:
fk::Tuple/fk::apply/fk::apply_dincore/data/tuple.h,fk::Arrayincore/data/array.h, vector-type traits (VBase,cn<>,make_,make_set) and channel-wise operators incore/utils/vector_utils.h. CPU-only builds still getuchar3/float4etc. viacore/data/vector_types.h. Ptr<ND,T>(core/data/ptr_nd.h) is a ref-counted owner — copies are shallow and share memory; wrapping an external pointer is non-owning (FKL never frees it). Kernels only ever see the PODRawPtr<ND,T>; host code passescontainer.ptr()intobuild(). DefaultMemTypeunder NVCC isDeviceAndPinned(device buffer + pinned host mirror, moved with.upload(stream)/.download(stream));Hostin CPU-only builds.ND::_3Ddims are width × height × planes × color_planes (planes = batch).Tensor<T>forces contiguous pitch.
algorithms/attention/ (softmax, flash_attention, flash_attention_mma, flash_decode) contains self-contained cooperative DPPs (whole kernels: online softmax, FlashAttention-2 SIMT and mma.sync tensor-core variants, split-KV FlashDecoding), not composable Operations. The DPP structs are inside #if defined(__NVCC__) with no CPU implementation (unlike the DPP skill's stated rule), though the directory also holds composable CPU-compiling pieces outside the guard (e.g. Int8TokenDequantRead, a normal Read Operation usable in any FKL pipeline, plus host packing helpers). Nothing in attention/ is included by any umbrella header. Cooperative exec bodies can't be constexpr (shared memory + barriers), so softmax and flash_attention use FK_COOP_DEVICE_FUSE while the mma and decode variants hand-write static __device__ void exec. Fusion happens via Read-IOp prologues (dequantization runs in-register at load time) and IOp-chain epilogues. DivergentBatchTransformDPP (Divergent HF) is likewise GPU-only in practice: no CPU executor specialization exists and its base class hardcodes ParArch::GPU_NVIDIA.
- Read nvcc/clang template errors bottom-up: the last "instantiation of" frame names your line; the first error names the real culprit.
qualifiers dropped in binding reference of type 'X&&'→ explicit template args on a forwarding-reference helper (never callBackFuser::fuse_back<Explicit...>; let deduction happen).name followed by "::" must be a class or namespace nameinsidefused_operation.h→ a raw Operation was passed where an IOp was expected; wrap it (Unary<...>,Binary<...>).- In DPP code, pass the whole IOp to
IOp::Operation::exec(thread, ..., iop)— passingiop.paramsroutes to the wrong overload and fails to compile.
CLANG_HOST_DEVICEdoes not exist;core/utils/compiler_macros.hdefines only_MSC_VER_EXISTS(docs referencing it are stale).- No CUDA arch filtering /
nvidia-smidetection / compute_70 minimum exists in cmake —CUDA_ARCHis passed verbatim (copilot-instructions claim is stale). ENABLE_CPUis never auto-disabled for old MSVC.- The README example includes
<fused_kernel/core/execution_model/memory_operations.h>— the real path isinclude/fused_kernel/algorithms/basic_ops/memory_operations.h. attention/is absent from all skills, README, and copilot-instructions.