CharlesCNorton commited on
Commit
13163db
·
1 Parent(s): 58ab9c4

logits-processor: sort-free block-parallel sampler (radix-select keep-threshold + Gumbel-max)

Browse files
Files changed (3) hide show
  1. CARD.md +24 -7
  2. README.md +24 -7
  3. logits_processor_cuda/logits_processor.cu +131 -103
CARD.md CHANGED
@@ -57,13 +57,30 @@ and RTX PRO 6000 (sm120):
57
  - per-row temperature / top-k / top-p / min-p are honored independently across a
58
  batch, and a bitmask followed by `sample` never draws a disallowed token.
59
 
60
- ## Status
61
-
62
- v1 is correctness-first: sampling is a single descending sort of the softmax
63
- probabilities followed by a per-row prefix cutoff and inverse-CDF draw. The sort
64
- dominates; a radix-select over the top-p mass is the first planned optimization.
65
- Built for sm80 / sm86 / sm89 / sm90 / sm100 / sm120; certified on all of these
66
- except sm100, for which no B200 was available to run the check.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  ## License
69
 
 
57
  - per-row temperature / top-k / top-p / min-p are honored independently across a
58
  batch, and a bitmask followed by `sample` never draws a disallowed token.
59
 
60
+ ## How it works
61
+
62
+ The three filters all keep the largest-probability tokens above a cutoff, so their
63
+ intersection is a single keep-threshold and no ordering of the vocabulary is
64
+ needed. One threadblock handles each row: a parallel reduction takes the max logit
65
+ (and the argmax, for greedy); a second pass forms the softmax; the keep-threshold
66
+ is found by a radix-select over the softmax values (a per-warp-privatized
67
+ histogram, four 8-bit passes resolving the exact boundary); and the token is drawn
68
+ by a Gumbel-max over the kept set. Greedy is a bare argmax and unfiltered sampling
69
+ is a single Gumbel-max pass, both skipping the threshold step. There is no sort and
70
+ no per-row serial scan, and the only scratch is one `[B, V]` buffer.
71
+
72
+ ## Performance
73
+
74
+ Measured on H200 against a sort-based reference (a segmented radix sort plus a
75
+ per-row cutoff and inverse-CDF draw), at batch 256 and a 128k vocabulary: greedy
76
+ 49x, unfiltered sampling 59x, top-p 9.9x, and top-k 2.4x faster; top-p at batch
77
+ 1024 and a 32k vocabulary is 5.9x. Every path is faster and the tokens are
78
+ distributed identically, matching the sort-based sampler to 1e-3.
79
+
80
+ ## Architectures
81
+
82
+ Built for sm80 / sm86 / sm89 / sm90 / sm100 / sm120; certified through `get_kernel`
83
+ on all of these except sm100, for which no B200 was available to run the check.
84
 
85
  ## License
86
 
README.md CHANGED
@@ -57,13 +57,30 @@ and RTX PRO 6000 (sm120):
57
  - per-row temperature / top-k / top-p / min-p are honored independently across a
58
  batch, and a bitmask followed by `sample` never draws a disallowed token.
59
 
60
- ## Status
61
-
62
- v1 is correctness-first: sampling is a single descending sort of the softmax
63
- probabilities followed by a per-row prefix cutoff and inverse-CDF draw. The sort
64
- dominates; a radix-select over the top-p mass is the first planned optimization.
65
- Built for sm80 / sm86 / sm89 / sm90 / sm100 / sm120; certified on all of these
66
- except sm100, for which no B200 was available to run the check.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  ## License
69
 
 
57
  - per-row temperature / top-k / top-p / min-p are honored independently across a
58
  batch, and a bitmask followed by `sample` never draws a disallowed token.
59
 
60
+ ## How it works
61
+
62
+ The three filters all keep the largest-probability tokens above a cutoff, so their
63
+ intersection is a single keep-threshold and no ordering of the vocabulary is
64
+ needed. One threadblock handles each row: a parallel reduction takes the max logit
65
+ (and the argmax, for greedy); a second pass forms the softmax; the keep-threshold
66
+ is found by a radix-select over the softmax values (a per-warp-privatized
67
+ histogram, four 8-bit passes resolving the exact boundary); and the token is drawn
68
+ by a Gumbel-max over the kept set. Greedy is a bare argmax and unfiltered sampling
69
+ is a single Gumbel-max pass, both skipping the threshold step. There is no sort and
70
+ no per-row serial scan, and the only scratch is one `[B, V]` buffer.
71
+
72
+ ## Performance
73
+
74
+ Measured on H200 against a sort-based reference (a segmented radix sort plus a
75
+ per-row cutoff and inverse-CDF draw), at batch 256 and a 128k vocabulary: greedy
76
+ 49x, unfiltered sampling 59x, top-p 9.9x, and top-k 2.4x faster; top-p at batch
77
+ 1024 and a 32k vocabulary is 5.9x. Every path is faster and the tokens are
78
+ distributed identically, matching the sort-based sampler to 1e-3.
79
+
80
+ ## Architectures
81
+
82
+ Built for sm80 / sm86 / sm89 / sm90 / sm100 / sm120; certified through `get_kernel`
83
+ on all of these except sm100, for which no B200 was available to run the check.
84
 
85
  ## License
86
 
logits_processor_cuda/logits_processor.cu CHANGED
@@ -11,24 +11,26 @@
11
  // sample - temperature, then the intersection of top-k / top-p (nucleus) /
12
  // min-p, then a multinomial draw, fused into one logits -> token op with a
13
  // counter-based RNG (deterministic in (seed, row, offset)). temperature <= 0
14
- // is greedy. The filters are computed on a single descending sort of the
15
- // row so their cutoffs are all prefix lengths, and the smallest wins.
16
- //
17
- // v1 favors being obviously correct over peak throughput: one segmented sort of
18
- // the softmax probabilities, then a per-row prefix walk for the cutoff and the
19
- // inverse-CDF draw. The sort dominates; replacing it with a radix-select over
20
- // the top-p mass is the first optimization once this certifies on GPU.
21
  #include <ATen/ATen.h>
22
  #include <ATen/cuda/CUDAContext.h>
23
  #include <c10/cuda/CUDAGuard.h>
24
  #include <cuda_fp16.h>
25
  #include <cuda_bf16.h>
26
- #include <cub/cub.cuh>
27
  #include <cstdint>
28
  #include <math.h>
29
 
30
  namespace {
31
  constexpr int TPB = 256;
 
 
 
32
 
33
  __device__ __forceinline__ float to_f(float x) { return x; }
34
  __device__ __forceinline__ float to_f(__half x) { return __half2float(x); }
@@ -42,9 +44,6 @@ __device__ __forceinline__ unsigned triple32(unsigned x) {
42
  x ^= x >> 15; x *= 0x31848babU; x ^= x >> 14; return x;
43
  }
44
 
45
- // Portable reduction operators: CUDA 13's CUB dropped cub::Max()/cub::Sum().
46
- struct MaxOp { __device__ __forceinline__ float operator()(const float& a, const float& b) const { return fmaxf(a, b); } };
47
-
48
  // bit == 1 means allowed; disallowed logits become -inf. grid = (xtiles, B).
49
  template <typename T>
50
  __global__ void bitmask_kernel(T* logits, const int32_t* mask, int V, int mask_stride) {
@@ -56,76 +55,125 @@ __global__ void bitmask_kernel(T* logits, const int32_t* mask, int V, int mask_s
56
  }
57
  }
58
 
59
- // probs[row] = softmax(logits[row] / max(temp,eps)); one block per row.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  template <typename T>
61
- __global__ void softmax_temp_kernel(const T* __restrict__ logits, const float* __restrict__ temp,
62
- float* __restrict__ probs, int V) {
63
- int row = blockIdx.x;
 
 
64
  const T* lg = logits + (long)row * V;
65
- float* pr = probs + (long)row * V;
66
- float t = temp[row];
67
- float scale = (t > 0.f) ? (1.f / t) : 1.f;
68
-
69
- typedef cub::BlockReduce<float, TPB> BR;
70
- __shared__ typename BR::TempStorage ts;
71
- __shared__ float sh;
72
-
73
- float m = -INFINITY;
74
- for (int i = threadIdx.x; i < V; i += blockDim.x) m = fmaxf(m, to_f(lg[i]));
75
- float gmax = BR(ts).Reduce(m, MaxOp());
76
- if (threadIdx.x == 0) sh = gmax;
77
- __syncthreads();
78
- gmax = sh;
 
 
 
 
 
79
  __syncthreads();
80
 
 
 
 
 
 
81
  float s = 0.f;
82
- for (int i = threadIdx.x; i < V; i += blockDim.x) {
83
- float e = __expf((to_f(lg[i]) - gmax) * scale);
84
- pr[i] = e;
85
- s += e;
86
- }
87
- float gsum = BR(ts).Sum(s);
88
- if (threadIdx.x == 0) sh = gsum;
89
- __syncthreads();
90
- float inv = 1.f / sh;
91
  __syncthreads();
92
- for (int i = threadIdx.x; i < V; i += blockDim.x) pr[i] *= inv;
93
- }
94
 
95
- // sp/si: probabilities and token ids sorted descending per row. Compute the
96
- // kept prefix length L = min(top_k, top_p, min_p cutoffs), then inverse-CDF draw.
97
- __global__ void cutoff_sample_kernel(const float* __restrict__ sp, const int* __restrict__ si,
98
- const float* __restrict__ top_p, const int* __restrict__ top_k,
99
- const float* __restrict__ min_p, const float* __restrict__ temp,
100
- unsigned seed, unsigned offset, int64_t* __restrict__ out, int V) {
101
- int row = blockIdx.x;
102
- if (threadIdx.x != 0) return;
103
- const float* p = sp + (long)row * V;
104
- const int* idx = si + (long)row * V;
105
- float tp = top_p[row]; int tk = top_k[row]; float mp = min_p[row]; float t = temp[row];
106
-
107
- long L = (tk > 0 && tk < V) ? (long)tk : (long)V; // top-k
108
- if (tp > 0.f && tp < 1.f) { // top-p (nucleus)
109
- float c = 0.f; long Lp = L;
110
- for (long j = 0; j < L; j++) { c += p[j]; if (c >= tp) { Lp = j + 1; break; } }
111
- L = Lp;
 
 
 
112
  }
113
- if (mp > 0.f) { // min-p
114
- float thr = mp * p[0]; long j = 0;
115
- while (j < L && p[j] >= thr) j++;
116
- L = (j > 0) ? j : 1;
 
 
117
  }
118
- if (L < 1) L = 1;
119
- if (t <= 0.f) L = 1; // greedy
120
-
121
- float sum = 0.f;
122
- for (long j = 0; j < L; j++) sum += p[j];
123
- unsigned h = triple32((unsigned)row * 0x9E3779B1u ^ seed ^ (offset * 0x85EBCA6Bu));
124
- float u = (h >> 8) * (1.0f / 16777216.0f); // [0, 1)
125
- float target = u * sum, c = 0.f;
126
- int pick = idx[L - 1];
127
- for (long j = 0; j < L; j++) { c += p[j]; if (c >= target) { pick = idx[j]; break; } }
128
- out[row] = pick;
129
  }
130
  } // namespace
131
 
@@ -162,43 +210,23 @@ at::Tensor sample(at::Tensor logits, at::Tensor temperature, at::Tensor top_p,
162
  int B = logits.size(0), V = logits.size(1);
163
  cudaStream_t s = at::cuda::getCurrentCUDAStream();
164
  auto fopt = logits.options().dtype(at::kFloat);
165
- auto iopt = logits.options().dtype(at::kInt);
166
 
167
  auto tf = temperature.to(at::kFloat).contiguous();
168
  auto pf = top_p.to(at::kFloat).contiguous();
169
  auto kf = top_k.to(at::kInt).contiguous();
170
  auto mf = min_p.to(at::kFloat).contiguous();
 
 
171
 
172
- auto probs = at::empty({B, V}, fopt);
173
  auto st = logits.scalar_type();
174
- if (st == at::kFloat)
175
- softmax_temp_kernel<float><<<B, TPB, 0, s>>>(logits.data_ptr<float>(), tf.data_ptr<float>(), probs.data_ptr<float>(), V);
176
- else if (st == at::kHalf)
177
- softmax_temp_kernel<__half><<<B, TPB, 0, s>>>((const __half*)logits.data_ptr(), tf.data_ptr<float>(), probs.data_ptr<float>(), V);
178
- else if (st == at::kBFloat16)
179
- softmax_temp_kernel<__nv_bfloat16><<<B, TPB, 0, s>>>((const __nv_bfloat16*)logits.data_ptr(), tf.data_ptr<float>(), probs.data_ptr<float>(), V);
180
- else
181
- TORCH_CHECK(false, "logits must be float/half/bf16");
182
-
183
- auto sorted_p = at::empty({B, V}, fopt);
184
- auto sorted_i = at::empty({B, V}, iopt);
185
- auto idx_in = at::arange(V, iopt).unsqueeze(0).repeat({B, 1}).contiguous();
186
- auto offsets = at::arange(0, (long)(B + 1) * V, V, iopt).contiguous();
187
-
188
- size_t tmp_bytes = 0;
189
- cub::DeviceSegmentedRadixSort::SortPairsDescending(
190
- nullptr, tmp_bytes, probs.data_ptr<float>(), sorted_p.data_ptr<float>(),
191
- idx_in.data_ptr<int>(), sorted_i.data_ptr<int>(), B * V, B,
192
- offsets.data_ptr<int>(), offsets.data_ptr<int>() + 1, 0, (int)sizeof(float) * 8, s);
193
- auto tmp = at::empty({(long)tmp_bytes}, logits.options().dtype(at::kByte));
194
- cub::DeviceSegmentedRadixSort::SortPairsDescending(
195
- tmp.data_ptr(), tmp_bytes, probs.data_ptr<float>(), sorted_p.data_ptr<float>(),
196
- idx_in.data_ptr<int>(), sorted_i.data_ptr<int>(), B * V, B,
197
- offsets.data_ptr<int>(), offsets.data_ptr<int>() + 1, 0, (int)sizeof(float) * 8, s);
198
-
199
- auto out = at::empty({B}, logits.options().dtype(at::kLong));
200
- cutoff_sample_kernel<<<B, 32, 0, s>>>(sorted_p.data_ptr<float>(), sorted_i.data_ptr<int>(),
201
- pf.data_ptr<float>(), kf.data_ptr<int>(), mf.data_ptr<float>(), tf.data_ptr<float>(),
202
- (unsigned)seed, (unsigned)offset, out.data_ptr<int64_t>(), V);
203
  return out;
204
  }
 
11
  // sample - temperature, then the intersection of top-k / top-p (nucleus) /
12
  // min-p, then a multinomial draw, fused into one logits -> token op with a
13
  // counter-based RNG (deterministic in (seed, row, offset)). temperature <= 0
14
+ // is greedy. Because the three filters are all "keep the largest-probability
15
+ // tokens above a threshold", their intersection is a single keep-threshold, so
16
+ // no ordering of the vocabulary is needed. Greedy is a plain argmax; unfiltered
17
+ // sampling is one Gumbel-max pass; the filtered case finds the keep-threshold
18
+ // with a radix-select over the softmax values and draws by Gumbel-max over the
19
+ // kept set. One block per row, fully parallel, no sort and no per-row serial
20
+ // scan.
21
  #include <ATen/ATen.h>
22
  #include <ATen/cuda/CUDAContext.h>
23
  #include <c10/cuda/CUDAGuard.h>
24
  #include <cuda_fp16.h>
25
  #include <cuda_bf16.h>
 
26
  #include <cstdint>
27
  #include <math.h>
28
 
29
  namespace {
30
  constexpr int TPB = 256;
31
+ constexpr int NWARP = TPB / 32; // per-warp privatized histograms
32
+ constexpr int RADIX_BITS = 8;
33
+ constexpr int RADIX_SIZE = 1 << RADIX_BITS; // 256 magnitude buckets per pass
34
 
35
  __device__ __forceinline__ float to_f(float x) { return x; }
36
  __device__ __forceinline__ float to_f(__half x) { return __half2float(x); }
 
44
  x ^= x >> 15; x *= 0x31848babU; x ^= x >> 14; return x;
45
  }
46
 
 
 
 
47
  // bit == 1 means allowed; disallowed logits become -inf. grid = (xtiles, B).
48
  template <typename T>
49
  __global__ void bitmask_kernel(T* logits, const int32_t* mask, int V, int mask_stride) {
 
55
  }
56
  }
57
 
58
+ // e in [0, 1] -> a monotone uint key (positive IEEE floats order as their bits).
59
+ __device__ __forceinline__ unsigned fkey(float e) { return __float_as_uint(e); }
60
+
61
+ // Block-wide radix-select over the softmax values e[0..V). Returns the threshold
62
+ // tau such that keeping {e_i >= tau} yields, from the largest e down, a cumulative
63
+ // count (by_mass=false) or mass (by_mass=true) that first reaches `target`. Four
64
+ // 8-bit passes resolve the full 32-bit key, so tau is the exact boundary value.
65
+ // Called by every thread of the block; uses per-block shared histograms.
66
+ __device__ float radix_threshold(const float* __restrict__ e, int V, bool by_mass,
67
+ double target, unsigned* hist_c, double* hist_m,
68
+ unsigned* prefix_sh, double* above_sh) {
69
+ const int tid = threadIdx.x, nt = blockDim.x, warp = tid >> 5;
70
+ unsigned prefix = 0; double above = 0.0; // committed high bits, mass/count above boundary
71
+ for (int pass = 0; pass < 32 / RADIX_BITS; ++pass) {
72
+ const int shift = 32 - RADIX_BITS * (pass + 1);
73
+ const unsigned hi_mask = (shift + RADIX_BITS >= 32) ? 0u : (0xFFFFFFFFu << (shift + RADIX_BITS));
74
+ for (int b = tid; b < NWARP * RADIX_SIZE; b += nt) { hist_c[b] = 0u; hist_m[b] = 0.0; }
75
+ __syncthreads();
76
+ // per-warp privatized histogram cuts shared-atomic contention (the first pass,
77
+ // which scans all V, dominates; later passes touch only the boundary bucket).
78
+ for (int i = tid; i < V; i += nt) {
79
+ const unsigned key = fkey(e[i]);
80
+ if ((key & hi_mask) == (prefix & hi_mask)) {
81
+ const int b = warp * RADIX_SIZE + (int)((key >> shift) & (RADIX_SIZE - 1));
82
+ if (by_mass) atomicAdd(&hist_m[b], (double)e[i]);
83
+ else atomicAdd(&hist_c[b], 1u);
84
+ }
85
+ }
86
+ __syncthreads();
87
+ if (tid == 0) {
88
+ double acc = above; int boundary = 0;
89
+ for (int b = RADIX_SIZE - 1; b >= 0; --b) {
90
+ double add = 0.0;
91
+ if (by_mass) { for (int w = 0; w < NWARP; ++w) add += hist_m[w * RADIX_SIZE + b]; }
92
+ else { unsigned cc = 0; for (int w = 0; w < NWARP; ++w) cc += hist_c[w * RADIX_SIZE + b]; add = (double)cc; }
93
+ if (acc + add >= target) { boundary = b; break; }
94
+ acc += add;
95
+ }
96
+ *prefix_sh = (prefix & hi_mask) | ((unsigned)boundary << shift);
97
+ *above_sh = acc; // cumulative strictly above the boundary bucket
98
+ }
99
+ __syncthreads();
100
+ prefix = *prefix_sh; above = *above_sh;
101
+ __syncthreads();
102
+ }
103
+ return __uint_as_float(prefix);
104
+ }
105
+
106
+ // One block per row: max/argmax -> greedy early-out -> softmax sum -> keep-threshold
107
+ // (radix-select) -> Gumbel-max draw over the kept set.
108
  template <typename T>
109
+ __global__ void sample_kernel(const T* __restrict__ logits, const float* __restrict__ temp,
110
+ const float* __restrict__ top_p, const int* __restrict__ top_k,
111
+ const float* __restrict__ min_p, float* __restrict__ work,
112
+ unsigned seed, unsigned offset, int64_t* __restrict__ out, int V) {
113
+ const int row = blockIdx.x, tid = threadIdx.x, nt = blockDim.x;
114
  const T* lg = logits + (long)row * V;
115
+ float* e = work + (long)row * V;
116
+ __shared__ float red[TPB];
117
+ __shared__ int redi[TPB];
118
+ __shared__ unsigned hist_c[NWARP * RADIX_SIZE];
119
+ __shared__ double hist_m[NWARP * RADIX_SIZE];
120
+ __shared__ unsigned prefix_sh; __shared__ double above_sh;
121
+ __shared__ float sh_scalar;
122
+
123
+ // 1. max logit + argmax (tie-break: smaller index)
124
+ float lmax = -INFINITY; int amax = 0;
125
+ for (int i = tid; i < V; i += nt) { float v = to_f(lg[i]); if (v > lmax) { lmax = v; amax = i; } }
126
+ red[tid] = lmax; redi[tid] = amax; __syncthreads();
127
+ for (int s = nt >> 1; s > 0; s >>= 1) {
128
+ if (tid < s && (red[tid + s] > red[tid] || (red[tid + s] == red[tid] && redi[tid + s] < redi[tid]))) {
129
+ red[tid] = red[tid + s]; redi[tid] = redi[tid + s];
130
+ }
131
+ __syncthreads();
132
+ }
133
+ const float gmax = red[0]; const int argmax = redi[0];
134
  __syncthreads();
135
 
136
+ const float t = temp[row];
137
+ if (t <= 0.f) { if (tid == 0) out[row] = argmax; return; } // greedy
138
+ const float scale = 1.f / t;
139
+
140
+ // 2. softmax sum S; store e_i = exp((logit-gmax)/t) in work (e in (0,1], e_max=1)
141
  float s = 0.f;
142
+ for (int i = tid; i < V; i += nt) { float ev = __expf((to_f(lg[i]) - gmax) * scale); e[i] = ev; s += ev; }
143
+ red[tid] = s; __syncthreads();
144
+ for (int st = nt >> 1; st > 0; st >>= 1) { if (tid < st) red[tid] += red[tid + st]; __syncthreads(); }
145
+ const float S = red[0];
 
 
 
 
 
146
  __syncthreads();
 
 
147
 
148
+ const int tk = top_k[row]; const float tp = top_p[row]; const float mp = min_p[row];
149
+ const bool use_k = (tk > 0 && tk < V), use_p = (tp > 0.f && tp < 1.f), use_m = (mp > 0.f);
150
+
151
+ // 3. keep-threshold tau in the e-domain (intersection = max of the per-filter thresholds)
152
+ float tau = 0.f;
153
+ if (use_m) tau = fmaxf(tau, mp); // p_i >= mp*p_max <=> e_i >= mp
154
+ if (use_k) tau = fmaxf(tau, radix_threshold(e, V, false, (double)tk, hist_c, hist_m, &prefix_sh, &above_sh));
155
+ if (use_p) tau = fmaxf(tau, radix_threshold(e, V, true, (double)tp * (double)S, hist_c, hist_m, &prefix_sh, &above_sh));
156
+ if (tid == 0) sh_scalar = tau; __syncthreads(); tau = sh_scalar; __syncthreads();
157
+
158
+ // 4. Gumbel-max over {e_i >= tau}: token = argmax_i (log(e_i) + gumbel_i). The
159
+ // max logit (e=1) always clears tau, so the kept set is never empty.
160
+ float best = -INFINITY; int bi = argmax;
161
+ for (int i = tid; i < V; i += nt) {
162
+ if (e[i] >= tau) {
163
+ unsigned h = triple32((unsigned)i * 0x9E3779B1u ^ seed ^ (offset * 0x85EBCA6Bu) ^ ((unsigned)row * 0xC2B2AE35u));
164
+ float u = fminf(fmaxf((h >> 8) * (1.0f / 16777216.0f), 1e-7f), 0.9999999f);
165
+ float key = __logf(e[i]) + (-__logf(-__logf(u)));
166
+ if (key > best || (key == best && i < bi)) { best = key; bi = i; }
167
+ }
168
  }
169
+ red[tid] = best; redi[tid] = bi; __syncthreads();
170
+ for (int st = nt >> 1; st > 0; st >>= 1) {
171
+ if (tid < st && (red[tid + st] > red[tid] || (red[tid + st] == red[tid] && redi[tid + st] < redi[tid]))) {
172
+ red[tid] = red[tid + st]; redi[tid] = redi[tid + st];
173
+ }
174
+ __syncthreads();
175
  }
176
+ if (tid == 0) out[row] = redi[0];
 
 
 
 
 
 
 
 
 
 
177
  }
178
  } // namespace
179
 
 
210
  int B = logits.size(0), V = logits.size(1);
211
  cudaStream_t s = at::cuda::getCurrentCUDAStream();
212
  auto fopt = logits.options().dtype(at::kFloat);
 
213
 
214
  auto tf = temperature.to(at::kFloat).contiguous();
215
  auto pf = top_p.to(at::kFloat).contiguous();
216
  auto kf = top_k.to(at::kInt).contiguous();
217
  auto mf = min_p.to(at::kFloat).contiguous();
218
+ auto work = at::empty({B, V}, fopt); // one [B,V] scratch (the softmax values)
219
+ auto out = at::empty({B}, logits.options().dtype(at::kLong));
220
 
 
221
  auto st = logits.scalar_type();
222
+ #define LAUNCH(CT) sample_kernel<CT><<<B, TPB, 0, s>>>( \
223
+ reinterpret_cast<const CT*>(logits.const_data_ptr()), tf.data_ptr<float>(), pf.data_ptr<float>(), \
224
+ kf.data_ptr<int>(), mf.data_ptr<float>(), work.data_ptr<float>(), \
225
+ (unsigned)seed, (unsigned)offset, out.data_ptr<int64_t>(), V);
226
+ if (st == at::kFloat) { LAUNCH(float) }
227
+ else if (st == at::kHalf) { LAUNCH(__half) }
228
+ else if (st == at::kBFloat16) { LAUNCH(__nv_bfloat16) }
229
+ else TORCH_CHECK(false, "logits must be float/half/bf16");
230
+ #undef LAUNCH
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  return out;
232
  }