Skip to content

Commit 8a875e1

Browse files
committed
Fix numerical errors
1 parent cec6c99 commit 8a875e1

3 files changed

Lines changed: 145 additions & 33 deletions

File tree

benchmarks/test_benchmark_nearest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
(8192, 4096),
2323
(8192, 8192),
2424
(8201, 4103),
25+
(32000, 32000),
2526
(255, 127),
2627
(256, 5),
2728
(1024, 5),

benchmarks/test_benchmark_radius.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
(8192, 4096),
2626
(8192, 8192),
2727
(8201, 4103),
28+
(32000, 32000),
2829
(255, 127),
2930
(256, 5),
3031
(1024, 5),

torch_cluster/triton/_kernels.py

Lines changed: 143 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import triton
55
import triton.language as tl
6+
from triton.language.extra import libdevice
67

78

89
@triton.autotune(
@@ -118,6 +119,7 @@ def _nearest_kernel(
118119
right = M
119120

120121
x_sq = tl.zeros((BLOCK_N,), dtype=tl.float32) # ||x||^2.
122+
x_sq_c = tl.zeros((BLOCK_N,), dtype=tl.float32)
121123
x_block_ptr_sq = tl.make_block_ptr(
122124
base=x_ptr,
123125
shape=(D, N),
@@ -135,7 +137,15 @@ def _nearest_kernel(
135137
boundary_check=(0, 1),
136138
padding_option="zero",
137139
)
138-
x_sq += tl.sum(x * x, axis=0)
140+
x_term = tl.sum(x * x, axis=0)
141+
x_t = x_sq + x_term
142+
x_cond = tl.abs(x_sq) >= tl.abs(x_term)
143+
x_sq_c += tl.where(
144+
x_cond,
145+
(x_sq - x_t) + x_term,
146+
(x_term - x_t) + x_sq,
147+
)
148+
x_sq = x_t
139149
x_block_ptr_sq = tl.advance(x_block_ptr_sq, (BLOCK_K, 0))
140150
if COSINE:
141151
inv_x = tl.rsqrt(x_sq + EPS) # 1/||x||.
@@ -155,7 +165,9 @@ def _nearest_kernel(
155165
full_y = y_start + BLOCK_M <= M # Full tile.
156166

157167
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) # Dot acc.
168+
acc_c = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
158169
y_sq = tl.zeros((BLOCK_M,), dtype=tl.float32) # ||y||^2.
170+
y_sq_c = tl.zeros((BLOCK_M,), dtype=tl.float32)
159171

160172
x_block_ptr = x_block_ptr_base
161173
y_block_ptr = tl.make_block_ptr(
@@ -187,16 +199,36 @@ def _nearest_kernel(
187199
boundary_check=(0, 1),
188200
padding_option="zero",
189201
)
190-
acc += tl.dot(y, x, input_precision=INPUT_PRECISION)
191-
y_sq += tl.sum(y * y, axis=1)
202+
dot_term = tl.dot(y, x, input_precision=INPUT_PRECISION)
203+
dot_t = acc + dot_term
204+
dot_cond = tl.abs(acc) >= tl.abs(dot_term)
205+
acc_c += tl.where(
206+
dot_cond,
207+
(acc - dot_t) + dot_term,
208+
(dot_term - dot_t) + acc,
209+
)
210+
acc = dot_t
211+
y_term = tl.sum(y * y, axis=1)
212+
y_t = y_sq + y_term
213+
y_cond = tl.abs(y_sq) >= tl.abs(y_term)
214+
y_sq_c += tl.where(
215+
y_cond,
216+
(y_sq - y_t) + y_term,
217+
(y_term - y_t) + y_sq,
218+
)
219+
y_sq = y_t
192220
y_block_ptr = tl.advance(y_block_ptr, (0, BLOCK_K))
193221
x_block_ptr = tl.advance(x_block_ptr, (BLOCK_K, 0))
194222

195223
if COSINE:
196-
inv_y = tl.rsqrt(y_sq + EPS) # 1/||y||.
224+
inv_y = tl.rsqrt(y_sq + y_sq_c + EPS) # 1/||y||.
197225
dist = 1.0 - acc * (inv_y[:, None] * inv_x[None, :])
198226
else:
199-
dist = y_sq[:, None] + x_sq[None, :] - 2.0 * acc
227+
dist = (
228+
(y_sq + y_sq_c)[:, None]
229+
+ (x_sq + x_sq_c)[None, :]
230+
- 2.0 * acc
231+
)
200232

201233
if full_y:
202234
valid = tl.broadcast_to(mask_x[None, :], (BLOCK_M, BLOCK_N))
@@ -279,7 +311,12 @@ def _nearest_kernel(
279311
args['BLOCK_N'],
280312
),
281313
'EVEN_D': lambda args: args['D'] % args['BLOCK_D'] == 0,
282-
'K_PAD': lambda args: triton.next_power_of_2(args['K'])
314+
'N_BLOCKS_PER_K': (
315+
lambda args: max(
316+
1,
317+
triton.next_power_of_2(args['K']) // args['BLOCK_N'],
318+
)
319+
),
283320
})
284321
@triton.jit
285322
def _knn_segmented_kernel(
@@ -297,7 +334,6 @@ def _knn_segmented_kernel(
297334
stride_ym,
298335
stride_yd,
299336
K: tl.constexpr,
300-
K_PAD: tl.constexpr,
301337
USE_BATCH: tl.constexpr,
302338
INPUT_PRECISION: tl.constexpr,
303339
BLOCK_N: tl.constexpr,
@@ -308,20 +344,34 @@ def _knn_segmented_kernel(
308344
COSINE: tl.constexpr,
309345
EPS: tl.constexpr,
310346
IGNORE_SAME_INDEX: tl.constexpr,
347+
N_BLOCKS_PER_K: tl.constexpr,
311348
):
312349
pid = tl.program_id(0) # Program id over y rows.
313350
n_y = pid # Current y index.
314351
mask_y = n_y < M # Mask for valid y.
315352

316-
k_offsets = tl.arange(0, K_PAD) # Offsets for padded top-k buffer.
353+
k_offsets = tl.arange(
354+
0,
355+
N_BLOCKS_PER_K * BLOCK_N,
356+
) # Offsets for padded top-k buffer.
317357
INF_KEY = 0x7f800000ffffffff
318358

319359
best_dist_key = tl.full(
320-
(K_PAD,),
360+
(N_BLOCKS_PER_K * BLOCK_N,),
321361
INF_KEY,
322362
tl.int64,
323363
)
324364

365+
if N_BLOCKS_PER_K > 1:
366+
acc_key = tl.full(
367+
(N_BLOCKS_PER_K, BLOCK_N),
368+
INF_KEY,
369+
tl.int64,
370+
)
371+
k_rows = tl.arange(0, N_BLOCKS_PER_K)[:, None].broadcast_to(
372+
(N_BLOCKS_PER_K, BLOCK_N)
373+
)
374+
325375
if USE_BATCH:
326376
example_idx = tl.load(
327377
batch_y_ptr + n_y,
@@ -342,6 +392,7 @@ def _knn_segmented_kernel(
342392
) # Segment end.
343393
if COSINE:
344394
y_sq = tl.zeros((1,), tl.float32) # ||y||^2 accumulator.
395+
y_sq_c = tl.zeros((1,), tl.float32)
345396
y_norm_ptr = tl.make_block_ptr(
346397
base=y_ptr,
347398
shape=(D, M),
@@ -359,7 +410,15 @@ def _knn_segmented_kernel(
359410
boundary_check=(0, 1),
360411
padding_option="zero",
361412
) # Tail D.
362-
y_sq += tl.sum(y * y, axis=0)
413+
y_term = tl.sum(y * y, axis=0)
414+
y_t = y_sq + y_term
415+
y_cond = tl.abs(y_sq) >= tl.abs(y_term)
416+
y_sq_c += tl.where(
417+
y_cond,
418+
(y_sq - y_t) + y_term,
419+
(y_term - y_t) + y_sq,
420+
)
421+
y_sq = y_t
363422
y_norm_ptr = tl.advance(y_norm_ptr, (BLOCK_D, 0))
364423
y_rnorm = tl.rsqrt(y_sq + EPS) # 1/||y|| for cosine.
365424

@@ -384,9 +443,12 @@ def _knn_segmented_kernel(
384443

385444
if COSINE:
386445
acc_dot = tl.zeros((BLOCK_N,), tl.float32) # Dot accumulator.
446+
acc_dot_c = tl.zeros((BLOCK_N,), tl.float32)
387447
acc_x_sq = tl.zeros((BLOCK_N,), tl.float32) # ||x||^2 accumulator.
448+
acc_x_sq_c = tl.zeros((BLOCK_N,), tl.float32)
388449
else:
389450
acc_dist = tl.zeros((BLOCK_N,), tl.float32)
451+
acc_dist_c = tl.zeros((BLOCK_N,), tl.float32)
390452
x_block_start_i32 = x_block_start.to(
391453
tl.int32
392454
) # Block ptr needs int32 offsets.
@@ -439,19 +501,43 @@ def _knn_segmented_kernel(
439501
y,
440502
input_precision=INPUT_PRECISION,
441503
) # MxV dot.
442-
acc_dot += tl.sum(prod, axis=1)
443-
acc_x_sq += tl.sum(x * x, axis=1)
504+
dot_term = tl.sum(prod, axis=1)
505+
dot_t = acc_dot + dot_term
506+
dot_cond = tl.abs(acc_dot) >= tl.abs(dot_term)
507+
acc_dot_c += tl.where(
508+
dot_cond,
509+
(acc_dot - dot_t) + dot_term,
510+
(dot_term - dot_t) + acc_dot,
511+
)
512+
acc_dot = dot_t
513+
x_term = tl.sum(x * x, axis=1)
514+
x_t = acc_x_sq + x_term
515+
x_cond = tl.abs(acc_x_sq) >= tl.abs(x_term)
516+
acc_x_sq_c += tl.where(
517+
x_cond,
518+
(acc_x_sq - x_t) + x_term,
519+
(x_term - x_t) + acc_x_sq,
520+
)
521+
acc_x_sq = x_t
444522
y_block_ptr = tl.advance(y_block_ptr, (BLOCK_D, 0))
445523
else:
446524
diff = x - y
447-
acc_dist += tl.sum(diff * diff, axis=1)
525+
term = tl.sum(diff * diff, axis=1)
526+
t_k = acc_dist + term
527+
k_cond = tl.abs(acc_dist) >= tl.abs(term)
528+
acc_dist_c += tl.where(
529+
k_cond,
530+
(acc_dist - t_k) + term,
531+
(term - t_k) + acc_dist,
532+
)
533+
acc_dist = t_k
448534
y_block_ptr = tl.advance(y_block_ptr, (0, BLOCK_D))
449535
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
450536
if COSINE:
451-
x_rnorm = tl.rsqrt(acc_x_sq + EPS) # 1/||x||.
537+
x_rnorm = tl.rsqrt(acc_x_sq + acc_x_sq_c + EPS) # 1/||x||.
452538
dist = 1.0 - acc_dot * (x_rnorm * y_rnorm) # Cosine distance.
453539
else:
454-
dist = acc_dist # L2^2 distance.
540+
dist = acc_dist + acc_dist_c # L2^2 distance.
455541

456542
if not full_block:
457543
dist = tl.where(mask_x, dist, float("inf")) # Mask invalid x.
@@ -460,30 +546,45 @@ def _knn_segmented_kernel(
460546
same_idx = x_idx == n_y
461547
if not full_block:
462548
same_idx = same_idx & mask_x
463-
dist = tl.where(same_idx, float("inf"), dist)
464549

465550
dist_bits = dist.to(tl.int32, bitcast=True)
466551
dist_bits = dist_bits.to(tl.int64) # Pack dist.
467552
idx_bits = x_idx.to(tl.int32).to(tl.int64) & 0xFFFFFFFF # Pack idx.
468553
key = (dist_bits << 32) | idx_bits # Lexicographic key.
469-
sorted_key = tl.sort(key, descending=False) # Full block sort.
470-
key_k = tl.gather(sorted_key, k_offsets, axis=0) # Take first k keys.
471-
472-
combo_key = tl.cat(
473-
best_dist_key,
474-
key_k,
475-
can_reorder=True
476-
) # Merge buffers.
477-
sorted_key = tl.sort(combo_key, descending=False) # Sort 2K keys.
478-
best_dist_key = tl.gather(
479-
sorted_key,
480-
k_offsets,
481-
axis=0,
482-
) # Keep best K.
554+
key = tl.where(libdevice.isinf(dist), INF_KEY, key)
555+
556+
absorb = False
557+
if N_BLOCKS_PER_K > 1:
558+
row = xb % N_BLOCKS_PER_K
559+
acc_key = tl.where(k_rows == row, key[None, :], acc_key)
560+
if row == (N_BLOCKS_PER_K - 1) or xb == (MAX_X_BLOCKS - 1):
561+
absorb = True
562+
final_key = acc_key.ravel()
563+
acc_key = tl.full(
564+
(N_BLOCKS_PER_K, BLOCK_N),
565+
INF_KEY,
566+
tl.int64,
567+
)
568+
else:
569+
absorb = True
570+
final_key = key
571+
572+
if absorb:
573+
combo_key = tl.cat(
574+
best_dist_key,
575+
final_key,
576+
can_reorder=True
577+
) # Merge buffers.
578+
sorted_key = tl.sort(combo_key, descending=False) # Sort 2K keys.
579+
best_dist_key = tl.gather(
580+
sorted_key,
581+
k_offsets,
582+
axis=0,
583+
) # Keep best K.
483584

484585
k_mask = k_offsets < K # Mask for real k.
485586
best_dist_key = tl.where(k_mask, best_dist_key, INF_KEY)
486-
best_idx = (best_dist_key & 0xFFFFFFFF)
587+
best_idx = (best_dist_key & 0xFFFFFFFF).to(tl.int32)
487588

488589
out_offsets = n_y * K + k_offsets # Output offsets.
489590
out_mask = mask_y & k_mask # Valid output mask.
@@ -616,6 +717,7 @@ def _radius_segmented_kernel(
616717
tl.max_contiguous(offs_n, 8)
617718

618719
acc_dist = tl.zeros((BLOCK_N,), tl.float32)
720+
acc_dist_c = tl.zeros((BLOCK_N,), tl.float32)
619721
x_block_start_i32 = x_block_start.to(tl.int32)
620722
x_block_ptr = tl.make_block_ptr(
621723
base=x_ptr,
@@ -651,11 +753,19 @@ def _radius_segmented_kernel(
651753
)
652754

653755
diff = x - y
654-
acc_dist += tl.sum(diff * diff, axis=1)
756+
term = tl.sum(diff * diff, axis=1)
757+
t_k = acc_dist + term
758+
k_cond = tl.abs(acc_dist) >= tl.abs(term)
759+
acc_dist_c += tl.where(
760+
k_cond,
761+
(acc_dist - t_k) + term,
762+
(term - t_k) + acc_dist,
763+
)
764+
acc_dist = t_k
655765
x_block_ptr = tl.advance(x_block_ptr, (0, BLOCK_D))
656766
y_row_ptr = tl.advance(y_row_ptr, (0, BLOCK_D))
657767

658-
dist = acc_dist
768+
dist = acc_dist + acc_dist_c
659769
active = count < max_neighbors
660770
mask = mask_x & (dist < R2) & active
661771
if IGNORE_SAME_INDEX:

0 commit comments

Comments
 (0)