verifyConcatGradients :: [[Int64]] -> Int32  -> IO ()
verifyConcatGradients shapes concatDim = do
    let floatsFromShape :: [Int64] -> IO [Float]
        floatsFromShape shape = replicateM (fromIntegral $ List.product shape) randomIO
        constantZip = zipWithM $ \x shape -> TF.render $ TF.constant (TF.Shape shape) x
    inputGrads <- mapM floatsFromShape shapes
    inputs     <- mapM floatsFromShape shapes
    dinputs <- TF.runSession $ do
        inputTensors     <- inputs `constantZip` shapes
        inputGradTensors <- inputGrads `constantZip` shapes
        inputTensor      <- TF.render $ TF.concat (TF.scalar concatDim) inputTensors
        inputGradTensor  <- TF.render $ TF.concat (TF.scalar concatDim) inputGradTensors
        output           <- TF.render $ inputTensor `TF.mul` inputGradTensor
        TF.gradients output inputTensors >>= TF.run
    (V.fromList <$> inputGrads) @=? dinputs

-- This test checks that the gradient of a concat op
--   is correct along the first, second, and third dimension.
verifyConcatGradients :: [[Int64]] -> Int32  -> IO ()
verifyConcatGradients shapes concatDim = do
    let floatsFromShape :: [Int64] -> IO [Float]
        floatsFromShape shape = replicateM (fromIntegral $ List.product shape) randomIO
        constantZip = zipWithM $ \x shape -> TF.render $ TF.constant (TF.Shape shape) x
    inputGrads <- mapM floatsFromShape shapes
    inputs     <- mapM floatsFromShape shapes
    dinputs <- TF.runSession $ do
        inputTensors     <- inputs `constantZip` shapes
        inputGradTensors <- inputGrads `constantZip` shapes
        inputTensor      <- TF.render $ TF.concat (TF.scalar concatDim) inputTensors
        inputGradTensor  <- TF.render $ TF.concat (TF.scalar concatDim) inputGradTensors
        output           <- TF.render $ inputTensor `TF.mul` inputGradTensor
        TF.gradients output inputTensors >>= TF.run
    (V.fromList <$> inputGrads) @=? dinputs

-- This test checks that the gradient of a concat op
--   is correct along the first, second, and third dimension.
verifyConcatGradients shapes concatDim = do
    let floatsFromShape :: [Int64] -> IO [Float]
        floatsFromShape shape = replicateM (fromIntegral $ List.product shape) randomIO
        constantZip = zipWithM $ \x shape -> TF.render $ TF.constant (TF.Shape shape) x
    inputGrads <- mapM floatsFromShape shapes
    inputs     <- mapM floatsFromShape shapes
    dinputs <- TF.runSession $ do
        inputTensors     <- inputs `constantZip` shapes
        inputGradTensors <- inputGrads `constantZip` shapes
        inputTensor      <- TF.render $ TF.concat (TF.scalar concatDim) inputTensors
        inputGradTensor  <- TF.render $ TF.concat (TF.scalar concatDim) inputGradTensors
        output           <- TF.render $ inputTensor `TF.mul` inputGradTensor
        TF.gradients output inputTensors >>= TF.run
    (V.fromList <$> inputGrads) @=? dinputs

-- This test checks that the gradient of a concat op
--   is correct along the first, second, and third dimension.