Skip to content

Commit 0ee0155

Browse files
arhikmaleadt
andauthored
Add scan (prefix sum) operations support (#39)
Co-authored-by: Tim Besard <tim.besard@gmail.com>
1 parent b8c9f19 commit 0ee0155

6 files changed

Lines changed: 406 additions & 168 deletions

File tree

src/bytecode/encodings.jl

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,78 @@ function encode_ReduceOp!(body::Function, cb::CodeBuilder,
13311331
end
13321332
end
13331333

1334+
1335+
#=============================================================================
1336+
Scan operations
1337+
=============================================================================#
1338+
1339+
"""
1340+
encode_ScanOp!(body::Function, cb::CodeBuilder,
1341+
result_types::Vector{TypeId},
1342+
operands::Vector{Value},
1343+
dim::Int,
1344+
reverse::Bool,
1345+
identities::Vector{<:IdentityVal},
1346+
body_scalar_types::Vector{TypeId})
1347+
1348+
Encode a ScanOp (parallel prefix sum) operation.
1349+
1350+
# Arguments
1351+
- body: Function that takes block args and yields result(s)
1352+
- cb: CodeBuilder for the bytecode
1353+
- result_types: Output tile types
1354+
- operands: Input tiles to scan
1355+
- dim: Dimension to scan along (0-indexed)
1356+
- reverse: Whether to scan in reverse order
1357+
- identities: Identity values for each operand
1358+
- body_scalar_types: 0D tile types for body arguments
1359+
"""
1360+
function encode_ScanOp!(body::Function, cb::CodeBuilder,
1361+
result_types::Vector{TypeId},
1362+
operands::Vector{Value},
1363+
dim::Int,
1364+
reverse::Bool,
1365+
identities::Vector{<:IdentityVal},
1366+
body_scalar_types::Vector{TypeId})
1367+
encode_varint!(cb.buf, Opcode.ScanOp)
1368+
1369+
# Variadic result types
1370+
encode_typeid_seq!(cb.buf, result_types)
1371+
1372+
# Attributes: dim (int), reverse (bool), identities (array)
1373+
encode_opattr_int!(cb, dim)
1374+
encode_opattr_bool!(cb, reverse)
1375+
encode_identity_array!(cb, identities)
1376+
1377+
# Variadic operands
1378+
encode_varint!(cb.buf, length(operands))
1379+
encode_operands!(cb.buf, operands)
1380+
1381+
# Number of regions
1382+
push!(cb.debug_attrs, cb.cur_debug_attr)
1383+
cb.num_ops += 1
1384+
encode_varint!(cb.buf, 1) # 1 region: body
1385+
1386+
# Body region - block args are pairs of (acc, elem) for each operand
1387+
# The body operates on 0D tiles (scalars)
1388+
body_arg_types = TypeId[]
1389+
for scalar_type in body_scalar_types
1390+
push!(body_arg_types, scalar_type) # accumulator
1391+
push!(body_arg_types, scalar_type) # element
1392+
end
1393+
with_region(body, cb, body_arg_types)
1394+
1395+
# Create result values
1396+
num_results = length(result_types)
1397+
if num_results == 0
1398+
return Value[]
1399+
else
1400+
vals = [Value(cb.next_value_id + i) for i in 0:num_results-1]
1401+
cb.next_value_id += num_results
1402+
return vals
1403+
end
1404+
end
1405+
13341406
#=============================================================================
13351407
Comparison and selection operations
13361408
=============================================================================#

src/compiler/intrinsics/conversions.jl

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,17 @@ function emit_intrinsic!(ctx::CGCtx, ::typeof(Intrinsics.astype), args)
4040
target_dtype = julia_to_tile_dtype!(tt, target_elem)
4141
target_tile_type = tile_type!(tt, target_dtype, tile_shape)
4242

43-
# Determine signedness for integer types
44-
function is_signed_int(T)
45-
T <: Signed || T === Int32 || T === Int64 || T === Int16 || T === Int8
46-
end
47-
4843
# Emit conversion based on source and target types
4944
result = if source_elem <: AbstractFloat && target_elem <: AbstractFloat
5045
# Float -> Float
5146
encode_FToFOp!(cb, target_tile_type, source.v)
5247
elseif source_elem <: Integer && target_elem <: AbstractFloat
5348
# Integer -> Float
54-
signedness = is_signed_int(source_elem) ? SignednessSigned : SignednessUnsigned
49+
signedness = source_elem <: Signed ? SignednessSigned : SignednessUnsigned
5550
encode_IToFOp!(cb, target_tile_type, source.v; signedness)
5651
elseif source_elem <: AbstractFloat && target_elem <: Integer
5752
# Float -> Integer
58-
signedness = is_signed_int(target_elem) ? SignednessSigned : SignednessUnsigned
53+
signedness = target_elem <: Signed ? SignednessSigned : SignednessUnsigned
5954
encode_FToIOp!(cb, target_tile_type, source.v; signedness)
6055
elseif source_elem <: Integer && target_elem <: Integer
6156
# Integer -> Integer
@@ -66,7 +61,7 @@ function emit_intrinsic!(ctx::CGCtx, ::typeof(Intrinsics.astype), args)
6661
source.v
6762
elseif target_size > source_size
6863
# Extension (upsize)
69-
signedness = is_signed_int(source_elem) ? SignednessSigned : SignednessUnsigned
64+
signedness = source_elem <: Signed ? SignednessSigned : SignednessUnsigned
7065
encode_ExtIOp!(cb, target_tile_type, source.v; signedness)
7166
else
7267
# Truncation (downsize)

src/compiler/intrinsics/core.jl

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ function emit_reduce!(ctx::CGCtx, args, reduce_fn::Symbol)
569569
results = encode_ReduceOp!(cb, [output_tile_type], [input_tv.v], axis, [identity], [scalar_tile_type]) do block_args
570570
acc, elem = block_args[1], block_args[2]
571571

572-
res = encode_reduce_body(cb, scalar_tile_type, acc, elem, reduce_fn, elem_type)
572+
res = encode_binop_body(cb, scalar_tile_type, acc, elem, reduce_fn, elem_type)
573573
encode_YieldOp!(cb, [res])
574574
end
575575

@@ -609,26 +609,18 @@ operation_identity(::Val{:max}, dtype, ::Type{T}) where T <: Integer =
609609
IntegerIdentityVal(to_uint128(typemin(T)), dtype, T)
610610

611611
#=============================================================================#
612-
# Reduce Body Operations
612+
# Binary Operation Body Encoding (shared by reduce and scan)
613613
#=============================================================================#
614-
function encode_reduce_body(cb, type, acc, elem, op::Symbol, ::Type{T}) where T
614+
function encode_binop_body(cb, type, acc, elem, op::Symbol, ::Type{T}) where T
615615
if T <: AbstractFloat
616-
if op == :add
617-
encode_AddFOp!(cb, type, acc, elem)
618-
elseif op == :max
619-
encode_MaxFOp!(cb, type, acc, elem)
620-
else
621-
error("Unsupported float reduction operation: $op")
622-
end
623-
else # Integer
616+
op == :add ? encode_AddFOp!(cb, type, acc, elem) :
617+
op == :max ? encode_MaxFOp!(cb, type, acc, elem) :
618+
error("Unsupported float operation: $op")
619+
else
624620
signedness = T <: Signed ? SignednessSigned : SignednessUnsigned
625-
if op == :add
626-
encode_AddIOp!(cb, type, acc, elem)
627-
elseif op == :max
628-
encode_MaxIOp!(cb, type, acc, elem; signedness)
629-
else
630-
error("Unsupported integer reduction operation: $op")
631-
end
621+
op == :add ? encode_AddIOp!(cb, type, acc, elem) :
622+
op == :max ? encode_MaxIOp!(cb, type, acc, elem; signedness) :
623+
error("Unsupported integer operation: $op")
632624
end
633625
end
634626

@@ -702,7 +694,72 @@ function emit_intrinsic!(ctx::CGCtx, ::typeof(Intrinsics.reshape), args)
702694
CGVal(current_val, result_type_id, Tile{elem_type, Tuple(target_shape)}, target_shape)
703695
end
704696

705-
# TODO: cuda_tile.scan
697+
# cuda_tile.scan
698+
@eval Intrinsics begin
699+
"""
700+
scan(tile, axis_val, fn_type; reverse=false)
701+
702+
Parallel prefix scan along specified dimension.
703+
fn_type=:add for cumulative sum (only supported operation).
704+
reverse=false for forward scan, true for reverse scan.
705+
Compiled to cuda_tile.scan.
706+
"""
707+
@noinline function scan(tile::Tile{T, S}, ::Val{axis}, fn::Symbol, reverse::Bool=false) where {T, S, axis}
708+
# Scan preserves shape - result has same dimensions as input
709+
Tile{T, S}()
710+
end
711+
end
712+
713+
function emit_intrinsic!(ctx::CGCtx, ::typeof(Intrinsics.scan), args)
714+
cb = ctx.cb
715+
tt = ctx.tt
716+
717+
# Get input tile
718+
input_tv = emit_value!(ctx, args[1])
719+
input_tv === nothing && error("Cannot resolve input tile for scan")
720+
721+
# Get scan axis
722+
axis = @something get_constant(ctx, args[2]) error("Scan axis must be a compile-time constant")
723+
724+
# Get scan function type (only :add is supported)
725+
fn_type = @something get_constant(ctx, args[3]) error("Scan function type must be a compile-time constant")
726+
fn_type == :add || error("Only :add (cumulative sum) is currently supported for scan operations")
727+
728+
# Get reverse flag (optional, defaults to false)
729+
reverse = false
730+
if length(args) >= 4
731+
reverse_val = get_constant(ctx, args[4])
732+
reverse = reverse_val === true
733+
end
734+
735+
# Get element type and shapes
736+
input_type = unwrap_type(input_tv.jltype)
737+
elem_type = input_type <: Tile ? input_type.parameters[1] : input_type
738+
input_shape = input_tv.shape
739+
740+
# For scan, output shape is same as input shape
741+
output_shape = copy(input_shape)
742+
743+
dtype = julia_to_tile_dtype!(tt, elem_type)
744+
745+
# Output tile type (same shape as input)
746+
output_tile_type = tile_type!(tt, dtype, output_shape)
747+
748+
# Scalar type for scan body (0D tile)
749+
scalar_tile_type = tile_type!(tt, dtype, Int[])
750+
751+
# Create identity value using operation_identity
752+
identity = operation_identity(Val(fn_type), dtype, elem_type)
753+
754+
# Emit ScanOp
755+
results = encode_ScanOp!(cb, [output_tile_type], [input_tv.v], axis, reverse, [identity], [scalar_tile_type]) do block_args
756+
acc, elem = block_args[1], block_args[2]
757+
res = encode_binop_body(cb, scalar_tile_type, acc, elem, fn_type, elem_type)
758+
encode_YieldOp!(cb, [res])
759+
end
760+
761+
CGVal(results[1], output_tile_type, Tile{elem_type, Tuple(output_shape)}, output_shape)
762+
end
706763

707764
# cuda_tile.select
708765
@eval Intrinsics begin

src/language/operations.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,19 @@ end
553553
Intrinsics.reduce_max(tile, Val(axis - 1))
554554
end
555555

556+
# Scan (Prefix Sum) Operations
557+
558+
@inline function scan(tile::Tile{T, S}, ::Val{axis},
559+
fn::Symbol=:add,
560+
reverse::Bool=false) where {T<:Number, S, axis}
561+
Intrinsics.scan(tile, Val(axis - 1), fn, reverse)
562+
end
563+
564+
@inline function cumsum(tile::Tile{T, S}, ::Val{axis},
565+
reverse::Bool=false) where {T<:Number, S, axis}
566+
scan(tile, Val(axis), :add, reverse)
567+
end
568+
556569
#=============================================================================
557570
Matrix multiplication
558571
=============================================================================#

test/codegen.jl

Lines changed: 75 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,63 @@
1919
# TODO: mmai - integer matrix multiply-accumulate
2020
# TODO: offset - tile offset computation
2121
# TODO: pack - pack tiles
22-
# TODO: scan - parallel scan/prefix sum
22+
@testset "scan" begin
23+
# Forward scan - float and integer types
24+
for (T, spec, op_check) in [
25+
(Float32, spec1d, "addf"),
26+
(Int32, spec1d, "addi"),
27+
]
28+
@test @filecheck begin
29+
@check_label "entry"
30+
code_tiled(Tuple{ct.TileArray{T,1,spec}}) do a
31+
pid = ct.bid(1)
32+
tile = ct.load(a, pid, (16,))
33+
@check "scan"
34+
@check op_check
35+
Base.donotdelete(ct.scan(tile, Val(1), :add, false))
36+
return
37+
end
38+
end
39+
end
40+
41+
# 2D scan along different axes
42+
@test @filecheck begin
43+
@check_label "entry"
44+
code_tiled(Tuple{ct.TileArray{Float32,2,spec2d}}) do a
45+
pid = ct.bid(1)
46+
tile = ct.load(a, pid, (4, 8))
47+
@check "scan"
48+
Base.donotdelete(ct.scan(tile, Val(1), :add, false))
49+
@check "scan"
50+
Base.donotdelete(ct.scan(tile, Val(2), :add, false))
51+
return
52+
end
53+
end
54+
55+
# Reverse scan
56+
@test @filecheck begin
57+
@check_label "entry"
58+
code_tiled(Tuple{ct.TileArray{Float32,2,spec2d}}) do a
59+
pid = ct.bid(1)
60+
tile = ct.load(a, pid, (4, 8))
61+
@check "scan"
62+
Base.donotdelete(ct.scan(tile, Val(1), :add, true))
63+
return
64+
end
65+
end
66+
67+
# cumsum convenience
68+
@test @filecheck begin
69+
@check_label "entry"
70+
code_tiled(Tuple{ct.TileArray{Float32,2,spec2d}}) do a
71+
pid = ct.bid(1)
72+
tile = ct.load(a, pid, (4, 8))
73+
@check "scan"
74+
Base.donotdelete(ct.cumsum(tile, Val(2), false))
75+
return
76+
end
77+
end
78+
end
2379
# TODO: unpack - unpack tiles
2480

2581
@testset "reshape" begin
@@ -385,61 +441,25 @@
385441
return
386442
end
387443
end
388-
end
389444

390-
# Integer reduce_sum (Int32)
391-
@test @filecheck begin
392-
@check_label "entry"
393-
code_tiled(Tuple{ct.TileArray{Int32,2,spec2d}, ct.TileArray{Int32,1,spec1d}}) do a, b
394-
pid = ct.bid(1)
395-
tile = ct.load(a, pid, (4, 16))
396-
@check "reduce"
397-
@check "addi"
398-
sums = ct.reduce_sum(tile, 2)
399-
ct.store(b, pid, sums)
400-
return
401-
end
402-
end
403-
404-
# Integer reduce_max (Int32)
405-
@test @filecheck begin
406-
@check_label "entry"
407-
code_tiled(Tuple{ct.TileArray{Int32,2,spec2d}, ct.TileArray{Int32,1,spec1d}}) do a, b
408-
pid = ct.bid(1)
409-
tile = ct.load(a, pid, (4, 16))
410-
@check "reduce"
411-
@check "maxi"
412-
maxes = ct.reduce_max(tile, 2)
413-
ct.store(b, pid, maxes)
414-
return
415-
end
416-
end
417-
418-
# Unsigned reduce_sum (UInt32)
419-
@test @filecheck begin
420-
@check_label "entry"
421-
code_tiled(Tuple{ct.TileArray{UInt32,2,spec2d}, ct.TileArray{UInt32,1,spec1d}}) do a, b
422-
pid = ct.bid(1)
423-
tile = ct.load(a, pid, (4, 16))
424-
@check "reduce"
425-
@check "addi"
426-
sums = ct.reduce_sum(tile, 2)
427-
ct.store(b, pid, sums)
428-
return
429-
end
430-
end
431-
432-
# Unsigned reduce_max (UInt32)
433-
@test @filecheck begin
434-
@check_label "entry"
435-
code_tiled(Tuple{ct.TileArray{UInt32,2,spec2d}, ct.TileArray{UInt32,1,spec1d}}) do a, b
436-
pid = ct.bid(1)
437-
tile = ct.load(a, pid, (4, 16))
438-
@check "reduce"
439-
@check "maxi"
440-
maxes = ct.reduce_max(tile, 2)
441-
ct.store(b, pid, maxes)
442-
return
445+
# Integer/unsigned reduce
446+
for (T, op, op_check) in [
447+
(Int32, ct.reduce_sum, "addi"),
448+
(Int32, ct.reduce_max, "maxi"),
449+
(UInt32, ct.reduce_sum, "addi"),
450+
(UInt32, ct.reduce_max, "maxi"),
451+
]
452+
@test @filecheck begin
453+
@check_label "entry"
454+
code_tiled(Tuple{ct.TileArray{T,2,spec2d}}) do a
455+
pid = ct.bid(1)
456+
tile = ct.load(a, pid, (4, 16))
457+
@check "reduce"
458+
@check op_check
459+
Base.donotdelete(op(tile, 2))
460+
return
461+
end
462+
end
443463
end
444464
end
445465

0 commit comments

Comments
 (0)