Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PageRank #88

Merged
merged 13 commits into from
Mar 18, 2024
42 changes: 33 additions & 9 deletions benchmarks/GraphBLAS-sharp.Benchmarks/Algorithms/BFS.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace GraphBLAS.FSharp.Benchmarks.Algorithms.BFS
namespace GraphBLAS.FSharp.Benchmarks.Algorithms.BFS

open System.IO
open BenchmarkDotNet.Attributes
Expand Down Expand Up @@ -27,7 +27,7 @@ type Benchmarks<'elem when 'elem : struct>(
let mutable matrix = Unchecked.defaultof<ClMatrix<'elem>>
let mutable matrixHost = Unchecked.defaultof<_>

member val ResultLevels = Unchecked.defaultof<ClVector<'elem>> with get,set
member val ResultLevels = Unchecked.defaultof<ClVector<int>> with get,set

[<ParamsSource("AvailableContexts")>]
member val OclContextInfo = Unchecked.defaultof<Utils.BenchmarkContext * int> with get, set
Expand Down Expand Up @@ -127,10 +127,34 @@ type WithoutTransferBenchmark<'elem when 'elem : struct>(
this.BFS()
this.Processor.PostAndReply Msg.MsgNotifyMe

type BFSWithoutTransferBenchmarkInt32() =
type BFSWithoutTransferBenchmarkBool() =

inherit WithoutTransferBenchmark<bool>(
(Algorithms.BFS.singleSource ArithmeticOperations.boolSumOption ArithmeticOperations.boolMulOption),
(fun _ -> true),
(fun _ -> true),
0,
(fun context matrix -> ClMatrix.CSR <| matrix.ToCSR.ToDevice context))

static member InputMatrixProvider =
Benchmarks<_>.InputMatrixProviderBuilder "BFSBenchmarks.txt"

type BFSPushPullWithoutTransferBenchmarkBool() =

inherit WithoutTransferBenchmark<bool>(
(Algorithms.BFS.singleSourcePushPull ArithmeticOperations.boolSumOption ArithmeticOperations.boolMulOption),
(fun _ -> true),
(fun _ -> true),
0,
(fun context matrix -> ClMatrix.CSR <| matrix.ToCSR.ToDevice context))

static member InputMatrixProvider =
Benchmarks<_>.InputMatrixProviderBuilder "BFSBenchmarks.txt"

type SSSPWithoutTransferBenchmarkInt32() =

inherit WithoutTransferBenchmark<int>(
(Algorithms.BFS.singleSource ArithmeticOperations.intSumOption ArithmeticOperations.intMulOption),
Algorithms.SSSP.run,
int32,
(fun _ -> Utils.nextInt (System.Random())),
0,
Expand Down Expand Up @@ -176,12 +200,12 @@ type WithTransferBenchmark<'elem when 'elem : struct>(
this.Processor.PostAndReply Msg.MsgNotifyMe
| _ -> failwith "Impossible"

type BFSWithTransferBenchmarkInt32() =
type BFSWithTransferBenchmarkBool() =

inherit WithTransferBenchmark<int>(
(Algorithms.BFS.singleSource ArithmeticOperations.intSumOption ArithmeticOperations.intMulOption),
int32,
(fun _ -> Utils.nextInt (System.Random())),
inherit WithTransferBenchmark<bool>(
(Algorithms.BFS.singleSource ArithmeticOperations.boolSumOption ArithmeticOperations.boolMulOption),
(fun _ -> true),
(fun _ -> true),
0,
(fun context matrix -> ClMatrix.CSR <| matrix.ToCSR.ToDevice context))

Expand Down
132 changes: 132 additions & 0 deletions benchmarks/GraphBLAS-sharp.Benchmarks/Algorithms/PageRank.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
namespace GraphBLAS.FSharp.Benchmarks.Algorithms.PageRank

open System.IO
open BenchmarkDotNet.Attributes
open GraphBLAS.FSharp
open GraphBLAS.FSharp.IO
open Brahma.FSharp
open Microsoft.FSharp.Core
open GraphBLAS.FSharp.Objects.ArraysExtensions
open GraphBLAS.FSharp.Benchmarks
open GraphBLAS.FSharp.Objects

[<AbstractClass>]
[<IterationCount(10)>]
[<WarmupCount(3)>]
[<Config(typeof<Configs.Matrix>)>]
type Benchmarks(
buildFunToBenchmark,
converter: string -> float32,
binaryConverter,
buildMatrix)
=

let mutable funToBenchmark = None
let mutable matrix = Unchecked.defaultof<ClMatrix<float32>>
let mutable matrixPrepared = Unchecked.defaultof<PageRankMatrix<float32>>
let mutable matrixHost = Unchecked.defaultof<_>

let accuracy = 0.00000001f
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are predefined constants in Expecto.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still, why is it necessary separately? Perhaps we need to create a module with constants and actions on them in order to avoid mistakes in the future?

Abstract types could also be used here, so that they could be used only with the help of this most magical module.


member val Result = Unchecked.defaultof<ClVector<float32>> with get,set

[<ParamsSource("AvailableContexts")>]
member val OclContextInfo = Unchecked.defaultof<Utils.BenchmarkContext * int> with get, set

[<ParamsSource("InputMatrixProvider")>]
member val InputMatrixReader = Unchecked.defaultof<MtxReader> with get, set

member this.OclContext = (fst this.OclContextInfo).ClContext
member this.WorkGroupSize = snd this.OclContextInfo

member this.Processor =
let p = (fst this.OclContextInfo).Queue
p.Error.Add(fun e -> failwithf "%A" e)
p

static member AvailableContexts = Utils.availableContexts

static member InputMatrixProviderBuilder pathToConfig =
let datasetFolder = ""
pathToConfig
|> Utils.getMatricesFilenames
|> Seq.map
(fun matrixFilename ->
printfn "%A" matrixFilename

match Path.GetExtension matrixFilename with
| ".mtx" -> MtxReader(Utils.getFullPathToMatrix datasetFolder matrixFilename)
| _ -> failwith "Unsupported matrix format")

member this.FunToBenchmark =
match funToBenchmark with
| None ->
let x = buildFunToBenchmark this.OclContext this.WorkGroupSize
funToBenchmark <- Some x
x
| Some x -> x

member this.PageRank() =
this.Result <- this.FunToBenchmark this.Processor matrixPrepared accuracy

member this.ClearInputMatrix() =
matrix.Dispose this.Processor

member this.ClearPreparedMatrix() =
matrixPrepared.Dispose this.Processor

member this.ClearResult() = this.Result.Dispose this.Processor

member this.ReadMatrix() =
let converter =
match this.InputMatrixReader.Field with
| Pattern -> binaryConverter
| _ -> converter

matrixHost <- this.InputMatrixReader.ReadMatrix converter

member this.LoadMatrixToGPU() =
matrix <- buildMatrix this.OclContext matrixHost

member this.PrepareMatrix() =
matrixPrepared <- Algorithms.PageRank.prepareMatrix this.OclContext this.WorkGroupSize this.Processor matrix

abstract member GlobalSetup : unit -> unit

abstract member IterationCleanup : unit -> unit

abstract member GlobalCleanup : unit -> unit

abstract member Benchmark : unit -> unit

type PageRankWithoutTransferBenchmarkFloat32() =

inherit Benchmarks(
Algorithms.PageRank.run,
float32,
(fun _ -> float32 <| Utils.nextInt (System.Random())),
(fun context matrix -> ClMatrix.CSR <| matrix.ToCSR.ToDevice context))

static member InputMatrixProvider =
Benchmarks.InputMatrixProviderBuilder "BFSBenchmarks.txt"

[<GlobalSetup>]
override this.GlobalSetup() =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've always thought about it. Don't you need to make the preparatory methods blocking? Then is it possible to avoid blocking in the [<Benchmark>] itself?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a good idea to block after preparotory methods in case some of them are not blocking by itself, but blocking in the [<Benchmark>] is still necessary to wait for all kernels to execute.

this.ReadMatrix()
this.LoadMatrixToGPU()
this.Processor.PostAndReply(Msg.MsgNotifyMe)
this.PrepareMatrix()
this.ClearInputMatrix()

[<IterationCleanup>]
override this.IterationCleanup() =
this.ClearResult()

[<GlobalCleanup>]
override this.GlobalCleanup() =
this.ClearPreparedMatrix()

[<Benchmark>]
override this.Benchmark() =
this.PageRank()
this.Processor.PostAndReply(Msg.MsgNotifyMe)
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@
<Compile Include="Matrix/Map2/MathNET.fs" />
<Compile Include="Vector/Map2.fs" />
<Compile Include="Algorithms/BFS.fs" />
<Compile Include="Algorithms/PageRank.fs" />
<Compile Include="Program.fs" />
<Folder Include="Datasets" />
</ItemGroup>
<Import Project="..\..\.paket\Paket.Restore.targets" />
</Project>
</Project>
2 changes: 1 addition & 1 deletion benchmarks/GraphBLAS-sharp.Benchmarks/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ open BenchmarkDotNet.Running
[<EntryPoint>]
let main argv =
let benchmarks =
BenchmarkSwitcher [| typeof<Algorithms.BFS.BFSWithoutTransferBenchmarkInt32> |]
BenchmarkSwitcher [| typeof<Algorithms.BFS.BFSWithoutTransferBenchmarkBool> |]

benchmarks.Run argv |> ignore
0
5 changes: 5 additions & 0 deletions src/GraphBLAS-sharp.Backend/Algorithms/Algorithms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ module Algorithms =

module SSSP =
let run = SSSP.run

module PageRank =
let run = PageRank.run
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think documentation is needed. At least we should say that matrix has to be prepared first by calling prepareMatrix method.


let prepareMatrix = PageRank.prepareMatrix
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that prepareMatrix should at least have some clarifying comments. Also, to run page rank algorithm user always has to call prepareMatrix first. And I don't think he wants to build a lego house every time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always calling prepareMatrix before algorithm isn't necessary. User might already have matrix in correct format, for example in benchmarks. User may also want to obtain the prepared matrix and use it elsewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we inform the user about this need? What guarantees are there that this condition will be met?

We cannot afford to set such semantic traps at any level of abstraction.

If a precondition arises, it must be expressed in types so that the user is not able to pass an unprepared matrix.

Otherwise, any mention of functional style should be removed from the title of our work "graphblas in functional style".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we do not need to inform user about it because it is not our goal to provide PageRank implementation but to provide enough building blocks to implement it.
This implementation is nothing more but example and test of our api.
It is required to know how PageRank works to implement it using our library, so I think we should not care a lot about specific graph algorithms such as BFS or PageRank inside our library

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsvgit What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ready-to-use garph analysis algorithms should be a part of library. It is the next (possibli highest) leyer of our solution (look at LaGraph). So, implementations of BFS, PAgeRank and other algorithms should be as clear and documented, as possible. Anyway, even basic implmenetation should be a clear anough to be a starting point for those who want ot develop new algorithms using provieed building blocks.

Regarding types. I agree with @IgorErin: if we can express something using types, we should do it. Ofcourse we should find the best way to do it.

8 changes: 4 additions & 4 deletions src/GraphBLAS-sharp.Backend/Algorithms/BFS.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ open GraphBLAS.FSharp.Objects.ClCellExtensions

module internal BFS =
let singleSource
(add: Expr<int option -> int option -> int option>)
(mul: Expr<'a option -> int option -> int option>)
(add: Expr<bool option -> bool option -> bool option>)
(mul: Expr<bool option -> bool option -> bool option>)
(clContext: ClContext)
workGroupSize
=
Expand All @@ -34,14 +34,14 @@ module internal BFS =
let containsNonZero =
Vector.exists Predicates.isSome clContext workGroupSize

fun (queue: MailboxProcessor<Msg>) (matrix: ClMatrix<'a>) (source: int) ->
fun (queue: MailboxProcessor<Msg>) (matrix: ClMatrix<bool>) (source: int) ->
let vertexCount = matrix.RowCount

let levels =
zeroCreate queue DeviceOnly vertexCount Dense

let front =
ofList queue DeviceOnly Dense vertexCount [ source, 1 ]
ofList queue DeviceOnly Dense vertexCount [ source, true ]

let mutable level = 0
let mutable stop = false
Expand Down
Loading