// LP probability dispatch kernel: collapse the ~7 torch ops in // _topk_ids_logical_to_physical_probability into one launch. // // Python equivalent: // // topk_probs = log2phy_prob[topk_ids] # gather // row_sums = topk_probs.sum(dim=-1) # reduce // fallback = (log2phy_map[topk_ids] >= 0).float() # gather + cast // topk_probs = where(row_sums > 0, topk_probs, fallback) # cmp + select // chosen = multinomial(topk_probs, 1).flatten() # sample // out = log2phy_map[topk_ids, chosen] # gather // // Each thread handles one (token, slot) in the flattened topk_ids. // `random_vals` is pre-generated by the caller via torch.rand (one kernel // launch, ~5 µs); we sample the multinomial via prefix-sum + comparison // against `random_vals[i] * row_sum`. // // Templated on (MAX_COPIES, BLOCK_DIM). MAX_COPIES is small (typically 2-3) // so the per-row prefix sum unrolls into a few instructions per thread. #include #include #include #include #include #include namespace { template __global__ void dispatch_probability_kernel( int32_t* __restrict__ out_topk_ids, // (N,) const int32_t* __restrict__ in_topk_ids, // (N,) const float* __restrict__ log2phy_prob, // (NUM_LOGICAL, MAX_COPIES) const int32_t* __restrict__ log2phy_map, // (NUM_LOGICAL, MAX_COPIES) const float* __restrict__ random_vals, // (N,) int N) { const int idx = blockIdx.x * BLOCK_DIM + threadIdx.x; if (idx >= N) return; const int32_t logical_id = in_topk_ids[idx]; const int32_t* row_map = log2phy_map + logical_id * MAX_COPIES; const float* row_prob = log2phy_prob + logical_id * MAX_COPIES; float probs[MAX_COPIES]; int32_t maps[MAX_COPIES]; float row_sum = 0.f; #pragma unroll for (int c = 0; c < MAX_COPIES; c++) { maps[c] = row_map[c]; probs[c] = row_prob[c]; row_sum += probs[c]; } // Fallback: if all LP probs for this row are 0, sample uniformly from the // valid physical copies (map != -1). if (row_sum <= 0.f) { row_sum = 0.f; #pragma unroll for (int c = 0; c < MAX_COPIES; c++) { probs[c] = (maps[c] >= 0) ? 1.0f : 0.0f; row_sum += probs[c]; } } // Multinomial sample: smallest c such that cumsum[0..c] > u * row_sum. // Implemented branch-free for unroll friendliness: chosen accumulates the // largest index where cumsum[..c] is still <= u, then we add 1 (clamped to // MAX_COPIES-1 for the all-cumsum-<=-u edge case from float rounding). const float u = random_vals[idx] * row_sum; float cum = 0.f; int chosen = 0; #pragma unroll for (int c = 0; c < MAX_COPIES; c++) { cum += probs[c]; if (u >= cum) chosen = c + 1; } if (chosen >= MAX_COPIES) chosen = MAX_COPIES - 1; out_topk_ids[idx] = maps[chosen]; } template void dispatch_probability( tvm::ffi::TensorView out_topk_ids, // (N,) int32 tvm::ffi::TensorView in_topk_ids, // (N,) int32 tvm::ffi::TensorView log2phy_prob, // (NUM_LOGICAL, MAX_COPIES) float32 tvm::ffi::TensorView log2phy_map, // (NUM_LOGICAL, MAX_COPIES) int32 tvm::ffi::TensorView random_vals) { // (N,) float32 using namespace host; SymbolicSize N{"num_topk_entries"}; SymbolicSize NUM_LOGICAL{"num_logical"}; SymbolicDevice device_; TensorMatcher({N}).with_dtype().with_device(device_).verify(out_topk_ids).verify(in_topk_ids); TensorMatcher({NUM_LOGICAL, MAX_COPIES}).with_dtype().with_device(device_).verify(log2phy_prob); TensorMatcher({NUM_LOGICAL, MAX_COPIES}).with_dtype().with_device(device_).verify(log2phy_map); TensorMatcher({N}).with_dtype().with_device(device_).verify(random_vals); const int n = static_cast(N.unwrap()); const int grid = (n + BLOCK_DIM - 1) / BLOCK_DIM; const DLDevice device = device_.unwrap(); using KernelT = void (*)(int32_t*, const int32_t*, const float*, const int32_t*, const float*, int); KernelT kernel = dispatch_probability_kernel; LaunchKernel(grid, BLOCK_DIM, device)( kernel, static_cast(out_topk_ids.data_ptr()), static_cast(in_topk_ids.data_ptr()), static_cast(log2phy_prob.data_ptr()), static_cast(log2phy_map.data_ptr()), static_cast(random_vals.data_ptr()), n); } } // namespace