Speculative decoding: accept, reject and resample

ResearchHard35 min12 checks

speculative-decodingsamplinginferenceprobability

Problem

Speculative decoding makes a large model generate text faster with no change to its output. A small draft model proposes a few tokens. The large target model checks all of them in one forward pass. A rejection rule keeps the output distribution exactly equal to the distribution of the target model.

Task

Write four functions. In this problem, p is a distribution of the target model and q is a distribution of the draft model, over a vocabulary of V tokens.

  1. acceptance_rate(target, draft) returns \alpha = \sum_x \min(p(x), q(x)). This is the probability that the rule accepts one draft token.
  2. residual_distribution(target, draft) returns \mathrm{norm}(\max(0, p - q)): the elementwise maximum of 0 and p - q, divided by its sum. When that sum is 0, return a copy of target as a float array.
  3. speculative_step(target_probs, draft_probs, draft_tokens, rng) runs the verify step for \gamma draft tokens and returns the output tokens.
  4. expected_tokens(alpha, gamma) returns the expected number of tokens of one step, (1 - \alpha^{\gamma + 1}) / (1 - \alpha). For \alpha = 1 it returns \gamma + 1.

speculative_step does these steps. The order of the random draws is part of the task.

  1. For each draft token x_i, with i from 0 to \gamma - 1, let p_i be row i of target_probs and q_i be row i of draft_probs.
  2. Draw u = rng.random(). When u < \min(1, p_i(x_i) / q_i(x_i)), accept x_i, add it to the output, and go to the next draft token.
  3. Otherwise, draw one token with rng.choice(V, p=r), where r is the residual distribution of p_i and q_i. Add it to the output and return the output.
  4. When all \gamma draft tokens are accepted, draw one bonus token with rng.choice(V, p=p_gamma) from the last row of target_probs. Add it and return the output.

Input and output

NameTypeShapeMeaning
targetfloat array(V,)a target distribution p
draftfloat array(V,)a draft distribution q
target_probsfloat array(gamma + 1, V)the target distributions at each draft position and one more
draft_probsfloat array(gamma, V)the draft distributions at each draft position
draft_tokenslist of intlength gammathe tokens that the draft model sampled
rngnp.random.Generatorthe source of all random draws
alphafloatan acceptance rate from 0 to 1
gammaintthe number of draft tokens
  • acceptance_rate returns a Python float.
  • residual_distribution returns a float array of shape (V,) that sums to 1.
  • speculative_step returns a list of Python ints of length from 1 to gamma + 1.
  • expected_tokens returns a Python float.

Constraints

  • 2 <= V <= 64 and 0 <= gamma <= 8. Each row of probabilities sums to 1.
  • Each draft token has q_i(x_i) > 0, because the draft model sampled it. The target probability p_i(x_i) can be 0.
  • Use only rng.random() and rng.choice(V, p=...), in the order above. Make no other draws.
  • Do not change the input arrays or the list of draft tokens.
  • Use numpy only.

Examples

pythontarget = np.array([0.5, 0.3, 0.2, 0.0])
draft = np.array([0.25, 0.25, 0.25, 0.25])
acceptance_rate(target, draft)
residual_distribution(target, draft)
text0.7
[0.83333333 0.16666667 0.         0.        ]
pythontarget_probs = np.array([[0.5, 0.3, 0.2, 0.0], [0.1, 0.6, 0.2, 0.1], [0.25, 0.25, 0.25, 0.25]])
draft_probs = np.array([[0.25, 0.25, 0.25, 0.25], [0.4, 0.2, 0.2, 0.2]])
speculative_step(target_probs, draft_probs, [0, 1], np.random.default_rng(0))
speculative_step(target_probs, draft_probs, [3, 0], np.random.default_rng(0))
speculative_step(target_probs, draft_probs, [0, 0], np.random.default_rng(3))
text[0, 1, 0]
[0]
[0, 0, 3]
pythonexpected_tokens(0.8, 4), expected_tokens(1.0, 4), expected_tokens(0.0, 4)
text(3.3616, 5.0, 1.0)

Source paper Fast Inference from Transformers via Speculative Decoding, Leviathan et al., 2023.

Hints

4 hints

    Solution and notes

    Locked

    Pass every check with Submit to open the reference solution and the notes.

    Run it on your computer

    Clone the repository, install numpy and run the same checks in a terminal:

    python3 scripts/practice/run.py speculative-decoding-acceptance my_answer.py
    python3 scripts/practice/run.py speculative-decoding-acceptance my_answer.py --examples

    Start my_answer.py from starter.py.

    Your code

    Python loads on the first run

    Your code is saved in this browser as you type. On a phone you can read and edit the code, but the checks are easier to use on a desktop.

    Keyboard shortcuts
    Ctrl Enter
    Run the examples
    Ctrl Shift Enter
    Submit
    Tab / Shift Tab
    Indent or outdent the lines
    Esc then Tab
    Move the focus out of the editor
    Alt H
    Show the next hint

    On a Mac, use Cmd in place of Ctrl.

    Select Run to check the examples, or Submit to run every check.