Skip to content

← Indexing and Selection step 5 of 9

Easy Primitives

Mask the padding before the softmax

Jump to the task

A Boolean mask can identify attention-score positions to exclude. In this exercise, True means replace the score and False means keep it:

import torch

scores = torch.tensor([[1., 2., 3.], [4., 5., 6.]])
mask = torch.tensor([[False, False, True], [False, True, True]])
out = scores.masked_fill(mask, float("-inf"))
print(out.tolist())  # [[1.0, 2.0, -inf], [4.0, -inf, -inf]]
print(scores.tolist())  # [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]

For finite unmasked scores and at least one valid position per row, negative infinity gives excluded positions zero probability under softmax. The task here only fills scores; it does not compute softmax.

Apply the mask as a tensor operation

The starter loops over every score and converts each mask entry to a Python Boolean. That introduces repeated indexing and dispatch overhead, and reading device values into Python can require synchronization. masked_fill expresses the whole replacement directly.

torch.where(mask, replacement, scores) is another option, particularly when replacements vary by position. A scalar replacement works here too. Both forms can broadcast compatible shapes, though this exercise supplies scores and mask with exactly matching shapes.

The harness counts dispatched PyTorch operations, not GPU kernels. Both forms fit its two-operation limit on the tested inputs; one Python API call does not necessarily mean one dispatched operation.

Preserve the input

masked_fill returns a new tensor. masked_fill_ changes the original, so it does not satisfy this task. In-place mutation is appropriate only when the surrounding code permits it, including any autograd constraints.

Negative infinity and fully masked rows

A finite value such as -1e9 is not interchangeable with negative infinity. It is outside float16's finite range, and passing it as a fill value for a float16 tensor can raise an overflow error. A finite masking value also need not exclude a position relative to every possible unmasked score.

If all scores in a row become negative infinity, a plain softmax returns NaNs. Filling all positions with the same finite negative value instead gives a uniform distribution, which also does not mean every position was excluded. Define a policy for fully masked rows before using softmax. The all-True test here expects a row of negative infinities, without softmax.

Your task

def mask_scores(scores: torch.Tensor, mask: torch.Tensor) -> torch.Tensor

Given a floating-point score matrix and a same-shaped Boolean mask on the same device, return a new tensor containing negative infinity where the mask is True and the original values elsewhere. Leave scores unchanged. Use at most two dispatched operations on the harness inputs.