Apply repeat penalty entirely on GPU to avoid costly GPU↔CPU round-trips. The upstream `candle_transformers::utils::apply_repeat_penalty` copies the entire logits tensor (vocab_size × 4 bytes ≈ 600 KB) to CPU, modifies a handful of elements, then copies everything back. This forces a full GPU synchronisation and two large PCIe transfers per token. This implementation stays on-device: it selects
(
logits: &Tensor,
penalty: f32,
context: &[u32],
)
| 58 | /// This implementation stays on-device: it selects only the penalty positions, computes |
| 59 | /// sign-aware multipliers, and scatters the deltas back with `index_add`. |
| 60 | fn apply_repeat_penalty_gpu( |
| 61 | logits: &Tensor, |
| 62 | penalty: f32, |
| 63 | context: &[u32], |
| 64 | ) -> Result<Tensor> { |
| 65 | // Deduplicate tokens (same semantics as the upstream version). |
| 66 | let mut seen = HashSet::new(); |
| 67 | let unique: Vec<u32> = context |
| 68 | .iter() |
| 69 | .filter(|t| seen.insert(**t)) |
| 70 | .copied() |
| 71 | .collect(); |
| 72 | |
| 73 | if unique.is_empty() { |
| 74 | return Ok(logits.clone()); |
| 75 | } |
| 76 | |
| 77 | let device = logits.device(); |
| 78 | let dtype = logits.dtype(); |
| 79 | let indices = Tensor::new(unique.as_slice(), device)?; |
| 80 | |
| 81 | // Gather logits at penalty positions (N elements, tiny). |
| 82 | let selected = logits.index_select(&indices, 0)?; |
| 83 | |
| 84 | // Sign-aware multiplier: 1/penalty for logits ≥ 0, penalty for logits < 0. |
| 85 | let is_non_negative = selected.ge(0f32)?; |
| 86 | let recip = Tensor::new(1.0f32 / penalty, device)? |
| 87 | .to_dtype(dtype)? |
| 88 | .broadcast_as(selected.shape())?; |
| 89 | let pen = Tensor::new(penalty, device)? |
| 90 | .to_dtype(dtype)? |
| 91 | .broadcast_as(selected.shape())?; |
| 92 | let mult = is_non_negative.where_cond(&recip, &pen)?; |
| 93 | |
| 94 | // delta = selected * mult - selected (what to add to the original logits). |
| 95 | let penalized = (&selected * &mult)?; |
| 96 | let delta = (&penalized - &selected)?; |
| 97 | |
| 98 | Ok(logits.index_add(&indices, &delta, 0)?) |
| 99 | } |
| 100 | |
| 101 | /// Create the logit sampling logic from the context. |
| 102 | pub fn create_logits_processor(ctx: &Context) -> LogitsProcessor { |