Skip to content

← Decoding Strategies step 5 of 8

Hard End-to-End

Beam Search

Practice the expand, score and prune steps of beam search using a fixed table of token scores. Keep several partial sequences during the search, then return only the best final sequence.

Your task

Implement beam_search(log_probs, beam_width).

  • log_probs is a floating-point matrix of shape (T, V), with V > 0. Row t supplies the finite additive scores for token IDs 0 through V-1 at step t. Use these scores directly; do not apply softmax or take logs again.
  • beam_width is a positive integer limiting how many partial sequences survive each step. Keep fewer if fewer candidates exist.
  • Return a Python dictionary with "sequences" and "score". Despite its plural name, "sequences" contains one list of T integer token IDs. "score" is that sequence's total score as a Python float, rounded to seven decimal places. Leave log_probs unchanged.

The parameter name reflects log-probability scoring, but the supplied rows need not be normalized probability distributions. The algorithm only adds and compares their scores.

Expand, score, prune

Start with one empty sequence of score 0. For each row of scores:

  1. Visit the surviving beams in their current order. For each beam, try token IDs in ascending order from 0 to V-1.
  2. Append each token to a copy of the sequence. Add its score from the current row to the beam's score.
  3. Sort candidates by descending total score, keeping their existing order when scores tie. Retain the first beam_width candidates.

After T steps, return the first surviving sequence and its score. Round only that final returned score; do not round partial scores while ranking candidates. If T=0, return {"sequences": [], "score": 0.0}. There is no prompt, EOS token, length penalty or early stopping in this exercise.

For rows [[-0.1, -1.0], [-0.2, -0.5]] and beam_width=2, the first step keeps [0] with score -0.1 and [1] with score -1.0. Expanding them gives:

[0, 0]: -0.3
[0, 1]: -0.6
[1, 0]: -1.2
[1, 1]: -1.5

Keep the first two candidates. Return {"sequences": [0, 0], "score": -0.3}. Higher scores are better: -0.3 beats -1.2.

What this simplified setup leaves out

Every beam uses the same score row at a given step. Scores do not depend on the tokens already chosen. As a result, independently choosing the largest score in each row also finds a best sequence here, for any positive beam width. Use beam expansion to practice the requested algorithm, but do not expect a wider beam to improve this particular table's best score.

A language model instead computes next-token scores from each candidate's prefix. In that setting, beam search is an approximate search and does not guarantee the globally best sequence.