Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3415,9 +3415,6 @@ def kern(
j = cutlass.Int32(0)
r = cutlass.Int32(0)
tinc = cutlass.Int32(0)
cnt = cutlass.Int32(0)
bit = cutlass.Int32(0)
abv = cutlass.Int32(0)
nA = cutlass.Int32(0)
nT = cutlass.Int32(0)
n1 = cutlass.Int32(0)
Expand Down Expand Up @@ -3463,8 +3460,6 @@ def kern(
nbw = cutlass.Int32(0)
uq = cutlass.Uint32(0)
vq = cutlass.Uint32(0)
kt = cutlass.Uint32(0)
klo = cutlass.Uint32(0)
kv = cutlass.Uint32(0)
rlo = cutlass.Uint32(0)
rhi = cutlass.Uint32(0)
Expand Down Expand Up @@ -3762,16 +3757,28 @@ def kern(
above = s_res[RES_ABOVE]
m = s_res[RES_M]
Bv = s_res[RES_B]
tot = s_res[RES_TOT]
need = k - above
whole = cutlass.Int32(0)
if need >= m:
whole = cutlass.Int32(1)

# ---- ESCAPE: 32-step key-space bisection
# ---- ESCAPE: radix descent over the key space
# Count-crossing enforcement: when the hint-derived bracket's low
# edge sits above the true k-th value, the classify arms count
# fewer than k entries (q < 0 is never histogrammed) and the
# crossing scan pins its bin-0 fallback as a fake crossing — the
# whole-bin emit would then stop at the histogram total and leave
# out_row[tot:k) unwritten. Exactness must come from the
# count-crossing invariant, never from the bracket estimate: when
# the histogram never reaches k (tot < k), take the escape — it
# ranks the FULL row in key space, independent of the bracket.
esc = cutlass.Int32(0)
if whole == cutlass.Int32(0):
if m > cmp_:
esc = cutlass.Int32(1)
if tot < k:
esc = cutlass.Int32(1)
Comment thread
longcheng-nv marked this conversation as resolved.
if esc == cutlass.Int32(1):
if tid == cutlass.Int32(0):
s_cnt[0] = cutlass.Int32(0)
Expand All @@ -3785,61 +3792,106 @@ def kern(
s_e12[0] = cutlass.Int32(0)
s_e12[1] = cutlass.Int32(0)
cute.arch.barrier() # escape init
klo = cutlass.Uint32(0)
bit = cutlass.Int32(31)
while bit >= cutlass.Int32(0):
kt = klo | (cutlass.Uint32(1) << cutlass.Uint32(bit))
cnt = cutlass.Int32(0)
for s in cutlass.range_constexpr(S):
ix = (
(tid + cutlass.Int32((s // 4) * self.blk)) << cutlass.Int32(2)
) + cutlass.Int32(s % 4)
if ix < n:
if fkey(_val(frags, s)) >= kt:
cnt = cnt + cutlass.Int32(1)
if tid < ntail:
if fkey(tval) >= kt:
cnt = cnt + cutlass.Int32(1)
cnt = cutlass.Int32(warp_add_i32(cnt))
if lane == cutlass.Int32(0):
if cnt != cutlass.Int32(0):
atomic_add_cta(s_cnt.iterator, cnt)
cute.arch.barrier() # count published
if s_cnt[0] >= k:
klo = kt
cute.arch.barrier() # count consumed
if tid == cutlass.Int32(0):
s_cnt[0] = cutlass.Int32(0)
cute.arch.barrier() # count reset
bit = bit - cutlass.Int32(1)
ethr = cutlass.Int64(klo) # k-th largest key
abv = cutlass.Int32(0)
for s in cutlass.range_constexpr(S):
ix = (
(tid + cutlass.Int32((s // 4) * self.blk)) << cutlass.Int32(2)
) + cutlass.Int32(s % 4)
if ix < n:
if cutlass.Int64(fkey(_val(frags, s))) > ethr:
abv = abv + cutlass.Int32(1)
if tid < ntail:
if cutlass.Int64(fkey(tval)) > ethr:
abv = abv + cutlass.Int32(1)
abv = cutlass.Int32(warp_add_i32(abv))
if lane == cutlass.Int32(0):
if abv != cutlass.Int32(0):
atomic_add_cta(s_cnt.iterator + 1, abv)
cute.arch.barrier() # above-count published
nA = s_cnt[1]
nT = k - nA
# (rezero dropped — emit counters live in s_e12, see race-fix note)
cute.arch.barrier() # nA consumed
# Vector-lane bound: the register batch holds real data only
# below 4*n4 — lanes in [4*n4, n) are the -inf FILL of the
# last partial float4 (the real values there live in tval).
# Bounding by n would count/emit each tail element twice
# (once as a fill key, once via tval): benign while the tie
# threshold is finite (a fill key loses every compare), but
# it emits duplicate indices when the tie class is the -inf
# key itself (in-window -inf entries are admissible).
nvec = n4 << cutlass.Int32(2)
# Narrow LNBH bits per level over the register batch — the
# same descent the Phase-3 ck fallback below runs — instead of
# one rescan per key bit: ceil(32/LNBH) levels at worst and
# two in practice, since the first level's LNBH bits already
# cut inside the exponent field, where the bisection took 32.
# The exit arms carry the count crossing, so the old separate
# above-count pass folds in.
# Neither ethr nor aboveC is carried across the descent: ethr
# is an Int64 derivable from rlo and the exit arm, and
# aboveC + needC == k holds at every exit.
rlo = cutlass.Uint32(0)
rhi = cutlass.Uint32(0xFFFFFFFF)
needC = k
mm = n # in-range count, carried from the previous level
lev = cutlass.Int32(0)
state = cutlass.Int32(0) # 1 -> ethr = rlo, 2 -> ethr = rlo - 1
while state == cutlass.Int32(0):
if needC == mm:
# whole in-range block is needed: threshold below it
needC = cutlass.Int32(0)
state = cutlass.Int32(2)
if state == cutlass.Int32(0):
if rlo >= rhi: # single key left == the k-th key
state = cutlass.Int32(1)
if lev >= cutlass.Int32(34): # non-binding: sh2 strictly shrinks
state = cutlass.Int32(1)
if state == cutlass.Int32(0):
d2 = rhi - rlo
b2w = cutlass.Int32(32) - clz_i32(cutlass.Int32(d2 | cutlass.Uint32(1)))
sh2 = cutlass.Int32(0)
if b2w > cutlass.Int32(LNBH):
sh2 = b2w - cutlass.Int32(LNBH)
for z in cutlass.range_constexpr(self.nbh // self.blk):
s_hist[tid + cutlass.Int32(z * self.blk)] = cutlass.Int32(0)
cute.arch.barrier() # esc level clear
for s in cutlass.range_constexpr(S):
ix = (
(tid + cutlass.Int32((s // 4) * self.blk)) << cutlass.Int32(2)
) + cutlass.Int32(s % 4)
if ix < nvec:
uev = fkey(_val(frags, s))
if uev >= rlo:
if uev <= rhi:
bne = _umin_u32(
(uev - rlo) >> cutlass.Uint32(sh2),
cutlass.Uint32(self.nbh - 1),
)
atomic_add_cta(
s_hist.iterator + cutlass.Int32(bne),
cutlass.Int32(1),
)
if tid < ntail:
uev = fkey(tval)
if uev >= rlo:
if uev <= rhi:
bne = _umin_u32(
(uev - rlo) >> cutlass.Uint32(sh2),
cutlass.Uint32(self.nbh - 1),
)
atomic_add_cta(
s_hist.iterator + cutlass.Int32(bne), cutlass.Int32(1)
)
cute.arch.barrier() # esc level hist
if cutlass.const_expr(self.nbh > 1024):
scan_cross_w(s_hist, s_ws, needC, tid, s_res, blk=self.blk, nb=self.nbh)
else:
find_cross(s_hist, needC, tid, s_res, nb=self.nbh)
cute.arch.barrier() # esc level scan
needC = needC - s_res[RES_ABOVE]
mm = s_res[RES_M]
b_lv = s_res[RES_B]
nlo = rlo + (cutlass.Uint32(b_lv) << cutlass.Uint32(sh2))
if b_lv != cutlass.Int32(self.nbh - 1):
rhi = nlo + (
(cutlass.Uint32(1) << cutlass.Uint32(sh2)) - cutlass.Uint32(1)
)
rlo = nlo
lev = lev + cutlass.Int32(1)
nA = k - needC
nT = needC
ethr = cutlass.Int64(rlo)
if state == cutlass.Int32(2):
ethr = ethr - cutlass.Int64(1)
cute.arch.barrier() # descent done
lml = cutlass.Int32(cute.arch.lanemask_lt())
for s in cutlass.range_constexpr(S):
ixv = (
(tid + cutlass.Int32((s // 4) * self.blk)) << cutlass.Int32(2)
) + cutlass.Int32(s % 4)
u64 = cutlass.Int64(-1)
if ixv < n:
if ixv < nvec:
u64 = cutlass.Int64(fkey(_val(frags, s)))
q1e = cutlass.Int32(0)
q2e = cutlass.Int32(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,68 @@ def test_selfsampling_topk_degenerate_hints(hint_kind, top_k, n_valid):
_check_exact(logits, indices, n_valid, ref_vals)


@pytest.mark.parametrize("n_valid", [3072, 4096], ids=["n3072", "n4096"])
def test_selfsampling_topk_high_anchor_hint_completeness(n_valid):
"""Anchor-only hints whose gathered values all sit ABOVE the true k-th
value (an argmax anchor over the all-zero cold-start buffer, with a high
row head) bracket the sampling band so it contains fewer than top_k
entries. The classify histogram then never reaches k and the crossing
scan pins its bin-0 fallback as a fake crossing; the register-family
whole-bin emit must escape to the key-space ranking instead of
stopping at the histogram total (regression: rows exited with
out[tot:k) unwritten -- a prefix-only write). Deterministic worst case:
row[0] = second-max, so the bracket holds exactly two entries. The
cells pin the vulnerable reg variant (hint-driven bracket + no-clamp
classify: BRL variants clamp out-of-bracket values into bin 0 and
cannot under-count, so batch/shape are chosen to compile BRL off)."""
top_k = 512
bs = 256
gen = torch.Generator(device=_DEV).manual_seed(top_k + n_valid)
logits = torch.randn((bs, n_valid), generator=gen, dtype=torch.float32, device=_DEV)
v2 = torch.topk(logits, 2, dim=1).values[:, 1]
logits[:, 0] = v2 # row head = second-max: bracket = [second-max, max]
ref_vals, _ = torch.topk(logits, top_k, dim=1)
pre_idx = torch.zeros((bs, top_k), dtype=torch.int32, device=_DEV)
pre_idx[:, 0] = logits.argmax(dim=1).to(torch.int32)
indices = torch.full((bs, top_k), -7, dtype=torch.int32, device=_DEV)
kv = torch.full((bs,), n_valid, dtype=torch.int32, device=_DEV)
ss_host.run_varlen(logits, pre_idx, kv, indices, max_seq_len=n_valid)
torch.cuda.synchronize()
assert int((indices == -7).sum()) == 0, "unwritten output slots (prefix-only emit)"
_check_exact(logits, indices, n_valid, ref_vals)


def test_selfsampling_topk_neginf_tail_completeness():
"""The DEG bracket arm folds the row tail element (the last n % 4
columns live outside the float4 register batch) into the bracket
without the > -inf guard the vector loop has: a single in-window -inf
there drags the bracket low edge to -inf, every classify product
becomes NaN, the histogram total is zero, and the row exited having
written nothing (regression: hint-independent zero-write rows). The
escape such rows now take must also not double-count the tail element
when the tie class is the -inf key itself (regression: duplicate
indices from the -inf fill lanes of the last partial float4) -- odd
rows keep fewer than top_k finite entries to exercise that lane
bound."""
top_k = 1024
bs, npad, n_valid = 256, 4096, 4093 # n_valid % 4 = 1: one tail column
gen = torch.Generator(device=_DEV).manual_seed(top_k + n_valid)
logits = torch.randn((bs, npad), generator=gen, dtype=torch.float32, device=_DEV)
logits[:, n_valid:] = 3e38 # poison past the window
logits[:, n_valid - 1] = float("-inf") # in-window -inf in the tail column
logits[1::2, 500:n_valid] = float("-inf") # odd rows: n_finite < top_k
masked = logits.clone()
masked[:, n_valid:] = float("-inf")
ref_vals, _ = torch.topk(masked, top_k, dim=1)
pre_idx = torch.zeros((bs, top_k), dtype=torch.int32, device=_DEV)
indices = torch.full((bs, top_k), -7, dtype=torch.int32, device=_DEV)
kv = torch.full((bs,), n_valid, dtype=torch.int32, device=_DEV)
ss_host.run_varlen(logits, pre_idx, kv, indices, max_seq_len=npad)
torch.cuda.synchronize()
assert int((indices == -7).sum()) == 0, "unwritten output slots (zero-write rows)"
_check_exact(logits, indices, n_valid, ref_vals)


def _run_varlen_case(kv, next_n, cr, top_k, seed, with_values=False, engine="auto"):
"""Build a per-row-poisoned varlen batch, run run_varlen, verify every
row against its own n_r (production formula) — short rows included."""
Expand Down
Loading