packages feed

hhlo-0.1.0.0: examples/29-multi-gpu-inference.hs

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

module Main where

import qualified Data.Vector.Storable as V
import Data.Int (Int64)
import Data.Time.Clock (diffUTCTime, getCurrentTime)

import HHLO.Core.Types
import HHLO.EDSL.Ops
import HHLO.IR.AST (FuncArg(..), TensorType(..), Module)
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.Execute
import HHLO.Runtime.Buffer

matmulSize :: Int
matmulSize = 4096

main :: IO ()
main = withPJRTGPU $ \api client -> do
    devs <- addressableDevices api client
    let numDevs = length devs
    putStrLn $ "Discovered " ++ show numDevs ++ " GPU(s)"

    -- Build a 4096×4096 matmul program
    let modu :: Module
        modu = moduleFromBuilder @'[4096, 4096] @'F32 "main"
            [ FuncArg "arg0" (TensorType [4096, 4096] F32)
            , FuncArg "arg1" (TensorType [4096, 4096] F32)
            ]
            $ do
                x <- arg @'[4096, 4096] @'F32
                y <- arg @'[4096, 4096] @'F32
                z <- matmul x y
                return z

    putStrLn "Compiling (with num_replicas = num_devices)..."
    exec <- compileWithOptions api client (render modu)
                (defaultCompileOptions { optNumReplicas = numDevs })

    let n = matmulSize * matmulSize
        dims = [fromIntegral matmulSize, fromIntegral matmulSize] :: [Int64]
        -- Input A: all 1.0s
        inputA = V.replicate n (1.0 :: Float)
        -- Input B: all 1.0s → each output element = sum of 4096 * 1.0 * 1.0 = 4096.0
        inputB = V.replicate n (1.0 :: Float)

    -- Upload inputs to every GPU
    putStrLn $ "Uploading inputs to " ++ show numDevs ++ " GPU(s)..."
    deviceArgs <- mapM (\dev -> do
        bufA <- toDeviceOn api client dev inputA dims bufferTypeF32
        bufB <- toDeviceOn api client dev inputB dims bufferTypeF32
        return (dev, [bufA, bufB])
      ) devs

    -- Warm-up run
    putStrLn "Warm-up..."
    _ <- executeReplicas api exec deviceArgs

    -- Timed run
    putStrLn "Running inference on all GPUs concurrently..."
    t0 <- getCurrentTime
    results <- executeReplicas api exec deviceArgs
    t1 <- getCurrentTime

    let elapsed = realToFrac (diffUTCTime t1 t0) :: Double
    putStrLn $ "All " ++ show numDevs ++ " GPU(s) completed in " ++ show elapsed ++ "s"

    -- Verify results from each GPU
    putStrLn "Verifying results..."
    allCorrect <- and <$> mapM (\(idx, outs) -> do
        let [bufOut] = outs
        result <- fromDeviceF32 api bufOut n
        let corner = result V.! 0
        let expected = 4096.0 :: Float
        let ok = abs (corner - expected) < 0.1
        putStrLn $ "  GPU " ++ show idx ++ " corner = " ++ show corner
                   ++ if ok then " ✓" else " ✗ FAILED"
        return ok
      ) (zip [0..] results)

    if allCorrect
        then putStrLn "Multi-GPU inference PASSED!"
        else putStrLn "Multi-GPU inference FAILED!"