Speed up training of Citrinet models by more efficient masking.
I am still waiting for the full run to complete in order to confirm that there is no degradation in quality.  For now, keeping in draft state.  Thanks a lot everybody for the review! 
The proposed change includes three optimization steps that I found were beneficial for model training efficiency.

### 1. Mask caching
Currently `arange` for mask is computed at each step from scratch. While it might be cached by GPU, there is no guarantee as it is not needed after forward function execution. 
By caching it dynamically, it will be easier to strike balance between numerous short sequences that will benefit from caching and arbitrarily long sequences during inference.
 

### 2. Multiplication by mask instead of `masked_fill`
`masked_fill `supports arbitrary values to fill into the tensor as, thus, implemented as an iterator over Tensor memory.
In our models masks are binary, so we can leverage vectorized multiplication operation to speedup mask application.

### 3. Shortcutting new sequence length computation
Given that Citrinet uses same padding everywhere and that striding only happens at certain levels. We can avoid doing arithmetic with sequence lengths when not needed.
Additionally, NeMo DatasetLoaders guarantee that lengths are represented as longs, so we can remove redundant `to` conversion.


## Benchmarks

### NGC
Time per epoch, 384 filters model, 8 GPUs, average across 10 epochs:

| baseline  |  +Mask caching  | +Multiplication   | +Shortcuts  |
|---|---|---|---|
|50 m   | 46.5 m   | 45 m   | 44.5m   |

 
Time per epoch, 384 filters model, 4 GPUs, aggregate grads from 2 batches, average across 34 epochs:

| baseline  |  +Mask caching  | +Multiplication   | +Shortcuts  |
|---|---|---|---|
|78 m   | 71 m   | 69 m   | 70m   |


**Accuracy** for all improvement runs **is in bounds of statistical noise** compared to the baseline.