packages feed

moonlight-linalg-0.1.0.0: bench/dense/DenseCore.hs

module DenseCore
  ( denseCoreBenchmarks,
    denseCoreOnceBenchmarks,
  )
where

import Control.DeepSeq (NFData (..))
import Data.Bifunctor (first)
import Data.Vector.Storable qualified as S
import Env (BenchmarkSelection (..))
import Fixtures
  ( denseBenchmarkRows,
    denseBenchmarkVector,
  )
import Types
  ( BenchmarkWeight (..),
    OnceBenchmark (..),
    benchmarkWeightEither,
    eitherBenchmarkWeight,
  )
import Moonlight.LinAlg.Dense
  ( DenseDoubleMatrix,
    denseDoubleMatrixToRowMajorVector,
    denseDoubleMatrixVectorProduct,
    mkDenseDoubleMatrixRowMajor,
  )
import Moonlight.LinAlg.Dense.Primitives
  ( matrixVectorProduct,
  )
import Moonlight.LinAlg.Dense.Rows
  ( hcatRowsExact,
    matrixProductRowsWith,
    transposeRowsExact,
    vcatRowsExact,
  )
import Moonlight.LinAlg.Native
  ( denseDoubleMatrixProductBlas,
  )
import Test.Tasty.Bench (Benchmark, bench, bgroup, nf, nfIO)
import Prelude

data DenseCoreCase = DenseCoreCase
  { denseCoreLabel :: !String,
    denseCoreRows :: ![[Double]],
    denseCoreVector :: ![Double],
    denseCoreFlatVector :: !(S.Vector Double),
    denseCoreFlatMatrix :: !(Either String DenseDoubleMatrix)
  }

instance NFData DenseCoreCase where
  rnf benchmarkCase =
    rnf (denseCoreLabel benchmarkCase)
      `seq` rnf (denseCoreRows benchmarkCase)
      `seq` rnf (denseCoreVector benchmarkCase)
      `seq` rnf (denseCoreFlatVector benchmarkCase)
      `seq` forceFlatMatrix (denseCoreFlatMatrix benchmarkCase)
      `seq` ()

denseCoreBenchmarks :: BenchmarkSelection -> Benchmark
denseCoreBenchmarks benchmarkSelection =
  bgroup
    "dense row validation surface"
    (denseCoreCaseBenchmarks =<< denseCoreCases benchmarkSelection)

denseCoreOnceBenchmarks :: BenchmarkSelection -> [OnceBenchmark]
denseCoreOnceBenchmarks benchmarkSelection =
  denseCoreCaseOnceBenchmarks =<< denseCoreCases benchmarkSelection

denseCoreCases :: BenchmarkSelection -> [DenseCoreCase]
denseCoreCases benchmarkSelection =
  fmap
    denseCoreCase
    ( [32]
        <> [96 | includeBroadMedium benchmarkSelection || includeBroadLarge benchmarkSelection]
        <> [192 | includeBroadLarge benchmarkSelection]
    )

denseCoreCase :: Int -> DenseCoreCase
denseCoreCase dimension =
  let rowValues = denseBenchmarkRows dimension
      vectorValues = denseBenchmarkVector dimension
      flatPayload = S.fromList (concat rowValues)
   in DenseCoreCase
        { denseCoreLabel = "n=" <> show dimension,
          denseCoreRows = rowValues,
          denseCoreVector = vectorValues,
          denseCoreFlatVector = S.fromList vectorValues,
          denseCoreFlatMatrix =
            first show
              ( mkDenseDoubleMatrixRowMajor
                  dimension
                  dimension
                  flatPayload
              )
        }

denseCoreCaseBenchmarks :: DenseCoreCase -> [Benchmark]
denseCoreCaseBenchmarks benchmarkCase =
  [ bench (denseCoreLabel benchmarkCase <> " matrix-vector reference rows") (nf denseMatvecWeight benchmarkCase),
    bench (denseCoreLabel benchmarkCase <> " flat matrix-vector") (nf denseFlatMatvecWeight benchmarkCase),
    bench (denseCoreLabel benchmarkCase <> " matrix-product reference rows") (nf denseMatmulWeight benchmarkCase),
    bench (denseCoreLabel benchmarkCase <> " native BLAS matrix-product") (nfIO (denseNativeMatmulWeight benchmarkCase)),
    bench (denseCoreLabel benchmarkCase <> " transpose") (nf denseTransposeWeight benchmarkCase),
    bench (denseCoreLabel benchmarkCase <> " hcat/vcat") (nf denseConcatWeight benchmarkCase)
  ]

denseCoreCaseOnceBenchmarks :: DenseCoreCase -> [OnceBenchmark]
denseCoreCaseOnceBenchmarks benchmarkCase =
  [ denseCoreOnceBenchmark benchmarkCase "matrix-vector reference rows" denseMatvecWeight,
    denseCoreOnceBenchmark benchmarkCase "flat matrix-vector" denseFlatMatvecWeight,
    denseCoreOnceBenchmark benchmarkCase "matrix-product reference rows" denseMatmulWeight,
    denseCoreOnceBenchmarkIO benchmarkCase "native BLAS matrix-product" denseNativeMatmulWeight,
    denseCoreOnceBenchmark benchmarkCase "transpose" denseTransposeWeight,
    denseCoreOnceBenchmark benchmarkCase "hcat/vcat" denseConcatWeight
  ]

denseCoreOnceBenchmark :: DenseCoreCase -> String -> (DenseCoreCase -> BenchmarkWeight) -> OnceBenchmark
denseCoreOnceBenchmark benchmarkCase label measure =
  OnceBenchmark
    { onceBenchmarkLabel = "dense row validation surface." <> denseCoreLabel benchmarkCase <> " " <> label,
      onceBenchmarkAction = pure (benchmarkWeightEither (measure benchmarkCase))
    }

denseCoreOnceBenchmarkIO :: DenseCoreCase -> String -> (DenseCoreCase -> IO BenchmarkWeight) -> OnceBenchmark
denseCoreOnceBenchmarkIO benchmarkCase label measure =
  OnceBenchmark
    { onceBenchmarkLabel = "dense row validation surface." <> denseCoreLabel benchmarkCase <> " " <> label,
      onceBenchmarkAction = benchmarkWeightEither <$> measure benchmarkCase
    }

denseMatvecWeight :: DenseCoreCase -> BenchmarkWeight
denseMatvecWeight benchmarkCase =
  eitherBenchmarkWeight
    (denseCoreLabel benchmarkCase <> " matrix-vector reference rows")
    vectorChecksum
    (matrixVectorProduct (denseCoreRows benchmarkCase) (denseCoreVector benchmarkCase))

denseFlatMatvecWeight :: DenseCoreCase -> BenchmarkWeight
denseFlatMatvecWeight benchmarkCase =
  eitherBenchmarkWeight
    (denseCoreLabel benchmarkCase <> " flat matrix-vector")
    storableVectorChecksum
    ( do
        matrixValue <- denseCoreFlatMatrix benchmarkCase
        first show (denseDoubleMatrixVectorProduct matrixValue (denseCoreFlatVector benchmarkCase))
    )

forceFlatMatrix :: Either String DenseDoubleMatrix -> ()
forceFlatMatrix matrixResult =
  case matrixResult of
    Left failureText -> rnf failureText
    Right matrixValue -> rnf (denseDoubleMatrixToRowMajorVector matrixValue)

denseMatmulWeight :: DenseCoreCase -> BenchmarkWeight
denseMatmulWeight benchmarkCase =
  eitherBenchmarkWeight
    (denseCoreLabel benchmarkCase <> " matrix-product reference rows")
    matrixChecksum
    (matrixProductRowsWith (*) (+) 0.0 (denseCoreRows benchmarkCase) (denseCoreRows benchmarkCase))

denseNativeMatmulWeight :: DenseCoreCase -> IO BenchmarkWeight
denseNativeMatmulWeight benchmarkCase =
  case denseCoreFlatMatrix benchmarkCase of
    Left failureText ->
      pure (BenchmarkMeasurementFailure (denseCoreLabel benchmarkCase <> " native BLAS matrix-product: " <> failureText))
    Right matrixValue ->
      eitherBenchmarkWeight
        (denseCoreLabel benchmarkCase <> " native BLAS matrix-product")
        (storableVectorChecksum . denseDoubleMatrixToRowMajorVector)
        <$> denseDoubleMatrixProductBlas matrixValue matrixValue

denseTransposeWeight :: DenseCoreCase -> BenchmarkWeight
denseTransposeWeight benchmarkCase =
  eitherBenchmarkWeight
    (denseCoreLabel benchmarkCase <> " transpose")
    matrixChecksum
    (transposeRowsExact (denseCoreRows benchmarkCase))

denseConcatWeight :: DenseCoreCase -> BenchmarkWeight
denseConcatWeight benchmarkCase =
  eitherBenchmarkWeight
    (denseCoreLabel benchmarkCase <> " hcat/vcat")
    matrixChecksum
    ( do
        horizontal <- hcatRowsExact [denseCoreRows benchmarkCase, denseCoreRows benchmarkCase]
        vertical <- vcatRowsExact [denseCoreRows benchmarkCase, denseCoreRows benchmarkCase]
        pure (horizontal <> vertical)
    )

vectorChecksum :: [Double] -> Double
vectorChecksum values =
  sum (abs <$> values)

storableVectorChecksum :: S.Vector Double -> Double
storableVectorChecksum values =
  S.foldl' (\accumulator value -> accumulator + abs value) 0.0 values

matrixChecksum :: [[Double]] -> Double
matrixChecksum rows =
  sum (vectorChecksum <$> rows)