Skip to content

Write Loops Dense! Run Loops Sparse!

License

Notifications You must be signed in to change notification settings

nullplay/Finch.jl

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Finch.jl

Documentation Build Status Try It Online!

Finch is a adaptable Julia-to-Julia compiler for loop nests over sparse or structured multidimensional arrays. In addition to supporting sparse arrays, Finch can also handle custom operators and fill values other than zero, runs of repeated values, or even special structures such as clustered nonzeros or triangular patterns.

Finch allows you to write for-loops as if they are dense, but compile them to be sparse! The compiler takes care of applying rules like x * 0 => 0 and the like to avoid redundant computation. Finch also supports if-statements and custom user types and functions. Users can add rewrite rules to inform the compiler about any special user-defined properties or optimizations. You can even modify indexing expressions to express sparse convolution, or to describe windows into structured arrays.

As an example, here's a program which calculates the minimum, maximum, sum, and variance of a sparse vector, reading the vector only once, and only reading nonzero values:

using Finch

X = @fiber(sl(e(0.0)), fsprand((10,), 0.5))
x = Scalar(0.0)
x_min = Scalar(Inf)
x_max = Scalar(-Inf)
x_sum = Scalar(0.0)
x_var = Scalar(0.0)
@finch begin
    for i = _
        x .= 0
        x[] = X[i]
        x_min[] <<min>>= x[]
        x_max[] <<max>>= x[]
        x_sum[] += x[]
        x_var[] += x[] * x[]
    end
end;

You can see what kind of code Finch generates using @finch_code

@finch_code begin
    for i = _
        x .= 0
        x[] = X[i]
        x_min[] <<min>>= x[]
        x_max[] <<max>>= x[]
        x_sum[] += x[]
        x_var[] += x[] * x[]
    end
end
quote
    X_lvl = (ex.body.bodies[2]).rhs.tns.tns.lvl
    X_lvl_2 = X_lvl.lvl
    x_min = (ex.body.bodies[3]).lhs.tns.tns
    x_min_val = x_min.val
    x_max = (ex.body.bodies[4]).lhs.tns.tns
    x_max_val = x_max.val
    x_sum = (ex.body.bodies[5]).lhs.tns.tns
    x_sum_val = x_sum.val
    x_var = (ex.body.bodies[6]).lhs.tns.tns
    x_var_val = x_var.val
    X_lvl_q = X_lvl.ptr[1]
    X_lvl_q_stop = X_lvl.ptr[1 + 1]
    if X_lvl_q < X_lvl_q_stop
        X_lvl_i1 = X_lvl.idx[X_lvl_q_stop - 1]
    else
        X_lvl_i1 = 0
    end
    i = 1
    phase_stop = min(X_lvl_i1, X_lvl.shape)
    if phase_stop >= 1
        i = 1
        if X_lvl.idx[X_lvl_q] < 1
            X_lvl_q = scansearch(X_lvl.idx, 1, X_lvl_q, X_lvl_q_stop - 1)
        end
        while i <= phase_stop
            X_lvl_i = X_lvl.idx[X_lvl_q]
            phase_stop_2 = min(phase_stop, X_lvl_i)
            if X_lvl_i == phase_stop_2
                cond = 0 < -i + phase_stop_2
                if cond
                    x_max_val = max(0.0, x_max_val)
                    x_min_val = min(0.0, x_min_val)
                end
                X_lvl_2_val_2 = X_lvl_2.val[X_lvl_q]
                x_min_val = min(x_min_val, X_lvl_2_val_2)
                x_max_val = max(x_max_val, X_lvl_2_val_2)
                x_sum_val = X_lvl_2_val_2 + x_sum_val
                x_var_val = X_lvl_2_val_2 * X_lvl_2_val_2 + x_var_val
                X_lvl_q += 1
            else
                x_max_val = max(0.0, x_max_val)
                x_min_val = min(0.0, x_min_val)
            end
            i = phase_stop_2 + 1
        end
        i = phase_stop + 1
    end
    if X_lvl.shape >= i
        cond_2 = 0 < X_lvl.shape + 1 + -i
        if cond_2
            x_max_val = max(0.0, x_max_val)
            x_min_val = min(0.0, x_min_val)
        end
    end
    (x_var = (Scalar){0.0, Float64}(x_var_val), x_sum = (Scalar){0.0, Float64}(x_sum_val), x_max = (Scalar){-Inf, Float64}(x_max_val), x_min = (Scalar){Inf, Float64}(x_min_val))
end

Array formats in Finch are described recursively mode by mode. Semantically, an array in Finch can be understood as a tree, where each level in the tree corresponds to a dimension and each edge corresponds to an index. For example, @fiber(d(sl(e(0.0)))) constructs a Float64 CSC-format sparse matrix, and @fiber(sl(sl(e(0.0)))) constructs a DCSC-format hypersparse matrix. As another example, here's a column-major sparse matrix-vector multiply:

x = @fiber(d(e(0.0)), rand(42));
A = @fiber(d(sl(e(0.0))), fsprand((42, 42), 0.1));
y = @fiber(d(e(0.0)));
@finch begin
    y .= 0
    for j=_, i=_
        y[i] += A[i, j] * x[j]
    end
end;

At it's heart, Finch is powered by a new domain specific language for coiteration, breaking structured iterators into control flow units we call Looplets. Looplets are lowered progressively with several stages for rewriting and simplification.

The technologies enabling Finch are described in our manuscript.

Installation

At the Julia REPL, install the latest stable version by running:

julia> using Pkg; Pkg.add("Finch")

About

Write Loops Dense! Run Loops Sparse!

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Julia 96.6%
  • C 3.1%
  • Makefile 0.3%