Imported from 14NGiestas/mfi (
AGENTS.md). Install upstream withnpx skills add 14NGiestas/mfi. Copyright stays with the author.
MFI — Agent Instructions
Critical Rules
- NEVER edit
.f90files directly — they are generated artifacts from.fpp/.fyppsources. - Always modify
.fpp/.fyppmacros, then run:make clean && make .f90files are gitignored (line 44 of.gitignore) but committed to deployment branches by CI.
Build & Test Commands
# Enter dev shell (provides gfortran, fpm, fypp, BLAS, LAPACK)
nix develop # cpu-only
nix develop .#gpu-modern # CUDA 12.3
nix develop .#gpu-legacy # CUDA 11.8
# CPU-only (default)
make
fpm test
# GPU/cuBLAS
make
fpm build --profile cublas
fpm test --profile cublas
Note:
gpu-modernCI uses--profile debugto avoid a gfortran -O2 optimizer bug (fixed in gfortran 15.2.0). SeeBUGS.md.
Nix Flake
A single flake.nix provides all dev shells (replaces old shells/*.nix):
| Shell | Command | CUDA |
|---|---|---|
cpu-only |
nix develop .#cpu-only |
— |
gpu-modern |
nix develop .#gpu-modern |
12.3 |
gpu-legacy |
nix develop .#gpu-legacy |
11.8 |
| default | nix develop |
— (same as cpu-only) |
- nixpkgs pinned to 24.11 (last version with CUDA 11.8/12.3)
- fpm 0.13.0 via inline overlay (PR #506818 in nixpkgs) — remove once merged
- gfortran, fpm, fypp, pkg-config all provided by the flake
- CI uses
magic-nix-cache-actionfor fast cached builds - Temp make files (
.mfi_*,.f77_*, *.tmp) are gitignored
Branch Model
| Branch | Purpose | Deployment Target |
|---|---|---|
main |
Primary development (CPU + cuBLAS via features) | mfi-fpm (via CI) |
impl/cublas |
GPU/experimental staging | mfi-cublas (via CI) |
mfi-fpm |
CPU-only deploy artifact (.f90 + .toml only) |
— |
mfi-cublas |
GPU deploy artifact (.f90 + .toml only) |
— |
CI triggers:
- Push to
main→ full test matrix → deploy tomfi-fpm - Push to
impl/cublas→ full test matrix → deploy tomfi-cublas - PR to
main→ full test matrix (no deploy) - Other branches → manual dispatch only
To deploy: commit changes, push to the corresponding branch. CI handles the rest.
Code Generation Architecture
Macro Files (edit these)
common.fpp— Core fypp macros: type prefixes (s,d,c,z),@:optional,@:defaults, interface generatorscublas.fpp— CUDA/cuBLAS v2 C-interop interfaces (pure+VALUEon allbind(c)args),@:allocate,@:deallocate,@:set_matrix,@:get_matrixmacros, cuBLAS constantsextensions.fpp— cuBLAS handle lifecycle (mfi_cublas_handle_ensure,mfi_cublas_finalize), execution mode control (mfi_force_gpu,mfi_force_cpu),mfi_cublas_error
Source Macros (edit these)
src/mfi/blas/*.fypp— MFI modern wrapper implementationssrc/f77/blas/*.fypp— F77 interface declarationssrc/mfi/lapack/*.fypp— LAPACK modern wrapperssrc/f77/lapack/*.fypp— LAPACK F77 interfaces
Generated (do not edit)
src/f77/blas.f90,src/mfi/blas.f90,src/f77/lapack.f90,src/mfi/lapack.f90- All
test/**/*.f90files
Naming Conventions
| Name | Kind | Purpose |
|---|---|---|
MFI_CUBLAS |
Preprocessor macro | Enables cuBLAS code at compile time (set by fpm cublas feature) |
MFI_USE_CUBLAS |
Internal variable + env var | Runtime GPU dispatch flag (read from env var on lazy init) |
MFI_EXTENSIONS |
Preprocessor macro | Enables BLAS extension routines (iamin, iamax, lamch) |
MFI_LINK_EXTERNAL |
Preprocessor macro | Links external BLAS extensions |
Purity: Why pure on GPU Wrappers is Correct
All MFI BLAS wrappers (mfi_gemm, mfi_gemv, mfi_trsm, mfi_trmm) and all bind(c) CUDA/cuBLAS interfaces are pure. This is intentional and semantically correct.
Do NOT remove pure from these routines. Reasons:
-
error stopis allowed inpureprocedures — permitted by Fortran 2008. The fact that a routine may abort on failure does not make it impure. -
GPU alloc → compute → dealloc is semantically pure from Fortran's perspective — The CUDA device memory (allocated via
cudaMalloc, freed viacudaFree) is opaque to the Fortran compiler. No Fortran-visible state is modified. This is exactly the same pattern as localallocate/deallocateinside apureCPU function. -
bind(c)+pureis valid — The compiler cannot verify purity of external C code, so it trusts the declaration. That's the whole point: you're asserting to the compiler that the side effects are not observable from Fortran. -
Dependent projects need this — Projects like CheesyHam call these wrappers from
purecontexts. Removingpurebreaks their compilation.
The pattern:
pure subroutine mfi_sgemm(a, b, c, ...)
! allocate GPU memory (opaque to Fortran)
! call cuBLAS (external C, compiler trusts purity claim)
! copy result back (no Fortran state modification)
! free GPU memory (opaque to Fortran)
! return — no observable side effects
end subroutine
is identical to:
pure function foo(x) result(y)
real, allocatable :: tmp(:)
allocate(tmp(size(x))) ! allowed in pure
tmp = x * 2.0
y = sum(tmp)
deallocate(tmp) ! allowed in pure
end function
cuBLAS v2 Specifics
- All
bind(c)interfaces must bepurewithVALUEon every argument (includingintent(out)pointers) - All MFI BLAS wrappers (
mfi_gemm,mfi_gemv,mfi_trsm,mfi_trmm) must bepure - Interface bodies at module level inherit from
use iso_c_binding— do NOT addimport,use, orimport ::insideinterfaceblocks at module scope. Host association handles type visibility. - cuBLAS stat checks use
call mfi_cublas_error(stat, 'name')(a pure subroutine wrapper) for consistency with the purity design - TRSM fill mode is inverted:
CUBLAS_TRSM_FILL_UPPER = 1,CUBLAS_TRSM_FILL_LOWER = 0(opposite of standard BLAS enums) - cuBLAS v1 (
cublasAlloc/cublasSgemm) is deprecated — use v2 (cudaMalloc,cublasCreate_v2,cublasSgemm_v2, etc.)
Runtime CPU/GPU Switching
- Zero-config default:
call mfi_gemm(A, B, C)— always works, no setup - Env var activation:
MFI_USE_CUBLAS=1 ./app— lazy init reads env var automatically - Manual switch:
call mfi_force_gpu/call mfi_force_cpu— always available (stub no-op when compiled withoutcublas, functional withcublasfeature) - OpenMP safe: Per-thread cuBLAS handles, pre-allocated from
OMP_NUM_THREADS - No state leaks: Each
mfi_force_*resets lazy-init state, so calls are safe to repeat
Dependency Usage
# CPU-only (stable)
mfi = { git="https://github.com/14NGiestas/mfi.git", branch="mfi-fpm" }
# GPU/cuBLAS (stable)
mfi = { git="https://github.com/14NGiestas/mfi.git", branch="mfi-cublas", features = ["cublas"] }
Note: When using fpm >= 0.13.0, the cublas and cudart linking requirements are automatically propagated to the consuming project via the cublas feature. No explicit [build] link = [...] is required in the consumer's fpm.toml.
Testing Notes
- LAPACK tests have pre-existing failures unrelated to cuBLAS work (
cunmrq,sormrq,heevxsegfault) - GPU testing available via
gpu_test.ipynb(Colab: Tesla T4, CUDA 12.8) - fpm ≥0.13.0 required for
[profiles]and[features]support - CI uses
MFI_TEST_ELEMENTS=50000andMFI_TEST_SAMPLES=1for fast runs
Pending test coverage
These wrappers exist but lack tests because the factorization routines (geqr2, gerq2)
are not yet implemented — they need tau from prior factorization:
| Routine | Needs | Status |
|---|---|---|
org2r, orgr2 |
sgeqr2/dgeqr2 |
Macro ready, not in _COLLECT |
ung2r, ungr2 |
cgeqr2/zgeqr2 |
Macro ready, not in _COLLECT |
orm2r, ormr2 |
sgeqr2/dgeqr2 |
Macro exists, not in _COLLECT |
unm2r, unmr2 |
cgeqr2/zgeqr2 |
Macro exists, not in _COLLECT |
Macros are in test/lapack/macros/ and can be added to test/lapack.fpp once the factorization routines are available.
fpm.toml Configuration
[preprocess.cpp]
macros = ["MFI_EXTENSIONS", "MFI_LINK_EXTERNAL"]
[features]
cublas.build.link = ["blas", "lapack", "cublas", "cudart"]
cublas.preprocess.cpp.macros = ["MFI_CUBLAS"]
[profiles]
cublas = ["cublas"]
- CPU builds use default macros (
MFI_EXTENSIONS,MFI_LINK_EXTERNAL) — no CUDA dependencies. fpm build --profile cublasactivates thecublasfeature, which addsMFI_CUBLASto the preprocessor and links CUDA libraries.- Do NOT use
[build] link = [...]at the root level withcublas/cudart— those must be gated behind a feature.