1313# limitations under the License.
1414
1515import torch
16- import torch .nn .functional as F
1716import triton
1817import triton .language as tl
1918
@@ -82,9 +81,6 @@ def _rnnt_logprobs_bwd_kernel(
8281 blank_id : int ,
8382 grad_target_scores_ptr ,
8483 grad_blank_scores_ptr ,
85- loss_grad_scale_ptr ,
86- clamp : float ,
87- CLAMP_GRAD : tl .constexpr ,
8884 BLOCK_SIZE : tl .constexpr ,
8985):
9086 """
@@ -99,7 +95,9 @@ def _rnnt_logprobs_bwd_kernel(
9995 # load lengths for source/target
10096 source_len = tl .load (source_lengths_ptr + batch_i )
10197 target_len = tl .load (target_lengths_ptr + batch_i )
102- valid_state = (source_i < source_len ) & (target_i <= target_len )
98+ if source_i >= source_len or target_i > target_len :
99+ # no calculations required
100+ return
103101
104102 # calculate offset in [B, T, U+1, V] tensor for the current vector with target logits/grad_logits
105103 flat_index = ((batch_i * max_source_len + source_i ) * max_target_len_plus_1 + target_i ) * num_labels
@@ -108,77 +106,27 @@ def _rnnt_logprobs_bwd_kernel(
108106
109107 col_offsets = tl .arange (0 , BLOCK_SIZE )
110108 mask = col_offsets < num_labels
111- logits = tl .load (logits_ptr + col_offsets , mask = mask & valid_state , other = - float ("inf" )).to (tl .float32 )
109+ logits = tl .load (logits_ptr + col_offsets , mask = mask , other = - float ("inf" )).to (tl .float32 )
112110 # stable log softmax calculation
113111 logits_max = tl .max (logits , axis = 0 )
114112 logits_minus_max = logits - logits_max
115- unnormalized = tl .exp (logits_minus_max )
116- denominator_sum = tl . sum ( unnormalized , axis = 0 )
113+ denominator = tl .log ( tl . sum ( tl . exp (logits_minus_max ), axis = 0 ) )
114+ log_softmax = logits_minus_max - denominator
117115 # softmax for gradient
118- softmax = unnormalized / denominator_sum
116+ softmax = tl . exp ( log_softmax )
119117
120118 flat_index_grad = (batch_i * max_source_len + source_i ) * max_target_len_plus_1 + target_i
121- blank_grad = tl .load (grad_blank_scores_ptr + flat_index_grad , mask = valid_state , other = 0.0 ).to (tl .float32 )
122- target_i_valid = valid_state & ( target_i < target_len )
119+ blank_grad = tl .load (grad_blank_scores_ptr + flat_index_grad ).to (tl .float32 )
120+ target_i_valid = target_i < target_len
123121 target_grad = tl .load (grad_target_scores_ptr + flat_index_grad , mask = target_i_valid , other = 0.0 ).to (tl .float32 )
124122 target_id = tl .load (targets_ptr + batch_i * (max_target_len_plus_1 - 1 ) + target_i , mask = target_i_valid , other = - 1 )
125123
126- if CLAMP_GRAD :
127- # The reference kernel clamps the per-sample gradient at unit scale, before the loss
128- # reduction or AMP scale reaches it, so divide that scale out, clamp, and reapply below.
129- loss_grad_scale = tl .load (loss_grad_scale_ptr + batch_i ).to (tl .float32 )
130- inverse_scale = tl .where (loss_grad_scale != 0.0 , 1.0 / loss_grad_scale , 0.0 )
131- blank_grad *= inverse_scale
132- target_grad *= inverse_scale
133-
134124 grad_not_in_targets = (- softmax ) * (blank_grad + target_grad )
135- # Add both deltas instead of overwriting one with the other. This also keeps
136- # malformed target==blank inputs mathematically correct.
137- grad = grad_not_in_targets
138- grad += tl .where (col_offsets == blank_id , blank_grad , 0.0 )
139- grad += tl .where (col_offsets == target_id , target_grad , 0.0 )
140- if CLAMP_GRAD :
141- grad = tl .maximum (tl .minimum (grad , clamp ), - clamp )
142- grad *= loss_grad_scale
143- grad = tl .where (valid_state , grad , 0.0 )
125+ grad = tl .where (col_offsets == blank_id , blank_grad + grad_not_in_targets , grad_not_in_targets )
126+ grad = tl .where (col_offsets == target_id , target_grad + grad_not_in_targets , grad )
144127 tl .store (grad_logits_ptr + col_offsets , grad , mask = mask )
145128
146129
147- def _validate_rnnt_logprobs_inputs (
148- logits : torch .Tensor ,
149- targets : torch .Tensor ,
150- blank_id : int ,
151- source_lengths : torch .Tensor | None ,
152- target_lengths : torch .Tensor | None ,
153- ) -> None :
154- """Validate the tensor layout assumed by the pointer arithmetic below."""
155- if logits .ndim != 4 :
156- raise ValueError (f"logits must have shape [B, T, U + 1, V], got { tuple (logits .shape )} " )
157- if targets .ndim != 2 :
158- raise ValueError (f"targets must have shape [B, U], got { tuple (targets .shape )} " )
159- expected_targets = (logits .shape [0 ], logits .shape [2 ] - 1 )
160- if targets .shape != expected_targets :
161- raise ValueError (f"targets must have shape { expected_targets } , got { tuple (targets .shape )} " )
162- if not 0 <= blank_id < logits .shape [- 1 ]:
163- raise ValueError (f"blank_id={ blank_id } must be in [0, { logits .shape [- 1 ]} )" )
164- if not logits .is_contiguous ():
165- raise ValueError ("logits must be contiguous" )
166- if targets .device != logits .device :
167- raise ValueError ("targets and logits must be on the same device" )
168- if targets .dtype not in (torch .int32 , torch .int64 ):
169- raise ValueError ("targets must use int32 or int64 indices" )
170-
171- for name , lengths in (("source_lengths" , source_lengths ), ("target_lengths" , target_lengths )):
172- if lengths is None :
173- continue
174- if lengths .shape != (logits .shape [0 ],):
175- raise ValueError (f"{ name } must have shape ({ logits .shape [0 ]} ,), got { tuple (lengths .shape )} " )
176- if lengths .device != logits .device :
177- raise ValueError (f"{ name } and logits must be on the same device" )
178- if lengths .dtype not in (torch .int32 , torch .int64 ):
179- raise ValueError (f"{ name } must use int32 or int64 values" )
180-
181-
182130class RnntLogProbs (torch .autograd .Function ):
183131 """
184132 Function to calculate log probabilities for target and blank labels for RNN-T, supporting torch.autograd.
@@ -192,11 +140,8 @@ def forward(
192140 blank_id : int ,
193141 source_lengths : torch .Tensor | None ,
194142 target_lengths : torch .Tensor | None ,
195- clamp : float ,
196- reuse_logits_for_grad : bool ,
197- loss_grad_scale : torch .Tensor | None ,
198143 ):
199- """Log probabilities of the target and blank labels at every lattice position.
144+ """
200145
201146 Args:
202147 ctx: ctx object for storing the context
@@ -205,13 +150,11 @@ def forward(
205150 blank_id: id of the blank output
206151 source_lengths: optional tensor with lengths for source utterances
207152 target_lengths: optional tensor with lengths for targets
208- clamp: bound on the unit-scale gradient, disabled when not positive
209- reuse_logits_for_grad: write the gradient over ``logits`` rather than allocating for it
210- loss_grad_scale: per-sample loss gradient the clamp divides out and reapplies
211153
212154 Returns:
213- Log probabilities for target and blank labels, both of size [B, T, U+1].
155+
214156 """
157+ assert logits .is_contiguous () # logits are huge, so here we just check if logits are contiguous
215158 targets = targets .contiguous ()
216159 device = logits .device
217160 float_dtype = torch .float32
@@ -247,12 +190,6 @@ def forward(
247190 # saving for backward
248191 ctx .save_for_backward (logits , targets , source_lengths , target_lengths )
249192 ctx .blank_id = blank_id
250- ctx .clamp = float (clamp ) if clamp > 0.0 else 0.0
251- ctx .reuse_logits_for_grad = reuse_logits_for_grad
252- ctx .reused_logits_consumed = False
253- # Held outside save_for_backward: the loss fills it during its own backward, which
254- # runs before ours because these scores are what it consumes.
255- ctx .loss_grad_scale = loss_grad_scale
256193 return target_scores , blank_scores
257194
258195 @staticmethod
@@ -268,19 +205,9 @@ def backward(ctx, grad_target_scores, grad_blank_scores):
268205 Returns:
269206 gradient for logits, None for all other arguments for `forward`
270207 """
271- if ctx .reuse_logits_for_grad :
272- if ctx .reused_logits_consumed :
273- raise RuntimeError ("reuse_logits_for_grad=True only supports one backward pass" )
274- ctx .reused_logits_consumed = True
275208 (logits , targets , source_lengths , target_lengths ) = ctx .saved_tensors
276209 blank_id = ctx .blank_id
277- clamp = ctx .clamp
278- grad_target_scores = grad_target_scores .contiguous ()
279- grad_blank_scores = grad_blank_scores .contiguous ()
280- grad_logits = logits if ctx .reuse_logits_for_grad else torch .zeros_like (logits )
281- # Any valid pointer will do when clamping is off: CLAMP_GRAD is a constexpr, so the
282- # only branch that reads this argument is compiled out.
283- loss_grad_scale = ctx .loss_grad_scale if ctx .loss_grad_scale is not None else grad_blank_scores
210+ grad_logits = torch .zeros_like (logits )
284211 _rnnt_logprobs_bwd_kernel [(logits .shape [0 ], logits .shape [1 ], logits .shape [2 ])](
285212 logits_ptr = logits ,
286213 grad_logits_ptr = grad_logits ,
@@ -293,12 +220,9 @@ def backward(ctx, grad_target_scores, grad_blank_scores):
293220 blank_id = blank_id ,
294221 grad_target_scores_ptr = grad_target_scores ,
295222 grad_blank_scores_ptr = grad_blank_scores ,
296- loss_grad_scale_ptr = loss_grad_scale ,
297- clamp = clamp ,
298- CLAMP_GRAD = clamp > 0.0 ,
299223 BLOCK_SIZE = triton .next_power_of_2 (logits .shape [- 1 ]),
300224 )
301- return grad_logits , None , None , None , None , None , None , None
225+ return grad_logits , None , None , None , None
302226
303227
304228def rnnt_logprobs_triton (
@@ -307,9 +231,6 @@ def rnnt_logprobs_triton(
307231 blank_id : int ,
308232 source_lengths : torch .Tensor | None = None ,
309233 target_lengths : torch .Tensor | None = None ,
310- clamp : float = - 1.0 ,
311- reuse_logits_for_grad : bool = False ,
312- loss_grad_scale : torch .Tensor | None = None ,
313234) -> tuple [torch .Tensor , torch .Tensor ]:
314235 """
315236 Given logits, calculate log probabilities for blank and target labels needed for transducer loss calculation.
@@ -321,56 +242,9 @@ def rnnt_logprobs_triton(
321242 blank_id: id of the blank output
322243 source_lengths: optional tensor with lengths for source utterances
323244 target_lengths: optional tensor with lengths for targets
324- clamp: bound on the unit-scale RNN-T gradient, applied before ``loss_grad_scale`` is
325- reapplied; disabled when not positive
326- reuse_logits_for_grad: overwrite logits with their gradient during backward; only safe for private,
327- disposable logits; a second backward through the same graph raises
328- loss_grad_scale: ``[B]`` float32 buffer holding the objective's gradient with respect to
329- each per-sample loss, filled by ``rnnt_loss_triton``. Required only when clamping: the
330- clamp bounds the unit-scale gradient, and by this point autograd has folded that scale
331- in, so the backward divides it out, clamps, and reapplies it.
332245
333246 Returns:
334247 Tuple of tensors with log probabilities for targets and blank labels, both of size [B, T, U+1].
335248 For the non-existent targets (U+1 or beyond target_lengths) output is zero.
336249 """
337- _validate_rnnt_logprobs_inputs (logits , targets , blank_id , source_lengths , target_lengths )
338- if clamp > 0.0 :
339- if loss_grad_scale is None :
340- raise ValueError ("Clamping the RNN-T gradient requires loss_grad_scale" )
341- if loss_grad_scale .shape != (logits .shape [0 ],) or loss_grad_scale .dtype != torch .float32 :
342- raise ValueError (
343- f"loss_grad_scale must be a float32 tensor of shape ({ logits .shape [0 ]} ,), "
344- f"got { tuple (loss_grad_scale .shape )} of { loss_grad_scale .dtype } "
345- )
346- return RnntLogProbs .apply (
347- logits , targets , blank_id , source_lengths , target_lengths , clamp , reuse_logits_for_grad , loss_grad_scale
348- )
349-
350-
351- def rnnt_logprobs_torch (
352- logits : torch .Tensor , targets : torch .Tensor , blank_id : int
353- ) -> tuple [torch .Tensor , torch .Tensor ]:
354- """
355- Given logits, calculate log probabilities for blank and target labels needed for transducer loss calculation.
356- Naive implementation in PyTorch, for testing and prototyping purposes.
357-
358- Args:
359- logits: Joint tensor of size [B, T, U+1, D]
360- targets: Targets of size [B, U]
361- blank_id: id of the blank output
362-
363- Returns:
364- Tuple of tensors with log probabilities for targets and blank labels, both of size [B, T, U+1].
365- For the last non-existent target (U+1) output is zero.
366- """
367- device = logits .device
368- batch_size = logits .shape [0 ]
369- log_probs = F .log_softmax (logits , dim = - 1 )
370- blank_scores = log_probs [..., blank_id ]
371- targets = torch .cat ((targets , torch .zeros ([batch_size ], dtype = targets .dtype , device = device ).unsqueeze (1 )), dim = - 1 )
372- target_scores = torch .gather (
373- log_probs , dim = - 1 , index = targets .unsqueeze (1 ).expand (log_probs .shape [:- 1 ]).unsqueeze (- 1 )
374- ).squeeze (- 1 )
375- target_scores [:, :, - 1 ] = 0.0
376- return target_scores , blank_scores
250+ return RnntLogProbs .apply (logits , targets , blank_id , source_lengths , target_lengths )
0 commit comments