// SIMT GEMM: a workgroup scores 64 corpus rows against 8–64 queries. Corpus
// components enter LDS once in FP16 and expand to FP32 in registers. Queries
// and accumulation remain FP32; components accumulate in increasing order.
amdgpu.target<gfx1151> @target {subgroup_size = 32}
config.decl @db.batch : %value: index where [range(%value, 8, 64), mul(%value, 8)]
config.decl @db.scan.dimensions : %value: index where [range(%value, 128, 16384), mul(%value, 128)]
kernel.def target(@target) export("batch_scan") @batch_scan(%count_arg: i32) {
%one = index.constant 1 : index
%four = index.constant 4 : index
%rows = index.constant 64 : index
%extra = index.constant 63 : index
%batch = config.get @db.batch : index
%threads = index.mul %batch, %four : index
%raw = index.cast %count_arg : i32 to index
%count = index.assume %raw [range(%raw, 1, 262144)] : index
%rounded = index.add %count, %extra : index
%groups = index.div %rounded, %rows : index
kernel.launch.config workgroups(%groups, %one, %one) workgroup_size(%threads, %one, %one) : index
} launch(%count_arg: i32, %masked_arg: i32, %row_base_arg: i32, %total_arg: i32, %data: buffer, %queries: buffer, %norms: buffer, %bitmap: buffer, %scores: buffer) {
%zero = index.constant 0 : offset
%i0 = index.constant 0 : index
%i1 = index.constant 1 : index
%i4 = index.constant 4 : index
%i8 = index.constant 8 : index
%c32 = index.constant 32 : index
%c64 = index.constant 64 : index
%c68 = index.constant 68 : index
%c512 = index.constant 512 : index
%batch = config.get @db.batch : index
%d = config.get @db.scan.dimensions : index
%threads = index.mul %batch, %i4 : index
%query_groups = index.div %batch, %i4 : index
%raw = index.cast %count_arg : i32 to index
%count = index.assume %raw [range(%raw, 1, 262144)] : index
%tid0 = kernel.workitem.id<x> : index
%tid = index.assume %tid0 [range(%tid0, 0, 255)] : index
%group0 = kernel.workgroup.id<x> : index
%group = index.assume %group0 [range(%group0, 0, 4095)] : index
%row_base = index.mul %group, %c64 : index
%row_group = index.div %tid, %query_groups : index
%query_group = index.rem %tid, %query_groups : index
%local_row_base = index.mul %row_group, %i4 : index
%local_query_base = index.mul %query_group, %i4 : index
%dv = buffer.view %data[%zero] : buffer -> view<[%count]x[%d]xf16>
%qv = buffer.view %queries[%zero] : buffer -> view<[%d]x[%batch]xf32>
%nv = buffer.view %norms[%zero] : buffer -> view<[%count]xf32>
%sv = buffer.view %scores[%zero] : buffer -> view<[%batch]x[%count]xf32>
%xbytes = index.constant 4352 : offset
%query_words = index.mul %c32, %batch : index
%four_offset = index.constant 4 : offset
%qbytes = index.scale %query_words, %four_offset : index, offset -> offset
%xscratch = buffer.alloca<workgroup> align(16) %xbytes : buffer
%qscratch = buffer.alloca<workgroup> align(16) %qbytes : buffer
// Padding breaks bank conflicts when transposing corpus components into LDS.
%xs = buffer.view %xscratch[%zero] : buffer -> view<32x68xf16>
%qs = buffer.view %qscratch[%zero] : buffer -> view<32x[%batch]xf32>
%f0 = scalar.constant 0.0 : f32
%initial = vector.splat %f0 : vector<16xf32>
%query_vectors = index.mul %i8, %batch : index
%accumulated = scf.for %base = [%i0 to %d step %c32](%acc = %initial : vector<16xf32>) -> (vector<16xf32>) {
scf.for %v = [%tid to %c512 step %threads] {
%r = index.div %v, %i8 : index
%column_group = index.rem %v, %i8 : index
%column = index.mul %column_group, %i4 : index
%row = index.add %row_base, %r : index
%valid = index.cmp ult, %row, %count : index
%safe = scf.select %valid, %row, %i0 : index
%at = index.assume %safe [range(%safe, 0, 262143), lt(%safe, %count)] : index
%dim = index.add %base, %column : index
%values = vector.load %dv[%at, %dim] : view<[%count]x[%d]xf16> -> vector<4xf16>
scf.for %j = [%i0 to %i4 step %i1] unroll {
%col = index.add %column, %j : index
%value = vector.extract %values[%j] : vector<4xf16> -> f16
view.store %value, %xs[%col, %r] : f16, view<32x68xf16>
}
}
scf.for %v = [%tid to %query_vectors step %threads] {
%col = index.div %v, %query_groups : index
%qgroup = index.rem %v, %query_groups : index
%q = index.mul %qgroup, %i4 : index
%dim = index.add %base, %col : index
%values = vector.load %qv[%dim, %q] : view<[%d]x[%batch]xf32> -> vector<4xf32>
vector.store %values, %qs[%col, %q] : vector<4xf32>, view<32x[%batch]xf32>
}
kernel.barrier<workgroup> scope(workgroup) ordering(acq_rel)
%next = scf.for %col = [%i0 to %c32 step %i1](%values = %acc : vector<16xf32>) -> (vector<16xf32>) unroll {
%xh = vector.load %xs[%col, %local_row_base] : view<32x68xf16> -> vector<4xf16>
%xvalues = vector.extf %xh : vector<4xf16> to vector<4xf32>
%qvalues = vector.load %qs[%col, %local_query_base] : view<32x[%batch]xf32> -> vector<4xf32>
%updated = scf.for %r = [%i0 to %i4 step %i1](%rs = %values : vector<16xf32>) -> (vector<16xf32>) unroll {
%row = index.add %local_row_base, %r : index
%x = vector.extract %xvalues[%r] : vector<4xf32> -> f32
%rbase = index.mul %r, %i4 : index
%new_row = scf.for %q = [%i0 to %i4 step %i1](%sums = %rs : vector<16xf32>) -> (vector<16xf32>) unroll {
%slot = index.add %rbase, %q : index
%old = vector.extract %sums[%slot] : vector<16xf32> -> f32
%y = vector.extract %qvalues[%q] : vector<4xf32> -> f32
%vx = vector.splat %x : vector<1xf32>
%vy = vector.splat %y : vector<1xf32>
%sum = vector.dotf %vx, %vy, %old : vector<1xf32>, vector<1xf32>, f32
%inserted = vector.insert %sum into %sums[%slot] : f32, vector<16xf32>
scf.yield %inserted : vector<16xf32>
}
scf.yield %new_row : vector<16xf32>
}
// Bound operand lifetimes to one column instead of hoisting loads from
// all 32 unrolled columns. This is a compiler hint, not a GPU barrier.
scf.schedule.fence
scf.yield %updated : vector<16xf32>
}
kernel.barrier<workgroup> scope(workgroup) ordering(acq_rel)
scf.yield %next : vector<16xf32>
}
%zero_i32 = scalar.constant 0 : i32
%masked = scalar.cmpi ne, %masked_arg, %zero_i32 : i32
%c31 = index.constant 31 : index
%total0 = index.cast %total_arg : i32 to index
%total = index.assume %total0 [range(%total0, 1, 1073741824)] : index
%global_base0 = index.cast %row_base_arg : i32 to index
%global_base = index.assume %global_base0 [range(%global_base0, 0, 1073741823)] : index
%rounded = index.add %total, %c31 : index
%words = index.div %rounded, %c32 : index
%bv = buffer.view %bitmap[%zero] : buffer -> view<[%words]xi32>
scf.for %r = [%i0 to %i4 step %i1] unroll {
%local = index.add %local_row_base, %r : index
%row = index.add %row_base, %local : index
%valid = index.cmp ult, %row, %count : index
scf.if %valid {
%at = index.assume %row [range(%row, 0, 262143), lt(%row, %count)] : index
%inverse = view.load %nv[%at] : view<[%count]xf32> -> f32
%excluded = scf.if %masked -> (i1) {
%global0 = index.add %global_base, %at : index
%global = index.assume %global0 [range(%global0, 0, 1073741823), lt(%global0, %total)] : index
%word0 = index.div %global, %c32 : index
%word = index.assume %word0 [range(%word0, 0, 33554431), lt(%word0, %words)] : index
%bit = index.rem %global, %c32 : index
%bits = view.load %bv[%word] : view<[%words]xi32> -> i32
%shift = index.cast %bit : index to i32
%shifted = scalar.shrui %bits, %shift : i32
%one = scalar.constant 1 : i32
%flag = scalar.andi %shifted, %one : i32
%test = scalar.cmpi ne, %flag, %zero_i32 : i32
scf.yield %test : i1
} else {
%no = scalar.constant false : i1
scf.yield %no : i1
}
%rbase = index.mul %r, %i4 : index
scf.for %q = [%i0 to %i4 step %i1] unroll {
%query = index.add %local_query_base, %q : index
%slot = index.add %rbase, %q : index
%sum = vector.extract %accumulated[%slot] : vector<16xf32> -> f32
%cosine = scalar.mulf %sum, %inverse : f32
%badbits = scalar.constant -8388608 : i32
%neg = scalar.bitcast %badbits : i32 to f32
%score = scf.select %excluded, %neg, %cosine : f32
view.store %score, %sv[%query, %at] : f32, view<[%batch]x[%count]xf32>
}
}
}
kernel.return
}