packages feed

hhlo-0.1.0.0: test/Test/Runtime/AsyncGPU.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Test.Runtime.AsyncGPU (tests) where

import qualified Data.Vector.Storable as V
import Test.Tasty
import Test.Tasty.HUnit

import HHLO.Core.Types
import HHLO.EDSL.Ops
import HHLO.IR.AST (FuncArg(..), TensorType(..))
import HHLO.IR.Builder
import HHLO.IR.Pretty
import HHLO.Runtime.PJRT.Plugin
import HHLO.Runtime.PJRT.Types
import HHLO.Runtime.Device
import HHLO.Runtime.Compile
import HHLO.Runtime.Buffer
import HHLO.Runtime.Async

tests :: TestTree
tests = testGroup "Runtime.AsyncGPU"
    [ testCase "gpu executeAsync + await" gpuExecuteAsyncAwait
    , testCase "gpu buffer ready poll" gpuBufferReadyPoll
    ]

gpuExecuteAsyncAwait :: IO ()
gpuExecuteAsyncAwait = withPJRTGPU $ \api client -> do
    mDev <- defaultGPUDevice api client
    dev <- maybe (assertFailure "No GPU found") return mDev

    let modu = moduleFromBuilder @'[2, 2] @'F32 "main"
            [ FuncArg "arg0" (TensorType [2, 2] F32)
            , FuncArg "arg1" (TensorType [2, 2] F32)
            ]
            $ do
                x <- arg
                y <- arg
                z <- add x y
                return z

    exec <- compile api client (render modu)

    let inputA = V.fromList [1, 2, 3, 4] :: V.Vector Float
        inputB = V.fromList [10, 20, 30, 40] :: V.Vector Float

    bufA <- toDeviceOn api client dev inputA [2, 2] bufferTypeF32
    bufB <- toDeviceOn api client dev inputB [2, 2] bufferTypeF32

    -- executeAsync should return immediately
    [bufOut] <- executeAsync api exec [bufA, bufB]

    -- await the output buffer
    awaitBuffers api [bufOut]

    result <- fromDeviceF32 api bufOut 4
    result @?= V.fromList [11, 22, 33, 44]

gpuBufferReadyPoll :: IO ()
gpuBufferReadyPoll = withPJRTGPU $ \api client -> do
    mDev <- defaultGPUDevice api client
    dev <- maybe (assertFailure "No GPU found") return mDev

    let modu = moduleFromBuilder @'[2, 2] @'F32 "main"
            [ FuncArg "arg0" (TensorType [2, 2] F32)
            ]
            $ do
                x <- arg
                y <- relu x
                return y

    exec <- compile api client (render modu)

    let input = V.fromList [-1, 2, -3, 4] :: V.Vector Float
    bufIn <- toDeviceOn api client dev input [2, 2] bufferTypeF32

    [bufOut] <- executeAsync api exec [bufIn]

    -- Poll until ready (should become ready quickly for tiny ops)
    let loop = do
            ready <- bufferReady api bufOut
            if ready then return () else loop
    loop

    result <- fromDeviceF32 api bufOut 4
    result @?= V.fromList [0, 2, 0, 4]