hankcs/HanLP · error · ValueError

mask of the first timestep must all be on

Error message

mask of the first timestep must all be on

What it means

HanLP's TorchCRF requires that the first timestep of every sequence is valid (mask all-on at timestep 0), because CRF scoring assumes each sequence starts at the first emission. _validate checks mask[0].all() (or mask[:,0].all() for batch_first) and rejects masks whose first step has any zeros. This is inherited from torchcrf's semantics where empty prefixes are not representable.

Source

Thrown at hanlp/layers/crf/crf.py:186

            raise ValueError(
                f'expected last dimension of emissions is {self.num_tags}, '
                f'got {emissions.size(2)}')

        if tags is not None:
            if emissions.shape[:2] != tags.shape:
                raise ValueError(
                    'the first two dimensions of emissions and tags must match, '
                    f'got {tuple(emissions.shape[:2])} and {tuple(tags.shape)}')

        if mask is not None:
            if emissions.shape[:2] != mask.shape:
                raise ValueError(
                    'the first two dimensions of emissions and mask must match, '
                    f'got {tuple(emissions.shape[:2])} and {tuple(mask.shape)}')
            no_empty_seq = not self.batch_first and mask[0].all()
            no_empty_seq_bf = self.batch_first and mask[:, 0].all()
            if not no_empty_seq and not no_empty_seq_bf:
                raise ValueError('mask of the first timestep must all be on')

    def _compute_score(
            self, emissions: torch.Tensor, tags: torch.LongTensor,
            mask: torch.ByteTensor) -> torch.Tensor:
        # emissions: (seq_length, batch_size, num_tags)
        # tags: (seq_length, batch_size)
        # mask: (seq_length, batch_size)
        assert emissions.dim() == 3 and tags.dim() == 2
        assert emissions.shape[:2] == tags.shape
        assert emissions.size(2) == self.num_tags
        assert mask.shape == tags.shape
        assert mask[0].all()

        seq_length, batch_size = tags.shape
        mask = mask.type_as(emissions)

        # Start transition score and first emission
        # shape: (batch_size,)

View on GitHub (pinned to ddb1299bdd)

Solutions

  1. Use right-padding so each sequence's first timestep is real and mask[:,0]==1 for all batches
  2. Double-check mask orientation matches batch_first (mask[:,0] vs mask[0])
  3. Rebuild mask as arange(T) < length per sequence

Example fix

# before
mask = (torch.arange(T)[None, :] >= lengths[:, None])  # inverted -> first step off
# after
mask = (torch.arange(T)[None, :] < lengths[:, None]).to(torch.uint8)
assert mask[:, 0].all()
Defensive patterns

Strategy: validation

Validate before calling

assert (mask[:, 0] if batch_first else mask[0]).all(), 'first timestep must be unmasked'

Type guard

def first_step_on(mask: torch.Tensor, batch_first: bool) -> bool:
    return bool((mask[:, 0] if batch_first else mask[0]).all())

Prevention

When it happens

Trigger: Passing a mask where any sequence has mask[..., 0] == 0, e.g. mask built with an off-by-one roll, sorted-by-length batches misaligned, or a mask that marks pad positions starting at index 0 for shorter sequences placed after longer ones without batch_first alignment.

Common situations: Left-padding sequences instead of right-padding; constructing mask from lengths with reversed or transposed axes; feeding a mask of all zeros for some sample.

Related errors


AI-assisted analysis of hankcs/HanLP@ddb1299bdd (2026-08-27). Data as JSON: /api/errors/8c50bac30956313c. Report an issue: GitHub.