packages feed

hhlo-0.11.0.0: test/Test/Autograd/Rules.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}
module Test.Autograd.Rules (tests) where

import Prelude hiding (negate)
import Test.Tasty
import Test.Tasty.HUnit

import Data.Proxy (Proxy(..))
import HHLO.Core.Types
import HHLO.EDSL.Ops
import HHLO.IR.AST (FuncArg(..), Module)
import HHLO.IR.Builder
import HHLO.IR.Pretty
import HHLO.Autograd
import HHLO.ShapeCheck

import qualified Data.Text as T

tests :: TestTree
tests = testGroup "Autograd.Rules"
    [ testCase "vjpAdd" $ do
        let f x = do
                y <- add x x
                sumAll y
            modu = gradModule @'[2] @'F32 f
            text = render modu
        assertBool "contains func.func" ("func.func" `T.isInfixOf` text)
        assertBool "contains return" ("return" `T.isInfixOf` text)
    , testCase "vjpMultiply" $ do
        let f x = do
                y <- multiply x x
                sumAll y
            modu = gradModule @'[2] @'F32 f
            text = render modu
        assertBool "non-empty module" (not $ T.null text)
    , testCase "vjpNegate" $ do
        let f x = do
                y <- negate x
                sumAll y
            modu = gradModule @'[2] @'F32 f
            text = render modu
        assertBool "non-empty module" (not $ T.null text)
    , testCase "vjpExponential" $ do
        let f x = do
                y <- exponential x
                sumAll y
            modu = gradModule @'[2] @'F32 f
            text = render modu
        assertBool "non-empty module" (not $ T.null text)
    , testCase "vjpReduceWindow (avgPool)" $ do
        let f x = do
                let windowDims = v4 1 2 2 1
                    strides    = v4 1 2 2 1
                    padding    = v4 (0,0) (0,0) (0,0) (0,0)
                initVal <- constant @'[] @'F32 0.0
                y <- reduceWindow windowDims strides padding "stablehlo.add" initVal x
                divisor <- constant @'[] @'F32 4.0
                divisorBC <- broadcastWithDims @'[] @'[1, 1, 1, 1] [] divisor
                z <- divide y divisorBC
                sumAll z
            modu = gradModule @'[1, 4, 4, 1] @'F32 f
            text = render modu
        assertBool "contains reduce_window" ("reduce_window" `T.isInfixOf` text)
        assertBool "contains pad" ("pad" `T.isInfixOf` text)
    , testCase "vjpConvolution" $ do
        let f x = do
                k <- constant @'[2, 2, 1, 1] @'F32 1.0
                y <- conv2d @1 @3 @3 @1 @1 @2 @2 @2 @2 x k
                sumAll y
            modu = gradModule @'[1, 3, 3, 1] @'F32 f
            text = render modu
        assertBool "contains convolution" ("convolution" `T.isInfixOf` text)
        assertBool "contains reverse" ("reverse" `T.isInfixOf` text)
    , testCase "vjpTransposeConvolution" $ do
        let f x = do
                k <- constant @'[2, 2, 1, 1] @'F32 1.0
                y <- transposeConvolution @1 @2 @2 @1 @1 @2 @2 @2 @2 (v2 2 2) (p2 (0,0) (0,0)) x k
                sumAll y
            modu = gradModule @'[1, 2, 2, 1] @'F32 f
            text = render modu
        assertBool "contains convolution" ("convolution" `T.isInfixOf` text)
    , testCase "vjpConvolution checkModule" $ do
        let f x = do
                k <- constant @'[2, 2, 1, 1] @'F32 1.0
                y <- conv2d @1 @3 @3 @1 @1 @2 @2 @2 @2 x k
                sumAll y
            modu = gradModule @'[1, 3, 3, 1] @'F32 f
        case checkModule modu of
            Left err -> assertFailure $ show err
            Right () -> return ()
    , testCase "vjpTransposeConvolution checkModule" $ do
        let f x = do
                k <- constant @'[2, 2, 1, 1] @'F32 1.0
                y <- transposeConvolution @1 @2 @2 @1 @1 @2 @2 @2 @2 (v2 2 2) (p2 (0,0) (0,0)) x k
                sumAll y
            modu = gradModule @'[1, 2, 2, 1] @'F32 f
        case checkModule modu of
            Left err -> assertFailure $ show err
            Right () -> return ()
    , testCase "vjpConvolution through moduleFromBuilder" $ do
        let modu :: Module
            modu = moduleFromBuilder @'[1, 3, 3, 1] @'F32 "main"
                [FuncArg "x" (tensorType (Proxy @'[1, 3, 3, 1]) (Proxy @'F32))]
                $ do
                    x <- arg @'[1, 3, 3, 1] @'F32
                    k <- constant @'[2, 2, 1, 1] @'F32 1.0
                    let f z = do
                            y <- conv2d @1 @3 @3 @1 @1 @2 @2 @2 @2 z k
                            sumAll y
                    seed <- constant @'[] @'F32 1.0
                    gradX <- vjp f x seed
                    return gradX
            text = render modu
        assertBool "contains convolution" ("convolution" `T.isInfixOf` text)
    , testCase "vjpTransposeConvolution through moduleFromBuilder" $ do
        let modu :: Module
            modu = moduleFromBuilder @'[1, 2, 2, 1] @'F32 "main"
                [FuncArg "x" (tensorType (Proxy @'[1, 2, 2, 1]) (Proxy @'F32))]
                $ do
                    x <- arg @'[1, 2, 2, 1] @'F32
                    k <- constant @'[2, 2, 1, 1] @'F32 1.0
                    let f z = do
                            y <- transposeConvolution @1 @2 @2 @1 @1 @2 @2 @2 @2 (v2 2 2) (p2 (0,0) (0,0)) z k
                            sumAll y
                    seed <- constant @'[] @'F32 1.0
                    gradX <- vjp f x seed
                    return gradX
            text = render modu
        assertBool "contains convolution" ("convolution" `T.isInfixOf` text)
    , testCase "gradModule2" $ do
        let f x y = do
                z <- multiply x y
                sumAll z
            modu = gradModule2 @'[2] @'F32 @'[2] @'F32 f
            text = render modu
        assertBool "contains func.func" ("func.func" `T.isInfixOf` text)
        assertBool "two results" $ (length $ filter (=="->") $ T.chunksOf 2 text) >= 1
    ]